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.
199299742Sdim
200299742Sdim   Use RA_SESSION_P to run the update if it is not NULL.  If it is then
201299742Sdim   open a new ra session and place it in RA_SESSION_P.  This allows
202299742Sdim   repeated calls to update_internal to reuse the same session.
203251881Speter*/
204251881Speterstatic svn_error_t *
205251881Speterupdate_internal(svn_revnum_t *result_rev,
206299742Sdim                svn_boolean_t *timestamp_sleep,
207251881Speter                apr_hash_t *conflicted_paths,
208299742Sdim                svn_ra_session_t **ra_session_p,
209251881Speter                const char *local_abspath,
210251881Speter                const char *anchor_abspath,
211251881Speter                const svn_opt_revision_t *revision,
212251881Speter                svn_depth_t depth,
213251881Speter                svn_boolean_t depth_is_sticky,
214251881Speter                svn_boolean_t ignore_externals,
215251881Speter                svn_boolean_t allow_unver_obstructions,
216251881Speter                svn_boolean_t adds_as_modification,
217251881Speter                svn_boolean_t notify_summary,
218251881Speter                svn_client_ctx_t *ctx,
219299742Sdim                apr_pool_t *result_pool,
220299742Sdim                apr_pool_t *scratch_pool)
221251881Speter{
222251881Speter  const svn_delta_editor_t *update_editor;
223251881Speter  void *update_edit_baton;
224251881Speter  const svn_ra_reporter3_t *reporter;
225251881Speter  void *report_baton;
226251881Speter  const char *corrected_url;
227251881Speter  const char *target;
228251881Speter  const char *repos_root_url;
229251881Speter  const char *repos_relpath;
230251881Speter  const char *repos_uuid;
231251881Speter  const char *anchor_url;
232251881Speter  svn_revnum_t revnum;
233251881Speter  svn_boolean_t use_commit_times;
234251881Speter  svn_boolean_t clean_checkout = FALSE;
235251881Speter  const char *diff3_cmd;
236251881Speter  apr_hash_t *wcroot_iprops;
237251881Speter  svn_opt_revision_t opt_rev;
238299742Sdim  svn_ra_session_t *ra_session = *ra_session_p;
239251881Speter  const char *preserved_exts_str;
240251881Speter  apr_array_header_t *preserved_exts;
241251881Speter  struct svn_client__dirent_fetcher_baton_t dfb;
242251881Speter  svn_boolean_t server_supports_depth;
243251881Speter  svn_boolean_t cropping_target;
244251881Speter  svn_boolean_t target_conflicted = FALSE;
245251881Speter  svn_config_t *cfg = ctx->config
246251881Speter                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
247251881Speter                      : NULL;
248251881Speter
249251881Speter  if (result_rev)
250251881Speter    *result_rev = SVN_INVALID_REVNUM;
251251881Speter
252251881Speter  /* An unknown depth can't be sticky. */
253251881Speter  if (depth == svn_depth_unknown)
254251881Speter    depth_is_sticky = FALSE;
255251881Speter
256251881Speter  if (strcmp(local_abspath, anchor_abspath))
257299742Sdim    target = svn_dirent_basename(local_abspath, scratch_pool);
258251881Speter  else
259251881Speter    target = "";
260251881Speter
261251881Speter  /* Check if our anchor exists in BASE. If it doesn't we can't update. */
262251881Speter  SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
263251881Speter                                &repos_uuid, NULL,
264251881Speter                                ctx->wc_ctx, anchor_abspath,
265299742Sdim                                TRUE /* ignore_enoent */,
266299742Sdim                                scratch_pool, scratch_pool));
267251881Speter
268251881Speter  /* It does not make sense to update conflict victims. */
269251881Speter  if (repos_relpath)
270251881Speter    {
271251881Speter      svn_error_t *err;
272251881Speter      svn_boolean_t text_conflicted, prop_conflicted;
273251881Speter
274251881Speter      anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
275299742Sdim                                               scratch_pool);
276251881Speter
277251881Speter      err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
278251881Speter                                 NULL,
279299742Sdim                                 ctx->wc_ctx, local_abspath, scratch_pool);
280251881Speter
281251881Speter      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
282251881Speter        return svn_error_trace(err);
283251881Speter      svn_error_clear(err);
284251881Speter
285251881Speter      /* tree-conflicts are handled by the update editor */
286251881Speter      if (!err && (text_conflicted || prop_conflicted))
287251881Speter        target_conflicted = TRUE;
288251881Speter    }
289251881Speter  else
290251881Speter    anchor_url = NULL;
291251881Speter
292251881Speter  if (! anchor_url || target_conflicted)
293251881Speter    {
294251881Speter      if (ctx->notify_func2)
295251881Speter        {
296251881Speter          svn_wc_notify_t *nt;
297251881Speter
298251881Speter          nt = svn_wc_create_notify(local_abspath,
299251881Speter                                    target_conflicted
300251881Speter                                      ? svn_wc_notify_skip_conflicted
301251881Speter                                      : svn_wc_notify_update_skip_working_only,
302299742Sdim                                    scratch_pool);
303251881Speter
304299742Sdim          ctx->notify_func2(ctx->notify_baton2, nt, scratch_pool);
305251881Speter        }
306251881Speter      return SVN_NO_ERROR;
307251881Speter    }
308251881Speter
309251881Speter  /* We may need to crop the tree if the depth is sticky */
310251881Speter  cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
311251881Speter  if (cropping_target)
312251881Speter    {
313251881Speter      svn_node_kind_t target_kind;
314251881Speter
315251881Speter      if (depth == svn_depth_exclude)
316251881Speter        {
317251881Speter          SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
318251881Speter                                 local_abspath,
319251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
320251881Speter                                 ctx->notify_func2, ctx->notify_baton2,
321299742Sdim                                 scratch_pool));
322251881Speter
323251881Speter          /* Target excluded, we are done now */
324251881Speter          return SVN_NO_ERROR;
325251881Speter        }
326251881Speter
327251881Speter      SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
328299742Sdim                                TRUE, TRUE, scratch_pool));
329251881Speter      if (target_kind == svn_node_dir)
330251881Speter        {
331251881Speter          SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
332251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
333251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
334299742Sdim                                    scratch_pool));
335251881Speter        }
336251881Speter    }
337251881Speter
338251881Speter  /* check whether the "clean c/o" optimization is applicable */
339299742Sdim  SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath,
340299742Sdim                      scratch_pool));
341251881Speter
342251881Speter  /* Get the external diff3, if any. */
343251881Speter  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
344251881Speter                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
345251881Speter
346251881Speter  if (diff3_cmd != NULL)
347299742Sdim    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
348251881Speter
349251881Speter  /* See if the user wants last-commit timestamps instead of current ones. */
350251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
351251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
352251881Speter                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
353251881Speter
354251881Speter  /* See which files the user wants to preserve the extension of when
355251881Speter     conflict files are made. */
356251881Speter  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
357251881Speter                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
358251881Speter  preserved_exts = *preserved_exts_str
359299742Sdim    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
360251881Speter    : NULL;
361251881Speter
362251881Speter  /* Let everyone know we're starting a real update (unless we're
363251881Speter     asked not to). */
364251881Speter  if (ctx->notify_func2 && notify_summary)
365251881Speter    {
366251881Speter      svn_wc_notify_t *notify
367251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
368299742Sdim                               scratch_pool);
369251881Speter      notify->kind = svn_node_none;
370251881Speter      notify->content_state = notify->prop_state
371251881Speter        = svn_wc_notify_state_inapplicable;
372251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
373299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
374251881Speter    }
375251881Speter
376299742Sdim  /* Try to reuse the RA session by reparenting it to the anchor_url.
377299742Sdim   * This code is probably overly cautious since we only use this
378299742Sdim   * currently when parents are missing and so all the anchor_urls
379299742Sdim   * have to be in the same repo. */
380299742Sdim  if (ra_session)
381299742Sdim    {
382299742Sdim      svn_error_t *err = svn_ra_reparent(ra_session, anchor_url, scratch_pool);
383299742Sdim      if (err)
384299742Sdim        {
385299742Sdim          if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
386299742Sdim            {
387299742Sdim            /* session changed repos, can't reuse it */
388299742Sdim              svn_error_clear(err);
389299742Sdim              ra_session = NULL;
390299742Sdim            }
391299742Sdim          else
392299742Sdim            {
393299742Sdim              return svn_error_trace(err);
394299742Sdim            }
395299742Sdim        }
396299742Sdim      else
397299742Sdim        {
398299742Sdim          corrected_url = NULL;
399299742Sdim        }
400299742Sdim    }
401251881Speter
402299742Sdim  /* Open an RA session for the URL if one isn't already available */
403299742Sdim  if (!ra_session)
404299742Sdim    {
405299742Sdim      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
406299742Sdim                                                   anchor_url,
407299742Sdim                                                   anchor_abspath, NULL,
408299742Sdim                                                   TRUE /* write_dav_props */,
409299742Sdim                                                   TRUE /* read_dav_props */,
410299742Sdim                                                   ctx,
411299742Sdim                                                   result_pool, scratch_pool));
412299742Sdim      *ra_session_p = ra_session;
413299742Sdim    }
414299742Sdim
415251881Speter  /* If we got a corrected URL from the RA subsystem, we'll need to
416251881Speter     relocate our working copy first. */
417251881Speter  if (corrected_url)
418251881Speter    {
419251881Speter      const char *new_repos_root_url;
420251881Speter
421251881Speter      /* To relocate everything inside our repository we need the old and new
422251881Speter         repos root. */
423299742Sdim      SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url,
424299742Sdim                                     scratch_pool));
425251881Speter
426251881Speter      /* svn_client_relocate2() will check the uuid */
427262253Speter      SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
428251881Speter                                   new_repos_root_url, ignore_externals,
429299742Sdim                                   ctx, scratch_pool));
430251881Speter
431251881Speter      /* Store updated repository root for externals */
432251881Speter      repos_root_url = new_repos_root_url;
433251881Speter      /* ### We should update anchor_loc->repos_uuid too, although currently
434251881Speter       * we don't use it. */
435251881Speter      anchor_url = corrected_url;
436251881Speter    }
437251881Speter
438251881Speter  /* Resolve unspecified REVISION now, because we need to retrieve the
439251881Speter     correct inherited props prior to the editor drive and we need to
440251881Speter     use the same value of HEAD for both. */
441251881Speter  opt_rev.kind = revision->kind;
442251881Speter  opt_rev.value = revision->value;
443251881Speter  if (opt_rev.kind == svn_opt_revision_unspecified)
444251881Speter    opt_rev.kind = svn_opt_revision_head;
445251881Speter
446251881Speter  /* ### todo: shouldn't svn_client__get_revision_number be able
447251881Speter     to take a URL as easily as a local path?  */
448251881Speter  SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
449251881Speter                                          local_abspath, ra_session, &opt_rev,
450299742Sdim                                          scratch_pool));
451251881Speter
452251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
453299742Sdim                                SVN_RA_CAPABILITY_DEPTH, scratch_pool));
454251881Speter
455251881Speter  dfb.ra_session = ra_session;
456251881Speter  dfb.target_revision = revnum;
457251881Speter  dfb.anchor_url = anchor_url;
458251881Speter
459251881Speter  SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
460251881Speter                                            revnum, depth, ra_session,
461299742Sdim                                            ctx, scratch_pool, scratch_pool));
462251881Speter
463251881Speter  /* Fetch the update editor.  If REVISION is invalid, that's okay;
464251881Speter     the RA driver will call editor->set_target_revision later on. */
465251881Speter  SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
466251881Speter                                    &revnum, ctx->wc_ctx, anchor_abspath,
467251881Speter                                    target, wcroot_iprops, use_commit_times,
468251881Speter                                    depth, depth_is_sticky,
469251881Speter                                    allow_unver_obstructions,
470251881Speter                                    adds_as_modification,
471251881Speter                                    server_supports_depth,
472251881Speter                                    clean_checkout,
473251881Speter                                    diff3_cmd, preserved_exts,
474251881Speter                                    svn_client__dirent_fetcher, &dfb,
475251881Speter                                    conflicted_paths ? record_conflict : NULL,
476251881Speter                                    conflicted_paths,
477251881Speter                                    NULL, NULL,
478251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
479251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
480299742Sdim                                    scratch_pool, scratch_pool));
481251881Speter
482251881Speter  /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
483251881Speter     invalid revnum, that means RA will use the latest revision.  */
484251881Speter  SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
485251881Speter                            revnum, target,
486251881Speter                            (!server_supports_depth || depth_is_sticky
487251881Speter                             ? depth
488251881Speter                             : svn_depth_unknown),
489251881Speter                            FALSE /* send_copyfrom_args */,
490251881Speter                            FALSE /* ignore_ancestry */,
491299742Sdim                            update_editor, update_edit_baton,
492299742Sdim                            scratch_pool, scratch_pool));
493251881Speter
494251881Speter  /* Past this point, we assume the WC is going to be modified so we will
495251881Speter   * need to sleep for timestamps. */
496251881Speter  *timestamp_sleep = TRUE;
497251881Speter
498251881Speter  /* Drive the reporter structure, describing the revisions within
499299742Sdim     LOCAL_ABSPATH.  When this calls reporter->finish_report, the
500299742Sdim     reporter will drive the update_editor. */
501251881Speter  SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
502251881Speter                                  report_baton, TRUE,
503251881Speter                                  depth, (! depth_is_sticky),
504251881Speter                                  (! server_supports_depth),
505251881Speter                                  use_commit_times,
506251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
507251881Speter                                  ctx->notify_func2, ctx->notify_baton2,
508299742Sdim                                  scratch_pool));
509251881Speter
510251881Speter  /* We handle externals after the update is complete, so that
511251881Speter     handling external items (and any errors therefrom) doesn't delay
512251881Speter     the primary operation.  */
513251881Speter  if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
514251881Speter      && (! ignore_externals))
515251881Speter    {
516251881Speter      apr_hash_t *new_externals;
517251881Speter      apr_hash_t *new_depths;
518251881Speter      SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
519251881Speter                                                   &new_depths,
520251881Speter                                                   ctx->wc_ctx, local_abspath,
521299742Sdim                                                   depth,
522299742Sdim                                                   scratch_pool, scratch_pool));
523251881Speter
524251881Speter      SVN_ERR(svn_client__handle_externals(new_externals,
525251881Speter                                           new_depths,
526251881Speter                                           repos_root_url, local_abspath,
527299742Sdim                                           depth, timestamp_sleep, ra_session,
528299742Sdim                                           ctx, scratch_pool));
529251881Speter    }
530251881Speter
531251881Speter  /* Let everyone know we're finished here (unless we're asked not to). */
532251881Speter  if (ctx->notify_func2 && notify_summary)
533251881Speter    {
534251881Speter      svn_wc_notify_t *notify
535251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
536299742Sdim                               scratch_pool);
537251881Speter      notify->kind = svn_node_none;
538251881Speter      notify->content_state = notify->prop_state
539251881Speter        = svn_wc_notify_state_inapplicable;
540251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
541251881Speter      notify->revision = revnum;
542299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
543251881Speter    }
544251881Speter
545251881Speter  /* If the caller wants the result revision, give it to them. */
546251881Speter  if (result_rev)
547251881Speter    *result_rev = revnum;
548251881Speter
549251881Speter  return SVN_NO_ERROR;
550251881Speter}
551251881Speter
552251881Spetersvn_error_t *
553251881Spetersvn_client__update_internal(svn_revnum_t *result_rev,
554299742Sdim                            svn_boolean_t *timestamp_sleep,
555251881Speter                            const char *local_abspath,
556251881Speter                            const svn_opt_revision_t *revision,
557251881Speter                            svn_depth_t depth,
558251881Speter                            svn_boolean_t depth_is_sticky,
559251881Speter                            svn_boolean_t ignore_externals,
560251881Speter                            svn_boolean_t allow_unver_obstructions,
561251881Speter                            svn_boolean_t adds_as_modification,
562251881Speter                            svn_boolean_t make_parents,
563251881Speter                            svn_boolean_t innerupdate,
564299742Sdim                            svn_ra_session_t *ra_session,
565251881Speter                            svn_client_ctx_t *ctx,
566251881Speter                            apr_pool_t *pool)
567251881Speter{
568251881Speter  const char *anchor_abspath, *lockroot_abspath;
569251881Speter  svn_error_t *err;
570251881Speter  svn_opt_revision_t peg_revision = *revision;
571251881Speter  apr_hash_t *conflicted_paths
572251881Speter    = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
573251881Speter
574251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
575251881Speter  SVN_ERR_ASSERT(! (innerupdate && make_parents));
576251881Speter
577251881Speter  if (make_parents)
578251881Speter    {
579251881Speter      int i;
580251881Speter      const char *parent_abspath = local_abspath;
581251881Speter      apr_array_header_t *missing_parents =
582251881Speter        apr_array_make(pool, 4, sizeof(const char *));
583299742Sdim      apr_pool_t *iterpool;
584251881Speter
585299742Sdim      iterpool = svn_pool_create(pool);
586299742Sdim
587251881Speter      while (1)
588251881Speter        {
589299742Sdim          svn_pool_clear(iterpool);
590299742Sdim
591251881Speter          /* Try to lock.  If we can't lock because our target (or its
592251881Speter             parent) isn't a working copy, we'll try to walk up the
593251881Speter             tree to find a working copy, remembering this path's
594251881Speter             parent as one we need to flesh out.  */
595251881Speter          err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
596251881Speter                                           parent_abspath, !innerupdate,
597299742Sdim                                           pool, iterpool);
598251881Speter          if (!err)
599251881Speter            break;
600251881Speter          if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
601251881Speter              || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
602251881Speter            return err;
603251881Speter          svn_error_clear(err);
604251881Speter
605251881Speter          /* Remember the parent of our update target as a missing
606251881Speter             parent. */
607251881Speter          parent_abspath = svn_dirent_dirname(parent_abspath, pool);
608251881Speter          APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
609251881Speter        }
610251881Speter
611251881Speter      /* Run 'svn up --depth=empty' (effectively) on the missing
612251881Speter         parents, if any. */
613251881Speter      anchor_abspath = lockroot_abspath;
614251881Speter      for (i = missing_parents->nelts - 1; i >= 0; i--)
615251881Speter        {
616251881Speter          const char *missing_parent =
617251881Speter            APR_ARRAY_IDX(missing_parents, i, const char *);
618251881Speter
619299742Sdim          svn_pool_clear(iterpool);
620299742Sdim
621299742Sdim          err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
622299742Sdim                                &ra_session, missing_parent,
623299742Sdim                                anchor_abspath, &peg_revision, svn_depth_empty,
624299742Sdim                                FALSE, ignore_externals,
625299742Sdim                                allow_unver_obstructions, adds_as_modification,
626299742Sdim                                FALSE, ctx, pool, iterpool);
627251881Speter          if (err)
628251881Speter            goto cleanup;
629251881Speter          anchor_abspath = missing_parent;
630251881Speter
631251881Speter          /* If we successfully updated a missing parent, let's re-use
632251881Speter             the returned revision number for future updates for the
633251881Speter             sake of consistency. */
634251881Speter          peg_revision.kind = svn_opt_revision_number;
635251881Speter          peg_revision.value.number = *result_rev;
636251881Speter        }
637299742Sdim
638299742Sdim      svn_pool_destroy(iterpool);
639251881Speter    }
640251881Speter  else
641251881Speter    {
642251881Speter      SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
643251881Speter                                         local_abspath, !innerupdate,
644251881Speter                                         pool, pool));
645251881Speter      anchor_abspath = lockroot_abspath;
646251881Speter    }
647251881Speter
648299742Sdim  err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
649299742Sdim                        &ra_session,
650251881Speter                        local_abspath, anchor_abspath,
651251881Speter                        &peg_revision, depth, depth_is_sticky,
652251881Speter                        ignore_externals, allow_unver_obstructions,
653299742Sdim                        adds_as_modification,
654299742Sdim                        TRUE, ctx, pool, pool);
655251881Speter
656251881Speter  /* Give the conflict resolver callback the opportunity to
657251881Speter   * resolve any conflicts that were raised. */
658299742Sdim  if (! err && ctx->conflict_func2 && apr_hash_count(conflicted_paths))
659251881Speter    {
660251881Speter      err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
661251881Speter    }
662251881Speter
663251881Speter cleanup:
664251881Speter  err = svn_error_compose_create(
665251881Speter            err,
666251881Speter            svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
667251881Speter
668251881Speter  return svn_error_trace(err);
669251881Speter}
670251881Speter
671251881Speter
672251881Spetersvn_error_t *
673251881Spetersvn_client_update4(apr_array_header_t **result_revs,
674251881Speter                   const apr_array_header_t *paths,
675251881Speter                   const svn_opt_revision_t *revision,
676251881Speter                   svn_depth_t depth,
677251881Speter                   svn_boolean_t depth_is_sticky,
678251881Speter                   svn_boolean_t ignore_externals,
679251881Speter                   svn_boolean_t allow_unver_obstructions,
680251881Speter                   svn_boolean_t adds_as_modification,
681251881Speter                   svn_boolean_t make_parents,
682251881Speter                   svn_client_ctx_t *ctx,
683251881Speter                   apr_pool_t *pool)
684251881Speter{
685251881Speter  int i;
686251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
687251881Speter  const char *path = NULL;
688251881Speter  svn_boolean_t sleep = FALSE;
689251881Speter  svn_error_t *err = SVN_NO_ERROR;
690299742Sdim  svn_boolean_t found_valid_target = FALSE;
691251881Speter
692251881Speter  if (result_revs)
693251881Speter    *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
694251881Speter
695251881Speter  for (i = 0; i < paths->nelts; ++i)
696251881Speter    {
697251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
698251881Speter
699251881Speter      if (svn_path_is_url(path))
700251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
701251881Speter                                 _("'%s' is not a local path"), path);
702251881Speter    }
703251881Speter
704251881Speter  for (i = 0; i < paths->nelts; ++i)
705251881Speter    {
706251881Speter      svn_revnum_t result_rev;
707251881Speter      const char *local_abspath;
708251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
709251881Speter
710251881Speter      svn_pool_clear(iterpool);
711251881Speter
712251881Speter      if (ctx->cancel_func)
713251881Speter        {
714251881Speter          err = ctx->cancel_func(ctx->cancel_baton);
715251881Speter          if (err)
716251881Speter            goto cleanup;
717251881Speter        }
718251881Speter
719251881Speter      err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
720251881Speter      if (err)
721251881Speter        goto cleanup;
722299742Sdim      err = svn_client__update_internal(&result_rev, &sleep, local_abspath,
723251881Speter                                        revision, depth, depth_is_sticky,
724251881Speter                                        ignore_externals,
725251881Speter                                        allow_unver_obstructions,
726251881Speter                                        adds_as_modification,
727251881Speter                                        make_parents,
728299742Sdim                                        FALSE, NULL, ctx,
729251881Speter                                        iterpool);
730251881Speter
731251881Speter      if (err)
732251881Speter        {
733251881Speter          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
734251881Speter            goto cleanup;
735251881Speter
736251881Speter          svn_error_clear(err);
737251881Speter          err = SVN_NO_ERROR;
738251881Speter
739251881Speter          /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
740251881Speter
741251881Speter          result_rev = SVN_INVALID_REVNUM;
742251881Speter          if (ctx->notify_func2)
743251881Speter            {
744251881Speter              svn_wc_notify_t *notify;
745251881Speter              notify = svn_wc_create_notify(path,
746251881Speter                                            svn_wc_notify_skip,
747251881Speter                                            iterpool);
748299742Sdim              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
749251881Speter            }
750251881Speter        }
751299742Sdim      else
752299742Sdim        found_valid_target = TRUE;
753299742Sdim
754251881Speter      if (result_revs)
755251881Speter        APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
756251881Speter    }
757251881Speter  svn_pool_destroy(iterpool);
758251881Speter
759251881Speter cleanup:
760299742Sdim  if (!err && !found_valid_target)
761299742Sdim    return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
762299742Sdim                            _("None of the targets are working copies"));
763251881Speter  if (sleep)
764262253Speter    {
765262253Speter      const char *wcroot_abspath;
766251881Speter
767262253Speter      if (paths->nelts == 1)
768262253Speter        {
769262253Speter          const char *abspath;
770262253Speter
771262253Speter          /* PATH iteslf may have been removed by the update. */
772262253Speter          SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
773262253Speter          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
774262253Speter                                     pool, pool));
775262253Speter        }
776262253Speter      else
777262253Speter        wcroot_abspath = NULL;
778262253Speter
779262253Speter      svn_io_sleep_for_timestamps(wcroot_abspath, pool);
780262253Speter    }
781262253Speter
782251881Speter  return svn_error_trace(err);
783251881Speter}
784