Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_wc / adm_ops.c
blobe96337ea7267e5011cdeb99bee6d1ad820890ed6
1 /*
2 * adm_ops.c: routines for affecting working copy administrative
3 * information. NOTE: this code doesn't know where the adm
4 * info is actually stored. Instead, generic handles to
5 * adm data are requested via a reference to some PATH
6 * (PATH being a regular, non-administrative directory or
7 * file in the working copy).
9 * ====================================================================
10 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
12 * This software is licensed as described in the file COPYING, which
13 * you should have received as part of this distribution. The terms
14 * are also available at http://subversion.tigris.org/license-1.html.
15 * If newer versions of this license are posted there, you may use a
16 * newer version instead, at your option.
18 * This software consists of voluntary contributions made by many
19 * individuals. For exact contribution history, see the revision
20 * history and logs, available at http://subversion.tigris.org/.
21 * ====================================================================
26 #include <string.h>
28 #include <apr_pools.h>
29 #include <apr_tables.h>
30 #include <apr_hash.h>
31 #include <apr_md5.h>
32 #include <apr_file_io.h>
33 #include <apr_time.h>
34 #include <apr_errno.h>
36 #include "svn_types.h"
37 #include "svn_pools.h"
38 #include "svn_string.h"
39 #include "svn_error.h"
40 #include "svn_path.h"
41 #include "svn_hash.h"
42 #include "svn_wc.h"
43 #include "svn_io.h"
44 #include "svn_md5.h"
45 #include "svn_xml.h"
46 #include "svn_time.h"
48 #include "wc.h"
49 #include "log.h"
50 #include "adm_files.h"
51 #include "adm_ops.h"
52 #include "entries.h"
53 #include "lock.h"
54 #include "props.h"
55 #include "translate.h"
57 #include "svn_private_config.h"
58 #include "private/svn_wc_private.h"
61 /*** Finishing updates and commits. ***/
64 /* The main body of svn_wc__do_update_cleanup. */
65 static svn_error_t *
66 tweak_entries(svn_wc_adm_access_t *dirpath,
67 const char *base_url,
68 const char *repos,
69 svn_revnum_t new_rev,
70 svn_wc_notify_func2_t notify_func,
71 void *notify_baton,
72 svn_boolean_t remove_missing_dirs,
73 svn_depth_t depth,
74 apr_hash_t *exclude_paths,
75 apr_pool_t *pool)
77 apr_hash_t *entries;
78 apr_hash_index_t *hi;
79 apr_pool_t *subpool = svn_pool_create(pool);
80 svn_boolean_t write_required = FALSE;
81 svn_wc_notify_t *notify;
83 /* Read DIRPATH's entries. */
84 SVN_ERR(svn_wc_entries_read(&entries, dirpath, TRUE, pool));
86 /* Tweak "this_dir" */
87 if (! apr_hash_get(exclude_paths, svn_wc_adm_access_path(dirpath),
88 APR_HASH_KEY_STRING))
89 SVN_ERR(svn_wc__tweak_entry(entries, SVN_WC_ENTRY_THIS_DIR,
90 base_url, repos, new_rev, FALSE,
91 &write_required,
92 svn_wc_adm_access_pool(dirpath)));
94 if (depth == svn_depth_unknown)
95 depth = svn_depth_infinity;
97 if (depth > svn_depth_empty)
99 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
101 const void *key;
102 void *val;
103 const char *name;
104 svn_wc_entry_t *current_entry;
105 const char *child_path;
106 const char *child_url = NULL;
107 svn_boolean_t excluded;
109 svn_pool_clear(subpool);
111 apr_hash_this(hi, &key, NULL, &val);
112 name = key;
113 current_entry = val;
115 /* Ignore the "this dir" entry. */
116 if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR))
117 continue;
119 /* Derive the new URL for the current (child) entry */
120 if (base_url)
121 child_url = svn_path_url_add_component(base_url, name, subpool);
123 child_path = svn_path_join(svn_wc_adm_access_path(dirpath), name,
124 subpool);
125 excluded = (apr_hash_get(exclude_paths, child_path,
126 APR_HASH_KEY_STRING) != NULL);
128 /* If a file, or deleted or absent dir, then tweak the entry
129 but don't recurse. */
130 if ((current_entry->kind == svn_node_file)
131 || (current_entry->deleted || current_entry->absent))
133 if (! excluded)
134 SVN_ERR(svn_wc__tweak_entry(entries, name,
135 child_url, repos, new_rev, TRUE,
136 &write_required,
137 svn_wc_adm_access_pool(dirpath)));
140 /* If a directory and recursive... */
141 else if ((depth == svn_depth_infinity
142 || depth == svn_depth_immediates)
143 && (current_entry->kind == svn_node_dir))
145 svn_depth_t depth_below_here = depth;
147 if (depth == svn_depth_immediates)
148 depth_below_here = svn_depth_empty;
150 /* If the directory is 'missing', remove it. This is safe as
151 long as this function is only called as a helper to
152 svn_wc__do_update_cleanup, since the update will already have
153 restored any missing items that it didn't want to delete. */
154 if (remove_missing_dirs
155 && svn_wc__adm_missing(dirpath, child_path))
157 if (current_entry->schedule != svn_wc_schedule_add
158 && !excluded)
160 svn_wc__entry_remove(entries, name);
161 if (notify_func)
163 notify = svn_wc_create_notify(child_path,
164 svn_wc_notify_delete,
165 subpool);
166 notify->kind = current_entry->kind;
167 (* notify_func)(notify_baton, notify, subpool);
170 /* Else if missing item is schedule-add, do nothing. */
173 /* Not missing, deleted, or absent, so recurse. */
174 else
176 svn_wc_adm_access_t *child_access;
177 SVN_ERR(svn_wc_adm_retrieve(&child_access, dirpath,
178 child_path, subpool));
179 SVN_ERR(tweak_entries
180 (child_access, child_url, repos, new_rev,
181 notify_func, notify_baton, remove_missing_dirs,
182 depth_below_here, exclude_paths, subpool));
188 /* Write a shiny new entries file to disk. */
189 if (write_required)
190 SVN_ERR(svn_wc__entries_write(entries, dirpath, subpool));
192 /* Cleanup */
193 svn_pool_destroy(subpool);
195 return SVN_NO_ERROR;
198 /* Helper for svn_wc_process_committed2. */
199 static svn_error_t *
200 remove_revert_file(svn_stringbuf_t **logtags,
201 svn_wc_adm_access_t *adm_access,
202 const char *path,
203 svn_boolean_t is_prop,
204 apr_pool_t * pool)
206 const char *revert_file;
207 svn_node_kind_t kind;
209 if (is_prop)
210 SVN_ERR(svn_wc__loggy_props_delete(logtags, path, svn_wc__props_revert,
211 adm_access, pool));
212 else
214 revert_file = svn_wc__text_revert_path(path, FALSE, pool);
216 SVN_ERR(svn_io_check_path(revert_file, &kind, pool));
218 if (kind == svn_node_file)
219 SVN_ERR(svn_wc__loggy_remove(logtags, adm_access, revert_file, pool));
222 return SVN_NO_ERROR;
225 svn_error_t *
226 svn_wc__do_update_cleanup(const char *path,
227 svn_wc_adm_access_t *adm_access,
228 svn_depth_t depth,
229 const char *base_url,
230 const char *repos,
231 svn_revnum_t new_revision,
232 svn_wc_notify_func2_t notify_func,
233 void *notify_baton,
234 svn_boolean_t remove_missing_dirs,
235 apr_hash_t *exclude_paths,
236 apr_pool_t *pool)
238 apr_hash_t *entries;
239 const svn_wc_entry_t *entry;
241 SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool));
242 if (entry == NULL)
243 return SVN_NO_ERROR;
245 if (entry->kind == svn_node_file
246 || (entry->kind == svn_node_dir && (entry->deleted || entry->absent)))
248 const char *parent, *base_name;
249 svn_wc_adm_access_t *dir_access;
250 svn_boolean_t write_required = FALSE;
251 if (apr_hash_get(exclude_paths, path, APR_HASH_KEY_STRING))
252 return SVN_NO_ERROR;
253 svn_path_split(path, &parent, &base_name, pool);
254 SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, parent, pool));
255 SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool));
256 SVN_ERR(svn_wc__tweak_entry(entries, base_name,
257 base_url, repos, new_revision,
258 FALSE, /* Parent not updated so don't
259 remove PATH entry */
260 &write_required,
261 svn_wc_adm_access_pool(dir_access)));
262 if (write_required)
263 SVN_ERR(svn_wc__entries_write(entries, dir_access, pool));
266 else if (entry->kind == svn_node_dir)
268 svn_wc_adm_access_t *dir_access;
269 SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, path, pool));
271 SVN_ERR(tweak_entries(dir_access, base_url, repos, new_revision,
272 notify_func, notify_baton, remove_missing_dirs,
273 depth, exclude_paths, pool));
276 else
277 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
278 _("Unrecognized node kind: '%s'"),
279 svn_path_local_style(path, pool));
281 return SVN_NO_ERROR;
284 svn_error_t *
285 svn_wc_maybe_set_repos_root(svn_wc_adm_access_t *adm_access,
286 const char *path,
287 const char *repos,
288 apr_pool_t *pool)
290 apr_hash_t *entries;
291 svn_boolean_t write_required = FALSE;
292 const svn_wc_entry_t *entry;
293 const char *base_name;
294 svn_wc_adm_access_t *dir_access;
296 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
297 if (! entry)
298 return SVN_NO_ERROR;
300 if (entry->kind == svn_node_file)
302 const char *parent;
304 svn_path_split(path, &parent, &base_name, pool);
305 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access,
306 parent, pool));
308 else
310 base_name = SVN_WC_ENTRY_THIS_DIR;
311 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access,
312 path, pool));
315 if (! dir_access)
316 return SVN_NO_ERROR;
318 SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool));
320 SVN_ERR(svn_wc__tweak_entry(entries, base_name,
321 NULL, repos, SVN_INVALID_REVNUM, FALSE,
322 &write_required,
323 svn_wc_adm_access_pool(dir_access)));
325 if (write_required)
326 SVN_ERR(svn_wc__entries_write(entries, dir_access, pool));
328 return SVN_NO_ERROR;
332 static svn_error_t *
333 process_committed_leaf(int log_number,
334 const char *path,
335 svn_wc_adm_access_t *adm_access,
336 svn_boolean_t *recurse,
337 svn_revnum_t new_revnum,
338 const char *rev_date,
339 const char *rev_author,
340 apr_array_header_t *wcprop_changes,
341 svn_boolean_t remove_lock,
342 svn_boolean_t remove_changelist,
343 const unsigned char *digest,
344 apr_pool_t *pool)
346 const char *base_name;
347 const char *hex_digest = NULL;
348 svn_wc_entry_t tmp_entry;
349 apr_uint64_t modify_flags = 0;
350 svn_stringbuf_t *logtags = svn_stringbuf_create("", pool);
352 SVN_ERR(svn_wc__adm_write_check(adm_access));
354 /* Set PATH's working revision to NEW_REVNUM; if REV_DATE and
355 REV_AUTHOR are both non-NULL, then set the 'committed-rev',
356 'committed-date', and 'last-author' entry values; and set the
357 checksum if a file. */
359 base_name = svn_path_is_child(svn_wc_adm_access_path(adm_access), path,
360 pool);
361 if (base_name)
363 /* If the props or text revert file exists it needs to be deleted when
364 * the file is committed. */
365 SVN_ERR(remove_revert_file(&logtags, adm_access, path, FALSE, pool));
366 SVN_ERR(remove_revert_file(&logtags, adm_access, path, TRUE, pool));
368 if (digest)
369 hex_digest = svn_md5_digest_to_cstring(digest, pool);
370 else
372 /* There may be a new text base sitting in the adm tmp area
373 by now, because the commit succeeded. A file that is
374 copied, but not otherwise modified, doesn't have a new
375 text base, so we use the unmodified text base.
377 ### Does this mean that a file committed with only prop mods
378 ### will still get its text base checksum recomputed? Yes it
379 ### does, sadly. But it's not enough to just check for that
380 ### condition, because in the case of an added file, there
381 ### may not be a pre-existing checksum in the entry.
382 ### Probably the best solution is to compute (or copy) the
383 ### checksum at 'svn add' (or 'svn cp') time, instead of
384 ### waiting until commit time.
386 const char *latest_base;
387 svn_error_t *err;
388 unsigned char local_digest[APR_MD5_DIGESTSIZE];
390 latest_base = svn_wc__text_base_path(path, TRUE, pool);
391 err = svn_io_file_checksum(local_digest, latest_base, pool);
393 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
395 svn_error_clear(err);
396 latest_base = svn_wc__text_base_path(path, FALSE, pool);
397 err = svn_io_file_checksum(local_digest, latest_base, pool);
400 if (! err)
401 hex_digest = svn_md5_digest_to_cstring(local_digest, pool);
402 else if (APR_STATUS_IS_ENOENT(err->apr_err))
403 svn_error_clear(err);
404 else
405 return err;
408 /* Oh, and recursing at this point isn't really sensible. */
409 if (recurse)
410 *recurse = FALSE;
412 else
414 /* PATH must be a dir */
415 base_name = SVN_WC_ENTRY_THIS_DIR;
419 /* Append a log command to set (overwrite) the 'committed-rev',
420 'committed-date', 'last-author', and possibly 'checksum'
421 attributes in the entry.
423 Note: it's important that this log command come *before* the
424 LOG_COMMITTED command, because log_do_committed() might actually
425 remove the entry! */
426 if (rev_date)
428 tmp_entry.cmt_rev = new_revnum;
429 SVN_ERR(svn_time_from_cstring(&tmp_entry.cmt_date, rev_date, pool));
430 modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV
431 | SVN_WC__ENTRY_MODIFY_CMT_DATE;
434 if (rev_author)
436 tmp_entry.cmt_rev = new_revnum;
437 tmp_entry.cmt_author = rev_author;
438 modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV
439 | SVN_WC__ENTRY_MODIFY_CMT_AUTHOR;
442 if (hex_digest)
444 tmp_entry.checksum = hex_digest;
445 modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM;
448 SVN_ERR(svn_wc__loggy_entry_modify(&logtags, adm_access,
449 path, &tmp_entry, modify_flags, pool));
451 if (remove_lock)
452 SVN_ERR(svn_wc__loggy_delete_lock(&logtags, adm_access, path, pool));
454 if (remove_changelist)
455 SVN_ERR(svn_wc__loggy_delete_changelist(&logtags, adm_access, path, pool));
457 /* Regardless of whether it's a file or dir, the "main" logfile
458 contains a command to bump the revision attribute (and
459 timestamp). */
460 SVN_ERR(svn_wc__loggy_committed(&logtags, adm_access,
461 path, new_revnum, pool));
464 /* Do wcprops in the same log txn as revision, etc. */
465 if (wcprop_changes && (wcprop_changes->nelts > 0))
467 int i;
469 for (i = 0; i < wcprop_changes->nelts; i++)
471 svn_prop_t *prop = APR_ARRAY_IDX(wcprop_changes, i, svn_prop_t *);
473 SVN_ERR(svn_wc__loggy_modify_wcprop
474 (&logtags, adm_access,
475 path, prop->name,
476 prop->value ? prop->value->data : NULL,
477 pool));
481 /* Write our accumulation of log entries into a log file */
482 SVN_ERR(svn_wc__write_log(adm_access, log_number, logtags, pool));
484 return SVN_NO_ERROR;
488 static svn_error_t *
489 process_committed_internal(int *log_number,
490 const char *path,
491 svn_wc_adm_access_t *adm_access,
492 svn_boolean_t recurse,
493 svn_revnum_t new_revnum,
494 const char *rev_date,
495 const char *rev_author,
496 apr_array_header_t *wcprop_changes,
497 svn_boolean_t remove_lock,
498 svn_boolean_t remove_changelist,
499 const unsigned char *digest,
500 apr_pool_t *pool)
502 SVN_ERR(process_committed_leaf((*log_number)++, path, adm_access, &recurse,
503 new_revnum, rev_date, rev_author,
504 wcprop_changes,
505 remove_lock, remove_changelist,
506 digest, pool));
508 if (recurse)
510 apr_hash_t *entries;
511 apr_hash_index_t *hi;
512 apr_pool_t *subpool = svn_pool_create(pool);
514 /* Read PATH's entries; this is the absolute path. */
515 SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
517 /* Recursively loop over all children. */
518 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
520 const void *key;
521 void *val;
522 const char *name;
523 const svn_wc_entry_t *current_entry;
524 const char *this_path;
525 svn_wc_adm_access_t *child_access;
527 svn_pool_clear(subpool);
529 apr_hash_this(hi, &key, NULL, &val);
530 name = key;
531 current_entry = val;
533 /* Ignore the "this dir" entry. */
534 if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR))
535 continue;
537 /* Create child path by telescoping the main path. */
538 this_path = svn_path_join(path, name, subpool);
540 if (current_entry->kind == svn_node_dir)
541 SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, this_path,
542 subpool));
543 else
544 child_access = adm_access;
546 /* Recurse, but only allow further recursion if the child is
547 a directory. Pass null for wcprop_changes, because the
548 ones present in the current call are only applicable to
549 this one committed item. */
550 if (current_entry->kind == svn_node_dir)
551 SVN_ERR(svn_wc_process_committed4
552 (this_path, child_access,
553 TRUE,
554 new_revnum, rev_date, rev_author, NULL, FALSE,
555 remove_changelist, NULL, subpool));
556 else
558 /* Suppress log creation for deleted entries in a replaced
559 directory. By the time any log we create here is run,
560 those entries will already have been removed (as a result
561 of running the log for the replaced directory that was
562 created at the start of this function). */
563 if (current_entry->schedule == svn_wc_schedule_delete)
565 svn_wc_entry_t *parent_entry;
567 parent_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
568 APR_HASH_KEY_STRING);
569 if (parent_entry->schedule == svn_wc_schedule_replace)
570 continue;
572 SVN_ERR(process_committed_leaf
573 ((*log_number)++, this_path, adm_access, NULL,
574 new_revnum, rev_date, rev_author, NULL, FALSE,
575 remove_changelist, NULL, subpool));
579 svn_pool_destroy(subpool);
582 return SVN_NO_ERROR;
586 struct svn_wc_committed_queue_t
588 apr_pool_t *pool;
589 apr_array_header_t *queue;
592 typedef struct committed_queue_item_t
594 const char *path;
595 svn_wc_adm_access_t *adm_access;
596 svn_boolean_t recurse;
597 svn_boolean_t remove_lock;
598 svn_boolean_t remove_changelist;
599 apr_array_header_t *wcprop_changes;
600 const unsigned char *digest;
601 } committed_queue_item_t;
604 svn_wc_committed_queue_t *
605 svn_wc_committed_queue_create(apr_pool_t *pool)
607 svn_wc_committed_queue_t *q;
609 q = apr_palloc(pool, sizeof(*q));
610 q->pool = pool;
611 q->queue = apr_array_make(pool, 1, sizeof(committed_queue_item_t *));
613 return q;
616 svn_error_t *
617 svn_wc_queue_committed(svn_wc_committed_queue_t **queue,
618 const char *path,
619 svn_wc_adm_access_t *adm_access,
620 svn_boolean_t recurse,
621 apr_array_header_t *wcprop_changes,
622 svn_boolean_t remove_lock,
623 svn_boolean_t remove_changelist,
624 const unsigned char *digest,
625 apr_pool_t *pool)
627 committed_queue_item_t *cqi;
629 /* Use the same pool as the one *QUEUE was allocated in,
630 to prevent lifetime issues. Intermediate operations
631 should use POOL. */
633 /* Add to the array with paths and options */
634 cqi = apr_palloc((*queue)->pool, sizeof(*cqi));
635 cqi->path = path;
636 cqi->adm_access = adm_access;
637 cqi->recurse = recurse;
638 cqi->remove_lock = remove_lock;
639 cqi->remove_changelist = remove_changelist;
640 cqi->wcprop_changes = wcprop_changes;
641 cqi->digest = digest;
643 APR_ARRAY_PUSH((*queue)->queue, committed_queue_item_t *) = cqi;
645 return SVN_NO_ERROR;
648 typedef struct affected_adm_t
650 int next_log;
651 svn_wc_adm_access_t *adm_access;
652 } affected_adm_t;
655 /* Return TRUE if any item of QUEUE
656 is a parent of ITEM and will be processed recursively,
657 return FALSE otherwise.
659 If HAVE_ANY_RECURSIVE is FALSE, exit early returning FALSE.
660 Recalculate its value otherwise, changing it to FALSE
661 iff no recursive items are found.
663 static svn_boolean_t
664 have_recursive_parent(svn_boolean_t *have_any_recursive,
665 apr_array_header_t *queue,
666 int item,
667 apr_pool_t *pool)
669 int i;
670 svn_boolean_t found_recursive = FALSE;
671 const char *path
672 = APR_ARRAY_IDX(queue, item, committed_queue_item_t *)->path;
674 if (! *have_any_recursive)
675 return FALSE;
677 for (i = 0; i < queue->nelts; i++)
679 committed_queue_item_t *qi
680 = APR_ARRAY_IDX(queue, i, committed_queue_item_t *);
682 found_recursive |= qi->recurse;
684 if (i == item)
685 continue;
687 if (qi->recurse
688 && svn_path_is_child(qi->path, path, pool))
689 return TRUE;
692 /* Now we walked the entire array, change the cached value
693 to reflect what we found. */
694 *have_any_recursive = found_recursive;
696 return FALSE;
699 svn_error_t *
700 svn_wc_process_committed_queue(svn_wc_committed_queue_t *queue,
701 svn_wc_adm_access_t *adm_access,
702 svn_revnum_t new_revnum,
703 const char *rev_date,
704 const char *rev_author,
705 apr_pool_t *pool)
707 int i;
708 apr_hash_index_t *hi;
709 apr_hash_t *updated_adms = apr_hash_make(pool);
710 apr_pool_t *iterpool = svn_pool_create(pool);
711 svn_boolean_t have_any_recursive = TRUE;
712 /* Assume we do have recursive items queued:
713 we need to search for recursive parents until proven otherwise */
716 /* Now, we write all log files,
717 collecting the affected adms in the process ... */
718 for (i = 0; i < queue->queue->nelts; i++)
720 affected_adm_t *affected_adm;
721 const char *adm_path;
722 committed_queue_item_t *cqi
723 = APR_ARRAY_IDX(queue->queue,
724 i, committed_queue_item_t *);
726 svn_pool_clear(iterpool);
728 if (have_recursive_parent(&have_any_recursive,
729 queue->queue,
730 i, iterpool))
731 continue;
733 adm_path = svn_wc_adm_access_path(cqi->adm_access);
734 affected_adm = apr_hash_get(updated_adms,
735 adm_path, APR_HASH_KEY_STRING);
736 if (! affected_adm)
738 /* allocate in pool instead of iterpool:
739 we don't want this cleared at the next iteration */
740 affected_adm = apr_palloc(pool, sizeof(*affected_adm));
741 affected_adm->next_log = 0;
742 affected_adm->adm_access = cqi->adm_access;
743 apr_hash_set(updated_adms, adm_path, APR_HASH_KEY_STRING,
744 affected_adm);
747 SVN_ERR(process_committed_internal(&affected_adm->next_log, cqi->path,
748 cqi->adm_access, cqi->recurse,
749 new_revnum, rev_date, rev_author,
750 cqi->wcprop_changes,
751 cqi->remove_lock,
752 cqi->remove_changelist,
753 cqi->digest, iterpool));
756 /* ... and then we run them; all at once.
758 This prevents writing the entries file
759 more than once per adm area */
760 for (hi = apr_hash_first(pool, updated_adms); hi; hi = apr_hash_next(hi))
762 void *val;
763 affected_adm_t *this_adm;
765 svn_pool_clear(iterpool);
767 apr_hash_this(hi, NULL, NULL, &val);
768 this_adm = val;
770 SVN_ERR(svn_wc__run_log(this_adm->adm_access, NULL, iterpool));
773 queue->queue->nelts = 0;
775 svn_pool_destroy(iterpool);
777 return SVN_NO_ERROR;
780 svn_error_t *
781 svn_wc_process_committed4(const char *path,
782 svn_wc_adm_access_t *adm_access,
783 svn_boolean_t recurse,
784 svn_revnum_t new_revnum,
785 const char *rev_date,
786 const char *rev_author,
787 apr_array_header_t *wcprop_changes,
788 svn_boolean_t remove_lock,
789 svn_boolean_t remove_changelist,
790 const unsigned char *digest,
791 apr_pool_t *pool)
793 int log_number = 0;
795 SVN_ERR(process_committed_internal(&log_number,
796 path, adm_access, recurse,
797 new_revnum, rev_date, rev_author,
798 wcprop_changes, remove_lock,
799 remove_changelist, digest, pool));
801 /* Run the log file(s) we just created. */
802 SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
804 return SVN_NO_ERROR;
807 svn_error_t *
808 svn_wc_process_committed3(const char *path,
809 svn_wc_adm_access_t *adm_access,
810 svn_boolean_t recurse,
811 svn_revnum_t new_revnum,
812 const char *rev_date,
813 const char *rev_author,
814 apr_array_header_t *wcprop_changes,
815 svn_boolean_t remove_lock,
816 const unsigned char *digest,
817 apr_pool_t *pool)
819 return svn_wc_process_committed4(path, adm_access, recurse, new_revnum,
820 rev_date, rev_author, wcprop_changes,
821 remove_lock, FALSE, digest, pool);
824 svn_error_t *
825 svn_wc_process_committed2(const char *path,
826 svn_wc_adm_access_t *adm_access,
827 svn_boolean_t recurse,
828 svn_revnum_t new_revnum,
829 const char *rev_date,
830 const char *rev_author,
831 apr_array_header_t *wcprop_changes,
832 svn_boolean_t remove_lock,
833 apr_pool_t *pool)
835 return svn_wc_process_committed3(path, adm_access, recurse, new_revnum,
836 rev_date, rev_author, wcprop_changes,
837 remove_lock, NULL, pool);
840 svn_error_t *
841 svn_wc_process_committed(const char *path,
842 svn_wc_adm_access_t *adm_access,
843 svn_boolean_t recurse,
844 svn_revnum_t new_revnum,
845 const char *rev_date,
846 const char *rev_author,
847 apr_array_header_t *wcprop_changes,
848 apr_pool_t *pool)
850 return svn_wc_process_committed2(path, adm_access, recurse, new_revnum,
851 rev_date, rev_author, wcprop_changes,
852 FALSE, pool);
855 /* Remove FILE if it exists and is a file. If it does not exist, do
856 nothing. If it is not a file, error. */
857 static svn_error_t *
858 remove_file_if_present(const char *file, apr_pool_t *pool)
860 svn_error_t *err;
862 /* Try to remove the file. */
863 err = svn_io_remove_file(file, pool);
865 /* Ignore file not found error. */
866 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
868 svn_error_clear(err);
869 err = SVN_NO_ERROR;
872 return err;
876 /* Recursively mark a tree ADM_ACCESS with a SCHEDULE, COPIED and/or KEEP_LOCAL
877 flag, depending on the state of MODIFY_FLAGS (which may contain only a
878 subset of the possible modification flags, namely, those indicating a change
879 to one of the three flags mentioned above). */
880 static svn_error_t *
881 mark_tree(svn_wc_adm_access_t *adm_access,
882 apr_uint64_t modify_flags,
883 svn_wc_schedule_t schedule,
884 svn_boolean_t copied,
885 svn_boolean_t keep_local,
886 svn_cancel_func_t cancel_func,
887 void *cancel_baton,
888 svn_wc_notify_func2_t notify_func,
889 void *notify_baton,
890 apr_pool_t *pool)
892 apr_pool_t *subpool = svn_pool_create(pool);
893 apr_hash_t *entries;
894 apr_hash_index_t *hi;
895 const svn_wc_entry_t *entry;
896 svn_wc_entry_t tmp_entry;
897 apr_uint64_t this_dir_flags;
899 /* Read the entries file for this directory. */
900 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
902 /* Mark each entry in the entries file. */
903 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
905 const char *fullpath;
906 const void *key;
907 void *val;
908 const char *base_name;
910 /* Clear our per-iteration pool. */
911 svn_pool_clear(subpool);
913 /* Get the next entry */
914 apr_hash_this(hi, &key, NULL, &val);
915 entry = val;
917 /* Skip "this dir". */
918 if (! strcmp((const char *)key, SVN_WC_ENTRY_THIS_DIR))
919 continue;
921 base_name = key;
922 fullpath = svn_path_join(svn_wc_adm_access_path(adm_access), base_name,
923 subpool);
925 /* If this is a directory, recurse. */
926 if (entry->kind == svn_node_dir)
928 svn_wc_adm_access_t *child_access;
929 SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, fullpath,
930 subpool));
931 SVN_ERR(mark_tree(child_access, modify_flags,
932 schedule, copied, keep_local,
933 cancel_func, cancel_baton,
934 notify_func, notify_baton,
935 subpool));
938 tmp_entry.schedule = schedule;
939 tmp_entry.copied = copied;
940 SVN_ERR(svn_wc__entry_modify
941 (adm_access, base_name, &tmp_entry,
942 modify_flags & (SVN_WC__ENTRY_MODIFY_SCHEDULE
943 | SVN_WC__ENTRY_MODIFY_COPIED),
944 TRUE, subpool));
946 if (copied)
947 /* Remove now obsolete wcprops */
948 SVN_ERR(svn_wc__props_delete(fullpath, svn_wc__props_wcprop,
949 adm_access, subpool));
951 /* Tell someone what we've done. */
952 if (schedule == svn_wc_schedule_delete && notify_func != NULL)
953 (*notify_func)(notify_baton,
954 svn_wc_create_notify(fullpath, svn_wc_notify_delete,
955 subpool), pool);
958 /* Handle "this dir" for states that need it done post-recursion. */
959 entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
960 this_dir_flags = 0;
962 /* Uncommitted directories (schedule add) that are to be scheduled for
963 deletion are a special case, they don't need to be changed as they
964 will be removed from their parent's entry list. */
965 if (! (entry->schedule == svn_wc_schedule_add
966 && schedule == svn_wc_schedule_delete))
968 if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE)
970 tmp_entry.schedule = schedule;
971 this_dir_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
974 if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED)
976 tmp_entry.copied = copied;
977 this_dir_flags |= SVN_WC__ENTRY_MODIFY_COPIED;
981 /* Set keep_local on the "this dir", if requested. */
982 if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL)
984 tmp_entry.keep_local = keep_local;
985 this_dir_flags |= SVN_WC__ENTRY_MODIFY_KEEP_LOCAL;
988 /* Modify this_dir entry if requested. */
989 if (this_dir_flags)
990 SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry, this_dir_flags,
991 TRUE, subpool));
993 /* Destroy our per-iteration pool. */
994 svn_pool_destroy(subpool);
995 return SVN_NO_ERROR;
998 /* Remove/erase PATH from the working copy. This involves deleting PATH
999 * from the physical filesystem. PATH is assumed to be an unversioned file
1000 * or directory.
1002 * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various
1003 * points, return any error immediately.
1005 static svn_error_t *
1006 erase_unversioned_from_wc(const char *path,
1007 svn_cancel_func_t cancel_func,
1008 void *cancel_baton,
1009 apr_pool_t *pool)
1011 svn_error_t *err;
1013 /* Optimize the common case: try to delete the file */
1014 err = svn_io_remove_file(path, pool);
1015 if (err)
1017 /* Then maybe it was a directory? */
1018 svn_error_clear(err);
1020 err = svn_io_remove_dir2(path, FALSE, cancel_func, cancel_baton, pool);
1022 if (err)
1024 /* We're unlikely to end up here. But we need this fallback
1025 to make sure we report the right error *and* try the
1026 correct deletion at least once. */
1027 svn_node_kind_t kind;
1029 svn_error_clear(err);
1030 SVN_ERR(svn_io_check_path(path, &kind, pool));
1031 if (kind == svn_node_file)
1032 SVN_ERR(svn_io_remove_file(path, pool));
1033 else if (kind == svn_node_dir)
1034 SVN_ERR(svn_io_remove_dir2(path, FALSE,
1035 cancel_func, cancel_baton, pool));
1036 else if (kind == svn_node_none)
1037 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
1038 _("'%s' does not exist"),
1039 svn_path_local_style(path, pool));
1040 else
1041 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1042 _("Unsupported node kind for path '%s'"),
1043 svn_path_local_style(path, pool));
1048 return SVN_NO_ERROR;
1051 /* Remove/erase PATH from the working copy. For files this involves
1052 * deletion from the physical filesystem. For directories it involves the
1053 * deletion from the filesystem of all unversioned children, and all
1054 * versioned children that are files. By the time we get here, added but
1055 * not committed items will have been scheduled for deletion which means
1056 * they have become unversioned.
1058 * The result is that all that remains are versioned directories, each with
1059 * its .svn directory and .svn contents.
1061 * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various
1062 * points, return any error immediately.
1064 * KIND is the node kind appropriate for PATH
1066 static svn_error_t *
1067 erase_from_wc(const char *path,
1068 svn_wc_adm_access_t *adm_access,
1069 svn_node_kind_t kind,
1070 svn_cancel_func_t cancel_func,
1071 void *cancel_baton,
1072 apr_pool_t *pool)
1074 const svn_wc_entry_t *entry;
1076 if (cancel_func)
1077 SVN_ERR(cancel_func(cancel_baton));
1079 if (kind == svn_node_file)
1080 SVN_ERR(remove_file_if_present(path, pool));
1082 else if (kind == svn_node_dir)
1083 /* This must be a directory or absent */
1085 apr_hash_t *ver, *unver;
1086 apr_hash_index_t *hi;
1087 svn_wc_adm_access_t *dir_access;
1088 svn_error_t *err;
1090 /* ### Suspect that an iteration or recursion subpool would be
1091 good here. */
1093 /* First handle the versioned items, this is better (probably) than
1094 simply using svn_io_get_dirents2 for everything as it avoids the
1095 need to do svn_io_check_path on each versioned item */
1096 err = svn_wc_adm_retrieve(&dir_access, adm_access, path, pool);
1098 /* If there's no on-disk item, be sure to exit early and
1099 not to return an error */
1100 if (err)
1102 svn_node_kind_t wc_kind;
1103 svn_error_t *err2 = svn_io_check_path(path, &wc_kind, pool);
1105 if (err2)
1107 svn_error_clear(err);
1108 return err2;
1111 if (wc_kind != svn_node_none)
1112 return err;
1114 svn_error_clear(err);
1115 return SVN_NO_ERROR;
1117 SVN_ERR(svn_wc_entries_read(&ver, dir_access, FALSE, pool));
1118 for (hi = apr_hash_first(pool, ver); hi; hi = apr_hash_next(hi))
1120 const void *key;
1121 void *val;
1122 const char *name;
1123 const char *down_path;
1125 apr_hash_this(hi, &key, NULL, &val);
1126 name = key;
1127 entry = val;
1129 if (!strcmp(name, SVN_WC_ENTRY_THIS_DIR))
1130 continue;
1132 down_path = svn_path_join(path, name, pool);
1133 SVN_ERR(erase_from_wc(down_path, adm_access, entry->kind,
1134 cancel_func, cancel_baton, pool));
1137 /* Now handle any remaining unversioned items */
1138 SVN_ERR(svn_io_get_dirents2(&unver, path, pool));
1139 for (hi = apr_hash_first(pool, unver); hi; hi = apr_hash_next(hi))
1141 const void *key;
1142 const char *name;
1143 const char *down_path;
1145 apr_hash_this(hi, &key, NULL, NULL);
1146 name = key;
1148 /* The admin directory will show up, we don't want to delete it */
1149 if (svn_wc_is_adm_dir(name, pool))
1150 continue;
1152 /* Versioned directories will show up, don't delete those either */
1153 if (apr_hash_get(ver, name, APR_HASH_KEY_STRING))
1154 continue;
1156 down_path = svn_path_join(path, name, pool);
1157 SVN_ERR(erase_unversioned_from_wc
1158 (down_path, cancel_func, cancel_baton, pool));
1162 return SVN_NO_ERROR;
1166 svn_error_t *
1167 svn_wc_delete3(const char *path,
1168 svn_wc_adm_access_t *adm_access,
1169 svn_cancel_func_t cancel_func,
1170 void *cancel_baton,
1171 svn_wc_notify_func2_t notify_func,
1172 void *notify_baton,
1173 svn_boolean_t keep_local,
1174 apr_pool_t *pool)
1176 svn_wc_adm_access_t *dir_access;
1177 const svn_wc_entry_t *entry;
1178 const char *parent, *base_name;
1179 svn_boolean_t was_schedule;
1180 svn_node_kind_t was_kind;
1181 svn_boolean_t was_copied;
1182 svn_boolean_t was_deleted = FALSE; /* Silence a gcc uninitialized warning */
1184 SVN_ERR(svn_wc_adm_probe_try3(&dir_access, adm_access, path,
1185 TRUE, -1, cancel_func, cancel_baton, pool));
1186 if (dir_access)
1187 SVN_ERR(svn_wc_entry(&entry, path, dir_access, FALSE, pool));
1188 else
1189 entry = NULL;
1191 if (!entry)
1192 return erase_unversioned_from_wc(path, cancel_func, cancel_baton, pool);
1194 /* Note: Entries caching? What happens to this entry when the entries
1195 file is updated? Lets play safe and copy the values */
1196 was_schedule = entry->schedule;
1197 was_kind = entry->kind;
1198 was_copied = entry->copied;
1200 svn_path_split(path, &parent, &base_name, pool);
1202 if (was_kind == svn_node_dir)
1204 svn_wc_adm_access_t *parent_access;
1205 apr_hash_t *entries;
1206 const svn_wc_entry_t *entry_in_parent;
1208 /* The deleted state is only available in the entry in parent's
1209 entries file */
1210 SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent, pool));
1211 SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, pool));
1212 entry_in_parent = apr_hash_get(entries, base_name, APR_HASH_KEY_STRING);
1213 was_deleted = entry_in_parent ? entry_in_parent->deleted : FALSE;
1215 if (was_schedule == svn_wc_schedule_add && !was_deleted)
1217 /* Deleting a directory that has been added but not yet
1218 committed is easy, just remove the administrative dir. */
1220 if (dir_access != adm_access)
1222 SVN_ERR(svn_wc_remove_from_revision_control
1223 (dir_access, SVN_WC_ENTRY_THIS_DIR, FALSE, FALSE,
1224 cancel_func, cancel_baton, pool));
1226 else
1228 /* adm_probe_retrieve returned the parent access baton,
1229 which is the same access baton that we came in here
1230 with! this means we're dealing with a missing item
1231 that's scheduled for addition. Easiest to just
1232 remove the entry. */
1233 svn_wc__entry_remove(entries, base_name);
1234 SVN_ERR(svn_wc__entries_write(entries, parent_access, pool));
1237 else
1239 /* if adm_probe_retrieve returned the parent access baton,
1240 (which is the same access baton that we came in here
1241 with), this means we're dealing with a missing directory.
1242 So there's no tree to mark for deletion. Instead, the
1243 next phase of code will simply schedule the directory for
1244 deletion in its parent. */
1245 if (dir_access != adm_access)
1247 /* Recursively mark a whole tree for deletion. */
1248 SVN_ERR(mark_tree(dir_access,
1249 SVN_WC__ENTRY_MODIFY_SCHEDULE
1250 | SVN_WC__ENTRY_MODIFY_KEEP_LOCAL,
1251 svn_wc_schedule_delete, FALSE, keep_local,
1252 cancel_func, cancel_baton,
1253 notify_func, notify_baton,
1254 pool));
1259 if (!(was_kind == svn_node_dir && was_schedule == svn_wc_schedule_add
1260 && !was_deleted))
1262 /* We need to mark this entry for deletion in its parent's entries
1263 file, so we split off base_name from the parent path, then fold in
1264 the addition of a delete flag. */
1265 svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
1266 svn_wc_entry_t tmp_entry;
1268 /* Edit the entry to reflect the now deleted state.
1269 entries.c:fold_entry() clears the values of copied, copyfrom_rev
1270 and copyfrom_url. */
1271 tmp_entry.schedule = svn_wc_schedule_delete;
1272 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
1273 path, &tmp_entry,
1274 SVN_WC__ENTRY_MODIFY_SCHEDULE,
1275 pool));
1277 /* is it a replacement with history? */
1278 if (was_schedule == svn_wc_schedule_replace && was_copied)
1280 const char *text_base =
1281 svn_wc__text_base_path(path, FALSE, pool);
1282 const char *text_revert =
1283 svn_wc__text_revert_path(path, FALSE, pool);
1285 if (was_kind != svn_node_dir) /* Dirs don't have text-bases */
1286 /* Restore the original text-base */
1287 SVN_ERR(svn_wc__loggy_move(&log_accum, NULL, adm_access,
1288 text_revert, text_base,
1289 FALSE, pool));
1291 SVN_ERR(svn_wc__loggy_revert_props_restore(&log_accum,
1292 path, adm_access, pool));
1294 if (was_schedule == svn_wc_schedule_add)
1295 SVN_ERR(svn_wc__loggy_props_delete(&log_accum, path,
1296 svn_wc__props_base,
1297 adm_access, pool));
1299 SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
1301 SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
1305 /* Report the deletion to the caller. */
1306 if (notify_func != NULL)
1307 (*notify_func)(notify_baton,
1308 svn_wc_create_notify(path, svn_wc_notify_delete,
1309 pool), pool);
1311 /* By the time we get here, anything that was scheduled to be added has
1312 become unversioned */
1313 if (!keep_local)
1315 if (was_schedule == svn_wc_schedule_add)
1316 SVN_ERR(erase_unversioned_from_wc
1317 (path, cancel_func, cancel_baton, pool));
1318 else
1319 SVN_ERR(erase_from_wc(path, adm_access, was_kind,
1320 cancel_func, cancel_baton, pool));
1323 return SVN_NO_ERROR;
1326 svn_error_t *
1327 svn_wc_delete2(const char *path,
1328 svn_wc_adm_access_t *adm_access,
1329 svn_cancel_func_t cancel_func,
1330 void *cancel_baton,
1331 svn_wc_notify_func2_t notify_func,
1332 void *notify_baton,
1333 apr_pool_t *pool)
1335 return svn_wc_delete3(path, adm_access, cancel_func, cancel_baton,
1336 notify_func, notify_baton, FALSE, pool);
1339 svn_error_t *
1340 svn_wc_delete(const char *path,
1341 svn_wc_adm_access_t *adm_access,
1342 svn_cancel_func_t cancel_func,
1343 void *cancel_baton,
1344 svn_wc_notify_func_t notify_func,
1345 void *notify_baton,
1346 apr_pool_t *pool)
1348 svn_wc__compat_notify_baton_t nb;
1350 nb.func = notify_func;
1351 nb.baton = notify_baton;
1353 return svn_wc_delete2(path, adm_access, cancel_func, cancel_baton,
1354 svn_wc__compat_call_notify_func, &nb, pool);
1357 svn_error_t *
1358 svn_wc_get_ancestry(char **url,
1359 svn_revnum_t *rev,
1360 const char *path,
1361 svn_wc_adm_access_t *adm_access,
1362 apr_pool_t *pool)
1364 const svn_wc_entry_t *ent;
1366 SVN_ERR(svn_wc__entry_versioned(&ent, path, adm_access, FALSE, pool));
1368 if (url)
1369 *url = apr_pstrdup(pool, ent->url);
1371 if (rev)
1372 *rev = ent->revision;
1374 return SVN_NO_ERROR;
1378 svn_error_t *
1379 svn_wc_add2(const char *path,
1380 svn_wc_adm_access_t *parent_access,
1381 const char *copyfrom_url,
1382 svn_revnum_t copyfrom_rev,
1383 svn_cancel_func_t cancel_func,
1384 void *cancel_baton,
1385 svn_wc_notify_func2_t notify_func,
1386 void *notify_baton,
1387 apr_pool_t *pool)
1389 const char *parent_dir, *base_name;
1390 const svn_wc_entry_t *orig_entry, *parent_entry;
1391 svn_wc_entry_t tmp_entry;
1392 svn_boolean_t is_replace = FALSE;
1393 svn_node_kind_t kind;
1394 apr_uint64_t modify_flags = 0;
1395 svn_wc_adm_access_t *adm_access;
1397 SVN_ERR(svn_path_check_valid(path, pool));
1399 /* Make sure something's there. */
1400 SVN_ERR(svn_io_check_path(path, &kind, pool));
1401 if (kind == svn_node_none)
1402 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1403 _("'%s' not found"),
1404 svn_path_local_style(path, pool));
1405 if (kind == svn_node_unknown)
1406 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1407 _("Unsupported node kind for path '%s'"),
1408 svn_path_local_style(path, pool));
1410 /* Get the original entry for this path if one exists (perhaps
1411 this is actually a replacement of a previously deleted thing).
1413 Note that this is one of the few functions that is allowed to see
1414 'deleted' entries; it's totally fine to have an entry that is
1415 scheduled for addition and still previously 'deleted'. */
1416 SVN_ERR(svn_wc_adm_probe_try3(&adm_access, parent_access, path,
1417 TRUE, copyfrom_url != NULL ? -1 : 0,
1418 cancel_func, cancel_baton, pool));
1419 if (adm_access)
1420 SVN_ERR(svn_wc_entry(&orig_entry, path, adm_access, TRUE, pool));
1421 else
1422 orig_entry = NULL;
1424 /* You can only add something that is not in revision control, or
1425 that is slated for deletion from revision control, or has been
1426 previously 'deleted', unless, of course, you're specifying an
1427 addition with -history-; then it's okay for the object to be
1428 under version control already; it's not really new. */
1429 if (orig_entry)
1431 if ((! copyfrom_url)
1432 && (orig_entry->schedule != svn_wc_schedule_delete)
1433 && (! orig_entry->deleted))
1435 return svn_error_createf
1436 (SVN_ERR_ENTRY_EXISTS, NULL,
1437 _("'%s' is already under version control"),
1438 svn_path_local_style(path, pool));
1440 else if (orig_entry->kind != kind)
1442 /* ### todo: At some point, we obviously don't want to block
1443 replacements where the node kind changes. When this
1444 happens, svn_wc_revert3() needs to learn how to revert
1445 this situation. At present we are using a specific node-change
1446 error so that clients can detect it. */
1447 return svn_error_createf
1448 (SVN_ERR_WC_NODE_KIND_CHANGE, NULL,
1449 _("Can't replace '%s' with a node of a differing type; "
1450 "the deletion must be committed and the parent updated "
1451 "before adding '%s'"),
1452 svn_path_local_style(path, pool),
1453 svn_path_local_style(path, pool));
1455 if (orig_entry->schedule == svn_wc_schedule_delete)
1456 is_replace = TRUE;
1459 /* Split off the base_name from the parent directory. */
1460 svn_path_split(path, &parent_dir, &base_name, pool);
1461 SVN_ERR(svn_wc_entry(&parent_entry, parent_dir, parent_access, FALSE,
1462 pool));
1463 if (! parent_entry)
1464 return svn_error_createf
1465 (SVN_ERR_ENTRY_NOT_FOUND, NULL,
1466 _("Can't find parent directory's entry while trying to add '%s'"),
1467 svn_path_local_style(path, pool));
1468 if (parent_entry->schedule == svn_wc_schedule_delete)
1469 return svn_error_createf
1470 (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
1471 _("Can't add '%s' to a parent directory scheduled for deletion"),
1472 svn_path_local_style(path, pool));
1474 /* Init the modify flags. */
1475 modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE | SVN_WC__ENTRY_MODIFY_KIND;
1476 if (! (is_replace || copyfrom_url))
1477 modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION;
1479 /* If a copy ancestor was given, make sure the copyfrom URL is in the same
1480 repository (if possible) and put the proper ancestry info in the new
1481 entry */
1482 if (copyfrom_url)
1484 if (parent_entry->repos
1485 && ! svn_path_is_ancestor(parent_entry->repos, copyfrom_url))
1486 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1487 _("The URL '%s' has a different repository "
1488 "root than its parent"), copyfrom_url);
1489 tmp_entry.copyfrom_url = copyfrom_url;
1490 tmp_entry.copyfrom_rev = copyfrom_rev;
1491 tmp_entry.copied = TRUE;
1492 modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_URL;
1493 modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_REV;
1494 modify_flags |= SVN_WC__ENTRY_MODIFY_COPIED;
1497 /* If this is a replacement we want to remove the checksum and the property
1498 flags so they are not set to their respective old values. */
1499 if (is_replace)
1501 tmp_entry.checksum = NULL;
1502 modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM;
1504 tmp_entry.has_props = FALSE;
1505 tmp_entry.has_prop_mods = FALSE;
1506 modify_flags |= SVN_WC__ENTRY_MODIFY_HAS_PROPS;
1507 modify_flags |= SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS;
1510 tmp_entry.revision = 0;
1511 tmp_entry.kind = kind;
1512 tmp_entry.schedule = svn_wc_schedule_add;
1514 /* Now, add the entry for this item to the parent_dir's
1515 entries file, marking it for addition. */
1516 SVN_ERR(svn_wc__entry_modify(parent_access, base_name, &tmp_entry,
1517 modify_flags, TRUE, pool));
1520 /* If this is a replacement without history, we need to reset the
1521 properties for PATH. */
1522 if (orig_entry && (! copyfrom_url))
1523 SVN_ERR(svn_wc__props_delete(path, svn_wc__props_working,
1524 adm_access, pool));
1526 if (kind == svn_node_dir) /* scheduling a directory for addition */
1528 /* Note that both calls to svn_wc_ensure_adm3() below pass
1529 svn_depth_infinity. Even if 'svn add' were invoked with some
1530 other depth, we'd want to create the adm area with
1531 svn_depth_infinity, because when the user passes add a depth,
1532 that's just a way of telling Subversion what items to add,
1533 not a way of telling Subversion what depth the resultant
1534 newly-versioned directory should have. */
1536 if (! copyfrom_url)
1538 const svn_wc_entry_t *p_entry; /* ### why not use parent_entry? */
1539 const char *new_url;
1541 /* Get the entry for this directory's parent. We need to snatch
1542 the ancestor path out of there. */
1543 SVN_ERR(svn_wc_entry(&p_entry, parent_dir, parent_access, FALSE,
1544 pool));
1546 /* Derive the parent path for our new addition here. */
1547 new_url = svn_path_url_add_component(p_entry->url, base_name, pool);
1549 /* Make sure this new directory has an admistrative subdirectory
1550 created inside of it */
1551 SVN_ERR(svn_wc_ensure_adm3(path, NULL, new_url, p_entry->repos,
1552 0, svn_depth_infinity, pool));
1554 else
1556 /* When we are called with the copyfrom arguments set and with
1557 the admin directory already in existence, then the dir will
1558 contain the copyfrom settings. So we need to pass the
1559 copyfrom arguments to the ensure call. */
1560 SVN_ERR(svn_wc_ensure_adm3(path, NULL, copyfrom_url,
1561 parent_entry->repos, copyfrom_rev,
1562 svn_depth_infinity, pool));
1565 /* We want the locks to persist, so use the access baton's pool */
1566 if (! orig_entry || orig_entry->deleted)
1568 apr_pool_t* access_pool = svn_wc_adm_access_pool(parent_access);
1569 SVN_ERR(svn_wc_adm_open3(&adm_access, parent_access, path,
1570 TRUE, copyfrom_url != NULL ? -1 : 0,
1571 cancel_func, cancel_baton,
1572 access_pool));
1575 /* We're making the same mods we made above, but this time we'll
1576 force the scheduling. Also make sure to undo the
1577 'incomplete' flag which svn_wc_ensure_adm3 sets by default. */
1578 modify_flags |= SVN_WC__ENTRY_MODIFY_FORCE;
1579 modify_flags |= SVN_WC__ENTRY_MODIFY_INCOMPLETE;
1580 tmp_entry.schedule = is_replace
1581 ? svn_wc_schedule_replace
1582 : svn_wc_schedule_add;
1583 tmp_entry.incomplete = FALSE;
1584 SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry,
1585 modify_flags, TRUE, pool));
1587 if (copyfrom_url)
1589 /* If this new directory has ancestry, it's not enough to
1590 schedule it for addition with copyfrom args. We also
1591 need to rewrite its ancestor-url, and rewrite the
1592 ancestor-url of ALL its children!
1594 We're doing this because our current commit model (for
1595 hysterical raisins, presumably) assumes an entry's URL is
1596 correct before commit -- i.e. the URL is not tweaked in
1597 the post-commit bumping process. We might want to change
1598 this model someday. */
1600 /* Figure out what the new url should be. */
1601 const char *new_url =
1602 svn_path_url_add_component(parent_entry->url, base_name, pool);
1604 /* Change the entry urls recursively (but not the working rev). */
1605 SVN_ERR(svn_wc__do_update_cleanup(path, adm_access,
1606 svn_depth_infinity, new_url,
1607 parent_entry->repos,
1608 SVN_INVALID_REVNUM, NULL,
1609 NULL, FALSE, apr_hash_make(pool),
1610 pool));
1612 /* Recursively add the 'copied' existence flag as well! */
1613 SVN_ERR(mark_tree(adm_access, SVN_WC__ENTRY_MODIFY_COPIED,
1614 svn_wc_schedule_normal, TRUE, FALSE,
1615 cancel_func,
1616 cancel_baton,
1617 NULL, NULL, /* N/A cuz we aren't deleting */
1618 pool));
1620 /* Clean out the now-obsolete wcprops. */
1621 SVN_ERR(svn_wc__props_delete(path, svn_wc__props_wcprop,
1622 adm_access, pool));
1626 /* Report the addition to the caller. */
1627 if (notify_func != NULL)
1629 svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_add,
1630 pool);
1631 notify->kind = kind;
1632 (*notify_func)(notify_baton, notify, pool);
1635 return SVN_NO_ERROR;
1639 svn_error_t *
1640 svn_wc_add(const char *path,
1641 svn_wc_adm_access_t *parent_access,
1642 const char *copyfrom_url,
1643 svn_revnum_t copyfrom_rev,
1644 svn_cancel_func_t cancel_func,
1645 void *cancel_baton,
1646 svn_wc_notify_func_t notify_func,
1647 void *notify_baton,
1648 apr_pool_t *pool)
1650 svn_wc__compat_notify_baton_t nb;
1652 nb.func = notify_func;
1653 nb.baton = notify_baton;
1655 return svn_wc_add2(path, parent_access, copyfrom_url, copyfrom_rev,
1656 cancel_func, cancel_baton,
1657 svn_wc__compat_call_notify_func, &nb, pool);
1662 /* Thoughts on Reversion.
1664 What does is mean to revert a given PATH in a tree? We'll
1665 consider things by their modifications.
1667 Adds
1669 - For files, svn_wc_remove_from_revision_control(), baby.
1671 - Added directories may contain nothing but added children, and
1672 reverting the addition of a directory necessarily means reverting
1673 the addition of all the directory's children. Again,
1674 svn_wc_remove_from_revision_control() should do the trick.
1676 Deletes
1678 - Restore properties to their unmodified state.
1680 - For files, restore the pristine contents, and reset the schedule
1681 to 'normal'.
1683 - For directories, reset the schedule to 'normal'. All children
1684 of a directory marked for deletion must also be marked for
1685 deletion, but it's okay for those children to remain deleted even
1686 if their parent directory is restored. That's what the
1687 recursive flag is for.
1689 Replaces
1691 - Restore properties to their unmodified state.
1693 - For files, restore the pristine contents, and reset the schedule
1694 to 'normal'.
1696 - For directories, reset the schedule to normal. A replaced
1697 directory can have deleted children (left over from the initial
1698 deletion), replaced children (children of the initial deletion
1699 now re-added), and added children (new entries under the
1700 replaced directory). Since this is technically an addition, it
1701 necessitates recursion.
1703 Modifications
1705 - Restore properties and, for files, contents to their unmodified
1706 state.
1710 /* Revert ENTRY for NAME in directory represented by ADM_ACCESS. Sets
1711 *REVERTED to TRUE if something actually is reverted.
1713 Use SVN_WC_ENTRY_THIS_DIR as NAME for reverting ADM_ACCESS directory
1714 itself.
1716 Use POOL for any temporary allocations.*/
1717 static svn_error_t *
1718 revert_admin_things(svn_wc_adm_access_t *adm_access,
1719 const char *name,
1720 const svn_wc_entry_t *entry,
1721 svn_boolean_t *reverted,
1722 svn_boolean_t use_commit_times,
1723 apr_pool_t *pool)
1725 const char *fullpath;
1726 svn_boolean_t reinstall_working = FALSE; /* force working file reinstall? */
1727 svn_wc_entry_t tmp_entry;
1728 apr_uint64_t flags = 0;
1729 svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
1730 apr_hash_t *baseprops = NULL;
1731 svn_boolean_t revert_base = FALSE;
1733 /* Build the full path of the thing we're reverting. */
1734 fullpath = svn_wc_adm_access_path(adm_access);
1735 if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) != 0)
1736 fullpath = svn_path_join(fullpath, name, pool);
1738 /* Deal with properties. */
1739 if (entry->schedule == svn_wc_schedule_replace)
1741 revert_base = entry->copied;
1742 /* Use the revertpath as the new propsbase if it exists. */
1744 baseprops = apr_hash_make(pool);
1745 SVN_ERR(svn_wc__load_props((! revert_base) ? &baseprops : NULL,
1746 NULL,
1747 revert_base ? &baseprops : NULL,
1748 adm_access, fullpath, pool));
1750 /* Ensure the revert propfile gets removed. */
1751 if (revert_base)
1752 SVN_ERR(svn_wc__loggy_props_delete(&log_accum,
1753 fullpath, svn_wc__props_revert,
1754 adm_access, pool));
1755 *reverted = TRUE;
1758 /* If not schedule replace, or no revert props, use the normal
1759 base-props and working props. */
1760 if (! baseprops)
1762 svn_boolean_t modified;
1764 /* Check for prop changes. */
1765 SVN_ERR(svn_wc_props_modified_p(&modified, fullpath, adm_access,
1766 pool));
1767 if (modified)
1769 apr_array_header_t *propchanges;
1771 /* Get the full list of property changes and see if any magic
1772 properties were changed. */
1773 SVN_ERR(svn_wc_get_prop_diffs(&propchanges, &baseprops, fullpath,
1774 adm_access, pool));
1776 /* Determine if any of the propchanges are the "magic" ones that
1777 might require changing the working file. */
1778 reinstall_working = svn_wc__has_magic_property(propchanges);
1783 /* Reinstall props if we need to. Only rewrite the baseprops,
1784 if we're reverting a replacement. This is just an optimization. */
1785 if (baseprops)
1787 SVN_ERR(svn_wc__install_props(&log_accum, adm_access, fullpath,
1788 baseprops, baseprops, revert_base, pool));
1789 *reverted = TRUE;
1792 /* Deal with the contents. */
1794 if (entry->kind == svn_node_file)
1796 svn_node_kind_t base_kind;
1797 const char *base_thing;
1798 svn_boolean_t tgt_modified;
1800 /* If the working file is missing, we need to reinstall it. */
1801 if (! reinstall_working)
1803 svn_node_kind_t kind;
1804 SVN_ERR(svn_io_check_path(fullpath, &kind, pool));
1805 if (kind == svn_node_none)
1806 reinstall_working = TRUE;
1809 base_thing = svn_wc__text_base_path(fullpath, FALSE, pool);
1811 /* Check for text base presence. */
1812 SVN_ERR(svn_io_check_path(base_thing, &base_kind, pool));
1814 if (base_kind != svn_node_file)
1815 return svn_error_createf(APR_ENOENT, NULL,
1816 _("Error restoring text for '%s'"),
1817 svn_path_local_style(fullpath, pool));
1819 /* Look for a revert base file. If it exists use it for the
1820 text base for the file. If it doesn't use the normal text base. */
1821 SVN_ERR(svn_wc__loggy_move
1822 (&log_accum, &tgt_modified, adm_access,
1823 svn_wc__text_revert_path(fullpath, FALSE, pool), base_thing,
1824 FALSE, pool));
1825 reinstall_working = reinstall_working || tgt_modified;
1827 /* A shortcut: since we will translate when reinstall_working,
1828 we don't need to check if the working file is modified. */
1829 if (! reinstall_working)
1830 SVN_ERR(svn_wc__text_modified_internal_p(&reinstall_working,
1831 fullpath, FALSE, adm_access,
1832 FALSE, pool));
1834 if (reinstall_working)
1836 /* If there are textual mods (or if the working file is
1837 missing altogether), copy the text-base out into
1838 the working copy, and update the timestamp in the entries
1839 file. */
1840 SVN_ERR(svn_wc__loggy_copy(&log_accum, NULL, adm_access,
1841 svn_wc__copy_translate,
1842 base_thing, fullpath, FALSE, pool));
1844 /* Possibly set the timestamp to last-commit-time, rather
1845 than the 'now' time that already exists. */
1846 if (use_commit_times && entry->cmt_date)
1847 SVN_ERR(svn_wc__loggy_set_timestamp
1848 (&log_accum, adm_access, fullpath,
1849 svn_time_to_cstring(entry->cmt_date, pool),
1850 pool));
1852 SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc
1853 (&log_accum, adm_access,
1854 fullpath, SVN_WC__ENTRY_ATTR_TEXT_TIME, pool));
1855 SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc
1856 (&log_accum, adm_access, fullpath, pool));
1858 *reverted = TRUE;
1862 /* Remove conflict state (and conflict files), if any.
1863 Handle the three possible text conflict files. */
1864 if (entry->conflict_old)
1866 flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD;
1867 tmp_entry.conflict_old = NULL;
1868 SVN_ERR(svn_wc__loggy_remove
1869 (&log_accum, adm_access,
1870 svn_path_join(svn_wc_adm_access_path(adm_access),
1871 entry->conflict_old, pool), pool));
1873 if (entry->conflict_new)
1875 flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW;
1876 tmp_entry.conflict_new = NULL;
1877 SVN_ERR(svn_wc__loggy_remove
1878 (&log_accum, adm_access,
1879 svn_path_join(svn_wc_adm_access_path(adm_access),
1880 entry->conflict_new, pool), pool));
1882 if (entry->conflict_wrk)
1884 flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK;
1885 tmp_entry.conflict_wrk = NULL;
1886 SVN_ERR(svn_wc__loggy_remove
1887 (&log_accum, adm_access,
1888 svn_path_join(svn_wc_adm_access_path(adm_access),
1889 entry->conflict_wrk, pool), pool));
1892 /* Remove the property conflict file if the entry lists one (and it
1893 exists) */
1894 if (entry->prejfile)
1896 flags |= SVN_WC__ENTRY_MODIFY_PREJFILE;
1897 tmp_entry.prejfile = NULL;
1898 SVN_ERR(svn_wc__loggy_remove
1899 (&log_accum, adm_access,
1900 svn_path_join(svn_wc_adm_access_path(adm_access),
1901 entry->prejfile, pool), pool));
1904 /* Clean up the copied state if this is a replacement. */
1905 if (entry->schedule == svn_wc_schedule_replace)
1907 flags |= SVN_WC__ENTRY_MODIFY_COPIED |
1908 SVN_WC__ENTRY_MODIFY_COPYFROM_URL |
1909 SVN_WC__ENTRY_MODIFY_COPYFROM_REV;
1910 tmp_entry.copied = FALSE;
1912 /* Reset the checksum if this is a replace-with-history. */
1913 if (entry->kind == svn_node_file && entry->copyfrom_url)
1915 const char *base_path;
1916 unsigned char digest[APR_MD5_DIGESTSIZE];
1918 base_path = svn_wc__text_revert_path(fullpath, FALSE, pool);
1919 SVN_ERR(svn_io_file_checksum(digest, base_path, pool));
1920 tmp_entry.checksum = svn_md5_digest_to_cstring(digest, pool);
1921 flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM;
1924 /* Set this to the empty string, because NULL values will disappear
1925 in the XML log file. */
1926 tmp_entry.copyfrom_url = "";
1927 tmp_entry.copyfrom_rev = SVN_INVALID_REVNUM;
1930 /* Reset schedule attribute to svn_wc_schedule_normal. */
1931 if (entry->schedule != svn_wc_schedule_normal)
1933 flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE;
1934 tmp_entry.schedule = svn_wc_schedule_normal;
1935 *reverted = TRUE;
1938 /* Modify the entry, loggily. */
1939 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access, fullpath,
1940 &tmp_entry, flags, pool));
1942 /* Don't run log if nothing to change. */
1943 if (! svn_stringbuf_isempty(log_accum))
1945 SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
1946 SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
1949 return SVN_NO_ERROR;
1953 /* Revert PATH of on-disk KIND. ENTRY is the working copy entry for
1954 PATH. *DEPTH is the depth of the reversion crawl the caller is
1955 using; this function may choose to override that value as needed.
1957 See svn_wc_revert3() for the interpretations of PARENT_ACCESS,
1958 USE_COMMIT_TIMES, CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC, and
1959 NOTIFY_BATON.
1961 Use POOL for allocations.
1963 static svn_error_t *
1964 revert_entry(svn_depth_t *depth,
1965 const char *path,
1966 svn_node_kind_t kind,
1967 const svn_wc_entry_t *entry,
1968 svn_wc_adm_access_t *parent_access,
1969 svn_boolean_t use_commit_times,
1970 svn_cancel_func_t cancel_func,
1971 void *cancel_baton,
1972 svn_wc_notify_func2_t notify_func,
1973 void *notify_baton,
1974 apr_pool_t *pool)
1976 const char *bname;
1977 svn_boolean_t reverted = FALSE;
1978 svn_boolean_t is_wc_root = FALSE;
1979 svn_wc_adm_access_t *dir_access;
1981 /* Fetch the access baton for this path. */
1982 SVN_ERR(svn_wc_adm_probe_retrieve(&dir_access, parent_access, path, pool));
1984 /* For directories, determine if PATH is a WC root so that we can
1985 tell if it is safe to split PATH into a parent directory and
1986 basename. For files, we always do this split. */
1987 if (kind == svn_node_dir)
1988 SVN_ERR(svn_wc_is_wc_root(&is_wc_root, path, dir_access, pool));
1989 bname = is_wc_root ? NULL : svn_path_basename(path, pool);
1991 /* Additions. */
1992 if (entry->schedule == svn_wc_schedule_add)
1994 /* Before removing item from revision control, notice if the
1995 entry is in a 'deleted' state; this is critical for
1996 directories, where this state only exists in its parent's
1997 entry. */
1998 svn_boolean_t was_deleted = FALSE;
1999 const char *parent, *basey;
2001 svn_path_split(path, &parent, &basey, pool);
2002 if (entry->kind == svn_node_file)
2004 was_deleted = entry->deleted;
2005 SVN_ERR(svn_wc_remove_from_revision_control(parent_access, bname,
2006 FALSE, FALSE,
2007 cancel_func,
2008 cancel_baton,
2009 pool));
2011 else if (entry->kind == svn_node_dir)
2013 apr_hash_t *entries;
2014 const svn_wc_entry_t *parents_entry;
2016 /* We are trying to revert the current directory which is
2017 scheduled for addition. This is supposed to fail (Issue #854) */
2018 if (path[0] == '\0')
2019 return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
2020 _("Cannot revert addition of current "
2021 "directory; please try again from the "
2022 "parent directory"));
2024 SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, pool));
2025 parents_entry = apr_hash_get(entries, basey, APR_HASH_KEY_STRING);
2026 if (parents_entry)
2027 was_deleted = parents_entry->deleted;
2029 if (kind == svn_node_none
2030 || svn_wc__adm_missing(parent_access, path))
2032 /* Schedule add but missing, just remove the entry
2033 or it's missing an adm area in which case
2034 svn_wc_adm_probe_retrieve() returned the parent's
2035 adm_access, for which we definitely can't use the 'else'
2036 code path (as it will remove the parent from version
2037 control... (See issue 2425) */
2038 svn_wc__entry_remove(entries, basey);
2039 SVN_ERR(svn_wc__entries_write(entries, parent_access, pool));
2041 else
2043 SVN_ERR(svn_wc_remove_from_revision_control
2044 (dir_access, SVN_WC_ENTRY_THIS_DIR, FALSE, FALSE,
2045 cancel_func, cancel_baton, pool));
2048 else /* Else it's `none', or something exotic like a symlink... */
2050 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
2051 _("Unknown or unexpected kind for path "
2052 "'%s'"),
2053 svn_path_local_style(path, pool));
2057 /* Recursivity is taken care of by svn_wc_remove_from_revision_control,
2058 and we've definitely reverted PATH at this point. */
2059 *depth = svn_depth_empty;
2060 reverted = TRUE;
2062 /* If the removed item was *also* in a 'deleted' state, make
2063 sure we leave just a plain old 'deleted' entry behind in the
2064 parent. */
2065 if (was_deleted)
2067 svn_wc_entry_t *tmpentry; /* ### FIXME: Why the heap alloc? */
2068 tmpentry = apr_pcalloc(pool, sizeof(*tmpentry));
2069 tmpentry->kind = entry->kind;
2070 tmpentry->deleted = TRUE;
2072 if (entry->kind == svn_node_dir)
2073 SVN_ERR(svn_wc__entry_modify(parent_access, basey, tmpentry,
2074 SVN_WC__ENTRY_MODIFY_KIND
2075 | SVN_WC__ENTRY_MODIFY_DELETED,
2076 TRUE, pool));
2077 else
2078 SVN_ERR(svn_wc__entry_modify(parent_access, bname, tmpentry,
2079 SVN_WC__ENTRY_MODIFY_KIND
2080 | SVN_WC__ENTRY_MODIFY_DELETED,
2081 TRUE, pool));
2084 /* Regular prop and text edit. */
2085 /* Deletions and replacements. */
2086 else if (entry->schedule == svn_wc_schedule_normal
2087 || entry->schedule == svn_wc_schedule_delete
2088 || entry->schedule == svn_wc_schedule_replace)
2090 /* Revert the prop and text mods (if any). */
2091 switch (entry->kind)
2093 case svn_node_file:
2094 SVN_ERR(revert_admin_things(parent_access, bname, entry,
2095 &reverted, use_commit_times, pool));
2096 break;
2098 case svn_node_dir:
2099 SVN_ERR(revert_admin_things(dir_access, SVN_WC_ENTRY_THIS_DIR, entry,
2100 &reverted, use_commit_times, pool));
2102 /* Also revert the entry in the parent (issue #2804). */
2103 if (reverted && bname)
2105 svn_boolean_t dummy_reverted;
2106 svn_wc_entry_t *entry_in_parent;
2107 apr_hash_t *entries;
2109 SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE,
2110 pool));
2111 entry_in_parent = apr_hash_get(entries, bname,
2112 APR_HASH_KEY_STRING);
2113 SVN_ERR(revert_admin_things(parent_access, bname,
2114 entry_in_parent, &dummy_reverted,
2115 use_commit_times, pool));
2118 /* Force recursion on replaced directories. */
2119 if (entry->schedule == svn_wc_schedule_replace)
2120 *depth = svn_depth_infinity;
2121 break;
2123 default:
2124 /* No op? */
2125 break;
2129 /* If PATH was reverted, tell our client that. */
2130 if ((notify_func != NULL) && reverted)
2131 (*notify_func)(notify_baton,
2132 svn_wc_create_notify(path, svn_wc_notify_revert, pool),
2133 pool);
2135 return SVN_NO_ERROR;
2139 /* This is just the guts of svn_wc_revert3() save that it accepts a
2140 hash CHANGELIST_HASH whose keys are changelist names instead of an
2141 array of said names. See svn_wc_revert3() for additional
2142 documentation. */
2143 static svn_error_t *
2144 revert_internal(const char *path,
2145 svn_wc_adm_access_t *parent_access,
2146 svn_depth_t depth,
2147 svn_boolean_t use_commit_times,
2148 apr_hash_t *changelist_hash,
2149 svn_cancel_func_t cancel_func,
2150 void *cancel_baton,
2151 svn_wc_notify_func2_t notify_func,
2152 void *notify_baton,
2153 apr_pool_t *pool)
2155 svn_node_kind_t kind;
2156 const svn_wc_entry_t *entry;
2157 svn_wc_adm_access_t *dir_access;
2159 /* Check cancellation here, so recursive calls get checked early. */
2160 if (cancel_func)
2161 SVN_ERR(cancel_func(cancel_baton));
2163 /* Fetch the access baton for this path. */
2164 SVN_ERR(svn_wc_adm_probe_retrieve(&dir_access, parent_access, path, pool));
2166 /* Safeguard 1: is this a versioned resource? */
2167 SVN_ERR_W(svn_wc__entry_versioned(&entry, path, dir_access, FALSE, pool),
2168 _("Cannot revert"));
2170 /* Safeguard 1.5: is this a missing versioned directory? */
2171 if (entry->kind == svn_node_dir)
2173 svn_node_kind_t disk_kind;
2174 SVN_ERR(svn_io_check_path(path, &disk_kind, pool));
2175 if ((disk_kind != svn_node_dir)
2176 && (entry->schedule != svn_wc_schedule_add))
2178 /* When the directory itself is missing, we can't revert without
2179 hitting the network. Someday a '--force' option will
2180 make this happen. For now, send notification of the failure. */
2181 if (notify_func != NULL)
2183 svn_wc_notify_t *notify =
2184 svn_wc_create_notify(path, svn_wc_notify_failed_revert, pool);
2185 notify_func(notify_baton, notify, pool);
2187 return SVN_NO_ERROR;
2191 /* Safeguard 2: can we handle this entry's recorded kind? */
2192 if ((entry->kind != svn_node_file) && (entry->kind != svn_node_dir))
2193 return svn_error_createf
2194 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2195 _("Cannot revert '%s': unsupported entry node kind"),
2196 svn_path_local_style(path, pool));
2198 /* Safeguard 3: can we deal with the node kind of PATH currently in
2199 the working copy? */
2200 SVN_ERR(svn_io_check_path(path, &kind, pool));
2201 if ((kind != svn_node_none)
2202 && (kind != svn_node_file)
2203 && (kind != svn_node_dir))
2204 return svn_error_createf
2205 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2206 _("Cannot revert '%s': unsupported node kind in working copy"),
2207 svn_path_local_style(path, pool));
2209 /* If the entry passes changelist filtering, revert it! */
2210 if (SVN_WC__CL_MATCH(changelist_hash, entry))
2212 /* Actually revert this entry. If this is a working copy root,
2213 we provide a base_name from the parent path. */
2214 SVN_ERR(revert_entry(&depth, path, kind, entry,
2215 parent_access, use_commit_times, cancel_func,
2216 cancel_baton, notify_func, notify_baton, pool));
2219 /* Finally, recurse if requested. */
2220 if (entry->kind == svn_node_dir && depth > svn_depth_empty)
2222 apr_hash_t *entries;
2223 apr_hash_index_t *hi;
2224 apr_pool_t *subpool = svn_pool_create(pool);
2226 SVN_ERR(svn_wc_entries_read(&entries, dir_access, FALSE, pool));
2227 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2229 const void *key;
2230 const char *keystring;
2231 void *val;
2232 const char *full_entry_path;
2233 svn_depth_t depth_under_here = depth;
2234 svn_wc_entry_t *child_entry;
2236 if (depth == svn_depth_files || depth == svn_depth_immediates)
2237 depth_under_here = svn_depth_empty;
2239 svn_pool_clear(subpool);
2241 /* Get the next entry */
2242 apr_hash_this(hi, &key, NULL, &val);
2243 keystring = key;
2244 child_entry = val;
2246 /* Skip "this dir" */
2247 if (! strcmp(keystring, SVN_WC_ENTRY_THIS_DIR))
2248 continue;
2250 /* Skip subdirectories if we're called with depth-files. */
2251 if ((depth == svn_depth_files)
2252 && (child_entry->kind != svn_node_file))
2253 continue;
2255 /* Add the entry name to FULL_ENTRY_PATH. */
2256 full_entry_path = svn_path_join(path, keystring, subpool);
2258 /* Revert the entry. */
2259 SVN_ERR(revert_internal(full_entry_path, dir_access,
2260 depth_under_here, use_commit_times,
2261 changelist_hash, cancel_func, cancel_baton,
2262 notify_func, notify_baton, subpool));
2265 svn_pool_destroy(subpool);
2268 return SVN_NO_ERROR;
2272 svn_error_t *
2273 svn_wc_revert3(const char *path,
2274 svn_wc_adm_access_t *parent_access,
2275 svn_depth_t depth,
2276 svn_boolean_t use_commit_times,
2277 const apr_array_header_t *changelists,
2278 svn_cancel_func_t cancel_func,
2279 void *cancel_baton,
2280 svn_wc_notify_func2_t notify_func,
2281 void *notify_baton,
2282 apr_pool_t *pool)
2284 apr_hash_t *changelist_hash = NULL;
2285 if (changelists && changelists->nelts)
2286 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool));
2287 return revert_internal(path, parent_access, depth, use_commit_times,
2288 changelist_hash, cancel_func, cancel_baton,
2289 notify_func, notify_baton, pool);
2293 svn_error_t *
2294 svn_wc_revert2(const char *path,
2295 svn_wc_adm_access_t *parent_access,
2296 svn_boolean_t recursive,
2297 svn_boolean_t use_commit_times,
2298 svn_cancel_func_t cancel_func,
2299 void *cancel_baton,
2300 svn_wc_notify_func2_t notify_func,
2301 void *notify_baton,
2302 apr_pool_t *pool)
2304 return svn_wc_revert3(path, parent_access,
2305 recursive ? svn_depth_infinity : svn_depth_empty,
2306 use_commit_times, NULL, cancel_func, cancel_baton,
2307 notify_func, notify_baton, pool);
2311 svn_error_t *
2312 svn_wc_revert(const char *path,
2313 svn_wc_adm_access_t *parent_access,
2314 svn_boolean_t recursive,
2315 svn_boolean_t use_commit_times,
2316 svn_cancel_func_t cancel_func,
2317 void *cancel_baton,
2318 svn_wc_notify_func_t notify_func,
2319 void *notify_baton,
2320 apr_pool_t *pool)
2322 svn_wc__compat_notify_baton_t nb;
2324 nb.func = notify_func;
2325 nb.baton = notify_baton;
2327 return svn_wc_revert2(path, parent_access, recursive, use_commit_times,
2328 cancel_func, cancel_baton,
2329 svn_wc__compat_call_notify_func, &nb, pool);
2332 svn_error_t *
2333 svn_wc_get_pristine_copy_path(const char *path,
2334 const char **pristine_path,
2335 apr_pool_t *pool)
2337 *pristine_path = svn_wc__text_base_path(path, FALSE, pool);
2338 return SVN_NO_ERROR;
2342 svn_error_t *
2343 svn_wc_remove_from_revision_control(svn_wc_adm_access_t *adm_access,
2344 const char *name,
2345 svn_boolean_t destroy_wf,
2346 svn_boolean_t instant_error,
2347 svn_cancel_func_t cancel_func,
2348 void *cancel_baton,
2349 apr_pool_t *pool)
2351 svn_error_t *err;
2352 svn_boolean_t is_file;
2353 svn_boolean_t left_something = FALSE;
2354 apr_hash_t *entries = NULL;
2355 const char *full_path = apr_pstrdup(pool,
2356 svn_wc_adm_access_path(adm_access));
2358 /* Check cancellation here, so recursive calls get checked early. */
2359 if (cancel_func)
2360 SVN_ERR(cancel_func(cancel_baton));
2362 /* NAME is either a file's basename or SVN_WC_ENTRY_THIS_DIR. */
2363 is_file = (strcmp(name, SVN_WC_ENTRY_THIS_DIR)) ? TRUE : FALSE;
2365 if (is_file)
2367 svn_node_kind_t kind;
2368 svn_boolean_t wc_special, local_special;
2369 svn_boolean_t text_modified_p;
2370 full_path = svn_path_join(full_path, name, pool);
2372 /* Only check if the file was modified when it wasn't overwritten with a
2373 special file */
2374 SVN_ERR(svn_wc__get_special(&wc_special, full_path, adm_access, pool));
2375 SVN_ERR(svn_io_check_special_path(full_path, &kind, &local_special,
2376 pool));
2377 if (wc_special || ! local_special)
2379 /* Check for local mods. before removing entry */
2380 SVN_ERR(svn_wc_text_modified_p(&text_modified_p, full_path,
2381 FALSE, adm_access, pool));
2382 if (text_modified_p && instant_error)
2383 return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL,
2384 _("File '%s' has local modifications"),
2385 svn_path_local_style(full_path, pool));
2388 /* Remove the wcprops. */
2389 SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_wcprop,
2390 adm_access, pool));
2391 /* Remove prop/NAME, prop-base/NAME.svn-base. */
2392 SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_working,
2393 adm_access, pool));
2394 SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_base,
2395 adm_access, pool));
2397 /* Remove NAME from PATH's entries file: */
2398 SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
2399 svn_wc__entry_remove(entries, name);
2400 SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
2402 /* Remove text-base/NAME.svn-base */
2403 SVN_ERR(remove_file_if_present(svn_wc__text_base_path(full_path, 0, pool),
2404 pool));
2406 /* If we were asked to destroy the working file, do so unless
2407 it has local mods. */
2408 if (destroy_wf)
2410 /* Don't kill local mods. */
2411 if (text_modified_p || (! wc_special && local_special))
2412 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL);
2413 else /* The working file is still present; remove it. */
2414 SVN_ERR(remove_file_if_present(full_path, pool));
2417 } /* done with file case */
2419 else /* looking at THIS_DIR */
2421 apr_pool_t *subpool = svn_pool_create(pool);
2422 apr_hash_index_t *hi;
2423 svn_wc_entry_t incomplete_entry;
2425 /* ### sanity check: check 2 places for DELETED flag? */
2427 /* Before we start removing entries from this dir's entries
2428 file, mark this directory as "incomplete". This allows this
2429 function to be interruptible and the wc recoverable by 'svn
2430 up' later on. */
2431 incomplete_entry.incomplete = TRUE;
2432 SVN_ERR(svn_wc__entry_modify(adm_access,
2433 SVN_WC_ENTRY_THIS_DIR,
2434 &incomplete_entry,
2435 SVN_WC__ENTRY_MODIFY_INCOMPLETE,
2436 TRUE, /* sync to disk immediately */
2437 pool));
2439 /* Get rid of all the wcprops in this directory. This avoids rewriting
2440 the wcprops file over and over (meaning O(n^2) complexity)
2441 below. */
2442 SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_wcprop,
2443 adm_access, pool));
2445 /* Walk over every entry. */
2446 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
2448 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2450 const void *key;
2451 void *val;
2452 const char *current_entry_name;
2453 const svn_wc_entry_t *current_entry;
2455 svn_pool_clear(subpool);
2457 apr_hash_this(hi, &key, NULL, &val);
2458 current_entry = val;
2459 if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR))
2460 current_entry_name = NULL;
2461 else
2462 current_entry_name = key;
2464 if (current_entry->kind == svn_node_file)
2466 err = svn_wc_remove_from_revision_control
2467 (adm_access, current_entry_name, destroy_wf, instant_error,
2468 cancel_func, cancel_baton, subpool);
2470 if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
2472 if (instant_error)
2474 return err;
2476 else
2478 svn_error_clear(err);
2479 left_something = TRUE;
2482 else if (err)
2483 return err;
2485 else if (current_entry_name && (current_entry->kind == svn_node_dir))
2487 svn_wc_adm_access_t *entry_access;
2488 const char *entrypath
2489 = svn_path_join(svn_wc_adm_access_path(adm_access),
2490 current_entry_name,
2491 subpool);
2493 if (svn_wc__adm_missing(adm_access, entrypath))
2495 /* The directory is already missing, so don't try to
2496 recurse, just delete the entry in the parent
2497 directory. */
2498 svn_wc__entry_remove(entries, current_entry_name);
2500 else
2502 SVN_ERR(svn_wc_adm_retrieve(&entry_access, adm_access,
2503 entrypath, subpool));
2505 err = svn_wc_remove_from_revision_control
2506 (entry_access, SVN_WC_ENTRY_THIS_DIR, destroy_wf,
2507 instant_error, cancel_func, cancel_baton, subpool);
2509 if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
2511 if (instant_error)
2513 return err;
2515 else
2517 svn_error_clear(err);
2518 left_something = TRUE;
2521 else if (err)
2522 return err;
2527 /* At this point, every directory below this one has been
2528 removed from revision control. */
2530 /* Remove self from parent's entries file, but only if parent is
2531 a working copy. If it's not, that's fine, we just move on. */
2533 const char *parent_dir, *base_name;
2534 svn_boolean_t is_root;
2536 SVN_ERR(svn_wc_is_wc_root(&is_root, full_path, adm_access, pool));
2538 /* If full_path is not the top of a wc, then its parent
2539 directory is also a working copy and has an entry for
2540 full_path. We need to remove that entry: */
2541 if (! is_root)
2543 svn_wc_adm_access_t *parent_access;
2545 svn_path_split(full_path, &parent_dir, &base_name, pool);
2547 SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access,
2548 parent_dir, pool));
2549 SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE,
2550 pool));
2551 svn_wc__entry_remove(entries, base_name);
2552 SVN_ERR(svn_wc__entries_write(entries, parent_access, pool));
2556 /* Remove the entire administrative .svn area, thereby removing
2557 _this_ dir from revision control too. */
2558 SVN_ERR(svn_wc__adm_destroy(adm_access, subpool));
2560 /* If caller wants us to recursively nuke everything on disk, go
2561 ahead, provided that there are no dangling local-mod files
2562 below */
2563 if (destroy_wf && (! left_something))
2565 /* If the dir is *truly* empty (i.e. has no unversioned
2566 resources, all versioned files are gone, all .svn dirs are
2567 gone, and contains nothing but empty dirs), then a
2568 *non*-recursive dir_remove should work. If it doesn't,
2569 no big deal. Just assume there are unversioned items in
2570 there and set "left_something" */
2571 err = svn_io_dir_remove_nonrecursive
2572 (svn_wc_adm_access_path(adm_access), subpool);
2573 if (err)
2575 left_something = TRUE;
2576 svn_error_clear(err);
2580 svn_pool_destroy(subpool);
2582 } /* end of directory case */
2584 if (left_something)
2585 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL);
2586 else
2587 return SVN_NO_ERROR;
2592 /*** Resolving a conflict automatically ***/
2595 /* Helper for resolve_conflict_on_entry. Delete the file BASE_NAME in
2596 PARENT_DIR if it exists. Set WAS_PRESENT to TRUE if the file existed,
2597 and to FALSE otherwise. */
2598 static svn_error_t *
2599 attempt_deletion(const char *parent_dir,
2600 const char *base_name,
2601 svn_boolean_t *was_present,
2602 apr_pool_t *pool)
2604 const char *full_path = svn_path_join(parent_dir, base_name, pool);
2605 svn_error_t *err = svn_io_remove_file(full_path, pool);
2607 *was_present = ! err || ! APR_STATUS_IS_ENOENT(err->apr_err);
2608 if (*was_present)
2609 return err;
2611 svn_error_clear(err);
2612 return SVN_NO_ERROR;
2616 /* Conflict resolution involves removing the conflict files, if they exist,
2617 and clearing the conflict filenames from the entry. The latter needs to
2618 be done whether or not the conflict files exist.
2620 PATH is the path to the item to be resolved, BASE_NAME is the basename
2621 of PATH, and CONFLICT_DIR is the access baton for PATH. ORIG_ENTRY is
2622 the entry prior to resolution. RESOLVE_TEXT and RESOLVE_PROPS are TRUE
2623 if text and property conficts respectively are to be resolved.
2625 See svn_wc_resolved_conflict3() for how CONFLICT_CHOICE behaves.
2627 static svn_error_t *
2628 resolve_conflict_on_entry(const char *path,
2629 const svn_wc_entry_t *orig_entry,
2630 svn_wc_adm_access_t *conflict_dir,
2631 const char *base_name,
2632 svn_boolean_t resolve_text,
2633 svn_boolean_t resolve_props,
2634 svn_wc_conflict_choice_t conflict_choice,
2635 svn_wc_notify_func2_t notify_func,
2636 void *notify_baton,
2637 apr_pool_t *pool)
2639 svn_boolean_t was_present, need_feedback = FALSE;
2640 apr_uint64_t modify_flags = 0;
2641 svn_wc_entry_t *entry = svn_wc_entry_dup(orig_entry, pool);
2642 const char *auto_resolve_src;
2644 /* Handle automatic conflict resolution before the temporary files are
2645 * deleted, if necessary. */
2646 switch (conflict_choice)
2648 case svn_wc_conflict_choose_base:
2649 auto_resolve_src = entry->conflict_old;
2650 break;
2651 case svn_wc_conflict_choose_mine_full:
2652 auto_resolve_src = entry->conflict_wrk;
2653 break;
2654 case svn_wc_conflict_choose_theirs_full:
2655 auto_resolve_src = entry->conflict_new;
2656 break;
2657 case svn_wc_conflict_choose_merged:
2658 auto_resolve_src = NULL;
2659 break;
2660 default:
2661 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2662 _("Invalid 'conflict_result' argument"));
2665 if (auto_resolve_src)
2666 SVN_ERR(svn_io_copy_file(
2667 svn_path_join(svn_wc_adm_access_path(conflict_dir), auto_resolve_src,
2668 pool),
2669 path, TRUE, pool));
2671 /* Yes indeed, being able to map a function over a list would be nice. */
2672 if (resolve_text && entry->conflict_old)
2674 SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir),
2675 entry->conflict_old, &was_present, pool));
2676 modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD;
2677 entry->conflict_old = NULL;
2678 need_feedback |= was_present;
2680 if (resolve_text && entry->conflict_new)
2682 SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir),
2683 entry->conflict_new, &was_present, pool));
2684 modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW;
2685 entry->conflict_new = NULL;
2686 need_feedback |= was_present;
2688 if (resolve_text && entry->conflict_wrk)
2690 SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir),
2691 entry->conflict_wrk, &was_present, pool));
2692 modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK;
2693 entry->conflict_wrk = NULL;
2694 need_feedback |= was_present;
2696 if (resolve_props && entry->prejfile)
2698 SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir),
2699 entry->prejfile, &was_present, pool));
2700 modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE;
2701 entry->prejfile = NULL;
2702 need_feedback |= was_present;
2705 if (modify_flags)
2707 /* Although removing the files is sufficient to indicate that the
2708 conflict is resolved, if we update the entry as well future checks
2709 for conflict state will be more efficient. */
2710 SVN_ERR(svn_wc__entry_modify
2711 (conflict_dir,
2712 (entry->kind == svn_node_dir ? NULL : base_name),
2713 entry, modify_flags, TRUE, pool));
2715 /* No feedback if no files were deleted and all we did was change the
2716 entry, such a file did not appear as a conflict */
2717 if (need_feedback && notify_func)
2719 /* Sanity check: see if libsvn_wc *still* thinks this item is in a
2720 state of conflict that we have asked to resolve. If not, report
2721 the successful resolution. */
2722 svn_boolean_t text_conflict, prop_conflict;
2723 SVN_ERR(svn_wc_conflicted_p(&text_conflict, &prop_conflict,
2724 svn_wc_adm_access_path(conflict_dir),
2725 entry, pool));
2726 if ((! (resolve_text && text_conflict))
2727 && (! (resolve_props && prop_conflict)))
2728 (*notify_func)(notify_baton,
2729 svn_wc_create_notify(path, svn_wc_notify_resolved,
2730 pool), pool);
2734 return SVN_NO_ERROR;
2737 /* Machinery for an automated entries walk... */
2739 struct resolve_callback_baton
2741 /* TRUE if text conflicts are to be resolved. */
2742 svn_boolean_t resolve_text;
2743 /* TRUE if property conflicts are to be resolved. */
2744 svn_boolean_t resolve_props;
2745 /* The type of automatic conflict resolution to perform */
2746 svn_wc_conflict_choice_t conflict_choice;
2747 /* An access baton for the tree, with write access */
2748 svn_wc_adm_access_t *adm_access;
2749 /* Notification function and baton */
2750 svn_wc_notify_func2_t notify_func;
2751 void *notify_baton;
2754 static svn_error_t *
2755 resolve_found_entry_callback(const char *path,
2756 const svn_wc_entry_t *entry,
2757 void *walk_baton,
2758 apr_pool_t *pool)
2760 struct resolve_callback_baton *baton = walk_baton;
2761 const char *conflict_dir, *base_name = NULL;
2762 svn_wc_adm_access_t *adm_access;
2764 /* We're going to receive dirents twice; we want to ignore the
2765 first one (where it's a child of a parent dir), and only print
2766 the second one (where we're looking at THIS_DIR). */
2767 if ((entry->kind == svn_node_dir)
2768 && (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR)))
2769 return SVN_NO_ERROR;
2771 /* Figger out the directory in which the conflict resides. */
2772 if (entry->kind == svn_node_dir)
2773 conflict_dir = path;
2774 else
2775 svn_path_split(path, &conflict_dir, &base_name, pool);
2776 SVN_ERR(svn_wc_adm_retrieve(&adm_access, baton->adm_access, conflict_dir,
2777 pool));
2779 return resolve_conflict_on_entry(path, entry, adm_access, base_name,
2780 baton->resolve_text, baton->resolve_props,
2781 baton->conflict_choice, baton->notify_func,
2782 baton->notify_baton, pool);
2785 static const svn_wc_entry_callbacks2_t
2786 resolve_walk_callbacks =
2788 resolve_found_entry_callback,
2789 svn_wc__walker_default_error_handler
2793 /* The public function */
2794 svn_error_t *
2795 svn_wc_resolved_conflict(const char *path,
2796 svn_wc_adm_access_t *adm_access,
2797 svn_boolean_t resolve_text,
2798 svn_boolean_t resolve_props,
2799 svn_boolean_t recurse,
2800 svn_wc_notify_func_t notify_func,
2801 void *notify_baton,
2802 apr_pool_t *pool)
2804 svn_wc__compat_notify_baton_t nb;
2806 nb.func = notify_func;
2807 nb.baton = notify_baton;
2809 return svn_wc_resolved_conflict2(path, adm_access,
2810 resolve_text, resolve_props, recurse,
2811 svn_wc__compat_call_notify_func, &nb,
2812 NULL, NULL, pool);
2816 svn_error_t *
2817 svn_wc_resolved_conflict2(const char *path,
2818 svn_wc_adm_access_t *adm_access,
2819 svn_boolean_t resolve_text,
2820 svn_boolean_t resolve_props,
2821 svn_boolean_t recurse,
2822 svn_wc_notify_func2_t notify_func,
2823 void *notify_baton,
2824 svn_cancel_func_t cancel_func,
2825 void *cancel_baton,
2826 apr_pool_t *pool)
2828 return svn_wc_resolved_conflict3(path, adm_access, resolve_text,
2829 resolve_props, recurse,
2830 svn_wc_conflict_choose_merged,
2831 notify_func, notify_baton, cancel_func,
2832 cancel_baton, pool);
2835 svn_error_t *
2836 svn_wc_resolved_conflict3(const char *path,
2837 svn_wc_adm_access_t *adm_access,
2838 svn_boolean_t resolve_text,
2839 svn_boolean_t resolve_props,
2840 svn_depth_t depth,
2841 svn_wc_conflict_choice_t conflict_choice,
2842 svn_wc_notify_func2_t notify_func,
2843 void *notify_baton,
2844 svn_cancel_func_t cancel_func,
2845 void *cancel_baton,
2846 apr_pool_t *pool)
2848 struct resolve_callback_baton *baton = apr_pcalloc(pool, sizeof(*baton));
2850 baton->resolve_text = resolve_text;
2851 baton->resolve_props = resolve_props;
2852 baton->adm_access = adm_access;
2853 baton->notify_func = notify_func;
2854 baton->notify_baton = notify_baton;
2855 baton->conflict_choice = conflict_choice;
2857 if (depth == svn_depth_empty)
2859 const svn_wc_entry_t *entry;
2860 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
2862 SVN_ERR(resolve_found_entry_callback(path, entry, baton, pool));
2864 else
2866 SVN_ERR(svn_wc_walk_entries3(path, adm_access,
2867 &resolve_walk_callbacks, baton, depth,
2868 FALSE, cancel_func, cancel_baton, pool));
2872 return SVN_NO_ERROR;
2875 svn_error_t *svn_wc_add_lock(const char *path, const svn_lock_t *lock,
2876 svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
2878 const svn_wc_entry_t *entry;
2879 svn_wc_entry_t newentry;
2881 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
2884 newentry.lock_token = lock->token;
2885 newentry.lock_owner = lock->owner;
2886 newentry.lock_comment = lock->comment;
2887 newentry.lock_creation_date = lock->creation_date;
2889 SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry,
2890 SVN_WC__ENTRY_MODIFY_LOCK_TOKEN
2891 | SVN_WC__ENTRY_MODIFY_LOCK_OWNER
2892 | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT
2893 | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE,
2894 TRUE, pool));
2896 { /* if svn:needs-lock is present, then make the file read-write. */
2897 const svn_string_t *needs_lock;
2899 SVN_ERR(svn_wc_prop_get(&needs_lock, SVN_PROP_NEEDS_LOCK,
2900 path, adm_access, pool));
2901 if (needs_lock)
2902 SVN_ERR(svn_io_set_file_read_write(path, FALSE, pool));
2905 return SVN_NO_ERROR;
2908 svn_error_t *svn_wc_remove_lock(const char *path,
2909 svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
2911 const svn_wc_entry_t *entry;
2912 svn_wc_entry_t newentry;
2914 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
2916 newentry.lock_token = newentry.lock_owner = newentry.lock_comment = NULL;
2917 newentry.lock_creation_date = 0;
2918 SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry,
2919 SVN_WC__ENTRY_MODIFY_LOCK_TOKEN
2920 | SVN_WC__ENTRY_MODIFY_LOCK_OWNER
2921 | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT
2922 | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE,
2923 TRUE, pool));
2925 { /* if svn:needs-lock is present, then make the file read-only. */
2926 const svn_string_t *needs_lock;
2928 SVN_ERR(svn_wc_prop_get(&needs_lock, SVN_PROP_NEEDS_LOCK,
2929 path, adm_access, pool));
2930 if (needs_lock)
2931 SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
2934 return SVN_NO_ERROR;
2938 svn_error_t *
2939 svn_wc_set_changelist(const char *path,
2940 const char *changelist,
2941 svn_wc_adm_access_t *adm_access,
2942 svn_cancel_func_t cancel_func,
2943 void *cancel_baton,
2944 svn_wc_notify_func2_t notify_func,
2945 void *notify_baton,
2946 apr_pool_t *pool)
2948 const svn_wc_entry_t *entry;
2949 svn_wc_entry_t newentry;
2950 svn_wc_notify_t *notify;
2952 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
2953 if (! entry)
2954 return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
2955 _("'%s' is not under version control"), path);
2957 /* We can't do changelists on directories. */
2958 if (entry->kind == svn_node_dir)
2959 return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
2960 _("'%s' is a directory, and thus cannot"
2961 " be a member of a changelist"), path);
2963 /* If the path has no changelist and we're removing changelist, skip it. */
2964 if (! (changelist || entry->changelist))
2965 return SVN_NO_ERROR;
2967 /* If the path is already assigned to the changelist we're
2968 trying to assign, skip it. */
2969 if (entry->changelist
2970 && changelist
2971 && strcmp(entry->changelist, changelist) == 0)
2972 return SVN_NO_ERROR;
2974 /* If the path is already a member of a changelist, warn the
2975 user about this, but still allow the reassignment to happen. */
2976 if (entry->changelist && changelist && notify_func)
2978 svn_error_t *unversioned_err =
2979 svn_error_createf(SVN_ERR_WC_CHANGELIST_MOVE, NULL,
2980 _("Removing '%s' from changelist '%s'."),
2981 path, entry->changelist);
2982 notify = svn_wc_create_notify(path, svn_wc_notify_changelist_moved,
2983 pool);
2984 notify->err = unversioned_err;
2985 notify_func(notify_baton, notify, pool);
2988 /* Tweak the entry. */
2989 newentry.changelist = changelist;
2990 SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry,
2991 SVN_WC__ENTRY_MODIFY_CHANGELIST, TRUE, pool));
2993 /* And tell someone what we've done. */
2994 if (notify_func)
2996 notify = svn_wc_create_notify(path,
2997 changelist
2998 ? svn_wc_notify_changelist_set
2999 : svn_wc_notify_changelist_clear,
3000 pool);
3001 notify->changelist_name = changelist;
3002 notify_func(notify_baton, notify, pool);
3005 return SVN_NO_ERROR;