2 * questions.c: routines for asking questions about working copies
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include <apr_pools.h>
29 #include <apr_file_io.h>
30 #include <apr_file_info.h>
33 #include "svn_pools.h"
34 #include "svn_types.h"
35 #include "svn_string.h"
36 #include "svn_error.h"
37 #include "svn_dirent_uri.h"
40 #include "svn_props.h"
43 #include "conflicts.h"
44 #include "translate.h"
47 #include "svn_private_config.h"
48 #include "private/svn_wc_private.h"
52 /*** svn_wc_text_modified_p ***/
54 /* svn_wc_text_modified_p answers the question:
56 "Are the contents of F different than the contents of
57 .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?"
59 In the first case, we're looking to see if a user has made local
60 modifications to a file since the last update or commit. In the
61 second, the file may not be versioned yet (it doesn't exist in
62 entries). Support for the latter case came about to facilitate
63 forced checkouts, updates, and switches, where an unversioned file
64 may obstruct a file about to be added.
66 Note: Assuming that F lives in a directory D at revision V, please
67 notice that we are *NOT* answering the question, "are the contents
68 of F different than revision V of F?" While F may be at a different
69 revision number than its parent directory, but we're only looking
70 for local edits on F, not for consistent directory revisions.
72 TODO: the logic of the routines on this page might change in the
73 future, as they bear some relation to the user interface. For
74 example, if a file is removed -- without telling subversion about
75 it -- how should subversion react? Should it copy the file back
76 out of text-base? Should it ask whether one meant to officially
81 /* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH
82 * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of
83 * PRISTINE_SIZE bytes), else to FALSE if not.
85 * If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL
86 * style and keywords to repository-normal form according to its properties,
87 * and compare the result with PRISTINE_STREAM. If EXACT_COMPARISON is
88 * TRUE, translate PRISTINE_STREAM's EOL style and keywords to working-copy
89 * form according to VERSIONED_FILE_ABSPATH's properties, and compare the
90 * result with VERSIONED_FILE_ABSPATH.
92 * HAS_PROPS should be TRUE if the file had properties when it was not
93 * modified, otherwise FALSE.
95 * PROPS_MOD should be TRUE if the file's properties have been changed,
98 * PRISTINE_STREAM will be closed before a successful return.
100 * DB is a wc_db; use SCRATCH_POOL for temporary allocation.
103 compare_and_verify(svn_boolean_t *modified_p,
105 const char *versioned_file_abspath,
106 svn_filesize_t versioned_file_size,
107 svn_stream_t *pristine_stream,
108 svn_filesize_t pristine_size,
109 svn_boolean_t has_props,
110 svn_boolean_t props_mod,
111 svn_boolean_t exact_comparison,
112 apr_pool_t *scratch_pool)
115 svn_subst_eol_style_t eol_style;
117 apr_hash_t *keywords;
118 svn_boolean_t special = FALSE;
119 svn_boolean_t need_translation;
120 svn_stream_t *v_stream; /* versioned_file */
122 SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
125 has_props = TRUE; /* Maybe it didn't have properties; but it has now */
129 SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
132 db, versioned_file_abspath, NULL,
134 scratch_pool, scratch_pool));
136 need_translation = svn_subst_translation_required(eol_style, eol_str,
141 need_translation = FALSE;
143 if (! need_translation
144 && (versioned_file_size != pristine_size))
148 /* ### Why did we open the pristine? */
149 return svn_error_trace(svn_stream_close(pristine_stream));
152 /* ### Other checks possible? */
154 /* Reading files is necessary. */
155 if (special && need_translation)
157 SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
158 scratch_pool, scratch_pool));
162 /* We don't use APR-level buffering because the comparison function
163 * will do its own buffering. */
165 SVN_ERR(svn_io_file_open(&file, versioned_file_abspath, APR_READ,
166 APR_OS_DEFAULT, scratch_pool));
167 v_stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
169 if (need_translation)
171 if (!exact_comparison)
173 if (eol_style == svn_subst_eol_style_native)
174 eol_str = SVN_SUBST_NATIVE_EOL_STR;
175 else if (eol_style != svn_subst_eol_style_fixed
176 && eol_style != svn_subst_eol_style_none)
177 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL,
178 svn_stream_close(v_stream), NULL);
180 /* Wrap file stream to detranslate into normal form,
181 * "repairing" the EOL style if it is inconsistent. */
182 v_stream = svn_subst_stream_translated(v_stream,
191 /* Wrap base stream to translate into working copy form, and
192 * arrange to throw an error if its EOL style is inconsistent. */
193 pristine_stream = svn_subst_stream_translated(pristine_stream,
201 SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
204 *modified_p = (! same);
210 svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
212 const char *local_abspath,
213 svn_boolean_t exact_comparison,
214 apr_pool_t *scratch_pool)
216 svn_stream_t *pristine_stream;
217 svn_filesize_t pristine_size;
218 svn_wc__db_status_t status;
219 svn_node_kind_t kind;
220 const svn_checksum_t *checksum;
221 svn_filesize_t recorded_size;
222 apr_time_t recorded_mod_time;
223 svn_boolean_t has_props;
224 svn_boolean_t props_mod;
225 const svn_io_dirent2_t *dirent;
227 /* Read the relevant info */
228 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
229 NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
231 &recorded_size, &recorded_mod_time,
232 NULL, NULL, NULL, &has_props, &props_mod,
235 scratch_pool, scratch_pool));
237 /* If we don't have a pristine or the node has a status that allows a
238 pristine, just say that the node is modified */
240 || (kind != svn_node_file)
241 || ((status != svn_wc__db_status_normal)
242 && (status != svn_wc__db_status_added)))
248 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
249 scratch_pool, scratch_pool));
251 if (dirent->kind != svn_node_file)
253 /* There is no file on disk, so the text is missing, not modified. */
258 if (! exact_comparison)
260 /* We're allowed to use a heuristic to determine whether files may
261 have changed. The heuristic has these steps:
263 1. Compare the working file's size
264 with the size cached in the entries file
265 2. If they differ, do a full file compare
266 3. Compare the working file's timestamp
267 with the timestamp cached in the entries file
268 4. If they differ, do a full file compare
269 5. Otherwise, return indicating an unchanged file.
271 There are 2 problematic situations which may occur:
273 1. The cached working size is missing
274 --> In this case, we forget we ever tried to compare
275 and skip to the timestamp comparison. This is
276 because old working copies do not contain cached sizes
278 2. The cached timestamp is missing
279 --> In this case, we forget we ever tried to compare
280 and skip to full file comparison. This is because
281 the timestamp will be removed when the library
282 updates a locally changed file. (ie, this only happens
283 when the file was locally modified.)
287 /* Compare the sizes, if applicable */
288 if (recorded_size != SVN_INVALID_FILESIZE
289 && dirent->filesize != recorded_size)
292 /* Compare the timestamps
294 Note: recorded_mod_time == 0 means not available,
295 which also means the timestamps won't be equal,
296 so there's no need to explicitly check the 'absent' value. */
297 if (recorded_mod_time != dirent->mtime)
305 SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
306 db, local_abspath, checksum,
307 scratch_pool, scratch_pool));
309 /* Check all bytes, and verify checksum if requested. */
312 err = compare_and_verify(modified_p, db,
313 local_abspath, dirent->filesize,
314 pristine_stream, pristine_size,
315 has_props, props_mod,
319 /* At this point we already opened the pristine file, so we know that
320 the access denied applies to the working copy path */
321 if (err && APR_STATUS_IS_EACCES(err->apr_err))
322 return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
329 svn_boolean_t own_lock;
331 /* The timestamp is missing or "broken" so "repair" it if we can. */
332 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
335 SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
346 svn_wc_text_modified_p2(svn_boolean_t *modified_p,
347 svn_wc_context_t *wc_ctx,
348 const char *local_abspath,
349 svn_boolean_t unused,
350 apr_pool_t *scratch_pool)
352 return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
353 local_abspath, FALSE, scratch_pool);
359 internal_conflicted_p(svn_boolean_t *text_conflicted_p,
360 svn_boolean_t *prop_conflicted_p,
361 svn_boolean_t *tree_conflicted_p,
362 svn_boolean_t *ignore_move_edit_p,
364 const char *local_abspath,
365 apr_pool_t *scratch_pool)
367 svn_node_kind_t kind;
368 svn_skel_t *conflicts;
369 svn_boolean_t resolved_text = FALSE;
370 svn_boolean_t resolved_props = FALSE;
372 SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL,
374 scratch_pool, scratch_pool));
378 if (text_conflicted_p)
379 *text_conflicted_p = FALSE;
380 if (prop_conflicted_p)
381 *prop_conflicted_p = FALSE;
382 if (tree_conflicted_p)
383 *tree_conflicted_p = FALSE;
384 if (ignore_move_edit_p)
385 *ignore_move_edit_p = FALSE;
390 SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p,
391 prop_conflicted_p, tree_conflicted_p,
392 db, local_abspath, conflicts,
393 scratch_pool, scratch_pool));
395 if (text_conflicted_p && *text_conflicted_p)
397 const char *mine_abspath;
398 const char *their_old_abspath;
399 const char *their_abspath;
400 svn_boolean_t done = FALSE;
402 /* Look for any text conflict, exercising only as much effort as
403 necessary to obtain a definitive answer. This only applies to
404 files, but we don't have to explicitly check that entry is a
405 file, since these attributes would never be set on a directory
406 anyway. A conflict file entry notation only counts if the
407 conflict file still exists on disk. */
409 SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
412 db, local_abspath, conflicts,
413 scratch_pool, scratch_pool));
417 SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
419 *text_conflicted_p = (kind == svn_node_file);
421 if (*text_conflicted_p)
425 if (!done && their_abspath)
427 SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool));
429 *text_conflicted_p = (kind == svn_node_file);
431 if (*text_conflicted_p)
435 if (!done && their_old_abspath)
437 SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool));
439 *text_conflicted_p = (kind == svn_node_file);
441 if (*text_conflicted_p)
445 if (!done && (mine_abspath || their_abspath || their_old_abspath))
446 resolved_text = TRUE; /* Remove in-db conflict marker */
449 if (prop_conflicted_p && *prop_conflicted_p)
451 const char *prej_abspath;
453 SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath,
454 NULL, NULL, NULL, NULL,
455 db, local_abspath, conflicts,
456 scratch_pool, scratch_pool));
460 SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool));
462 *prop_conflicted_p = (kind == svn_node_file);
464 if (! *prop_conflicted_p)
465 resolved_props = TRUE; /* Remove in-db conflict marker */
469 if (ignore_move_edit_p)
471 *ignore_move_edit_p = FALSE;
472 if (tree_conflicted_p && *tree_conflicted_p)
474 svn_wc_conflict_reason_t reason;
475 svn_wc_conflict_action_t action;
477 SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
483 if (reason == svn_wc_conflict_reason_moved_away
484 && action == svn_wc_conflict_action_edit)
486 *tree_conflicted_p = FALSE;
487 *ignore_move_edit_p = TRUE;
492 if (resolved_text || resolved_props)
494 svn_boolean_t own_lock;
496 /* The marker files are missing, so "repair" wc.db if we can */
497 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
500 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
503 FALSE /* resolved_tree */,
504 NULL /* work_items */,
512 svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
513 svn_boolean_t *prop_conflicted_p,
514 svn_boolean_t *tree_conflicted_p,
516 const char *local_abspath,
517 apr_pool_t *scratch_pool)
519 SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p,
520 tree_conflicted_p, NULL,
521 db, local_abspath, scratch_pool));
526 svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
527 svn_boolean_t *conflict_ignored_p,
529 const char *local_abspath,
530 svn_boolean_t tree_only,
531 apr_pool_t *scratch_pool)
533 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
534 svn_boolean_t conflict_ignored;
536 if (!conflict_ignored_p)
537 conflict_ignored_p = &conflict_ignored;
539 SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted,
540 tree_only ? NULL: &prop_conflicted,
541 &tree_conflicted, conflict_ignored_p,
542 db, local_abspath, scratch_pool));
544 *conflicted_p = tree_conflicted;
546 *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted;
553 svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p,
554 svn_boolean_t *prop_conflicted_p,
555 svn_boolean_t *tree_conflicted_p,
556 svn_wc_context_t *wc_ctx,
557 const char *local_abspath,
558 apr_pool_t *scratch_pool)
560 return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p,
569 svn_wc__min_max_revisions(svn_revnum_t *min_revision,
570 svn_revnum_t *max_revision,
571 svn_wc_context_t *wc_ctx,
572 const char *local_abspath,
573 svn_boolean_t committed,
574 apr_pool_t *scratch_pool)
576 return svn_error_trace(svn_wc__db_min_max_revisions(min_revision,
586 svn_wc__has_switched_subtrees(svn_boolean_t *is_switched,
587 svn_wc_context_t *wc_ctx,
588 const char *local_abspath,
589 const char *trail_url,
590 apr_pool_t *scratch_pool)
592 return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched,
600 /* A baton for use with modcheck_found_entry(). */
601 typedef struct modcheck_baton_t {
602 svn_boolean_t ignore_unversioned;
603 svn_boolean_t found_mod; /* whether a modification has been found */
604 svn_boolean_t found_not_delete; /* Found a not-delete modification */
607 /* An implementation of svn_wc_status_func4_t. */
609 modcheck_callback(void *baton,
610 const char *local_abspath,
611 const svn_wc_status3_t *status,
612 apr_pool_t *scratch_pool)
614 modcheck_baton_t *mb = baton;
616 switch (status->node_status)
618 case svn_wc_status_normal:
619 case svn_wc_status_ignored:
620 case svn_wc_status_none:
621 case svn_wc_status_external:
624 case svn_wc_status_incomplete:
625 if ((status->text_status != svn_wc_status_normal
626 && status->text_status != svn_wc_status_none)
627 || (status->prop_status != svn_wc_status_normal
628 && status->prop_status != svn_wc_status_none))
630 mb->found_mod = TRUE;
631 mb->found_not_delete = TRUE;
632 /* Incomplete, but local modifications */
633 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
637 case svn_wc_status_deleted:
638 mb->found_mod = TRUE;
639 if (!mb->ignore_unversioned
640 && status->actual_kind != svn_node_none
641 && status->actual_kind != svn_node_unknown)
643 /* The delete is obstructed by something unversioned */
644 mb->found_not_delete = TRUE;
645 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
649 case svn_wc_status_unversioned:
650 if (mb->ignore_unversioned)
652 /* else fall through */
653 case svn_wc_status_missing:
654 case svn_wc_status_obstructed:
655 mb->found_mod = TRUE;
656 mb->found_not_delete = TRUE;
657 /* Exit from the status walker: We know what we want to know */
658 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
661 case svn_wc_status_added:
662 case svn_wc_status_replaced:
663 case svn_wc_status_modified:
664 mb->found_mod = TRUE;
665 mb->found_not_delete = TRUE;
666 /* Exit from the status walker: We know what we want to know */
667 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
674 /* Set *MODIFIED to true iff there are any local modifications within the
675 * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED
676 * is set to true and all the local modifications were deletes then set
677 * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH
678 * may be a file or a directory. */
680 svn_wc__node_has_local_mods(svn_boolean_t *modified,
681 svn_boolean_t *all_edits_are_deletes,
683 const char *local_abspath,
684 svn_boolean_t ignore_unversioned,
685 svn_cancel_func_t cancel_func,
687 apr_pool_t *scratch_pool)
689 modcheck_baton_t modcheck_baton = { FALSE, FALSE, FALSE };
692 if (!all_edits_are_deletes)
694 SVN_ERR(svn_wc__db_has_db_mods(modified, db, local_abspath,
701 modcheck_baton.ignore_unversioned = ignore_unversioned;
703 /* Walk the WC tree for status with depth infinity, looking for any local
704 * modifications. If it's a "sparse" directory, that's OK: there can be
705 * no local mods in the pieces that aren't present in the WC. */
707 err = svn_wc__internal_walk_status(db, local_abspath,
709 FALSE, FALSE, FALSE, NULL,
710 modcheck_callback, &modcheck_baton,
711 cancel_func, cancel_baton,
714 if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
715 svn_error_clear(err);
719 *modified = modcheck_baton.found_mod;
720 if (all_edits_are_deletes)
721 *all_edits_are_deletes = (modcheck_baton.found_mod
722 && !modcheck_baton.found_not_delete);
728 svn_wc__has_local_mods(svn_boolean_t *is_modified,
729 svn_wc_context_t *wc_ctx,
730 const char *local_abspath,
731 svn_boolean_t ignore_unversioned,
732 svn_cancel_func_t cancel_func,
734 apr_pool_t *scratch_pool)
736 svn_boolean_t modified;
738 SVN_ERR(svn_wc__node_has_local_mods(&modified, NULL,
739 wc_ctx->db, local_abspath,
741 cancel_func, cancel_baton,
744 *is_modified = modified;