source: valtobtest/subversion-1.6.2/subversion/libsvn_wc/questions.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 21.6 KB
Line 
1/*
2 * questions.c:  routines for asking questions about working copies
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2004, 2006, 2008 CollabNet.  All rights reserved.
6 *
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.
12 *
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 * ====================================================================
17 */
18
19
20
21#include <string.h>
22#include <apr_pools.h>
23#include <apr_file_io.h>
24#include <apr_file_info.h>
25#include <apr_time.h>
26#include "svn_pools.h"
27#include "svn_types.h"
28#include "svn_string.h"
29#include "svn_error.h"
30#include "svn_path.h"
31#include "svn_time.h"
32#include "svn_io.h"
33#include "svn_props.h"
34
35#include "wc.h"
36#include "adm_files.h"
37#include "questions.h"
38#include "entries.h"
39#include "props.h"
40#include "translate.h"
41
42#include "svn_private_config.h"
43#include "private/svn_wc_private.h"
44
45
46/* ### todo: make this compare repository too?  Or do so in parallel
47   code.  */
48svn_error_t *
49svn_wc_check_wc(const char *path,
50                int *wc_format,
51                apr_pool_t *pool)
52{
53  svn_error_t *err;
54  const char *format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_ENTRIES,
55                                                   pool);
56
57  /* First try to read the format number from the entries file. */
58  err = svn_io_read_version_file(wc_format, format_file_path, pool);
59
60  /* If that didn't work and the first line of the entries file contains
61     something other than a number, then it is probably in XML format. */
62  if (err && err->apr_err == SVN_ERR_BAD_VERSION_FILE_FORMAT)
63    {
64      svn_error_clear(err);
65      /* Fall back on reading the format file instead.
66         Note that the format file might not exist in newer working copies
67         (format 7 and higher), but in that case, the entries file should
68         have contained the format number. */
69      format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_FORMAT, pool);
70
71      err = svn_io_read_version_file(wc_format, format_file_path, pool);
72    }
73
74  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
75              || APR_STATUS_IS_ENOTDIR(err->apr_err)))
76    {
77      svn_node_kind_t kind;
78
79      svn_error_clear(err);
80
81      /* If the format file does not exist or path not directory, then for
82         our purposes this is not a working copy, so return 0. */
83      *wc_format = 0;
84
85      /* Check path itself exists. */
86      SVN_ERR(svn_io_check_path(path, &kind, pool));
87
88      if (kind == svn_node_none)
89        {
90          return svn_error_createf
91            (APR_ENOENT, NULL, _("'%s' does not exist"),
92            svn_path_local_style(path, pool));
93        }
94
95    }
96  else if (err)
97    return err;
98  else
99    {
100      /* If we managed to read the format file we assume that we
101          are dealing with a real wc so we can return a nice
102          error. */
103      SVN_ERR(svn_wc__check_format(*wc_format, path, pool));
104    }
105
106  return SVN_NO_ERROR;
107}
108
109
110svn_error_t *
111svn_wc__check_format(int wc_format, const char *path, apr_pool_t *pool)
112{
113  if (wc_format < 2)
114    {
115      return svn_error_createf
116        (SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
117         _("Working copy format of '%s' is too old (%d); "
118           "please check out your working copy again"),
119         svn_path_local_style(path, pool), wc_format);
120    }
121  else if (wc_format > SVN_WC__VERSION)
122    {
123      /* This won't do us much good for the 1.4<->1.5 crossgrade,
124         since 1.4.x clients don't refer to this FAQ entry, but at
125         least post-1.5 crossgrades will be somewhat less painful. */
126      return svn_error_createf
127        (SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
128         _("This client is too old to work with working copy '%s'.  You need\n"
129           "to get a newer Subversion client, or to downgrade this working "
130           "copy.\n"
131           "See "
132           "http://subversion.tigris.org/faq.html#working-copy-format-change\n"
133           "for details."
134           ),
135         svn_path_local_style(path, pool));
136    }
137
138  return SVN_NO_ERROR;
139}
140
141
142
143/*** svn_wc_text_modified_p ***/
144
145/* svn_wc_text_modified_p answers the question:
146
147   "Are the contents of F different than the contents of
148   .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?"
149
150   In the first case, we're looking to see if a user has made local
151   modifications to a file since the last update or commit.  In the
152   second, the file may not be versioned yet (it doesn't exist in
153   entries).  Support for the latter case came about to facilitate
154   forced checkouts, updates, and switches, where an unversioned file
155   may obstruct a file about to be added.
156
157   Note: Assuming that F lives in a directory D at revision V, please
158   notice that we are *NOT* answering the question, "are the contents
159   of F different than revision V of F?"  While F may be at a different
160   revision number than its parent directory, but we're only looking
161   for local edits on F, not for consistent directory revisions.
162
163   TODO:  the logic of the routines on this page might change in the
164   future, as they bear some relation to the user interface.  For
165   example, if a file is removed -- without telling subversion about
166   it -- how should subversion react?  Should it copy the file back
167   out of text-base?  Should it ask whether one meant to officially
168   mark it for removal?
169*/
170
171
172/* Is PATH's timestamp the same as the one recorded in our
173   `entries' file?  Return the answer in EQUAL_P.  TIMESTAMP_KIND
174   should be one of the enumerated type above. */
175svn_error_t *
176svn_wc__timestamps_equal_p(svn_boolean_t *equal_p,
177                           const char *path,
178                           svn_wc_adm_access_t *adm_access,
179                           apr_pool_t *pool)
180{
181  const svn_wc_entry_t *entry;
182  apr_time_t wfile_time;
183
184  /* Get the timestamp from the entries file */
185  SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
186
187  /* Get the timestamp from the working file and the entry */
188  SVN_ERR(svn_io_file_affected_time(&wfile_time, path, pool));
189
190  *equal_p = wfile_time == entry->text_time;
191
192  return SVN_NO_ERROR;
193}
194
195
196/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE
197 * differs from BASE_FILE, else to FALSE if not.  Also verify that
198 * BASE_FILE matches the entry checksum for VERSIONED_FILE, if
199 * verify_checksum is TRUE. If checksum does not match, return the error
200 * SVN_ERR_WC_CORRUPT_TEXT_BASE.
201 *
202 * ADM_ACCESS is an access baton for VERSIONED_FILE.  Use POOL for
203 * temporary allocation.
204 */
205static svn_error_t *
206compare_and_verify(svn_boolean_t *modified_p,
207                   const char *versioned_file,
208                   svn_wc_adm_access_t *adm_access,
209                   const char *base_file,
210                   svn_boolean_t compare_textbases,
211                   svn_boolean_t verify_checksum,
212                   apr_pool_t *pool)
213{
214  svn_boolean_t same;
215  svn_subst_eol_style_t eol_style;
216  const char *eol_str;
217  apr_hash_t *keywords;
218  svn_boolean_t special;
219  svn_boolean_t need_translation;
220
221
222  SVN_ERR(svn_wc__get_eol_style(&eol_style, &eol_str, versioned_file,
223                                adm_access, pool));
224  SVN_ERR(svn_wc__get_keywords(&keywords, versioned_file,
225                              adm_access, NULL, pool));
226  SVN_ERR(svn_wc__get_special(&special, versioned_file, adm_access, pool));
227
228  need_translation = svn_subst_translation_required(eol_style, eol_str,
229                                                    keywords, special, TRUE);
230
231  if (verify_checksum || need_translation)
232    {
233      /* Reading files is necessary. */
234      svn_checksum_t *checksum;
235      svn_stream_t *v_stream;  /* versioned_file */
236      svn_stream_t *b_stream;  /* base_file */
237      const svn_wc_entry_t *entry;
238
239      SVN_ERR(svn_stream_open_readonly(&b_stream, base_file, pool, pool));
240
241      if (verify_checksum)
242        {
243          /* Need checksum verification, so read checksum from entries file
244           * and setup checksummed stream for base file. */
245          SVN_ERR(svn_wc__entry_versioned(&entry, versioned_file, adm_access,
246                                         TRUE, pool));
247
248          if (entry->checksum)
249            b_stream = svn_stream_checksummed2(b_stream, &checksum, NULL,
250                                               svn_checksum_md5, TRUE, pool);
251        }
252
253      if (special)
254        {
255          SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file,
256                                             pool, pool));
257        }
258      else
259        {
260          SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file,
261                                           pool, pool));
262
263          if (compare_textbases && need_translation)
264            {
265              if (eol_style == svn_subst_eol_style_native)
266                eol_str = SVN_SUBST_NATIVE_EOL_STR;
267              else if (eol_style != svn_subst_eol_style_fixed
268                       && eol_style != svn_subst_eol_style_none)
269                return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
270
271              /* Wrap file stream to detranslate into normal form. */
272              v_stream = svn_subst_stream_translated(v_stream,
273                                                     eol_str,
274                                                     TRUE,
275                                                     keywords,
276                                                     FALSE /* expand */,
277                                                     pool);
278            }
279          else if (need_translation)
280            {
281              /* Wrap base stream to translate into working copy form. */
282              b_stream = svn_subst_stream_translated(b_stream, eol_str,
283                                                     FALSE, keywords, TRUE,
284                                                     pool);
285            }
286        }
287
288      SVN_ERR(svn_stream_contents_same(&same, b_stream, v_stream, pool));
289
290      SVN_ERR(svn_stream_close(v_stream));
291      SVN_ERR(svn_stream_close(b_stream));
292
293      if (verify_checksum && entry->checksum)
294        {
295          const char *digest;
296          digest = svn_checksum_to_cstring_display(checksum, pool);
297          if (strcmp(digest, entry->checksum) != 0)
298            {
299              return svn_error_createf
300                (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
301                  _("Checksum mismatch indicates corrupt text base: '%s'\n"
302                    "   expected:  %s\n"
303                    "     actual:  %s\n"),
304                  svn_path_local_style(base_file, pool),
305                  entry->checksum,
306                  digest);
307            }
308        }
309    }
310  else
311    {
312      /* Translation would be a no-op, so compare the original file. */
313      SVN_ERR(svn_io_files_contents_same_p(&same, base_file, versioned_file,
314                                           pool));
315    }
316
317  *modified_p = (! same);
318
319  return SVN_NO_ERROR;
320}
321
322svn_error_t *
323svn_wc__versioned_file_modcheck(svn_boolean_t *modified_p,
324                                const char *versioned_file,
325                                svn_wc_adm_access_t *adm_access,
326                                const char *base_file,
327                                svn_boolean_t compare_textbases,
328                                apr_pool_t *pool)
329{
330  return compare_and_verify(modified_p, versioned_file, adm_access,
331                            base_file, compare_textbases, FALSE, pool);
332}
333
334svn_error_t *
335svn_wc__text_modified_internal_p(svn_boolean_t *modified_p,
336                                 const char *filename,
337                                 svn_boolean_t force_comparison,
338                                 svn_wc_adm_access_t *adm_access,
339                                 svn_boolean_t compare_textbases,
340                                 apr_pool_t *pool)
341{
342  const char *textbase_filename;
343  svn_node_kind_t kind;
344  svn_error_t *err;
345  apr_finfo_t finfo;
346
347
348  /* No matter which way you look at it, the file needs to exist. */
349  err = svn_io_stat(&finfo, filename,
350                    APR_FINFO_SIZE | APR_FINFO_MTIME | APR_FINFO_TYPE
351                    | APR_FINFO_LINK, pool);
352  if ((err && APR_STATUS_IS_ENOENT(err->apr_err))
353      || (!err && !(finfo.filetype == APR_REG ||
354                    finfo.filetype == APR_LNK)))
355    {
356      /* There is no entity, or, the entity is not a regular file or link.
357         So, it can't be modified. */
358      svn_error_clear(err);
359      *modified_p = FALSE;
360      return SVN_NO_ERROR;
361    }
362  else if (err)
363    return err;
364
365  if (! force_comparison)
366    {
367      const svn_wc_entry_t *entry;
368
369      /* We're allowed to use a heuristic to determine whether files may
370         have changed.  The heuristic has these steps:
371
372
373         1. Compare the working file's size
374            with the size cached in the entries file
375         2. If they differ, do a full file compare
376         3. Compare the working file's timestamp
377            with the timestamp cached in the entries file
378         4. If they differ, do a full file compare
379         5. Otherwise, return indicating an unchanged file.
380
381         There are 2 problematic situations which may occur:
382
383         1. The cached working size is missing
384         --> In this case, we forget we ever tried to compare
385             and skip to the timestamp comparison.  This is
386             because old working copies do not contain cached sizes
387
388         2. The cached timestamp is missing
389         --> In this case, we forget we ever tried to compare
390             and skip to full file comparison.  This is because
391             the timestamp will be removed when the library
392             updates a locally changed file.  (ie, this only happens
393             when the file was locally modified.)
394
395      */
396
397
398      /* Get the entry */
399      err = svn_wc_entry(&entry, filename, adm_access, FALSE, pool);
400      if (err)
401        {
402          svn_error_clear(err);
403          goto compare_them;
404        }
405
406      if (! entry)
407        goto compare_them;
408
409      /* Compare the sizes, if applicable */
410      if (entry->working_size != SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN
411          && finfo.size != entry->working_size)
412        goto compare_them;
413
414
415      /* Compare the timestamps
416
417         Note: text_time == 0 means absent from entries,
418               which also means the timestamps won't be equal,
419               so there's no need to explicitly check the 'absent' value. */
420      if (entry->text_time != finfo.mtime)
421        goto compare_them;
422
423
424      *modified_p = FALSE;
425      return SVN_NO_ERROR;
426    }
427
428 compare_them:
429 /* If there's no text-base file, we have to assume the working file
430     is modified.  For example, a file scheduled for addition but not
431     yet committed. */
432  /* We used to stat for the working base here, but we just give
433     compare_and_verify a try; we'll check for errors afterwards */
434  textbase_filename = svn_wc__text_base_path(filename, FALSE, pool);
435
436  /* Check all bytes, and verify checksum if requested. */
437  {
438    apr_pool_t *subpool = svn_pool_create(pool);
439
440    err = compare_and_verify(modified_p,
441                             filename,
442                             adm_access,
443                             textbase_filename,
444                             compare_textbases,
445                             force_comparison,
446                             subpool);
447    if (err)
448      {
449        svn_error_t *err2;
450
451        err2 = svn_io_check_path(textbase_filename, &kind, pool);
452        if (! err2 && kind != svn_node_file)
453          {
454            svn_error_clear(err);
455            *modified_p = TRUE;
456            return SVN_NO_ERROR;
457          }
458
459        svn_error_clear(err);
460        return err2;
461      }
462
463    svn_pool_destroy(subpool);
464  }
465
466  /* It is quite legitimate for modifications to the working copy to
467     produce a timestamp variation with no text variation. If it turns out
468     that there are no differences then we might be able to "repair" the
469     text-time in the entries file and so avoid the expensive file contents
470     comparison in the future.
471     Though less likely, the same may be true for the size
472     of the working file. */
473  if (! *modified_p && svn_wc_adm_locked(adm_access))
474    {
475      svn_wc_entry_t tmp;
476
477      tmp.working_size = finfo.size;
478      tmp.text_time = finfo.mtime;
479      SVN_ERR(svn_wc__entry_modify(adm_access,
480                                   svn_path_basename(filename, pool),
481                                   &tmp,
482                                   SVN_WC__ENTRY_MODIFY_TEXT_TIME
483                                   | SVN_WC__ENTRY_MODIFY_WORKING_SIZE,
484                                   TRUE, pool));
485    }
486
487  return SVN_NO_ERROR;
488}
489
490
491svn_error_t *
492svn_wc_text_modified_p(svn_boolean_t *modified_p,
493                        const char *filename,
494                        svn_boolean_t force_comparison,
495                        svn_wc_adm_access_t *adm_access,
496                        apr_pool_t *pool)
497{
498  return svn_wc__text_modified_internal_p(modified_p, filename,
499                                          force_comparison, adm_access,
500                                          TRUE, pool);
501}
502
503
504
505svn_error_t *
506svn_wc_conflicted_p2(svn_boolean_t *text_conflicted_p,
507                     svn_boolean_t *prop_conflicted_p,
508                     svn_boolean_t *tree_conflicted_p,
509                     const char *path,
510                     svn_wc_adm_access_t *adm_access,
511                     apr_pool_t *pool)
512{
513  svn_node_kind_t kind;
514  const svn_wc_entry_t *entry;
515  const char* dir_path = svn_path_dirname(path, pool);
516
517  SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool));
518
519  if (text_conflicted_p)
520    {
521      *text_conflicted_p = FALSE;
522
523      if (entry)
524        {
525          /* Look for any text conflict, exercising only as much effort as
526             necessary to obtain a definitive answer.  This only applies to
527             files, but we don't have to explicitly check that entry is a
528             file, since these attributes would never be set on a directory
529             anyway.  A conflict file entry notation only counts if the
530             conflict file still exists on disk.  */
531
532          if (entry->conflict_old)
533            {
534              path = svn_path_join(dir_path, entry->conflict_old, pool);
535              SVN_ERR(svn_io_check_path(path, &kind, pool));
536              *text_conflicted_p = (kind == svn_node_file);
537            }
538
539          if ((! *text_conflicted_p) && (entry->conflict_new))
540            {
541              path = svn_path_join(dir_path, entry->conflict_new, pool);
542              SVN_ERR(svn_io_check_path(path, &kind, pool));
543              *text_conflicted_p = (kind == svn_node_file);
544            }
545
546          if ((! *text_conflicted_p) && (entry->conflict_wrk))
547            {
548              path = svn_path_join(dir_path, entry->conflict_wrk, pool);
549              SVN_ERR(svn_io_check_path(path, &kind, pool));
550              *text_conflicted_p = (kind == svn_node_file);
551            }
552        }
553    }
554
555  /* What about prop conflicts? */
556  if (prop_conflicted_p)
557    {
558      *prop_conflicted_p = FALSE;
559
560      if (entry && entry->prejfile)
561        {
562          /* A dir's .prej file is _inside_ the dir. */
563          if (entry->kind == svn_node_dir)
564            path = svn_path_join(path, entry->prejfile, pool);
565          else
566            path = svn_path_join(dir_path, entry->prejfile, pool);
567
568          SVN_ERR(svn_io_check_path(path, &kind, pool));
569          *prop_conflicted_p = (kind == svn_node_file);
570        }
571    }
572
573  /* Find out whether it's a tree conflict victim. */
574  if (tree_conflicted_p)
575    {
576      svn_wc_conflict_description_t *conflict;
577
578      SVN_ERR_ASSERT(adm_access != NULL);
579      SVN_ERR(svn_wc__get_tree_conflict(&conflict, path, adm_access, pool));
580      *tree_conflicted_p = (conflict != NULL);
581    }
582
583  return SVN_NO_ERROR;
584}
585
586svn_error_t *
587svn_wc_conflicted_p(svn_boolean_t *text_conflicted_p,
588                    svn_boolean_t *prop_conflicted_p,
589                    const char *dir_path,
590                    const svn_wc_entry_t *entry,
591                    apr_pool_t *pool)
592{
593  svn_node_kind_t kind;
594  const char *path;
595
596  *text_conflicted_p = FALSE;
597  *prop_conflicted_p = FALSE;
598
599  if (entry->conflict_old)
600    {
601      path = svn_path_join(dir_path, entry->conflict_old, pool);
602      SVN_ERR(svn_io_check_path(path, &kind, pool));
603      *text_conflicted_p = (kind == svn_node_file);
604    }
605
606  if ((! *text_conflicted_p) && (entry->conflict_new))
607    {
608      path = svn_path_join(dir_path, entry->conflict_new, pool);
609      SVN_ERR(svn_io_check_path(path, &kind, pool));
610      *text_conflicted_p = (kind == svn_node_file);
611    }
612
613  if ((! *text_conflicted_p) && (entry->conflict_wrk))
614    {
615      path = svn_path_join(dir_path, entry->conflict_wrk, pool);
616      SVN_ERR(svn_io_check_path(path, &kind, pool));
617      *text_conflicted_p = (kind == svn_node_file);
618    }
619
620  if (entry->prejfile)
621    {
622      path = svn_path_join(dir_path, entry->prejfile, pool);
623      SVN_ERR(svn_io_check_path(path, &kind, pool));
624      *prop_conflicted_p = (kind == svn_node_file);
625    }
626
627  return SVN_NO_ERROR;
628}
629
630
631
632svn_error_t *
633svn_wc_has_binary_prop(svn_boolean_t *has_binary_prop,
634                       const char *path,
635                       svn_wc_adm_access_t *adm_access,
636                       apr_pool_t *pool)
637{
638  const svn_string_t *value;
639  apr_pool_t *subpool = svn_pool_create(pool);
640
641  SVN_ERR(svn_wc_prop_get(&value, SVN_PROP_MIME_TYPE, path, adm_access,
642                          subpool));
643
644  if (value && (svn_mime_type_is_binary(value->data)))
645    *has_binary_prop = TRUE;
646  else
647    *has_binary_prop = FALSE;
648
649  svn_pool_destroy(subpool);
650  return SVN_NO_ERROR;
651}
Note: See TracBrowser for help on using the repository browser.