questions.c revision 362181
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 */
102static svn_error_t *
103compare_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  svn_stream_t *v_stream; /* versioned_file */
121
122  SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
123
124  if (props_mod)
125    has_props = TRUE; /* Maybe it didn't have properties; but it has now */
126
127  if (has_props)
128    {
129      SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
130                                         &keywords,
131                                         &special,
132                                         db, versioned_file_abspath, NULL,
133                                         !exact_comparison,
134                                         scratch_pool, scratch_pool));
135
136      need_translation = svn_subst_translation_required(eol_style, eol_str,
137                                                        keywords, special,
138                                                        TRUE);
139    }
140  else
141    need_translation = FALSE;
142
143  if (! need_translation
144      && (versioned_file_size != pristine_size))
145    {
146      *modified_p = TRUE;
147
148      /* ### Why did we open the pristine? */
149      return svn_error_trace(svn_stream_close(pristine_stream));
150    }
151
152  /* ### Other checks possible? */
153
154  /* Reading files is necessary. */
155  if (special && need_translation)
156    {
157      SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
158                                          scratch_pool, scratch_pool));
159    }
160  else
161    {
162      /* We don't use APR-level buffering because the comparison function
163       * will do its own buffering. */
164      apr_file_t *file;
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);
168
169      if (need_translation)
170        {
171          if (!exact_comparison)
172            {
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);
179
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,
183                                                     eol_str,
184                                                     TRUE /* repair */,
185                                                     keywords,
186                                                     FALSE /* expand */,
187                                                     scratch_pool);
188            }
189          else
190            {
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,
194                                                            eol_str, FALSE,
195                                                            keywords, TRUE,
196                                                            scratch_pool);
197            }
198        }
199    }
200
201  SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
202                                    scratch_pool));
203
204  *modified_p = (! same);
205
206  return SVN_NO_ERROR;
207}
208
209svn_error_t *
210svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
211                                 svn_wc__db_t *db,
212                                 const char *local_abspath,
213                                 svn_boolean_t exact_comparison,
214                                 apr_pool_t *scratch_pool)
215{
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;
226
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,
230                               NULL, NULL, NULL,
231                               &recorded_size, &recorded_mod_time,
232                               NULL, NULL, NULL, &has_props, &props_mod,
233                               NULL, NULL, NULL,
234                               db, local_abspath,
235                               scratch_pool, scratch_pool));
236
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 */
239  if (!checksum
240      || (kind != svn_node_file)
241      || ((status != svn_wc__db_status_normal)
242          && (status != svn_wc__db_status_added)))
243    {
244      *modified_p = TRUE;
245      return SVN_NO_ERROR;
246    }
247
248  SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
249                              scratch_pool, scratch_pool));
250
251  if (dirent->kind != svn_node_file)
252    {
253      /* There is no file on disk, so the text is missing, not modified. */
254      *modified_p = FALSE;
255      return SVN_NO_ERROR;
256    }
257
258  if (! exact_comparison)
259    {
260      /* We're allowed to use a heuristic to determine whether files may
261         have changed.  The heuristic has these steps:
262
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.
270
271         There are 2 problematic situations which may occur:
272
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
277
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.)
284
285      */
286
287      /* Compare the sizes, if applicable */
288      if (recorded_size != SVN_INVALID_FILESIZE
289          && dirent->filesize != recorded_size)
290        goto compare_them;
291
292      /* Compare the timestamps
293
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)
298        goto compare_them;
299
300      *modified_p = FALSE;
301      return SVN_NO_ERROR;
302    }
303
304 compare_them:
305  SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
306                                   db, local_abspath, checksum,
307                                   scratch_pool, scratch_pool));
308
309  /* Check all bytes, and verify checksum if requested. */
310  {
311    svn_error_t *err;
312    err = compare_and_verify(modified_p, db,
313                             local_abspath, dirent->filesize,
314                             pristine_stream, pristine_size,
315                             has_props, props_mod,
316                             exact_comparison,
317                             scratch_pool);
318
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);
323    else
324      SVN_ERR(err);
325  }
326
327  if (!*modified_p)
328    {
329      svn_boolean_t own_lock;
330
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,
333                                          scratch_pool));
334      if (own_lock)
335        SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
336                                                  dirent->filesize,
337                                                  dirent->mtime,
338                                                  scratch_pool));
339    }
340
341  return SVN_NO_ERROR;
342}
343
344
345svn_error_t *
346svn_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)
351{
352  return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
353                                          local_abspath, FALSE, scratch_pool);
354}
355
356
357
358static svn_error_t *
359internal_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,
363                      svn_wc__db_t *db,
364                      const char *local_abspath,
365                      apr_pool_t *scratch_pool)
366{
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;
371
372  SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL,
373                                   db, local_abspath,
374                                   scratch_pool, scratch_pool));
375
376  if (!conflicts)
377    {
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;
386
387      return SVN_NO_ERROR;
388    }
389
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));
394
395  if (text_conflicted_p && *text_conflicted_p)
396    {
397      const char *mine_abspath;
398      const char *their_old_abspath;
399      const char *their_abspath;
400      svn_boolean_t done = FALSE;
401
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.  */
408
409      SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
410                                                  &their_old_abspath,
411                                                  &their_abspath,
412                                                  db, local_abspath, conflicts,
413                                                  scratch_pool, scratch_pool));
414
415      if (mine_abspath)
416        {
417          SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
418
419          *text_conflicted_p = (kind == svn_node_file);
420
421          if (*text_conflicted_p)
422            done = TRUE;
423        }
424
425      if (!done && their_abspath)
426        {
427          SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool));
428
429          *text_conflicted_p = (kind == svn_node_file);
430
431          if (*text_conflicted_p)
432            done = TRUE;
433        }
434
435        if (!done && their_old_abspath)
436        {
437          SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool));
438
439          *text_conflicted_p = (kind == svn_node_file);
440
441          if (*text_conflicted_p)
442            done = TRUE;
443        }
444
445        if (!done && (mine_abspath || their_abspath || their_old_abspath))
446          resolved_text = TRUE; /* Remove in-db conflict marker */
447    }
448
449  if (prop_conflicted_p && *prop_conflicted_p)
450    {
451      const char *prej_abspath;
452
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));
457
458      if (prej_abspath)
459        {
460          SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool));
461
462          *prop_conflicted_p = (kind == svn_node_file);
463
464          if (! *prop_conflicted_p)
465            resolved_props = TRUE; /* Remove in-db conflict marker */
466        }
467    }
468
469  if (ignore_move_edit_p)
470    {
471      *ignore_move_edit_p = FALSE;
472      if (tree_conflicted_p && *tree_conflicted_p)
473        {
474          svn_wc_conflict_reason_t reason;
475          svn_wc_conflict_action_t action;
476
477          SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
478                                                      NULL, db, local_abspath,
479                                                      conflicts,
480                                                      scratch_pool,
481                                                      scratch_pool));
482
483          if (reason == svn_wc_conflict_reason_moved_away
484              && action == svn_wc_conflict_action_edit)
485            {
486              *tree_conflicted_p = FALSE;
487              *ignore_move_edit_p = TRUE;
488            }
489        }
490    }
491
492  if (resolved_text || resolved_props)
493    {
494      svn_boolean_t own_lock;
495
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,
498                                          scratch_pool));
499      if (own_lock)
500        SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
501                                            resolved_text,
502                                            resolved_props,
503                                            FALSE /* resolved_tree */,
504                                            NULL /* work_items */,
505                                            scratch_pool));
506    }
507
508  return SVN_NO_ERROR;
509}
510
511svn_error_t *
512svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
513                              svn_boolean_t *prop_conflicted_p,
514                              svn_boolean_t *tree_conflicted_p,
515                              svn_wc__db_t *db,
516                              const char *local_abspath,
517                              apr_pool_t *scratch_pool)
518{
519  SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p,
520                                tree_conflicted_p, NULL,
521                                db, local_abspath, scratch_pool));
522  return SVN_NO_ERROR;
523}
524
525svn_error_t *
526svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
527                                svn_boolean_t *conflict_ignored_p,
528                                svn_wc__db_t *db,
529                                const char *local_abspath,
530                                svn_boolean_t tree_only,
531                                apr_pool_t *scratch_pool)
532{
533  svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
534  svn_boolean_t conflict_ignored;
535
536  if (!conflict_ignored_p)
537    conflict_ignored_p = &conflict_ignored;
538
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));
543  if (tree_only)
544    *conflicted_p = tree_conflicted;
545  else
546    *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted;
547
548  return SVN_NO_ERROR;
549}
550
551
552svn_error_t *
553svn_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)
559{
560  return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p,
561                                                       prop_conflicted_p,
562                                                       tree_conflicted_p,
563                                                       wc_ctx->db,
564                                                       local_abspath,
565                                                       scratch_pool));
566}
567
568svn_error_t *
569svn_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)
575{
576  return svn_error_trace(svn_wc__db_min_max_revisions(min_revision,
577                                                      max_revision,
578                                                      wc_ctx->db,
579                                                      local_abspath,
580                                                      committed,
581                                                      scratch_pool));
582}
583
584
585svn_error_t *
586svn_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)
591{
592  return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched,
593                                                          wc_ctx->db,
594                                                          local_abspath,
595                                                          trail_url,
596                                                          scratch_pool));
597}
598
599
600/* A baton for use with modcheck_found_entry(). */
601typedef 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 */
605} modcheck_baton_t;
606
607/* An implementation of svn_wc_status_func4_t. */
608static svn_error_t *
609modcheck_callback(void *baton,
610                  const char *local_abspath,
611                  const svn_wc_status3_t *status,
612                  apr_pool_t *scratch_pool)
613{
614  modcheck_baton_t *mb = baton;
615
616  switch (status->node_status)
617    {
618      case svn_wc_status_normal:
619      case svn_wc_status_ignored:
620      case svn_wc_status_none:
621      case svn_wc_status_external:
622        break;
623
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))
629          {
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);
634          }
635        break;
636
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)
642          {
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);
646          }
647        break;
648
649      case svn_wc_status_unversioned:
650        if (mb->ignore_unversioned)
651          break;
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);
659
660      default:
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);
668    }
669
670  return SVN_NO_ERROR;
671}
672
673
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. */
679svn_error_t *
680svn_wc__node_has_local_mods(svn_boolean_t *modified,
681                            svn_boolean_t *all_edits_are_deletes,
682                            svn_wc__db_t *db,
683                            const char *local_abspath,
684                            svn_boolean_t ignore_unversioned,
685                            svn_cancel_func_t cancel_func,
686                            void *cancel_baton,
687                            apr_pool_t *scratch_pool)
688{
689  modcheck_baton_t modcheck_baton = { FALSE, FALSE, FALSE };
690  svn_error_t *err;
691
692  if (!all_edits_are_deletes)
693    {
694      SVN_ERR(svn_wc__db_has_db_mods(modified, db, local_abspath,
695                                     scratch_pool));
696
697      if (*modified)
698        return SVN_NO_ERROR;
699    }
700
701  modcheck_baton.ignore_unversioned = ignore_unversioned;
702
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. */
706
707  err = svn_wc__internal_walk_status(db, local_abspath,
708                                     svn_depth_infinity,
709                                     FALSE, FALSE, FALSE, NULL,
710                                     modcheck_callback, &modcheck_baton,
711                                     cancel_func, cancel_baton,
712                                     scratch_pool);
713
714  if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
715    svn_error_clear(err);
716  else
717    SVN_ERR(err);
718
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);
723
724  return SVN_NO_ERROR;
725}
726
727svn_error_t *
728svn_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,
733                       void *cancel_baton,
734                       apr_pool_t *scratch_pool)
735{
736  svn_boolean_t modified;
737
738  SVN_ERR(svn_wc__node_has_local_mods(&modified, NULL,
739                                      wc_ctx->db, local_abspath,
740                                      ignore_unversioned,
741                                      cancel_func, cancel_baton,
742                                      scratch_pool));
743
744  *is_modified = modified;
745  return SVN_NO_ERROR;
746}
747