]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_wc/questions.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_wc / questions.c
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 \f
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 \f
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 *
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   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
209 svn_error_t *
210 svn_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
345 svn_error_t *
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)
351 {
352   return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
353                                           local_abspath, FALSE, scratch_pool);
354 }
355
356
357 \f
358 static svn_error_t *
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,
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                                                       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
511 svn_error_t *
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,
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
525 svn_error_t *
526 svn_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
552 svn_error_t *
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)
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
568 svn_error_t *
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)
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
585 svn_error_t *
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)
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(). */
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 */
605 } modcheck_baton_t;
606
607 /* An implementation of svn_wc_status_func4_t. */
608 static svn_error_t *
609 modcheck_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. */
679 svn_error_t *
680 svn_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
727 svn_error_t *
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,
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 }