1 /*
2 * questions.c: routines for asking questions about working copies
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <string.h>
27
28 #include <apr_pools.h>
29 #include <apr_file_io.h>
30 #include <apr_file_info.h>
31 #include <apr_time.h>
32
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"
38 #include "svn_time.h"
39 #include "svn_io.h"
40 #include "svn_props.h"
41
42 #include "wc.h"
43 #include "conflicts.h"
44 #include "translate.h"
45 #include "wc_db.h"
46
47 #include "svn_private_config.h"
48 #include "private/svn_wc_private.h"
49
50
51
52 /*** svn_wc_text_modified_p ***/
53
54 /* svn_wc_text_modified_p answers the question:
55
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?"
58
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.
65
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.
71
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
77 mark it for removal?
78 */
79
80
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.
84 *
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.
91 *
92 * HAS_PROPS should be TRUE if the file had properties when it was not
93 * modified, otherwise FALSE.
94 *
95 * PROPS_MOD should be TRUE if the file's properties have been changed,
96 * otherwise FALSE.
97 *
98 * PRISTINE_STREAM will be closed before a successful return.
99 *
100 * DB is a wc_db; use SCRATCH_POOL for temporary allocation.
101 */
102 static svn_error_t *
compare_and_verify(svn_boolean_t * modified_p,svn_wc__db_t * db,const char * versioned_file_abspath,svn_filesize_t versioned_file_size,svn_stream_t * pristine_stream,svn_filesize_t pristine_size,svn_boolean_t has_props,svn_boolean_t props_mod,svn_boolean_t exact_comparison,apr_pool_t * scratch_pool)103 compare_and_verify(svn_boolean_t *modified_p,
104 svn_wc__db_t *db,
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)
113 {
114 svn_boolean_t same;
115 svn_subst_eol_style_t eol_style;
116 const char *eol_str;
117 apr_hash_t *keywords;
118 svn_boolean_t special = FALSE;
119 svn_boolean_t need_translation;
120
121 SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
122
123 if (props_mod)
124 has_props = TRUE; /* Maybe it didn't have properties; but it has now */
125
126 if (has_props)
127 {
128 SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
129 &keywords,
130 &special,
131 db, versioned_file_abspath, NULL,
132 !exact_comparison,
133 scratch_pool, scratch_pool));
134
135 need_translation = svn_subst_translation_required(eol_style, eol_str,
136 keywords, special,
137 TRUE);
138 }
139 else
140 need_translation = FALSE;
141
142 if (! need_translation
143 && (versioned_file_size != pristine_size))
144 {
145 *modified_p = TRUE;
146
147 /* ### Why did we open the pristine? */
148 return svn_error_trace(svn_stream_close(pristine_stream));
149 }
150
151 /* ### Other checks possible? */
152
153 if (need_translation)
154 {
155 /* Reading files is necessary. */
156 svn_stream_t *v_stream; /* versioned_file */
157
158 if (special)
159 {
160 SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
161 scratch_pool, scratch_pool));
162 }
163 else
164 {
165 SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
166 scratch_pool, scratch_pool));
167
168 if (!exact_comparison && need_translation)
169 {
170 if (eol_style == svn_subst_eol_style_native)
171 eol_str = SVN_SUBST_NATIVE_EOL_STR;
172 else if (eol_style != svn_subst_eol_style_fixed
173 && eol_style != svn_subst_eol_style_none)
174 return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL,
175 svn_stream_close(v_stream), NULL);
176
177 /* Wrap file stream to detranslate into normal form,
178 * "repairing" the EOL style if it is inconsistent. */
179 v_stream = svn_subst_stream_translated(v_stream,
180 eol_str,
181 TRUE /* repair */,
182 keywords,
183 FALSE /* expand */,
184 scratch_pool);
185 }
186 else if (need_translation)
187 {
188 /* Wrap base stream to translate into working copy form, and
189 * arrange to throw an error if its EOL style is inconsistent. */
190 pristine_stream = svn_subst_stream_translated(pristine_stream,
191 eol_str, FALSE,
192 keywords, TRUE,
193 scratch_pool);
194 }
195 }
196
197 SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
198 scratch_pool));
199 }
200 else
201 {
202 /* Translation would be a no-op, so compare the original file. */
203 svn_stream_t *v_stream; /* versioned_file */
204
205 SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
206 scratch_pool, scratch_pool));
207
208 SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
209 scratch_pool));
210 }
211
212 *modified_p = (! same);
213
214 return SVN_NO_ERROR;
215 }
216
217 svn_error_t *
svn_wc__internal_file_modified_p(svn_boolean_t * modified_p,svn_wc__db_t * db,const char * local_abspath,svn_boolean_t exact_comparison,apr_pool_t * scratch_pool)218 svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
219 svn_wc__db_t *db,
220 const char *local_abspath,
221 svn_boolean_t exact_comparison,
222 apr_pool_t *scratch_pool)
223 {
224 svn_stream_t *pristine_stream;
225 svn_filesize_t pristine_size;
226 svn_wc__db_status_t status;
227 svn_node_kind_t kind;
228 const svn_checksum_t *checksum;
229 svn_filesize_t recorded_size;
230 apr_time_t recorded_mod_time;
231 svn_boolean_t has_props;
232 svn_boolean_t props_mod;
233 const svn_io_dirent2_t *dirent;
234
235 /* Read the relevant info */
236 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
237 NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
238 NULL, NULL, NULL,
239 &recorded_size, &recorded_mod_time,
240 NULL, NULL, NULL, &has_props, &props_mod,
241 NULL, NULL, NULL,
242 db, local_abspath,
243 scratch_pool, scratch_pool));
244
245 /* If we don't have a pristine or the node has a status that allows a
246 pristine, just say that the node is modified */
247 if (!checksum
248 || (kind != svn_node_file)
249 || ((status != svn_wc__db_status_normal)
250 && (status != svn_wc__db_status_added)))
251 {
252 *modified_p = TRUE;
253 return SVN_NO_ERROR;
254 }
255
256 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
257 scratch_pool, scratch_pool));
258
259 if (dirent->kind != svn_node_file)
260 {
261 /* There is no file on disk, so the text is missing, not modified. */
262 *modified_p = FALSE;
263 return SVN_NO_ERROR;
264 }
265
266 if (! exact_comparison)
267 {
268 /* We're allowed to use a heuristic to determine whether files may
269 have changed. The heuristic has these steps:
270
271 1. Compare the working file's size
272 with the size cached in the entries file
273 2. If they differ, do a full file compare
274 3. Compare the working file's timestamp
275 with the timestamp cached in the entries file
276 4. If they differ, do a full file compare
277 5. Otherwise, return indicating an unchanged file.
278
279 There are 2 problematic situations which may occur:
280
281 1. The cached working size is missing
282 --> In this case, we forget we ever tried to compare
283 and skip to the timestamp comparison. This is
284 because old working copies do not contain cached sizes
285
286 2. The cached timestamp is missing
287 --> In this case, we forget we ever tried to compare
288 and skip to full file comparison. This is because
289 the timestamp will be removed when the library
290 updates a locally changed file. (ie, this only happens
291 when the file was locally modified.)
292
293 */
294
295 /* Compare the sizes, if applicable */
296 if (recorded_size != SVN_INVALID_FILESIZE
297 && dirent->filesize != recorded_size)
298 goto compare_them;
299
300 /* Compare the timestamps
301
302 Note: recorded_mod_time == 0 means not available,
303 which also means the timestamps won't be equal,
304 so there's no need to explicitly check the 'absent' value. */
305 if (recorded_mod_time != dirent->mtime)
306 goto compare_them;
307
308 *modified_p = FALSE;
309 return SVN_NO_ERROR;
310 }
311
312 compare_them:
313 SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
314 db, local_abspath, checksum,
315 scratch_pool, scratch_pool));
316
317 /* Check all bytes, and verify checksum if requested. */
318 {
319 svn_error_t *err;
320 err = compare_and_verify(modified_p, db,
321 local_abspath, dirent->filesize,
322 pristine_stream, pristine_size,
323 has_props, props_mod,
324 exact_comparison,
325 scratch_pool);
326
327 /* At this point we already opened the pristine file, so we know that
328 the access denied applies to the working copy path */
329 if (err && APR_STATUS_IS_EACCES(err->apr_err))
330 return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
331 else
332 SVN_ERR(err);
333 }
334
335 if (!*modified_p)
336 {
337 svn_boolean_t own_lock;
338
339 /* The timestamp is missing or "broken" so "repair" it if we can. */
340 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
341 scratch_pool));
342 if (own_lock)
343 SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
344 dirent->filesize,
345 dirent->mtime,
346 scratch_pool));
347 }
348
349 return SVN_NO_ERROR;
350 }
351
352
353 svn_error_t *
svn_wc_text_modified_p2(svn_boolean_t * modified_p,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t unused,apr_pool_t * scratch_pool)354 svn_wc_text_modified_p2(svn_boolean_t *modified_p,
355 svn_wc_context_t *wc_ctx,
356 const char *local_abspath,
357 svn_boolean_t unused,
358 apr_pool_t *scratch_pool)
359 {
360 return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
361 local_abspath, FALSE, scratch_pool);
362 }
363
364
365
366 static svn_error_t *
internal_conflicted_p(svn_boolean_t * text_conflicted_p,svn_boolean_t * prop_conflicted_p,svn_boolean_t * tree_conflicted_p,svn_boolean_t * ignore_move_edit_p,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * scratch_pool)367 internal_conflicted_p(svn_boolean_t *text_conflicted_p,
368 svn_boolean_t *prop_conflicted_p,
369 svn_boolean_t *tree_conflicted_p,
370 svn_boolean_t *ignore_move_edit_p,
371 svn_wc__db_t *db,
372 const char *local_abspath,
373 apr_pool_t *scratch_pool)
374 {
375 svn_node_kind_t kind;
376 svn_skel_t *conflicts;
377 svn_boolean_t resolved_text = FALSE;
378 svn_boolean_t resolved_props = FALSE;
379
380 SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
381 scratch_pool, scratch_pool));
382
383 if (!conflicts)
384 {
385 if (text_conflicted_p)
386 *text_conflicted_p = FALSE;
387 if (prop_conflicted_p)
388 *prop_conflicted_p = FALSE;
389 if (tree_conflicted_p)
390 *tree_conflicted_p = FALSE;
391 if (ignore_move_edit_p)
392 *ignore_move_edit_p = FALSE;
393
394 return SVN_NO_ERROR;
395 }
396
397 SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p,
398 prop_conflicted_p, tree_conflicted_p,
399 db, local_abspath, conflicts,
400 scratch_pool, scratch_pool));
401
402 if (text_conflicted_p && *text_conflicted_p)
403 {
404 const char *mine_abspath;
405 const char *their_old_abspath;
406 const char *their_abspath;
407 svn_boolean_t done = FALSE;
408
409 /* Look for any text conflict, exercising only as much effort as
410 necessary to obtain a definitive answer. This only applies to
411 files, but we don't have to explicitly check that entry is a
412 file, since these attributes would never be set on a directory
413 anyway. A conflict file entry notation only counts if the
414 conflict file still exists on disk. */
415
416 SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
417 &their_old_abspath,
418 &their_abspath,
419 db, local_abspath, conflicts,
420 scratch_pool, scratch_pool));
421
422 if (mine_abspath)
423 {
424 SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
425
426 *text_conflicted_p = (kind == svn_node_file);
427
428 if (*text_conflicted_p)
429 done = TRUE;
430 }
431
432 if (!done && their_abspath)
433 {
434 SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool));
435
436 *text_conflicted_p = (kind == svn_node_file);
437
438 if (*text_conflicted_p)
439 done = TRUE;
440 }
441
442 if (!done && their_old_abspath)
443 {
444 SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool));
445
446 *text_conflicted_p = (kind == svn_node_file);
447
448 if (*text_conflicted_p)
449 done = TRUE;
450 }
451
452 if (!done && (mine_abspath || their_abspath || their_old_abspath))
453 resolved_text = TRUE; /* Remove in-db conflict marker */
454 }
455
456 if (prop_conflicted_p && *prop_conflicted_p)
457 {
458 const char *prej_abspath;
459
460 SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath,
461 NULL, NULL, NULL, NULL,
462 db, local_abspath, conflicts,
463 scratch_pool, scratch_pool));
464
465 if (prej_abspath)
466 {
467 SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool));
468
469 *prop_conflicted_p = (kind == svn_node_file);
470
471 if (! *prop_conflicted_p)
472 resolved_props = TRUE; /* Remove in-db conflict marker */
473 }
474 }
475
476 if (ignore_move_edit_p)
477 {
478 *ignore_move_edit_p = FALSE;
479 if (tree_conflicted_p && *tree_conflicted_p)
480 {
481 svn_wc_conflict_reason_t reason;
482 svn_wc_conflict_action_t action;
483
484 SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
485 db, local_abspath,
486 conflicts,
487 scratch_pool,
488 scratch_pool));
489
490 if (reason == svn_wc_conflict_reason_moved_away
491 && action == svn_wc_conflict_action_edit)
492 {
493 *tree_conflicted_p = FALSE;
494 *ignore_move_edit_p = TRUE;
495 }
496 }
497 }
498
499 if (resolved_text || resolved_props)
500 {
501 svn_boolean_t own_lock;
502
503 /* The marker files are missing, so "repair" wc.db if we can */
504 SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
505 scratch_pool));
506 if (own_lock)
507 SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
508 resolved_text,
509 resolved_props,
510 FALSE /* resolved_tree */,
511 NULL /* work_items */,
512 scratch_pool));
513 }
514
515 return SVN_NO_ERROR;
516 }
517
518 svn_error_t *
svn_wc__internal_conflicted_p(svn_boolean_t * text_conflicted_p,svn_boolean_t * prop_conflicted_p,svn_boolean_t * tree_conflicted_p,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * scratch_pool)519 svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
520 svn_boolean_t *prop_conflicted_p,
521 svn_boolean_t *tree_conflicted_p,
522 svn_wc__db_t *db,
523 const char *local_abspath,
524 apr_pool_t *scratch_pool)
525 {
526 SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p,
527 tree_conflicted_p, NULL,
528 db, local_abspath, scratch_pool));
529 return SVN_NO_ERROR;
530 }
531
532 svn_error_t *
svn_wc__conflicted_for_update_p(svn_boolean_t * conflicted_p,svn_boolean_t * conflict_ignored_p,svn_wc__db_t * db,const char * local_abspath,svn_boolean_t tree_only,apr_pool_t * scratch_pool)533 svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
534 svn_boolean_t *conflict_ignored_p,
535 svn_wc__db_t *db,
536 const char *local_abspath,
537 svn_boolean_t tree_only,
538 apr_pool_t *scratch_pool)
539 {
540 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
541 svn_boolean_t conflict_ignored;
542
543 if (!conflict_ignored_p)
544 conflict_ignored_p = &conflict_ignored;
545
546 SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted,
547 tree_only ? NULL: &prop_conflicted,
548 &tree_conflicted, conflict_ignored_p,
549 db, local_abspath, scratch_pool));
550 if (tree_only)
551 *conflicted_p = tree_conflicted;
552 else
553 *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted;
554
555 return SVN_NO_ERROR;
556 }
557
558
559 svn_error_t *
svn_wc_conflicted_p3(svn_boolean_t * text_conflicted_p,svn_boolean_t * prop_conflicted_p,svn_boolean_t * tree_conflicted_p,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * scratch_pool)560 svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p,
561 svn_boolean_t *prop_conflicted_p,
562 svn_boolean_t *tree_conflicted_p,
563 svn_wc_context_t *wc_ctx,
564 const char *local_abspath,
565 apr_pool_t *scratch_pool)
566 {
567 return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p,
568 prop_conflicted_p,
569 tree_conflicted_p,
570 wc_ctx->db,
571 local_abspath,
572 scratch_pool));
573 }
574
575 svn_error_t *
svn_wc__min_max_revisions(svn_revnum_t * min_revision,svn_revnum_t * max_revision,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t committed,apr_pool_t * scratch_pool)576 svn_wc__min_max_revisions(svn_revnum_t *min_revision,
577 svn_revnum_t *max_revision,
578 svn_wc_context_t *wc_ctx,
579 const char *local_abspath,
580 svn_boolean_t committed,
581 apr_pool_t *scratch_pool)
582 {
583 return svn_error_trace(svn_wc__db_min_max_revisions(min_revision,
584 max_revision,
585 wc_ctx->db,
586 local_abspath,
587 committed,
588 scratch_pool));
589 }
590
591
592 svn_error_t *
svn_wc__has_switched_subtrees(svn_boolean_t * is_switched,svn_wc_context_t * wc_ctx,const char * local_abspath,const char * trail_url,apr_pool_t * scratch_pool)593 svn_wc__has_switched_subtrees(svn_boolean_t *is_switched,
594 svn_wc_context_t *wc_ctx,
595 const char *local_abspath,
596 const char *trail_url,
597 apr_pool_t *scratch_pool)
598 {
599 return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched,
600 wc_ctx->db,
601 local_abspath,
602 trail_url,
603 scratch_pool));
604 }
605
606
607 svn_error_t *
svn_wc__has_local_mods(svn_boolean_t * is_modified,svn_wc_context_t * wc_ctx,const char * local_abspath,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)608 svn_wc__has_local_mods(svn_boolean_t *is_modified,
609 svn_wc_context_t *wc_ctx,
610 const char *local_abspath,
611 svn_cancel_func_t cancel_func,
612 void *cancel_baton,
613 apr_pool_t *scratch_pool)
614 {
615 return svn_error_trace(svn_wc__db_has_local_mods(is_modified,
616 wc_ctx->db,
617 local_abspath,
618 cancel_func,
619 cancel_baton,
620 scratch_pool));
621 }
622