1251881Speter/*
2251881Speter * update.c:  wrappers around wc update functionality
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include "svn_hash.h"
31251881Speter#include "svn_wc.h"
32251881Speter#include "svn_client.h"
33251881Speter#include "svn_error.h"
34251881Speter#include "svn_config.h"
35251881Speter#include "svn_time.h"
36251881Speter#include "svn_dirent_uri.h"
37251881Speter#include "svn_path.h"
38251881Speter#include "svn_pools.h"
39251881Speter#include "svn_io.h"
40251881Speter#include "client.h"
41251881Speter
42251881Speter#include "svn_private_config.h"
43251881Speter#include "private/svn_wc_private.h"
44251881Speter
45251881Speter/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
46251881Speter   a struct svn_client__dirent_fetcher_baton_t * baton */
47251881Spetersvn_error_t *
48251881Spetersvn_client__dirent_fetcher(void *baton,
49251881Speter                           apr_hash_t **dirents,
50251881Speter                           const char *repos_root_url,
51251881Speter                           const char *repos_relpath,
52251881Speter                           apr_pool_t *result_pool,
53251881Speter                           apr_pool_t *scratch_pool)
54251881Speter{
55251881Speter  struct svn_client__dirent_fetcher_baton_t *dfb = baton;
56251881Speter  const char *old_url = NULL;
57251881Speter  const char *session_relpath;
58251881Speter  svn_node_kind_t kind;
59251881Speter  const char *url;
60251881Speter
61251881Speter  url = svn_path_url_add_component2(repos_root_url, repos_relpath,
62251881Speter                                    scratch_pool);
63251881Speter
64251881Speter  if (!svn_uri__is_ancestor(dfb->anchor_url, url))
65251881Speter    {
66251881Speter      SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session,
67251881Speter                                                url, scratch_pool));
68251881Speter      session_relpath = "";
69251881Speter    }
70251881Speter  else
71251881Speter    SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session,
72251881Speter                                                &session_relpath, url,
73251881Speter                                                scratch_pool));
74251881Speter
75251881Speter  /* Is session_relpath still a directory? */
76251881Speter  SVN_ERR(svn_ra_check_path(dfb->ra_session, session_relpath,
77251881Speter                            dfb->target_revision, &kind, scratch_pool));
78251881Speter
79251881Speter  if (kind == svn_node_dir)
80251881Speter    SVN_ERR(svn_ra_get_dir2(dfb->ra_session, dirents, NULL, NULL,
81251881Speter                            session_relpath, dfb->target_revision,
82251881Speter                            SVN_DIRENT_KIND, result_pool));
83251881Speter  else
84251881Speter    *dirents = NULL;
85251881Speter
86251881Speter  if (old_url)
87251881Speter    SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool));
88251881Speter
89251881Speter  return SVN_NO_ERROR;
90251881Speter}
91251881Speter
92251881Speter
93251881Speter/*** Code. ***/
94251881Speter
95251881Speter/* Set *CLEAN_CHECKOUT to FALSE only if LOCAL_ABSPATH is a non-empty
96251881Speter   folder. ANCHOR_ABSPATH is the w/c root and LOCAL_ABSPATH will still
97251881Speter   be considered empty, if it is equal to ANCHOR_ABSPATH and only
98251881Speter   contains the admin sub-folder.
99251881Speter   If the w/c folder already exists but cannot be openend, we return
100251881Speter   "unclean" - just in case. Most likely, the caller will have to bail
101251881Speter   out later due to the same error we got here.
102251881Speter */
103251881Speterstatic svn_error_t *
104251881Speteris_empty_wc(svn_boolean_t *clean_checkout,
105251881Speter            const char *local_abspath,
106251881Speter            const char *anchor_abspath,
107251881Speter            apr_pool_t *pool)
108251881Speter{
109251881Speter  apr_dir_t *dir;
110251881Speter  apr_finfo_t finfo;
111251881Speter  svn_error_t *err;
112251881Speter
113251881Speter  /* "clean" until found dirty */
114251881Speter  *clean_checkout = TRUE;
115251881Speter
116251881Speter  /* open directory. If it does not exist, yet, a clean one will
117251881Speter     be created by the caller. */
118251881Speter  err = svn_io_dir_open(&dir, local_abspath, pool);
119251881Speter  if (err)
120251881Speter    {
121251881Speter      if (! APR_STATUS_IS_ENOENT(err->apr_err))
122251881Speter        *clean_checkout = FALSE;
123251881Speter
124251881Speter      svn_error_clear(err);
125251881Speter      return SVN_NO_ERROR;
126251881Speter    }
127251881Speter
128251881Speter  for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool);
129251881Speter       err == SVN_NO_ERROR;
130251881Speter       err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool))
131251881Speter    {
132251881Speter      /* Ignore entries for this dir and its parent, robustly.
133251881Speter         (APR promises that they'll come first, so technically
134251881Speter         this guard could be moved outside the loop.  But Ryan Bloom
135251881Speter         says he doesn't believe it, and I believe him. */
136251881Speter      if (! (finfo.name[0] == '.'
137251881Speter             && (finfo.name[1] == '\0'
138251881Speter                 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
139251881Speter        {
140251881Speter          if (   ! svn_wc_is_adm_dir(finfo.name, pool)
141251881Speter              || strcmp(local_abspath, anchor_abspath) != 0)
142251881Speter            {
143251881Speter              *clean_checkout = FALSE;
144251881Speter              break;
145251881Speter            }
146251881Speter        }
147251881Speter    }
148251881Speter
149251881Speter  if (err)
150251881Speter    {
151251881Speter      if (! APR_STATUS_IS_ENOENT(err->apr_err))
152251881Speter        {
153251881Speter          /* There was some issue reading the folder content.
154251881Speter           * We better disable optimizations in that case. */
155251881Speter          *clean_checkout = FALSE;
156251881Speter        }
157251881Speter
158251881Speter      svn_error_clear(err);
159251881Speter    }
160251881Speter
161251881Speter  return svn_io_dir_close(dir);
162251881Speter}
163251881Speter
164251881Speter/* A conflict callback that simply records the conflicted path in BATON.
165251881Speter
166251881Speter   Implements svn_wc_conflict_resolver_func2_t.
167251881Speter*/
168251881Speterstatic svn_error_t *
169251881Speterrecord_conflict(svn_wc_conflict_result_t **result,
170251881Speter                const svn_wc_conflict_description2_t *description,
171251881Speter                void *baton,
172251881Speter                apr_pool_t *result_pool,
173251881Speter                apr_pool_t *scratch_pool)
174251881Speter{
175251881Speter  apr_hash_t *conflicted_paths = baton;
176251881Speter
177251881Speter  svn_hash_sets(conflicted_paths,
178251881Speter                apr_pstrdup(apr_hash_pool_get(conflicted_paths),
179251881Speter                            description->local_abspath), "");
180251881Speter  *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
181251881Speter                                          NULL, result_pool);
182251881Speter  return SVN_NO_ERROR;
183251881Speter}
184251881Speter
185251881Speter/* This is a helper for svn_client__update_internal(), which see for
186251881Speter   an explanation of most of these parameters.  Some stuff that's
187251881Speter   unique is as follows:
188251881Speter
189251881Speter   ANCHOR_ABSPATH is the local absolute path of the update anchor.
190251881Speter   This is typically either the same as LOCAL_ABSPATH, or the
191251881Speter   immediate parent of LOCAL_ABSPATH.
192251881Speter
193251881Speter   If NOTIFY_SUMMARY is set (and there's a notification handler in
194251881Speter   CTX), transmit the final update summary upon successful
195251881Speter   completion of the update.
196251881Speter
197251881Speter   Add the paths of any conflict victims to CONFLICTED_PATHS, if that
198251881Speter   is not null.
199251881Speter*/
200251881Speterstatic svn_error_t *
201251881Speterupdate_internal(svn_revnum_t *result_rev,
202251881Speter                apr_hash_t *conflicted_paths,
203251881Speter                const char *local_abspath,
204251881Speter                const char *anchor_abspath,
205251881Speter                const svn_opt_revision_t *revision,
206251881Speter                svn_depth_t depth,
207251881Speter                svn_boolean_t depth_is_sticky,
208251881Speter                svn_boolean_t ignore_externals,
209251881Speter                svn_boolean_t allow_unver_obstructions,
210251881Speter                svn_boolean_t adds_as_modification,
211251881Speter                svn_boolean_t *timestamp_sleep,
212251881Speter                svn_boolean_t notify_summary,
213251881Speter                svn_client_ctx_t *ctx,
214251881Speter                apr_pool_t *pool)
215251881Speter{
216251881Speter  const svn_delta_editor_t *update_editor;
217251881Speter  void *update_edit_baton;
218251881Speter  const svn_ra_reporter3_t *reporter;
219251881Speter  void *report_baton;
220251881Speter  const char *corrected_url;
221251881Speter  const char *target;
222251881Speter  const char *repos_root_url;
223251881Speter  const char *repos_relpath;
224251881Speter  const char *repos_uuid;
225251881Speter  const char *anchor_url;
226251881Speter  svn_revnum_t revnum;
227251881Speter  svn_boolean_t use_commit_times;
228251881Speter  svn_boolean_t clean_checkout = FALSE;
229251881Speter  const char *diff3_cmd;
230251881Speter  apr_hash_t *wcroot_iprops;
231251881Speter  svn_opt_revision_t opt_rev;
232251881Speter  svn_ra_session_t *ra_session;
233251881Speter  const char *preserved_exts_str;
234251881Speter  apr_array_header_t *preserved_exts;
235251881Speter  struct svn_client__dirent_fetcher_baton_t dfb;
236251881Speter  svn_boolean_t server_supports_depth;
237251881Speter  svn_boolean_t cropping_target;
238251881Speter  svn_boolean_t target_conflicted = FALSE;
239251881Speter  svn_config_t *cfg = ctx->config
240251881Speter                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
241251881Speter                      : NULL;
242251881Speter
243251881Speter  if (result_rev)
244251881Speter    *result_rev = SVN_INVALID_REVNUM;
245251881Speter
246251881Speter  /* An unknown depth can't be sticky. */
247251881Speter  if (depth == svn_depth_unknown)
248251881Speter    depth_is_sticky = FALSE;
249251881Speter
250251881Speter  if (strcmp(local_abspath, anchor_abspath))
251251881Speter    target = svn_dirent_basename(local_abspath, pool);
252251881Speter  else
253251881Speter    target = "";
254251881Speter
255251881Speter  /* Check if our anchor exists in BASE. If it doesn't we can't update. */
256251881Speter  SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
257251881Speter                                &repos_uuid, NULL,
258251881Speter                                ctx->wc_ctx, anchor_abspath,
259251881Speter                                TRUE, FALSE,
260251881Speter                                pool, pool));
261251881Speter
262251881Speter  /* It does not make sense to update conflict victims. */
263251881Speter  if (repos_relpath)
264251881Speter    {
265251881Speter      svn_error_t *err;
266251881Speter      svn_boolean_t text_conflicted, prop_conflicted;
267251881Speter
268251881Speter      anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
269251881Speter                                               pool);
270251881Speter
271251881Speter      err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
272251881Speter                                 NULL,
273251881Speter                                 ctx->wc_ctx, local_abspath, pool);
274251881Speter
275251881Speter      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
276251881Speter        return svn_error_trace(err);
277251881Speter      svn_error_clear(err);
278251881Speter
279251881Speter      /* tree-conflicts are handled by the update editor */
280251881Speter      if (!err && (text_conflicted || prop_conflicted))
281251881Speter        target_conflicted = TRUE;
282251881Speter    }
283251881Speter  else
284251881Speter    anchor_url = NULL;
285251881Speter
286251881Speter  if (! anchor_url || target_conflicted)
287251881Speter    {
288251881Speter      if (ctx->notify_func2)
289251881Speter        {
290251881Speter          svn_wc_notify_t *nt;
291251881Speter
292251881Speter          nt = svn_wc_create_notify(local_abspath,
293251881Speter                                    target_conflicted
294251881Speter                                      ? svn_wc_notify_skip_conflicted
295251881Speter                                      : svn_wc_notify_update_skip_working_only,
296251881Speter                                    pool);
297251881Speter
298251881Speter          ctx->notify_func2(ctx->notify_baton2, nt, pool);
299251881Speter        }
300251881Speter      return SVN_NO_ERROR;
301251881Speter    }
302251881Speter
303251881Speter  /* We may need to crop the tree if the depth is sticky */
304251881Speter  cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
305251881Speter  if (cropping_target)
306251881Speter    {
307251881Speter      svn_node_kind_t target_kind;
308251881Speter
309251881Speter      if (depth == svn_depth_exclude)
310251881Speter        {
311251881Speter          SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
312251881Speter                                 local_abspath,
313251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
314251881Speter                                 ctx->notify_func2, ctx->notify_baton2,
315251881Speter                                 pool));
316251881Speter
317251881Speter          /* Target excluded, we are done now */
318251881Speter          return SVN_NO_ERROR;
319251881Speter        }
320251881Speter
321251881Speter      SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
322251881Speter                                TRUE, TRUE, pool));
323251881Speter      if (target_kind == svn_node_dir)
324251881Speter        {
325251881Speter          SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
326251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
327251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
328251881Speter                                    pool));
329251881Speter        }
330251881Speter    }
331251881Speter
332251881Speter  /* check whether the "clean c/o" optimization is applicable */
333251881Speter  SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool));
334251881Speter
335251881Speter  /* Get the external diff3, if any. */
336251881Speter  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
337251881Speter                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
338251881Speter
339251881Speter  if (diff3_cmd != NULL)
340251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
341251881Speter
342251881Speter  /* See if the user wants last-commit timestamps instead of current ones. */
343251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
344251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
345251881Speter                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
346251881Speter
347251881Speter  /* See which files the user wants to preserve the extension of when
348251881Speter     conflict files are made. */
349251881Speter  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
350251881Speter                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
351251881Speter  preserved_exts = *preserved_exts_str
352251881Speter    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
353251881Speter    : NULL;
354251881Speter
355251881Speter  /* Let everyone know we're starting a real update (unless we're
356251881Speter     asked not to). */
357251881Speter  if (ctx->notify_func2 && notify_summary)
358251881Speter    {
359251881Speter      svn_wc_notify_t *notify
360251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
361251881Speter                               pool);
362251881Speter      notify->kind = svn_node_none;
363251881Speter      notify->content_state = notify->prop_state
364251881Speter        = svn_wc_notify_state_inapplicable;
365251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
366251881Speter      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
367251881Speter    }
368251881Speter
369251881Speter  /* Open an RA session for the URL */
370251881Speter  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
371251881Speter                                               anchor_url,
372251881Speter                                               anchor_abspath, NULL, TRUE,
373251881Speter                                               TRUE, ctx, pool, pool));
374251881Speter
375251881Speter  /* If we got a corrected URL from the RA subsystem, we'll need to
376251881Speter     relocate our working copy first. */
377251881Speter  if (corrected_url)
378251881Speter    {
379251881Speter      const char *new_repos_root_url;
380251881Speter
381251881Speter      /* To relocate everything inside our repository we need the old and new
382251881Speter         repos root. */
383251881Speter      SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool));
384251881Speter
385251881Speter      /* svn_client_relocate2() will check the uuid */
386262253Speter      SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
387251881Speter                                   new_repos_root_url, ignore_externals,
388251881Speter                                   ctx, pool));
389251881Speter
390251881Speter      /* Store updated repository root for externals */
391251881Speter      repos_root_url = new_repos_root_url;
392251881Speter      /* ### We should update anchor_loc->repos_uuid too, although currently
393251881Speter       * we don't use it. */
394251881Speter      anchor_url = corrected_url;
395251881Speter    }
396251881Speter
397251881Speter  /* Resolve unspecified REVISION now, because we need to retrieve the
398251881Speter     correct inherited props prior to the editor drive and we need to
399251881Speter     use the same value of HEAD for both. */
400251881Speter  opt_rev.kind = revision->kind;
401251881Speter  opt_rev.value = revision->value;
402251881Speter  if (opt_rev.kind == svn_opt_revision_unspecified)
403251881Speter    opt_rev.kind = svn_opt_revision_head;
404251881Speter
405251881Speter  /* ### todo: shouldn't svn_client__get_revision_number be able
406251881Speter     to take a URL as easily as a local path?  */
407251881Speter  SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
408251881Speter                                          local_abspath, ra_session, &opt_rev,
409251881Speter                                          pool));
410251881Speter
411251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
412251881Speter                                SVN_RA_CAPABILITY_DEPTH, pool));
413251881Speter
414251881Speter  dfb.ra_session = ra_session;
415251881Speter  dfb.target_revision = revnum;
416251881Speter  dfb.anchor_url = anchor_url;
417251881Speter
418251881Speter  SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
419251881Speter                                            revnum, depth, ra_session,
420251881Speter                                            ctx, pool, pool));
421251881Speter
422251881Speter  /* Fetch the update editor.  If REVISION is invalid, that's okay;
423251881Speter     the RA driver will call editor->set_target_revision later on. */
424251881Speter  SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
425251881Speter                                    &revnum, ctx->wc_ctx, anchor_abspath,
426251881Speter                                    target, wcroot_iprops, use_commit_times,
427251881Speter                                    depth, depth_is_sticky,
428251881Speter                                    allow_unver_obstructions,
429251881Speter                                    adds_as_modification,
430251881Speter                                    server_supports_depth,
431251881Speter                                    clean_checkout,
432251881Speter                                    diff3_cmd, preserved_exts,
433251881Speter                                    svn_client__dirent_fetcher, &dfb,
434251881Speter                                    conflicted_paths ? record_conflict : NULL,
435251881Speter                                    conflicted_paths,
436251881Speter                                    NULL, NULL,
437251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
438251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
439251881Speter                                    pool, pool));
440251881Speter
441251881Speter  /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
442251881Speter     invalid revnum, that means RA will use the latest revision.  */
443251881Speter  SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
444251881Speter                            revnum, target,
445251881Speter                            (!server_supports_depth || depth_is_sticky
446251881Speter                             ? depth
447251881Speter                             : svn_depth_unknown),
448251881Speter                            FALSE /* send_copyfrom_args */,
449251881Speter                            FALSE /* ignore_ancestry */,
450251881Speter                            update_editor, update_edit_baton, pool, pool));
451251881Speter
452251881Speter  /* Past this point, we assume the WC is going to be modified so we will
453251881Speter   * need to sleep for timestamps. */
454251881Speter  *timestamp_sleep = TRUE;
455251881Speter
456251881Speter  /* Drive the reporter structure, describing the revisions within
457251881Speter     PATH.  When we call reporter->finish_report, the
458251881Speter     update_editor will be driven by svn_repos_dir_delta2. */
459251881Speter  SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
460251881Speter                                  report_baton, TRUE,
461251881Speter                                  depth, (! depth_is_sticky),
462251881Speter                                  (! server_supports_depth),
463251881Speter                                  use_commit_times,
464251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
465251881Speter                                  ctx->notify_func2, ctx->notify_baton2,
466251881Speter                                  pool));
467251881Speter
468251881Speter  /* We handle externals after the update is complete, so that
469251881Speter     handling external items (and any errors therefrom) doesn't delay
470251881Speter     the primary operation.  */
471251881Speter  if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
472251881Speter      && (! ignore_externals))
473251881Speter    {
474251881Speter      apr_hash_t *new_externals;
475251881Speter      apr_hash_t *new_depths;
476251881Speter      SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
477251881Speter                                                   &new_depths,
478251881Speter                                                   ctx->wc_ctx, local_abspath,
479251881Speter                                                   depth, pool, pool));
480251881Speter
481251881Speter      SVN_ERR(svn_client__handle_externals(new_externals,
482251881Speter                                           new_depths,
483251881Speter                                           repos_root_url, local_abspath,
484251881Speter                                           depth, timestamp_sleep,
485251881Speter                                           ctx, pool));
486251881Speter    }
487251881Speter
488251881Speter  /* Let everyone know we're finished here (unless we're asked not to). */
489251881Speter  if (ctx->notify_func2 && notify_summary)
490251881Speter    {
491251881Speter      svn_wc_notify_t *notify
492251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
493251881Speter                               pool);
494251881Speter      notify->kind = svn_node_none;
495251881Speter      notify->content_state = notify->prop_state
496251881Speter        = svn_wc_notify_state_inapplicable;
497251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
498251881Speter      notify->revision = revnum;
499251881Speter      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
500251881Speter    }
501251881Speter
502251881Speter  /* If the caller wants the result revision, give it to them. */
503251881Speter  if (result_rev)
504251881Speter    *result_rev = revnum;
505251881Speter
506251881Speter  return SVN_NO_ERROR;
507251881Speter}
508251881Speter
509251881Spetersvn_error_t *
510251881Spetersvn_client__update_internal(svn_revnum_t *result_rev,
511251881Speter                            const char *local_abspath,
512251881Speter                            const svn_opt_revision_t *revision,
513251881Speter                            svn_depth_t depth,
514251881Speter                            svn_boolean_t depth_is_sticky,
515251881Speter                            svn_boolean_t ignore_externals,
516251881Speter                            svn_boolean_t allow_unver_obstructions,
517251881Speter                            svn_boolean_t adds_as_modification,
518251881Speter                            svn_boolean_t make_parents,
519251881Speter                            svn_boolean_t innerupdate,
520251881Speter                            svn_boolean_t *timestamp_sleep,
521251881Speter                            svn_client_ctx_t *ctx,
522251881Speter                            apr_pool_t *pool)
523251881Speter{
524251881Speter  const char *anchor_abspath, *lockroot_abspath;
525251881Speter  svn_error_t *err;
526251881Speter  svn_opt_revision_t peg_revision = *revision;
527251881Speter  apr_hash_t *conflicted_paths
528251881Speter    = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
529251881Speter
530251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
531251881Speter  SVN_ERR_ASSERT(! (innerupdate && make_parents));
532251881Speter
533251881Speter  if (make_parents)
534251881Speter    {
535251881Speter      int i;
536251881Speter      const char *parent_abspath = local_abspath;
537251881Speter      apr_array_header_t *missing_parents =
538251881Speter        apr_array_make(pool, 4, sizeof(const char *));
539251881Speter
540251881Speter      while (1)
541251881Speter        {
542251881Speter          /* Try to lock.  If we can't lock because our target (or its
543251881Speter             parent) isn't a working copy, we'll try to walk up the
544251881Speter             tree to find a working copy, remembering this path's
545251881Speter             parent as one we need to flesh out.  */
546251881Speter          err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
547251881Speter                                           parent_abspath, !innerupdate,
548251881Speter                                           pool, pool);
549251881Speter          if (!err)
550251881Speter            break;
551251881Speter          if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
552251881Speter              || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
553251881Speter            return err;
554251881Speter          svn_error_clear(err);
555251881Speter
556251881Speter          /* Remember the parent of our update target as a missing
557251881Speter             parent. */
558251881Speter          parent_abspath = svn_dirent_dirname(parent_abspath, pool);
559251881Speter          APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
560251881Speter        }
561251881Speter
562251881Speter      /* Run 'svn up --depth=empty' (effectively) on the missing
563251881Speter         parents, if any. */
564251881Speter      anchor_abspath = lockroot_abspath;
565251881Speter      for (i = missing_parents->nelts - 1; i >= 0; i--)
566251881Speter        {
567251881Speter          const char *missing_parent =
568251881Speter            APR_ARRAY_IDX(missing_parents, i, const char *);
569251881Speter
570251881Speter          err = update_internal(result_rev, conflicted_paths,
571251881Speter                                missing_parent, anchor_abspath,
572251881Speter                                &peg_revision, svn_depth_empty, FALSE,
573251881Speter                                ignore_externals, allow_unver_obstructions,
574251881Speter                                adds_as_modification, timestamp_sleep,
575251881Speter                                FALSE, ctx, pool);
576251881Speter          if (err)
577251881Speter            goto cleanup;
578251881Speter          anchor_abspath = missing_parent;
579251881Speter
580251881Speter          /* If we successfully updated a missing parent, let's re-use
581251881Speter             the returned revision number for future updates for the
582251881Speter             sake of consistency. */
583251881Speter          peg_revision.kind = svn_opt_revision_number;
584251881Speter          peg_revision.value.number = *result_rev;
585251881Speter        }
586251881Speter    }
587251881Speter  else
588251881Speter    {
589251881Speter      SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
590251881Speter                                         local_abspath, !innerupdate,
591251881Speter                                         pool, pool));
592251881Speter      anchor_abspath = lockroot_abspath;
593251881Speter    }
594251881Speter
595251881Speter  err = update_internal(result_rev, conflicted_paths,
596251881Speter                        local_abspath, anchor_abspath,
597251881Speter                        &peg_revision, depth, depth_is_sticky,
598251881Speter                        ignore_externals, allow_unver_obstructions,
599251881Speter                        adds_as_modification, timestamp_sleep,
600251881Speter                        TRUE, ctx, pool);
601251881Speter
602251881Speter  /* Give the conflict resolver callback the opportunity to
603251881Speter   * resolve any conflicts that were raised. */
604251881Speter  if (! err && ctx->conflict_func2)
605251881Speter    {
606251881Speter      err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
607251881Speter    }
608251881Speter
609251881Speter cleanup:
610251881Speter  err = svn_error_compose_create(
611251881Speter            err,
612251881Speter            svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
613251881Speter
614251881Speter  return svn_error_trace(err);
615251881Speter}
616251881Speter
617251881Speter
618251881Spetersvn_error_t *
619251881Spetersvn_client_update4(apr_array_header_t **result_revs,
620251881Speter                   const apr_array_header_t *paths,
621251881Speter                   const svn_opt_revision_t *revision,
622251881Speter                   svn_depth_t depth,
623251881Speter                   svn_boolean_t depth_is_sticky,
624251881Speter                   svn_boolean_t ignore_externals,
625251881Speter                   svn_boolean_t allow_unver_obstructions,
626251881Speter                   svn_boolean_t adds_as_modification,
627251881Speter                   svn_boolean_t make_parents,
628251881Speter                   svn_client_ctx_t *ctx,
629251881Speter                   apr_pool_t *pool)
630251881Speter{
631251881Speter  int i;
632251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
633251881Speter  const char *path = NULL;
634251881Speter  svn_boolean_t sleep = FALSE;
635251881Speter  svn_error_t *err = SVN_NO_ERROR;
636251881Speter
637251881Speter  if (result_revs)
638251881Speter    *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
639251881Speter
640251881Speter  for (i = 0; i < paths->nelts; ++i)
641251881Speter    {
642251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
643251881Speter
644251881Speter      if (svn_path_is_url(path))
645251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
646251881Speter                                 _("'%s' is not a local path"), path);
647251881Speter    }
648251881Speter
649251881Speter  for (i = 0; i < paths->nelts; ++i)
650251881Speter    {
651251881Speter      svn_revnum_t result_rev;
652251881Speter      const char *local_abspath;
653251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
654251881Speter
655251881Speter      svn_pool_clear(iterpool);
656251881Speter
657251881Speter      if (ctx->cancel_func)
658251881Speter        {
659251881Speter          err = ctx->cancel_func(ctx->cancel_baton);
660251881Speter          if (err)
661251881Speter            goto cleanup;
662251881Speter        }
663251881Speter
664251881Speter      err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
665251881Speter      if (err)
666251881Speter        goto cleanup;
667251881Speter      err = svn_client__update_internal(&result_rev, local_abspath,
668251881Speter                                        revision, depth, depth_is_sticky,
669251881Speter                                        ignore_externals,
670251881Speter                                        allow_unver_obstructions,
671251881Speter                                        adds_as_modification,
672251881Speter                                        make_parents,
673251881Speter                                        FALSE, &sleep,
674251881Speter                                        ctx,
675251881Speter                                        iterpool);
676251881Speter
677251881Speter      if (err)
678251881Speter        {
679251881Speter          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
680251881Speter            goto cleanup;
681251881Speter
682251881Speter          svn_error_clear(err);
683251881Speter          err = SVN_NO_ERROR;
684251881Speter
685251881Speter          /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
686251881Speter
687251881Speter          result_rev = SVN_INVALID_REVNUM;
688251881Speter          if (ctx->notify_func2)
689251881Speter            {
690251881Speter              svn_wc_notify_t *notify;
691251881Speter              notify = svn_wc_create_notify(path,
692251881Speter                                            svn_wc_notify_skip,
693251881Speter                                            iterpool);
694251881Speter              (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
695251881Speter            }
696251881Speter        }
697251881Speter      if (result_revs)
698251881Speter        APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
699251881Speter    }
700251881Speter  svn_pool_destroy(iterpool);
701251881Speter
702251881Speter cleanup:
703251881Speter  if (sleep)
704262253Speter    {
705262253Speter      const char *wcroot_abspath;
706251881Speter
707262253Speter      if (paths->nelts == 1)
708262253Speter        {
709262253Speter          const char *abspath;
710262253Speter
711262253Speter          /* PATH iteslf may have been removed by the update. */
712262253Speter          SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
713262253Speter          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
714262253Speter                                     pool, pool));
715262253Speter        }
716262253Speter      else
717262253Speter        wcroot_abspath = NULL;
718262253Speter
719262253Speter      svn_io_sleep_for_timestamps(wcroot_abspath, pool);
720262253Speter    }
721262253Speter
722251881Speter  return svn_error_trace(err);
723251881Speter}
724