1 | /* |
---|
2 | * update.c: wrappers around wc update functionality |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2000-2009 CollabNet. All rights reserved. |
---|
6 | * |
---|
7 | * This software is licensed as described in the file COPYING, which |
---|
8 | * you should have received as part of this distribution. The terms |
---|
9 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
10 | * If newer versions of this license are posted there, you may use a |
---|
11 | * newer version instead, at your option. |
---|
12 | * |
---|
13 | * This software consists of voluntary contributions made by many |
---|
14 | * individuals. For exact contribution history, see the revision |
---|
15 | * history and logs, available at http://subversion.tigris.org/. |
---|
16 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | /* ==================================================================== */ |
---|
20 | |
---|
21 | |
---|
22 | |
---|
23 | /*** Includes. ***/ |
---|
24 | |
---|
25 | #include "svn_wc.h" |
---|
26 | #include "svn_client.h" |
---|
27 | #include "svn_error.h" |
---|
28 | #include "svn_config.h" |
---|
29 | #include "svn_time.h" |
---|
30 | #include "svn_dirent_uri.h" |
---|
31 | #include "svn_path.h" |
---|
32 | #include "svn_pools.h" |
---|
33 | #include "svn_io.h" |
---|
34 | #include "client.h" |
---|
35 | |
---|
36 | #include "svn_private_config.h" |
---|
37 | #include "private/svn_wc_private.h" |
---|
38 | |
---|
39 | |
---|
40 | /*** Code. ***/ |
---|
41 | |
---|
42 | |
---|
43 | /* Context baton for file_fetcher below. */ |
---|
44 | struct ff_baton |
---|
45 | { |
---|
46 | svn_client_ctx_t *ctx; /* client context used to open ra session */ |
---|
47 | const char *repos_root; /* the root of the ra session */ |
---|
48 | svn_ra_session_t *session; /* the secondary ra session itself */ |
---|
49 | apr_pool_t *pool; /* the pool where the ra session is allocated */ |
---|
50 | }; |
---|
51 | |
---|
52 | |
---|
53 | /* Implementation of svn_wc_get_file_t. A feeble callback wrapper |
---|
54 | around svn_ra_get_file(), so that the update_editor can use it to |
---|
55 | fetch any file, any time. */ |
---|
56 | static svn_error_t * |
---|
57 | file_fetcher(void *baton, |
---|
58 | const char *path, |
---|
59 | svn_revnum_t revision, |
---|
60 | svn_stream_t *stream, |
---|
61 | svn_revnum_t *fetched_rev, |
---|
62 | apr_hash_t **props, |
---|
63 | apr_pool_t *pool) |
---|
64 | { |
---|
65 | struct ff_baton *ffb = (struct ff_baton *)baton; |
---|
66 | |
---|
67 | if (! ffb->session) |
---|
68 | SVN_ERR(svn_client__open_ra_session_internal(&(ffb->session), |
---|
69 | ffb->repos_root, |
---|
70 | NULL, NULL, NULL, |
---|
71 | FALSE, TRUE, |
---|
72 | ffb->ctx, ffb->pool)); |
---|
73 | return svn_ra_get_file(ffb->session, path, revision, stream, |
---|
74 | fetched_rev, props, pool); |
---|
75 | } |
---|
76 | |
---|
77 | |
---|
78 | svn_error_t * |
---|
79 | svn_client__update_internal(svn_revnum_t *result_rev, |
---|
80 | const char *path, |
---|
81 | const svn_opt_revision_t *revision, |
---|
82 | svn_depth_t depth, |
---|
83 | svn_boolean_t depth_is_sticky, |
---|
84 | svn_boolean_t ignore_externals, |
---|
85 | svn_boolean_t allow_unver_obstructions, |
---|
86 | svn_boolean_t *timestamp_sleep, |
---|
87 | svn_boolean_t send_copyfrom_args, |
---|
88 | svn_client_ctx_t *ctx, |
---|
89 | apr_pool_t *pool) |
---|
90 | { |
---|
91 | const svn_delta_editor_t *update_editor; |
---|
92 | void *update_edit_baton; |
---|
93 | const svn_ra_reporter3_t *reporter; |
---|
94 | void *report_baton; |
---|
95 | const svn_wc_entry_t *entry; |
---|
96 | const char *anchor, *target; |
---|
97 | const char *repos_root; |
---|
98 | svn_error_t *err; |
---|
99 | svn_revnum_t revnum; |
---|
100 | int levels_to_lock; |
---|
101 | svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); |
---|
102 | svn_wc_adm_access_t *adm_access; |
---|
103 | svn_boolean_t use_commit_times; |
---|
104 | svn_boolean_t sleep_here = FALSE; |
---|
105 | svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; |
---|
106 | const char *diff3_cmd; |
---|
107 | svn_ra_session_t *ra_session; |
---|
108 | svn_wc_adm_access_t *dir_access; |
---|
109 | const char *preserved_exts_str; |
---|
110 | apr_array_header_t *preserved_exts; |
---|
111 | struct ff_baton *ffb; |
---|
112 | svn_boolean_t server_supports_depth; |
---|
113 | svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, |
---|
114 | SVN_CONFIG_CATEGORY_CONFIG, |
---|
115 | APR_HASH_KEY_STRING) : NULL; |
---|
116 | |
---|
117 | /* An unknown depth can't be sticky. */ |
---|
118 | if (depth == svn_depth_unknown) |
---|
119 | depth_is_sticky = FALSE; |
---|
120 | |
---|
121 | /* ### Ah, the irony. We'd like to base our levels_to_lock on the |
---|
122 | ### depth we're going to use for the update. But that may depend |
---|
123 | ### on the depth in the working copy, which we can't discover |
---|
124 | ### without calling adm_open. We could expend an extra call, |
---|
125 | ### with levels_to_lock=0, to get the real depth (but only if we |
---|
126 | ### need to) and then make the real call... but it's not worth |
---|
127 | ### the complexity right now. If the requested depth tells us to |
---|
128 | ### lock the entire tree when we don't actually need to, that's a |
---|
129 | ### performance hit, but (except for access contention) it is not |
---|
130 | ### a correctness problem. */ |
---|
131 | |
---|
132 | /* We may have to crop the subtree if the depth is sticky, so lock the |
---|
133 | entire tree in such a situation*/ |
---|
134 | levels_to_lock = depth_is_sticky |
---|
135 | ? -1 : SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth); |
---|
136 | |
---|
137 | /* Sanity check. Without this, the update is meaningless. */ |
---|
138 | SVN_ERR_ASSERT(path); |
---|
139 | |
---|
140 | if (svn_path_is_url(path)) |
---|
141 | return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, |
---|
142 | _("Path '%s' is not a directory"), |
---|
143 | path); |
---|
144 | |
---|
145 | /* Use PATH to get the update's anchor and targets and get a write lock */ |
---|
146 | SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, |
---|
147 | TRUE, levels_to_lock, |
---|
148 | ctx->cancel_func, ctx->cancel_baton, |
---|
149 | pool)); |
---|
150 | anchor = svn_wc_adm_access_path(adm_access); |
---|
151 | |
---|
152 | /* Get full URL from the ANCHOR. */ |
---|
153 | SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool)); |
---|
154 | if (! entry->url) |
---|
155 | return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, |
---|
156 | _("Entry '%s' has no URL"), |
---|
157 | svn_path_local_style(anchor, pool)); |
---|
158 | |
---|
159 | /* We may need to crop the tree if the depth is sticky */ |
---|
160 | if (depth_is_sticky && depth < svn_depth_infinity) |
---|
161 | { |
---|
162 | const svn_wc_entry_t *target_entry; |
---|
163 | SVN_ERR(svn_wc_entry(&target_entry, |
---|
164 | svn_dirent_join(svn_wc_adm_access_path(adm_access), target, pool), |
---|
165 | adm_access, TRUE, pool)); |
---|
166 | |
---|
167 | if (target_entry && target_entry->kind == svn_node_dir) |
---|
168 | { |
---|
169 | SVN_ERR(svn_wc_crop_tree(adm_access, target, depth, |
---|
170 | ctx->notify_func2, ctx->notify_baton2, |
---|
171 | ctx->cancel_func, ctx->cancel_baton, |
---|
172 | pool)); |
---|
173 | /* If we are asked to exclude a target, we can just stop now. */ |
---|
174 | if (depth == svn_depth_exclude) |
---|
175 | { |
---|
176 | SVN_ERR(svn_wc_adm_close2(adm_access, pool)); |
---|
177 | return SVN_NO_ERROR; |
---|
178 | } |
---|
179 | } |
---|
180 | } |
---|
181 | |
---|
182 | /* Get revnum set to something meaningful, so we can fetch the |
---|
183 | update editor. */ |
---|
184 | if (revision->kind == svn_opt_revision_number) |
---|
185 | revnum = revision->value.number; |
---|
186 | else |
---|
187 | revnum = SVN_INVALID_REVNUM; |
---|
188 | |
---|
189 | /* Get the external diff3, if any. */ |
---|
190 | svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, |
---|
191 | SVN_CONFIG_OPTION_DIFF3_CMD, NULL); |
---|
192 | |
---|
193 | /* See if the user wants last-commit timestamps instead of current ones. */ |
---|
194 | SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, |
---|
195 | SVN_CONFIG_SECTION_MISCELLANY, |
---|
196 | SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); |
---|
197 | |
---|
198 | /* See which files the user wants to preserve the extension of when |
---|
199 | conflict files are made. */ |
---|
200 | svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, |
---|
201 | SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); |
---|
202 | preserved_exts = *preserved_exts_str |
---|
203 | ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) |
---|
204 | : NULL; |
---|
205 | |
---|
206 | /* Open an RA session for the URL */ |
---|
207 | SVN_ERR(svn_client__open_ra_session_internal(&ra_session, entry->url, |
---|
208 | anchor, adm_access, |
---|
209 | NULL, TRUE, TRUE, |
---|
210 | ctx, pool)); |
---|
211 | |
---|
212 | /* ### todo: shouldn't svn_client__get_revision_number be able |
---|
213 | to take a URL as easily as a local path? */ |
---|
214 | SVN_ERR(svn_client__get_revision_number |
---|
215 | (&revnum, NULL, ra_session, revision, path, pool)); |
---|
216 | |
---|
217 | /* Take the chance to set the repository root on the target. |
---|
218 | Why do we bother doing this for old working copies? |
---|
219 | There are two reasons: first, it's nice to get this information into |
---|
220 | old WCs so they are "ready" when we start depending on it. (We can |
---|
221 | never *depend* upon it in a strict sense, however.) |
---|
222 | Second, if people mix old and new clients, this information will |
---|
223 | be dropped by the old clients, which might be annoying. */ |
---|
224 | SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); |
---|
225 | SVN_ERR(svn_wc_maybe_set_repos_root(dir_access, path, repos_root, pool)); |
---|
226 | |
---|
227 | /* Build a baton for the file-fetching callback. */ |
---|
228 | ffb = apr_pcalloc(pool, sizeof(*ffb)); |
---|
229 | ffb->ctx = ctx; |
---|
230 | ffb->repos_root = repos_root; |
---|
231 | ffb->pool = pool; |
---|
232 | |
---|
233 | /* Fetch the update editor. If REVISION is invalid, that's okay; |
---|
234 | the RA driver will call editor->set_target_revision later on. */ |
---|
235 | SVN_ERR(svn_wc_get_update_editor3(&revnum, adm_access, target, |
---|
236 | use_commit_times, depth, depth_is_sticky, |
---|
237 | allow_unver_obstructions, |
---|
238 | ctx->notify_func2, ctx->notify_baton2, |
---|
239 | ctx->cancel_func, ctx->cancel_baton, |
---|
240 | ctx->conflict_func, ctx->conflict_baton, |
---|
241 | file_fetcher, ffb, |
---|
242 | diff3_cmd, preserved_exts, |
---|
243 | &update_editor, &update_edit_baton, |
---|
244 | traversal_info, |
---|
245 | pool)); |
---|
246 | |
---|
247 | /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an |
---|
248 | invalid revnum, that means RA will use the latest revision. */ |
---|
249 | SVN_ERR(svn_ra_do_update2(ra_session, |
---|
250 | &reporter, &report_baton, |
---|
251 | revnum, |
---|
252 | target, |
---|
253 | depth, |
---|
254 | send_copyfrom_args, |
---|
255 | update_editor, update_edit_baton, pool)); |
---|
256 | |
---|
257 | SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, |
---|
258 | SVN_RA_CAPABILITY_DEPTH, pool)); |
---|
259 | |
---|
260 | /* Drive the reporter structure, describing the revisions within |
---|
261 | PATH. When we call reporter->finish_report, the |
---|
262 | update_editor will be driven by svn_repos_dir_delta2. */ |
---|
263 | err = svn_wc_crawl_revisions4(path, dir_access, reporter, report_baton, |
---|
264 | TRUE, depth, (! depth_is_sticky), |
---|
265 | (! server_supports_depth), |
---|
266 | use_commit_times, |
---|
267 | ctx->notify_func2, ctx->notify_baton2, |
---|
268 | traversal_info, pool); |
---|
269 | |
---|
270 | if (err) |
---|
271 | { |
---|
272 | /* Don't rely on the error handling to handle the sleep later, do |
---|
273 | it now */ |
---|
274 | svn_io_sleep_for_timestamps(path, pool); |
---|
275 | return err; |
---|
276 | } |
---|
277 | *use_sleep = TRUE; |
---|
278 | |
---|
279 | /* We handle externals after the update is complete, so that |
---|
280 | handling external items (and any errors therefrom) doesn't delay |
---|
281 | the primary operation. */ |
---|
282 | if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) |
---|
283 | SVN_ERR(svn_client__handle_externals(adm_access, |
---|
284 | traversal_info, |
---|
285 | entry->url, |
---|
286 | anchor, |
---|
287 | repos_root, |
---|
288 | depth, |
---|
289 | use_sleep, ctx, pool)); |
---|
290 | |
---|
291 | if (sleep_here) |
---|
292 | svn_io_sleep_for_timestamps(path, pool); |
---|
293 | |
---|
294 | SVN_ERR(svn_wc_adm_close2(adm_access, pool)); |
---|
295 | |
---|
296 | /* Let everyone know we're finished here. */ |
---|
297 | if (ctx->notify_func2) |
---|
298 | { |
---|
299 | svn_wc_notify_t *notify |
---|
300 | = svn_wc_create_notify(path, svn_wc_notify_update_completed, pool); |
---|
301 | notify->kind = svn_node_none; |
---|
302 | notify->content_state = notify->prop_state |
---|
303 | = svn_wc_notify_state_inapplicable; |
---|
304 | notify->lock_state = svn_wc_notify_lock_state_inapplicable; |
---|
305 | notify->revision = revnum; |
---|
306 | (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); |
---|
307 | } |
---|
308 | |
---|
309 | /* If the caller wants the result revision, give it to them. */ |
---|
310 | if (result_rev) |
---|
311 | *result_rev = revnum; |
---|
312 | |
---|
313 | return SVN_NO_ERROR; |
---|
314 | } |
---|
315 | |
---|
316 | svn_error_t * |
---|
317 | svn_client_update3(apr_array_header_t **result_revs, |
---|
318 | const apr_array_header_t *paths, |
---|
319 | const svn_opt_revision_t *revision, |
---|
320 | svn_depth_t depth, |
---|
321 | svn_boolean_t depth_is_sticky, |
---|
322 | svn_boolean_t ignore_externals, |
---|
323 | svn_boolean_t allow_unver_obstructions, |
---|
324 | svn_client_ctx_t *ctx, |
---|
325 | apr_pool_t *pool) |
---|
326 | { |
---|
327 | int i; |
---|
328 | svn_error_t *err = SVN_NO_ERROR; |
---|
329 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
330 | const char *path = NULL; |
---|
331 | |
---|
332 | if (result_revs) |
---|
333 | *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t)); |
---|
334 | |
---|
335 | for (i = 0; i < paths->nelts; ++i) |
---|
336 | { |
---|
337 | svn_boolean_t sleep; |
---|
338 | svn_revnum_t result_rev; |
---|
339 | path = APR_ARRAY_IDX(paths, i, const char *); |
---|
340 | |
---|
341 | svn_pool_clear(subpool); |
---|
342 | |
---|
343 | if (ctx->cancel_func && (err = ctx->cancel_func(ctx->cancel_baton))) |
---|
344 | break; |
---|
345 | |
---|
346 | err = svn_client__update_internal(&result_rev, path, revision, depth, |
---|
347 | depth_is_sticky, ignore_externals, |
---|
348 | allow_unver_obstructions, |
---|
349 | &sleep, TRUE, ctx, subpool); |
---|
350 | if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY) |
---|
351 | { |
---|
352 | return err; |
---|
353 | } |
---|
354 | else if (err) |
---|
355 | { |
---|
356 | /* SVN_ERR_WC_NOT_DIRECTORY: it's not versioned */ |
---|
357 | svn_error_clear(err); |
---|
358 | err = SVN_NO_ERROR; |
---|
359 | result_rev = SVN_INVALID_REVNUM; |
---|
360 | if (ctx->notify_func2) |
---|
361 | (*ctx->notify_func2)(ctx->notify_baton2, |
---|
362 | svn_wc_create_notify(path, |
---|
363 | svn_wc_notify_skip, |
---|
364 | subpool), subpool); |
---|
365 | } |
---|
366 | if (result_revs) |
---|
367 | APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev; |
---|
368 | } |
---|
369 | |
---|
370 | svn_pool_destroy(subpool); |
---|
371 | svn_io_sleep_for_timestamps((paths->nelts == 1) ? path : NULL, pool); |
---|
372 | |
---|
373 | return err; |
---|
374 | } |
---|