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 * ====================================================================
28 #include <apr_pools.h>
29 #include <apr_tables.h>
32 #include <apr_file_io.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"
50 #include "adm_files.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. */
66 tweak_entries(svn_wc_adm_access_t
*dirpath
,
70 svn_wc_notify_func2_t notify_func
,
72 svn_boolean_t remove_missing_dirs
,
74 apr_hash_t
*exclude_paths
,
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
),
89 SVN_ERR(svn_wc__tweak_entry(entries
, SVN_WC_ENTRY_THIS_DIR
,
90 base_url
, repos
, new_rev
, FALSE
,
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
))
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
);
115 /* Ignore the "this dir" entry. */
116 if (! strcmp(name
, SVN_WC_ENTRY_THIS_DIR
))
119 /* Derive the new URL for the current (child) entry */
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
,
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
))
134 SVN_ERR(svn_wc__tweak_entry(entries
, name
,
135 child_url
, repos
, new_rev
, TRUE
,
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
160 svn_wc__entry_remove(entries
, name
);
163 notify
= svn_wc_create_notify(child_path
,
164 svn_wc_notify_delete
,
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. */
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. */
190 SVN_ERR(svn_wc__entries_write(entries
, dirpath
, subpool
));
193 svn_pool_destroy(subpool
);
198 /* Helper for svn_wc_process_committed2. */
200 remove_revert_file(svn_stringbuf_t
**logtags
,
201 svn_wc_adm_access_t
*adm_access
,
203 svn_boolean_t is_prop
,
206 const char *revert_file
;
207 svn_node_kind_t kind
;
210 SVN_ERR(svn_wc__loggy_props_delete(logtags
, path
, svn_wc__props_revert
,
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
));
226 svn_wc__do_update_cleanup(const char *path
,
227 svn_wc_adm_access_t
*adm_access
,
229 const char *base_url
,
231 svn_revnum_t new_revision
,
232 svn_wc_notify_func2_t notify_func
,
234 svn_boolean_t remove_missing_dirs
,
235 apr_hash_t
*exclude_paths
,
239 const svn_wc_entry_t
*entry
;
241 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, TRUE
, pool
));
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
))
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
261 svn_wc_adm_access_pool(dir_access
)));
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
));
277 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND
, NULL
,
278 _("Unrecognized node kind: '%s'"),
279 svn_path_local_style(path
, pool
));
285 svn_wc_maybe_set_repos_root(svn_wc_adm_access_t
*adm_access
,
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
));
300 if (entry
->kind
== svn_node_file
)
304 svn_path_split(path
, &parent
, &base_name
, pool
);
305 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access
, adm_access
,
310 base_name
= SVN_WC_ENTRY_THIS_DIR
;
311 SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access
, adm_access
,
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
,
323 svn_wc_adm_access_pool(dir_access
)));
326 SVN_ERR(svn_wc__entries_write(entries
, dir_access
, pool
));
333 process_committed_leaf(int log_number
,
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
,
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
,
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
));
369 hex_digest
= svn_md5_digest_to_cstring(digest
, pool
);
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
;
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
);
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
);
408 /* Oh, and recursing at this point isn't really sensible. */
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
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
;
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
;
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
));
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
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))
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
,
476 prop
->value
? prop
->value
->data
: NULL
,
481 /* Write our accumulation of log entries into a log file */
482 SVN_ERR(svn_wc__write_log(adm_access
, log_number
, logtags
, pool
));
489 process_committed_internal(int *log_number
,
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
,
502 SVN_ERR(process_committed_leaf((*log_number
)++, path
, adm_access
, &recurse
,
503 new_revnum
, rev_date
, rev_author
,
505 remove_lock
, remove_changelist
,
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
))
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
);
533 /* Ignore the "this dir" entry. */
534 if (! strcmp(name
, SVN_WC_ENTRY_THIS_DIR
))
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
,
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
,
554 new_revnum
, rev_date
, rev_author
, NULL
, FALSE
,
555 remove_changelist
, NULL
, subpool
));
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
)
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
);
586 struct svn_wc_committed_queue_t
589 apr_array_header_t
*queue
;
592 typedef struct committed_queue_item_t
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
));
611 q
->queue
= apr_array_make(pool
, 1, sizeof(committed_queue_item_t
*));
617 svn_wc_queue_committed(svn_wc_committed_queue_t
**queue
,
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
,
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
633 /* Add to the array with paths and options */
634 cqi
= apr_palloc((*queue
)->pool
, sizeof(*cqi
));
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
;
648 typedef struct affected_adm_t
651 svn_wc_adm_access_t
*adm_access
;
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.
664 have_recursive_parent(svn_boolean_t
*have_any_recursive
,
665 apr_array_header_t
*queue
,
670 svn_boolean_t found_recursive
= FALSE
;
672 = APR_ARRAY_IDX(queue
, item
, committed_queue_item_t
*)->path
;
674 if (! *have_any_recursive
)
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
;
688 && svn_path_is_child(qi
->path
, path
, pool
))
692 /* Now we walked the entire array, change the cached value
693 to reflect what we found. */
694 *have_any_recursive
= found_recursive
;
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
,
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
,
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
);
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
,
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
,
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
))
763 affected_adm_t
*this_adm
;
765 svn_pool_clear(iterpool
);
767 apr_hash_this(hi
, NULL
, NULL
, &val
);
770 SVN_ERR(svn_wc__run_log(this_adm
->adm_access
, NULL
, iterpool
));
773 queue
->queue
->nelts
= 0;
775 svn_pool_destroy(iterpool
);
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
,
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
));
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
,
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
);
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
,
835 return svn_wc_process_committed3(path
, adm_access
, recurse
, new_revnum
,
836 rev_date
, rev_author
, wcprop_changes
,
837 remove_lock
, NULL
, pool
);
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
,
850 return svn_wc_process_committed2(path
, adm_access
, recurse
, new_revnum
,
851 rev_date
, rev_author
, wcprop_changes
,
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. */
858 remove_file_if_present(const char *file
, apr_pool_t
*pool
)
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
);
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). */
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
,
888 svn_wc_notify_func2_t notify_func
,
892 apr_pool_t
*subpool
= svn_pool_create(pool
);
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
;
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
);
917 /* Skip "this dir". */
918 if (! strcmp((const char *)key
, SVN_WC_ENTRY_THIS_DIR
))
922 fullpath
= svn_path_join(svn_wc_adm_access_path(adm_access
), base_name
,
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
,
931 SVN_ERR(mark_tree(child_access
, modify_flags
,
932 schedule
, copied
, keep_local
,
933 cancel_func
, cancel_baton
,
934 notify_func
, notify_baton
,
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
),
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
,
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
);
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. */
990 SVN_ERR(svn_wc__entry_modify(adm_access
, NULL
, &tmp_entry
, this_dir_flags
,
993 /* Destroy our per-iteration pool. */
994 svn_pool_destroy(subpool
);
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
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
,
1013 /* Optimize the common case: try to delete the file */
1014 err
= svn_io_remove_file(path
, pool
);
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
);
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
));
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
,
1074 const svn_wc_entry_t
*entry
;
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
;
1090 /* ### Suspect that an iteration or recursion subpool would be
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 */
1102 svn_node_kind_t wc_kind
;
1103 svn_error_t
*err2
= svn_io_check_path(path
, &wc_kind
, pool
);
1107 svn_error_clear(err
);
1111 if (wc_kind
!= svn_node_none
)
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
))
1123 const char *down_path
;
1125 apr_hash_this(hi
, &key
, NULL
, &val
);
1129 if (!strcmp(name
, SVN_WC_ENTRY_THIS_DIR
))
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
))
1143 const char *down_path
;
1145 apr_hash_this(hi
, &key
, NULL
, NULL
);
1148 /* The admin directory will show up, we don't want to delete it */
1149 if (svn_wc_is_adm_dir(name
, pool
))
1152 /* Versioned directories will show up, don't delete those either */
1153 if (apr_hash_get(ver
, name
, APR_HASH_KEY_STRING
))
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
;
1167 svn_wc_delete3(const char *path
,
1168 svn_wc_adm_access_t
*adm_access
,
1169 svn_cancel_func_t cancel_func
,
1171 svn_wc_notify_func2_t notify_func
,
1173 svn_boolean_t keep_local
,
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
));
1187 SVN_ERR(svn_wc_entry(&entry
, path
, dir_access
, FALSE
, pool
));
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
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
));
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
));
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
,
1259 if (!(was_kind
== svn_node_dir
&& was_schedule
== svn_wc_schedule_add
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
,
1274 SVN_WC__ENTRY_MODIFY_SCHEDULE
,
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
,
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
,
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
,
1311 /* By the time we get here, anything that was scheduled to be added has
1312 become unversioned */
1315 if (was_schedule
== svn_wc_schedule_add
)
1316 SVN_ERR(erase_unversioned_from_wc
1317 (path
, cancel_func
, cancel_baton
, pool
));
1319 SVN_ERR(erase_from_wc(path
, adm_access
, was_kind
,
1320 cancel_func
, cancel_baton
, pool
));
1323 return SVN_NO_ERROR
;
1327 svn_wc_delete2(const char *path
,
1328 svn_wc_adm_access_t
*adm_access
,
1329 svn_cancel_func_t cancel_func
,
1331 svn_wc_notify_func2_t notify_func
,
1335 return svn_wc_delete3(path
, adm_access
, cancel_func
, cancel_baton
,
1336 notify_func
, notify_baton
, FALSE
, pool
);
1340 svn_wc_delete(const char *path
,
1341 svn_wc_adm_access_t
*adm_access
,
1342 svn_cancel_func_t cancel_func
,
1344 svn_wc_notify_func_t notify_func
,
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
);
1358 svn_wc_get_ancestry(char **url
,
1361 svn_wc_adm_access_t
*adm_access
,
1364 const svn_wc_entry_t
*ent
;
1366 SVN_ERR(svn_wc__entry_versioned(&ent
, path
, adm_access
, FALSE
, pool
));
1369 *url
= apr_pstrdup(pool
, ent
->url
);
1372 *rev
= ent
->revision
;
1374 return SVN_NO_ERROR
;
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
,
1385 svn_wc_notify_func2_t notify_func
,
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
));
1420 SVN_ERR(svn_wc_entry(&orig_entry
, path
, adm_access
, TRUE
, pool
));
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. */
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
)
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
,
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
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. */
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
,
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. */
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
,
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
));
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
,
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
));
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
),
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
,
1617 NULL
, NULL
, /* N/A cuz we aren't deleting */
1620 /* Clean out the now-obsolete wcprops. */
1621 SVN_ERR(svn_wc__props_delete(path
, svn_wc__props_wcprop
,
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
,
1631 notify
->kind
= kind
;
1632 (*notify_func
)(notify_baton
, notify
, pool
);
1635 return SVN_NO_ERROR
;
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
,
1646 svn_wc_notify_func_t notify_func
,
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.
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.
1678 - Restore properties to their unmodified state.
1680 - For files, restore the pristine contents, and reset the schedule
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.
1691 - Restore properties to their unmodified state.
1693 - For files, restore the pristine contents, and reset the schedule
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.
1705 - Restore properties and, for files, contents to their unmodified
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
1716 Use POOL for any temporary allocations.*/
1717 static svn_error_t
*
1718 revert_admin_things(svn_wc_adm_access_t
*adm_access
,
1720 const svn_wc_entry_t
*entry
,
1721 svn_boolean_t
*reverted
,
1722 svn_boolean_t use_commit_times
,
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
,
1747 revert_base
? &baseprops
: NULL
,
1748 adm_access
, fullpath
, pool
));
1750 /* Ensure the revert propfile gets removed. */
1752 SVN_ERR(svn_wc__loggy_props_delete(&log_accum
,
1753 fullpath
, svn_wc__props_revert
,
1758 /* If not schedule replace, or no revert props, use the normal
1759 base-props and working props. */
1762 svn_boolean_t modified
;
1764 /* Check for prop changes. */
1765 SVN_ERR(svn_wc_props_modified_p(&modified
, fullpath
, adm_access
,
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
,
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. */
1787 SVN_ERR(svn_wc__install_props(&log_accum
, adm_access
, fullpath
,
1788 baseprops
, baseprops
, revert_base
, pool
));
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
,
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
,
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
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
),
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
));
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
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
;
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
1961 Use POOL for allocations.
1963 static svn_error_t
*
1964 revert_entry(svn_depth_t
*depth
,
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
,
1972 svn_wc_notify_func2_t notify_func
,
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
);
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
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
,
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
);
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
));
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 "
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
;
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
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
,
2078 SVN_ERR(svn_wc__entry_modify(parent_access
, bname
, tmpentry
,
2079 SVN_WC__ENTRY_MODIFY_KIND
2080 | SVN_WC__ENTRY_MODIFY_DELETED
,
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
)
2094 SVN_ERR(revert_admin_things(parent_access
, bname
, entry
,
2095 &reverted
, use_commit_times
, pool
));
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
,
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
;
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
),
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
2143 static svn_error_t
*
2144 revert_internal(const char *path
,
2145 svn_wc_adm_access_t
*parent_access
,
2147 svn_boolean_t use_commit_times
,
2148 apr_hash_t
*changelist_hash
,
2149 svn_cancel_func_t cancel_func
,
2151 svn_wc_notify_func2_t notify_func
,
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. */
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
))
2230 const char *keystring
;
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
);
2246 /* Skip "this dir" */
2247 if (! strcmp(keystring
, SVN_WC_ENTRY_THIS_DIR
))
2250 /* Skip subdirectories if we're called with depth-files. */
2251 if ((depth
== svn_depth_files
)
2252 && (child_entry
->kind
!= svn_node_file
))
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
;
2273 svn_wc_revert3(const char *path
,
2274 svn_wc_adm_access_t
*parent_access
,
2276 svn_boolean_t use_commit_times
,
2277 const apr_array_header_t
*changelists
,
2278 svn_cancel_func_t cancel_func
,
2280 svn_wc_notify_func2_t notify_func
,
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
);
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
,
2300 svn_wc_notify_func2_t notify_func
,
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
);
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
,
2318 svn_wc_notify_func_t notify_func
,
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
);
2333 svn_wc_get_pristine_copy_path(const char *path
,
2334 const char **pristine_path
,
2337 *pristine_path
= svn_wc__text_base_path(path
, FALSE
, pool
);
2338 return SVN_NO_ERROR
;
2343 svn_wc_remove_from_revision_control(svn_wc_adm_access_t
*adm_access
,
2345 svn_boolean_t destroy_wf
,
2346 svn_boolean_t instant_error
,
2347 svn_cancel_func_t cancel_func
,
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. */
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
;
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
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
,
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
,
2391 /* Remove prop/NAME, prop-base/NAME.svn-base. */
2392 SVN_ERR(svn_wc__props_delete(full_path
, svn_wc__props_working
,
2394 SVN_ERR(svn_wc__props_delete(full_path
, svn_wc__props_base
,
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
),
2406 /* If we were asked to destroy the working file, do so unless
2407 it has local mods. */
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
2431 incomplete_entry
.incomplete
= TRUE
;
2432 SVN_ERR(svn_wc__entry_modify(adm_access
,
2433 SVN_WC_ENTRY_THIS_DIR
,
2435 SVN_WC__ENTRY_MODIFY_INCOMPLETE
,
2436 TRUE
, /* sync to disk immediately */
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)
2442 SVN_ERR(svn_wc__props_delete(full_path
, svn_wc__props_wcprop
,
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
))
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
;
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
))
2478 svn_error_clear(err
);
2479 left_something
= TRUE
;
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
),
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
2498 svn_wc__entry_remove(entries
, current_entry_name
);
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
))
2517 svn_error_clear(err
);
2518 left_something
= TRUE
;
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: */
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
,
2549 SVN_ERR(svn_wc_entries_read(&entries
, parent_access
, TRUE
,
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
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
);
2575 left_something
= TRUE
;
2576 svn_error_clear(err
);
2580 svn_pool_destroy(subpool
);
2582 } /* end of directory case */
2585 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD
, NULL
, NULL
);
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
,
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
);
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
,
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
;
2651 case svn_wc_conflict_choose_mine_full
:
2652 auto_resolve_src
= entry
->conflict_wrk
;
2654 case svn_wc_conflict_choose_theirs_full
:
2655 auto_resolve_src
= entry
->conflict_new
;
2657 case svn_wc_conflict_choose_merged
:
2658 auto_resolve_src
= NULL
;
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
,
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
;
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
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
),
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
,
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
;
2754 static svn_error_t
*
2755 resolve_found_entry_callback(const char *path
,
2756 const svn_wc_entry_t
*entry
,
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
;
2775 svn_path_split(path
, &conflict_dir
, &base_name
, pool
);
2776 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, baton
->adm_access
, conflict_dir
,
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 */
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
,
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
,
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
,
2824 svn_cancel_func_t cancel_func
,
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
);
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
,
2841 svn_wc_conflict_choice_t conflict_choice
,
2842 svn_wc_notify_func2_t notify_func
,
2844 svn_cancel_func_t cancel_func
,
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
));
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
,
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
));
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
,
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
));
2931 SVN_ERR(svn_io_set_file_read_only(path
, FALSE
, pool
));
2934 return SVN_NO_ERROR
;
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
,
2944 svn_wc_notify_func2_t notify_func
,
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
));
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
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
,
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. */
2996 notify
= svn_wc_create_notify(path
,
2998 ? svn_wc_notify_changelist_set
2999 : svn_wc_notify_changelist_clear
,
3001 notify
->changelist_name
= changelist
;
3002 notify_func(notify_baton
, notify
, pool
);
3005 return SVN_NO_ERROR
;