2 * questions.c: routines for asking questions about working copies
4 * ====================================================================
5 * Copyright (c) 2000-2004, 2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
22 #include <apr_pools.h>
23 #include <apr_file_io.h>
24 #include <apr_file_info.h>
26 #include "svn_pools.h"
27 #include "svn_types.h"
28 #include "svn_string.h"
29 #include "svn_error.h"
33 #include "svn_props.h"
36 #include "adm_files.h"
37 #include "questions.h"
40 #include "translate.h"
45 #include "svn_private_config.h"
46 #include "private/svn_wc_private.h"
49 /* ### todo: make this compare repository too? Or do so in parallel
50 code. See also adm_files.c:check_adm_exists(), which should
51 probably be merged with this. */
53 svn_wc_check_wc(const char *path
,
57 svn_error_t
*err
= SVN_NO_ERROR
;
59 const char *format_file_path
60 = svn_wc__adm_path(path
, FALSE
, pool
, SVN_WC__ADM_ENTRIES
, NULL
);
62 /* First try to read the format number from the entries file. */
63 err
= svn_io_read_version_file(wc_format
, format_file_path
, pool
);
65 /* If that didn't work and the first line of the entries file contains
66 something other than a number, then it is probably in XML format. */
67 if (err
&& err
->apr_err
== SVN_ERR_BAD_VERSION_FILE_FORMAT
)
70 /* Fall back on reading the format file instead.
71 Note that the format file might not exist in newer working copies
72 (format 7 and higher), but in that case, the entries file should
73 have contained the format number. */
75 = svn_wc__adm_path(path
, FALSE
, pool
, SVN_WC__ADM_FORMAT
, NULL
);
77 err
= svn_io_read_version_file(wc_format
, format_file_path
, pool
);
80 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)
81 || APR_STATUS_IS_ENOTDIR(err
->apr_err
)))
87 /* Check path itself exists. */
88 SVN_ERR(svn_io_check_path(path
, &kind
, pool
));
90 if (kind
== svn_node_none
)
92 return svn_error_createf
93 (APR_ENOENT
, NULL
, _("'%s' does not exist"),
94 svn_path_local_style(path
, pool
));
97 /* If the format file does not exist or path not directory, then for
98 our purposes this is not a working copy, so return 0. */
105 /* If we managed to read the format file we assume that we
106 are dealing with a real wc so we can return a nice
108 SVN_ERR(svn_wc__check_format(*wc_format
, path
, pool
));
116 svn_wc__check_format(int wc_format
, const char *path
, apr_pool_t
*pool
)
120 return svn_error_createf
121 (SVN_ERR_WC_UNSUPPORTED_FORMAT
, NULL
,
122 _("Working copy format of '%s' is too old (%d); "
123 "please check out your working copy again"),
124 svn_path_local_style(path
, pool
), wc_format
);
126 else if (wc_format
> SVN_WC__VERSION
)
128 /* This won't do us much good for the 1.4<->1.5 crossgrade,
129 since 1.4.x clients don't refer to this FAQ entry, but at
130 least post-1.5 crossgrades will be somewhat less painful. */
131 return svn_error_createf
132 (SVN_ERR_WC_UNSUPPORTED_FORMAT
, NULL
,
133 _("This client is too old to work with working copy '%s'. You need\n"
134 "to get a newer Subversion client, or to downgrade this working "
137 "http://subversion.tigris.org/faq.html#working-copy-format-change\n"
140 svn_path_local_style(path
, pool
));
148 /*** svn_wc_text_modified_p ***/
150 /* svn_wc_text_modified_p answers the question:
152 "Are the contents of F different than the contents of
153 .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?"
155 In the first case, we're looking to see if a user has made local
156 modifications to a file since the last update or commit. In the
157 second, the file may not be versioned yet (it doesn't exist in
158 entries). Support for the latter case came about to facilitate
159 forced checkouts, updates, and switches, where an unversioned file
160 may obstruct a file about to be added.
162 Note: Assuming that F lives in a directory D at revision V, please
163 notice that we are *NOT* answering the question, "are the contents
164 of F different than revision V of F?" While F may be at a different
165 revision number than its parent directory, but we're only looking
166 for local edits on F, not for consistent directory revisions.
168 TODO: the logic of the routines on this page might change in the
169 future, as they bear some relation to the user interface. For
170 example, if a file is removed -- without telling subversion about
171 it -- how should subversion react? Should it copy the file back
172 out of text-base? Should it ask whether one meant to officially
177 /* Is PATH's timestamp the same as the one recorded in our
178 `entries' file? Return the answer in EQUAL_P. TIMESTAMP_KIND
179 should be one of the enumerated type above. */
181 svn_wc__timestamps_equal_p(svn_boolean_t
*equal_p
,
183 svn_wc_adm_access_t
*adm_access
,
184 enum svn_wc__timestamp_kind timestamp_kind
,
187 apr_time_t wfile_time
, entrytime
= 0;
188 const svn_wc_entry_t
*entry
;
190 /* Get the timestamp from the entries file */
191 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
193 /* Get the timestamp from the working file and the entry */
194 if (timestamp_kind
== svn_wc__text_time
)
196 SVN_ERR(svn_io_file_affected_time(&wfile_time
, path
, pool
));
197 entrytime
= entry
->text_time
;
200 else if (timestamp_kind
== svn_wc__prop_time
)
202 SVN_ERR(svn_wc__props_last_modified(&wfile_time
,
203 path
, svn_wc__props_working
,
205 entrytime
= entry
->prop_time
;
210 /* TODO: If either timestamp is inaccessible, the test cannot
211 return an answer. Assume that the timestamps are
218 /* Put the disk timestamp through a string conversion, so it's
219 at the same resolution as entry timestamps. */
220 /* This string conversion here may be goodness, but it does
221 nothing currently _and_ it is somewhat expensive _and_ it eats
222 memory _and_ it is tested for in the regression tests. But I
223 will only comment it out because I do not possess the guts to
224 remove it altogether. */
226 const char *tstr = svn_time_to_cstring (wfile_time, pool);
227 SVN_ERR (svn_time_from_cstring (&wfile_time, tstr, pool));
231 if (wfile_time
== entrytime
)
240 /* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE
241 * differs from BASE_FILE, else to FALSE if not. Also verify that
242 * BASE_FILE matches the entry checksum for VERSIONED_FILE, if
243 * verify_checksum is TRUE. If checksum does not match, return the error
244 * SVN_ERR_WC_CORRUPT_TEXT_BASE.
246 * ADM_ACCESS is an access baton for VERSIONED_FILE. Use POOL for
247 * temporary allocation.
250 compare_and_verify(svn_boolean_t
*modified_p
,
251 const char *versioned_file
,
252 svn_wc_adm_access_t
*adm_access
,
253 const char *base_file
,
254 svn_boolean_t compare_textbases
,
255 svn_boolean_t verify_checksum
,
259 svn_subst_eol_style_t eol_style
;
261 apr_hash_t
*keywords
;
262 svn_boolean_t special
;
263 svn_boolean_t need_translation
;
266 SVN_ERR(svn_wc__get_eol_style(&eol_style
, &eol_str
, versioned_file
,
268 SVN_ERR(svn_wc__get_keywords(&keywords
, versioned_file
,
269 adm_access
, NULL
, pool
));
270 SVN_ERR(svn_wc__get_special(&special
, versioned_file
, adm_access
, pool
));
273 need_translation
= svn_subst_translation_required(eol_style
, eol_str
,
274 keywords
, special
, TRUE
);
275 /* Special files can only be compared through their text bases:
276 they have no working copy representation
277 for example: symlinks aren't guaranteed to be valid, nor does
278 it make sense to compare with the linked file-or-directory. */
279 compare_textbases
|= special
;
280 if (verify_checksum
|| need_translation
)
282 /* Reading files is necessary. */
283 const unsigned char *digest
;
284 /* "v_" means versioned_file, "b_" means base_file. */
285 apr_file_t
*v_file_h
, *b_file_h
;
286 svn_stream_t
*v_stream
, *b_stream
;
287 const svn_wc_entry_t
*entry
;
289 SVN_ERR(svn_io_file_open(&b_file_h
, base_file
, APR_READ
,
290 APR_OS_DEFAULT
, pool
));
292 b_stream
= svn_stream_from_aprfile2(b_file_h
, FALSE
, pool
);
296 /* Need checksum verification, so read checksum from entries file
297 * and setup checksummed stream for base file. */
298 SVN_ERR(svn_wc__entry_versioned(&entry
, versioned_file
, adm_access
,
302 b_stream
= svn_stream_checksummed(b_stream
, &digest
, NULL
, TRUE
,
306 if (compare_textbases
&& need_translation
)
308 /* Create stream for detranslate versioned file to normal form. */
309 SVN_ERR(svn_subst_stream_detranslated(&v_stream
,
318 SVN_ERR(svn_io_file_open(&v_file_h
, versioned_file
, APR_READ
,
319 APR_OS_DEFAULT
, pool
));
320 v_stream
= svn_stream_from_aprfile2(v_file_h
, FALSE
, pool
);
322 if (need_translation
)
324 /* Translate text-base to working copy form. */
325 b_stream
= svn_subst_stream_translated(b_stream
, eol_str
,
326 FALSE
, keywords
, TRUE
,
331 SVN_ERR(svn_stream_contents_same(&same
, b_stream
, v_stream
, pool
));
333 SVN_ERR(svn_stream_close(v_stream
));
334 SVN_ERR(svn_stream_close(b_stream
));
336 if (verify_checksum
&& entry
->checksum
)
338 const char *checksum
;
339 checksum
= svn_md5_digest_to_cstring_display(digest
, pool
);
340 if (strcmp(checksum
, entry
->checksum
) != 0)
342 return svn_error_createf
343 (SVN_ERR_WC_CORRUPT_TEXT_BASE
, NULL
,
344 _("Checksum mismatch indicates corrupt text base: '%s'\n"
347 svn_path_local_style(base_file
, pool
),
355 /* Translation would be a no-op, so compare the original file. */
356 SVN_ERR(svn_io_files_contents_same_p(&same
, base_file
, versioned_file
,
362 *modified_p
= (! same
);
367 svn_wc__versioned_file_modcheck(svn_boolean_t
*modified_p
,
368 const char *versioned_file
,
369 svn_wc_adm_access_t
*adm_access
,
370 const char *base_file
,
371 svn_boolean_t compare_textbases
,
374 return compare_and_verify(modified_p
, versioned_file
, adm_access
,
375 base_file
, compare_textbases
, FALSE
, pool
);
379 svn_wc__text_modified_internal_p(svn_boolean_t
*modified_p
,
380 const char *filename
,
381 svn_boolean_t force_comparison
,
382 svn_wc_adm_access_t
*adm_access
,
383 svn_boolean_t compare_textbases
,
386 const char *textbase_filename
;
387 svn_node_kind_t kind
;
392 /* No matter which way you look at it, the file needs to exist. */
393 err
= svn_io_stat(&finfo
, filename
,
394 APR_FINFO_SIZE
| APR_FINFO_MTIME
| APR_FINFO_TYPE
395 | APR_FINFO_LINK
, pool
);
396 if ((err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
397 || (!err
&& !(finfo
.filetype
== APR_REG
||
398 finfo
.filetype
== APR_LNK
)))
400 /* There is no entity, or, the entity is not a regular file or link.
401 So, it can't be modified. */
402 svn_error_clear(err
);
409 if (! force_comparison
)
411 const svn_wc_entry_t
*entry
;
413 /* We're allowed to use a heuristic to determine whether files may
414 have changed. The heuristic has these steps:
417 1. Compare the working file's size
418 with the size cached in the entries file
419 2. If they differ, do a full file compare
420 3. Compare the working file's timestamp
421 with the timestamp cached in the entries file
422 4. If they differ, do a full file compare
423 5. Otherwise, return indicating an unchanged file.
425 There are 2 problematic situations which may occur:
427 1. The cached working size is missing
428 --> In this case, we forget we ever tried to compare
429 and skip to the timestamp comparison. This is
430 because old working copies do not contain cached sizes
432 2. The cached timestamp is missing
433 --> In this case, we forget we ever tried to compare
434 and skip to full file comparison. This is because
435 the timestamp will be removed when the library
436 updates a locally changed file. (ie, this only happens
437 when the file was locally modified.)
443 err
= svn_wc_entry(&entry
, filename
, adm_access
, FALSE
, pool
);
446 svn_error_clear(err
);
453 /* Compare the sizes, if applicable */
454 if (entry
->working_size
!= SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN
455 && finfo
.size
!= entry
->working_size
)
459 /* Compare the timestamps
461 Note: text_time == 0 means absent from entries,
462 which also means the timestamps won't be equal,
463 so there's no need to explicitly check the 'absent' value. */
464 if (entry
->text_time
!= finfo
.mtime
)
473 /* If there's no text-base file, we have to assume the working file
474 is modified. For example, a file scheduled for addition but not
476 /* We used to stat for the working base here, but we just give
477 compare_and_verify a try; we'll check for errors afterwards */
478 textbase_filename
= svn_wc__text_base_path(filename
, FALSE
, pool
);
480 /* Check all bytes, and verify checksum if requested. */
482 apr_pool_t
*subpool
= svn_pool_create(pool
);
484 err
= compare_and_verify(modified_p
,
495 err2
= svn_io_check_path(textbase_filename
, &kind
, pool
);
496 if (! err2
&& kind
!= svn_node_file
)
498 svn_error_clear(err
);
503 svn_error_clear(err
);
507 svn_pool_destroy(subpool
);
510 /* It is quite legitimate for modifications to the working copy to
511 produce a timestamp variation with no text variation. If it turns out
512 that there are no differences then we might be able to "repair" the
513 text-time in the entries file and so avoid the expensive file contents
514 comparison in the future.
515 Though less likely, the same may be true for the size
516 of the working file. */
517 if (! *modified_p
&& svn_wc_adm_locked(adm_access
))
521 tmp
.working_size
= finfo
.size
;
522 tmp
.text_time
= finfo
.mtime
;
523 SVN_ERR(svn_wc__entry_modify(adm_access
,
524 svn_path_basename(filename
, pool
),
526 SVN_WC__ENTRY_MODIFY_TEXT_TIME
527 | SVN_WC__ENTRY_MODIFY_WORKING_SIZE
,
536 svn_wc_text_modified_p(svn_boolean_t
*modified_p
,
537 const char *filename
,
538 svn_boolean_t force_comparison
,
539 svn_wc_adm_access_t
*adm_access
,
542 return svn_wc__text_modified_internal_p(modified_p
, filename
,
543 force_comparison
, adm_access
,
550 svn_wc_conflicted_p(svn_boolean_t
*text_conflicted_p
,
551 svn_boolean_t
*prop_conflicted_p
,
552 const char *dir_path
,
553 const svn_wc_entry_t
*entry
,
557 svn_node_kind_t kind
;
558 apr_pool_t
*subpool
= svn_pool_create(pool
); /* ### Why? */
560 *text_conflicted_p
= FALSE
;
561 *prop_conflicted_p
= FALSE
;
563 /* Look for any text conflict, exercising only as much effort as
564 necessary to obtain a definitive answer. This only applies to
565 files, but we don't have to explicitly check that entry is a
566 file, since these attributes would never be set on a directory
567 anyway. A conflict file entry notation only counts if the
568 conflict file still exists on disk. */
569 if (entry
->conflict_old
)
571 path
= svn_path_join(dir_path
, entry
->conflict_old
, subpool
);
572 SVN_ERR(svn_io_check_path(path
, &kind
, subpool
));
573 if (kind
== svn_node_file
)
574 *text_conflicted_p
= TRUE
;
577 if ((! *text_conflicted_p
) && (entry
->conflict_new
))
579 path
= svn_path_join(dir_path
, entry
->conflict_new
, subpool
);
580 SVN_ERR(svn_io_check_path(path
, &kind
, subpool
));
581 if (kind
== svn_node_file
)
582 *text_conflicted_p
= TRUE
;
585 if ((! *text_conflicted_p
) && (entry
->conflict_wrk
))
587 path
= svn_path_join(dir_path
, entry
->conflict_wrk
, subpool
);
588 SVN_ERR(svn_io_check_path(path
, &kind
, subpool
));
589 if (kind
== svn_node_file
)
590 *text_conflicted_p
= TRUE
;
593 /* What about prop conflicts? */
596 path
= svn_path_join(dir_path
, entry
->prejfile
, subpool
);
597 SVN_ERR(svn_io_check_path(path
, &kind
, subpool
));
598 if (kind
== svn_node_file
)
599 *prop_conflicted_p
= TRUE
;
602 svn_pool_destroy(subpool
);
611 svn_wc_has_binary_prop(svn_boolean_t
*has_binary_prop
,
613 svn_wc_adm_access_t
*adm_access
,
616 const svn_string_t
*value
;
617 apr_pool_t
*subpool
= svn_pool_create(pool
);
619 SVN_ERR(svn_wc_prop_get(&value
, SVN_PROP_MIME_TYPE
, path
, adm_access
,
622 if (value
&& (svn_mime_type_is_binary(value
->data
)))
623 *has_binary_prop
= TRUE
;
625 *has_binary_prop
= FALSE
;
627 svn_pool_destroy(subpool
);