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
185362181Sdim/* Perform post-update processing of externals defined below LOCAL_ABSPATH. */
186362181Sdimstatic svn_error_t *
187362181Sdimhandle_externals(svn_boolean_t *timestamp_sleep,
188362181Sdim                 const char *local_abspath,
189362181Sdim                 svn_depth_t depth,
190362181Sdim                 const char *repos_root_url,
191362181Sdim                 svn_ra_session_t *ra_session,
192362181Sdim                 svn_client_ctx_t *ctx,
193362181Sdim                 apr_pool_t *scratch_pool)
194362181Sdim{
195362181Sdim  apr_hash_t *new_externals;
196362181Sdim  apr_hash_t *new_depths;
197362181Sdim
198362181Sdim  SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
199362181Sdim                                               &new_depths,
200362181Sdim                                               ctx->wc_ctx, local_abspath,
201362181Sdim                                               depth,
202362181Sdim                                               scratch_pool, scratch_pool));
203362181Sdim
204362181Sdim  SVN_ERR(svn_client__handle_externals(new_externals,
205362181Sdim                                       new_depths,
206362181Sdim                                       repos_root_url, local_abspath,
207362181Sdim                                       depth, timestamp_sleep, ra_session,
208362181Sdim                                       ctx, scratch_pool));
209362181Sdim  return SVN_NO_ERROR;
210362181Sdim}
211362181Sdim
212362181Sdim/* Try to reuse the RA session by reparenting it to the anchor_url.
213362181Sdim * This code is probably overly cautious since we only use this
214362181Sdim * currently when parents are missing and so all the anchor_urls
215362181Sdim * have to be in the same repo.
216362181Sdim * Note that ra_session_p is an (optional) input parameter as well
217362181Sdim * as an output parameter. */
218362181Sdimstatic svn_error_t *
219362181Sdimreuse_ra_session(svn_ra_session_t **ra_session_p,
220362181Sdim                 const char **corrected_url,
221362181Sdim                 const char *anchor_url,
222362181Sdim                 const char *anchor_abspath,
223362181Sdim                 svn_client_ctx_t *ctx,
224362181Sdim                 apr_pool_t *result_pool,
225362181Sdim                 apr_pool_t *scratch_pool)
226362181Sdim{
227362181Sdim  svn_ra_session_t *ra_session = *ra_session_p;
228362181Sdim
229362181Sdim  if (ra_session)
230362181Sdim    {
231362181Sdim      svn_error_t *err = svn_ra_reparent(ra_session, anchor_url, scratch_pool);
232362181Sdim      if (err)
233362181Sdim        {
234362181Sdim          if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
235362181Sdim            {
236362181Sdim            /* session changed repos, can't reuse it */
237362181Sdim              svn_error_clear(err);
238362181Sdim              ra_session = NULL;
239362181Sdim            }
240362181Sdim          else
241362181Sdim            {
242362181Sdim              return svn_error_trace(err);
243362181Sdim            }
244362181Sdim        }
245362181Sdim      else
246362181Sdim        {
247362181Sdim          *corrected_url = NULL;
248362181Sdim        }
249362181Sdim    }
250362181Sdim
251362181Sdim  /* Open an RA session for the URL if one isn't already available */
252362181Sdim  if (!ra_session)
253362181Sdim    {
254362181Sdim      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, corrected_url,
255362181Sdim                                                   anchor_url,
256362181Sdim                                                   anchor_abspath, NULL,
257362181Sdim                                                   TRUE /* write_dav_props */,
258362181Sdim                                                   TRUE /* read_dav_props */,
259362181Sdim                                                   ctx,
260362181Sdim                                                   result_pool, scratch_pool));
261362181Sdim      *ra_session_p = ra_session;
262362181Sdim    }
263362181Sdim
264362181Sdim  return SVN_NO_ERROR;
265362181Sdim}
266362181Sdim
267251881Speter/* This is a helper for svn_client__update_internal(), which see for
268251881Speter   an explanation of most of these parameters.  Some stuff that's
269251881Speter   unique is as follows:
270251881Speter
271251881Speter   ANCHOR_ABSPATH is the local absolute path of the update anchor.
272251881Speter   This is typically either the same as LOCAL_ABSPATH, or the
273251881Speter   immediate parent of LOCAL_ABSPATH.
274251881Speter
275251881Speter   If NOTIFY_SUMMARY is set (and there's a notification handler in
276251881Speter   CTX), transmit the final update summary upon successful
277251881Speter   completion of the update.
278251881Speter
279251881Speter   Add the paths of any conflict victims to CONFLICTED_PATHS, if that
280251881Speter   is not null.
281289180Speter
282289180Speter   Use RA_SESSION_P to run the update if it is not NULL.  If it is then
283289180Speter   open a new ra session and place it in RA_SESSION_P.  This allows
284289180Speter   repeated calls to update_internal to reuse the same session.
285251881Speter*/
286251881Speterstatic svn_error_t *
287251881Speterupdate_internal(svn_revnum_t *result_rev,
288289180Speter                svn_boolean_t *timestamp_sleep,
289251881Speter                apr_hash_t *conflicted_paths,
290289180Speter                svn_ra_session_t **ra_session_p,
291251881Speter                const char *local_abspath,
292251881Speter                const char *anchor_abspath,
293251881Speter                const svn_opt_revision_t *revision,
294251881Speter                svn_depth_t depth,
295251881Speter                svn_boolean_t depth_is_sticky,
296251881Speter                svn_boolean_t ignore_externals,
297251881Speter                svn_boolean_t allow_unver_obstructions,
298251881Speter                svn_boolean_t adds_as_modification,
299251881Speter                svn_boolean_t notify_summary,
300251881Speter                svn_client_ctx_t *ctx,
301289180Speter                apr_pool_t *result_pool,
302289180Speter                apr_pool_t *scratch_pool)
303251881Speter{
304251881Speter  const svn_delta_editor_t *update_editor;
305251881Speter  void *update_edit_baton;
306251881Speter  const svn_ra_reporter3_t *reporter;
307251881Speter  void *report_baton;
308251881Speter  const char *corrected_url;
309251881Speter  const char *target;
310251881Speter  const char *repos_root_url;
311251881Speter  const char *repos_relpath;
312251881Speter  const char *repos_uuid;
313251881Speter  const char *anchor_url;
314251881Speter  svn_revnum_t revnum;
315251881Speter  svn_boolean_t use_commit_times;
316251881Speter  svn_boolean_t clean_checkout = FALSE;
317251881Speter  const char *diff3_cmd;
318251881Speter  apr_hash_t *wcroot_iprops;
319251881Speter  svn_opt_revision_t opt_rev;
320289180Speter  svn_ra_session_t *ra_session = *ra_session_p;
321251881Speter  const char *preserved_exts_str;
322251881Speter  apr_array_header_t *preserved_exts;
323251881Speter  struct svn_client__dirent_fetcher_baton_t dfb;
324251881Speter  svn_boolean_t server_supports_depth;
325251881Speter  svn_boolean_t cropping_target;
326251881Speter  svn_boolean_t target_conflicted = FALSE;
327251881Speter  svn_config_t *cfg = ctx->config
328251881Speter                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
329251881Speter                      : NULL;
330251881Speter
331251881Speter  if (result_rev)
332251881Speter    *result_rev = SVN_INVALID_REVNUM;
333251881Speter
334251881Speter  /* An unknown depth can't be sticky. */
335251881Speter  if (depth == svn_depth_unknown)
336251881Speter    depth_is_sticky = FALSE;
337251881Speter
338251881Speter  if (strcmp(local_abspath, anchor_abspath))
339289180Speter    target = svn_dirent_basename(local_abspath, scratch_pool);
340251881Speter  else
341251881Speter    target = "";
342251881Speter
343251881Speter  /* Check if our anchor exists in BASE. If it doesn't we can't update. */
344251881Speter  SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
345251881Speter                                &repos_uuid, NULL,
346251881Speter                                ctx->wc_ctx, anchor_abspath,
347289180Speter                                TRUE /* ignore_enoent */,
348289180Speter                                scratch_pool, scratch_pool));
349251881Speter
350251881Speter  /* It does not make sense to update conflict victims. */
351251881Speter  if (repos_relpath)
352251881Speter    {
353251881Speter      svn_error_t *err;
354251881Speter      svn_boolean_t text_conflicted, prop_conflicted;
355251881Speter
356251881Speter      anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
357289180Speter                                               scratch_pool);
358251881Speter
359251881Speter      err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
360251881Speter                                 NULL,
361289180Speter                                 ctx->wc_ctx, local_abspath, scratch_pool);
362251881Speter
363251881Speter      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
364251881Speter        return svn_error_trace(err);
365251881Speter      svn_error_clear(err);
366251881Speter
367251881Speter      /* tree-conflicts are handled by the update editor */
368251881Speter      if (!err && (text_conflicted || prop_conflicted))
369251881Speter        target_conflicted = TRUE;
370251881Speter    }
371251881Speter  else
372251881Speter    anchor_url = NULL;
373251881Speter
374251881Speter  if (! anchor_url || target_conflicted)
375251881Speter    {
376251881Speter      if (ctx->notify_func2)
377251881Speter        {
378251881Speter          svn_wc_notify_t *nt;
379251881Speter
380251881Speter          nt = svn_wc_create_notify(local_abspath,
381251881Speter                                    target_conflicted
382251881Speter                                      ? svn_wc_notify_skip_conflicted
383251881Speter                                      : svn_wc_notify_update_skip_working_only,
384289180Speter                                    scratch_pool);
385251881Speter
386289180Speter          ctx->notify_func2(ctx->notify_baton2, nt, scratch_pool);
387251881Speter        }
388251881Speter      return SVN_NO_ERROR;
389251881Speter    }
390251881Speter
391251881Speter  /* We may need to crop the tree if the depth is sticky */
392251881Speter  cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
393251881Speter  if (cropping_target)
394251881Speter    {
395251881Speter      svn_node_kind_t target_kind;
396251881Speter
397251881Speter      if (depth == svn_depth_exclude)
398251881Speter        {
399251881Speter          SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
400251881Speter                                 local_abspath,
401251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
402251881Speter                                 ctx->notify_func2, ctx->notify_baton2,
403289180Speter                                 scratch_pool));
404251881Speter
405362181Sdim          if (!ignore_externals)
406362181Sdim            {
407362181Sdim              /* We may now be able to remove externals below LOCAL_ABSPATH. */
408362181Sdim              SVN_ERR(reuse_ra_session(ra_session_p, &corrected_url,
409362181Sdim                                       anchor_url, anchor_abspath,
410362181Sdim                                       ctx, result_pool, scratch_pool));
411362181Sdim              ra_session = *ra_session_p;
412362181Sdim              SVN_ERR(handle_externals(timestamp_sleep, local_abspath, depth,
413362181Sdim                                       repos_root_url, ra_session, ctx,
414362181Sdim                                       scratch_pool));
415362181Sdim            }
416362181Sdim
417251881Speter          /* Target excluded, we are done now */
418251881Speter          return SVN_NO_ERROR;
419251881Speter        }
420251881Speter
421251881Speter      SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
422289180Speter                                TRUE, TRUE, scratch_pool));
423251881Speter      if (target_kind == svn_node_dir)
424251881Speter        {
425251881Speter          SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
426251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
427251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
428289180Speter                                    scratch_pool));
429251881Speter        }
430251881Speter    }
431251881Speter
432251881Speter  /* check whether the "clean c/o" optimization is applicable */
433289180Speter  SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath,
434289180Speter                      scratch_pool));
435251881Speter
436251881Speter  /* Get the external diff3, if any. */
437251881Speter  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
438251881Speter                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
439251881Speter
440251881Speter  if (diff3_cmd != NULL)
441289180Speter    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
442251881Speter
443251881Speter  /* See if the user wants last-commit timestamps instead of current ones. */
444251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
445251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
446251881Speter                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
447251881Speter
448251881Speter  /* See which files the user wants to preserve the extension of when
449251881Speter     conflict files are made. */
450251881Speter  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
451251881Speter                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
452251881Speter  preserved_exts = *preserved_exts_str
453289180Speter    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
454251881Speter    : NULL;
455251881Speter
456251881Speter  /* Let everyone know we're starting a real update (unless we're
457251881Speter     asked not to). */
458251881Speter  if (ctx->notify_func2 && notify_summary)
459251881Speter    {
460251881Speter      svn_wc_notify_t *notify
461251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
462289180Speter                               scratch_pool);
463251881Speter      notify->kind = svn_node_none;
464251881Speter      notify->content_state = notify->prop_state
465251881Speter        = svn_wc_notify_state_inapplicable;
466251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
467289180Speter      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
468251881Speter    }
469251881Speter
470362181Sdim  SVN_ERR(reuse_ra_session(ra_session_p, &corrected_url, anchor_url,
471362181Sdim                           anchor_abspath, ctx, result_pool, scratch_pool));
472362181Sdim  ra_session = *ra_session_p;
473251881Speter
474251881Speter  /* If we got a corrected URL from the RA subsystem, we'll need to
475251881Speter     relocate our working copy first. */
476251881Speter  if (corrected_url)
477251881Speter    {
478251881Speter      const char *new_repos_root_url;
479251881Speter
480251881Speter      /* To relocate everything inside our repository we need the old and new
481251881Speter         repos root. */
482289180Speter      SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url,
483289180Speter                                     scratch_pool));
484251881Speter
485251881Speter      /* svn_client_relocate2() will check the uuid */
486262250Speter      SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
487251881Speter                                   new_repos_root_url, ignore_externals,
488289180Speter                                   ctx, scratch_pool));
489251881Speter
490251881Speter      /* Store updated repository root for externals */
491251881Speter      repos_root_url = new_repos_root_url;
492251881Speter      /* ### We should update anchor_loc->repos_uuid too, although currently
493251881Speter       * we don't use it. */
494251881Speter      anchor_url = corrected_url;
495251881Speter    }
496251881Speter
497251881Speter  /* Resolve unspecified REVISION now, because we need to retrieve the
498251881Speter     correct inherited props prior to the editor drive and we need to
499251881Speter     use the same value of HEAD for both. */
500251881Speter  opt_rev.kind = revision->kind;
501251881Speter  opt_rev.value = revision->value;
502251881Speter  if (opt_rev.kind == svn_opt_revision_unspecified)
503251881Speter    opt_rev.kind = svn_opt_revision_head;
504251881Speter
505251881Speter  /* ### todo: shouldn't svn_client__get_revision_number be able
506251881Speter     to take a URL as easily as a local path?  */
507251881Speter  SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
508251881Speter                                          local_abspath, ra_session, &opt_rev,
509289180Speter                                          scratch_pool));
510251881Speter
511251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
512289180Speter                                SVN_RA_CAPABILITY_DEPTH, scratch_pool));
513251881Speter
514251881Speter  dfb.ra_session = ra_session;
515251881Speter  dfb.target_revision = revnum;
516251881Speter  dfb.anchor_url = anchor_url;
517251881Speter
518251881Speter  SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
519251881Speter                                            revnum, depth, ra_session,
520289180Speter                                            ctx, scratch_pool, scratch_pool));
521251881Speter
522251881Speter  /* Fetch the update editor.  If REVISION is invalid, that's okay;
523251881Speter     the RA driver will call editor->set_target_revision later on. */
524251881Speter  SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
525251881Speter                                    &revnum, ctx->wc_ctx, anchor_abspath,
526251881Speter                                    target, wcroot_iprops, use_commit_times,
527251881Speter                                    depth, depth_is_sticky,
528251881Speter                                    allow_unver_obstructions,
529251881Speter                                    adds_as_modification,
530251881Speter                                    server_supports_depth,
531251881Speter                                    clean_checkout,
532251881Speter                                    diff3_cmd, preserved_exts,
533251881Speter                                    svn_client__dirent_fetcher, &dfb,
534251881Speter                                    conflicted_paths ? record_conflict : NULL,
535251881Speter                                    conflicted_paths,
536251881Speter                                    NULL, NULL,
537251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
538251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
539289180Speter                                    scratch_pool, scratch_pool));
540251881Speter
541251881Speter  /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
542251881Speter     invalid revnum, that means RA will use the latest revision.  */
543251881Speter  SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
544251881Speter                            revnum, target,
545251881Speter                            (!server_supports_depth || depth_is_sticky
546251881Speter                             ? depth
547251881Speter                             : svn_depth_unknown),
548251881Speter                            FALSE /* send_copyfrom_args */,
549251881Speter                            FALSE /* ignore_ancestry */,
550289180Speter                            update_editor, update_edit_baton,
551289180Speter                            scratch_pool, scratch_pool));
552251881Speter
553251881Speter  /* Past this point, we assume the WC is going to be modified so we will
554251881Speter   * need to sleep for timestamps. */
555251881Speter  *timestamp_sleep = TRUE;
556251881Speter
557251881Speter  /* Drive the reporter structure, describing the revisions within
558289180Speter     LOCAL_ABSPATH.  When this calls reporter->finish_report, the
559289180Speter     reporter will drive the update_editor. */
560251881Speter  SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
561251881Speter                                  report_baton, TRUE,
562251881Speter                                  depth, (! depth_is_sticky),
563251881Speter                                  (! server_supports_depth),
564251881Speter                                  use_commit_times,
565251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
566251881Speter                                  ctx->notify_func2, ctx->notify_baton2,
567289180Speter                                  scratch_pool));
568251881Speter
569251881Speter  /* We handle externals after the update is complete, so that
570251881Speter     handling external items (and any errors therefrom) doesn't delay
571251881Speter     the primary operation.  */
572251881Speter  if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
573251881Speter      && (! ignore_externals))
574251881Speter    {
575362181Sdim      SVN_ERR(handle_externals(timestamp_sleep, local_abspath, depth,
576362181Sdim                               repos_root_url, ra_session, ctx, scratch_pool));
577251881Speter    }
578251881Speter
579251881Speter  /* Let everyone know we're finished here (unless we're asked not to). */
580251881Speter  if (ctx->notify_func2 && notify_summary)
581251881Speter    {
582251881Speter      svn_wc_notify_t *notify
583251881Speter        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
584289180Speter                               scratch_pool);
585251881Speter      notify->kind = svn_node_none;
586251881Speter      notify->content_state = notify->prop_state
587251881Speter        = svn_wc_notify_state_inapplicable;
588251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
589251881Speter      notify->revision = revnum;
590289180Speter      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
591251881Speter    }
592251881Speter
593251881Speter  /* If the caller wants the result revision, give it to them. */
594251881Speter  if (result_rev)
595251881Speter    *result_rev = revnum;
596251881Speter
597251881Speter  return SVN_NO_ERROR;
598251881Speter}
599251881Speter
600251881Spetersvn_error_t *
601251881Spetersvn_client__update_internal(svn_revnum_t *result_rev,
602289180Speter                            svn_boolean_t *timestamp_sleep,
603251881Speter                            const char *local_abspath,
604251881Speter                            const svn_opt_revision_t *revision,
605251881Speter                            svn_depth_t depth,
606251881Speter                            svn_boolean_t depth_is_sticky,
607251881Speter                            svn_boolean_t ignore_externals,
608251881Speter                            svn_boolean_t allow_unver_obstructions,
609251881Speter                            svn_boolean_t adds_as_modification,
610251881Speter                            svn_boolean_t make_parents,
611251881Speter                            svn_boolean_t innerupdate,
612289180Speter                            svn_ra_session_t *ra_session,
613251881Speter                            svn_client_ctx_t *ctx,
614251881Speter                            apr_pool_t *pool)
615251881Speter{
616251881Speter  const char *anchor_abspath, *lockroot_abspath;
617251881Speter  svn_error_t *err;
618362181Sdim  svn_opt_revision_t opt_rev = *revision;  /* operative revision */
619251881Speter  apr_hash_t *conflicted_paths
620251881Speter    = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
621251881Speter
622251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
623251881Speter  SVN_ERR_ASSERT(! (innerupdate && make_parents));
624251881Speter
625251881Speter  if (make_parents)
626251881Speter    {
627251881Speter      int i;
628251881Speter      const char *parent_abspath = local_abspath;
629251881Speter      apr_array_header_t *missing_parents =
630251881Speter        apr_array_make(pool, 4, sizeof(const char *));
631289180Speter      apr_pool_t *iterpool;
632251881Speter
633289180Speter      iterpool = svn_pool_create(pool);
634289180Speter
635251881Speter      while (1)
636251881Speter        {
637289180Speter          svn_pool_clear(iterpool);
638289180Speter
639251881Speter          /* Try to lock.  If we can't lock because our target (or its
640251881Speter             parent) isn't a working copy, we'll try to walk up the
641251881Speter             tree to find a working copy, remembering this path's
642251881Speter             parent as one we need to flesh out.  */
643251881Speter          err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
644251881Speter                                           parent_abspath, !innerupdate,
645289180Speter                                           pool, iterpool);
646251881Speter          if (!err)
647251881Speter            break;
648251881Speter          if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
649251881Speter              || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
650251881Speter            return err;
651251881Speter          svn_error_clear(err);
652251881Speter
653251881Speter          /* Remember the parent of our update target as a missing
654251881Speter             parent. */
655251881Speter          parent_abspath = svn_dirent_dirname(parent_abspath, pool);
656251881Speter          APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
657251881Speter        }
658251881Speter
659251881Speter      /* Run 'svn up --depth=empty' (effectively) on the missing
660251881Speter         parents, if any. */
661251881Speter      anchor_abspath = lockroot_abspath;
662251881Speter      for (i = missing_parents->nelts - 1; i >= 0; i--)
663251881Speter        {
664251881Speter          const char *missing_parent =
665251881Speter            APR_ARRAY_IDX(missing_parents, i, const char *);
666251881Speter
667289180Speter          svn_pool_clear(iterpool);
668289180Speter
669289180Speter          err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
670289180Speter                                &ra_session, missing_parent,
671362181Sdim                                anchor_abspath, &opt_rev, svn_depth_empty,
672289180Speter                                FALSE, ignore_externals,
673289180Speter                                allow_unver_obstructions, adds_as_modification,
674289180Speter                                FALSE, ctx, pool, iterpool);
675251881Speter          if (err)
676251881Speter            goto cleanup;
677251881Speter          anchor_abspath = missing_parent;
678251881Speter
679251881Speter          /* If we successfully updated a missing parent, let's re-use
680251881Speter             the returned revision number for future updates for the
681251881Speter             sake of consistency. */
682362181Sdim          opt_rev.kind = svn_opt_revision_number;
683362181Sdim          opt_rev.value.number = *result_rev;
684251881Speter        }
685289180Speter
686289180Speter      svn_pool_destroy(iterpool);
687251881Speter    }
688251881Speter  else
689251881Speter    {
690251881Speter      SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
691251881Speter                                         local_abspath, !innerupdate,
692251881Speter                                         pool, pool));
693251881Speter      anchor_abspath = lockroot_abspath;
694251881Speter    }
695251881Speter
696289180Speter  err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
697289180Speter                        &ra_session,
698251881Speter                        local_abspath, anchor_abspath,
699362181Sdim                        &opt_rev, depth, depth_is_sticky,
700251881Speter                        ignore_externals, allow_unver_obstructions,
701289180Speter                        adds_as_modification,
702289180Speter                        TRUE, ctx, pool, pool);
703251881Speter
704251881Speter  /* Give the conflict resolver callback the opportunity to
705251881Speter   * resolve any conflicts that were raised. */
706289180Speter  if (! err && ctx->conflict_func2 && apr_hash_count(conflicted_paths))
707251881Speter    {
708251881Speter      err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
709251881Speter    }
710251881Speter
711251881Speter cleanup:
712251881Speter  err = svn_error_compose_create(
713251881Speter            err,
714251881Speter            svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
715251881Speter
716251881Speter  return svn_error_trace(err);
717251881Speter}
718251881Speter
719251881Speter
720251881Spetersvn_error_t *
721251881Spetersvn_client_update4(apr_array_header_t **result_revs,
722251881Speter                   const apr_array_header_t *paths,
723251881Speter                   const svn_opt_revision_t *revision,
724251881Speter                   svn_depth_t depth,
725251881Speter                   svn_boolean_t depth_is_sticky,
726251881Speter                   svn_boolean_t ignore_externals,
727251881Speter                   svn_boolean_t allow_unver_obstructions,
728251881Speter                   svn_boolean_t adds_as_modification,
729251881Speter                   svn_boolean_t make_parents,
730251881Speter                   svn_client_ctx_t *ctx,
731251881Speter                   apr_pool_t *pool)
732251881Speter{
733251881Speter  int i;
734251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
735251881Speter  const char *path = NULL;
736251881Speter  svn_boolean_t sleep = FALSE;
737251881Speter  svn_error_t *err = SVN_NO_ERROR;
738289180Speter  svn_boolean_t found_valid_target = FALSE;
739251881Speter
740251881Speter  if (result_revs)
741251881Speter    *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
742251881Speter
743251881Speter  for (i = 0; i < paths->nelts; ++i)
744251881Speter    {
745251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
746251881Speter
747251881Speter      if (svn_path_is_url(path))
748251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
749251881Speter                                 _("'%s' is not a local path"), path);
750251881Speter    }
751251881Speter
752251881Speter  for (i = 0; i < paths->nelts; ++i)
753251881Speter    {
754251881Speter      svn_revnum_t result_rev;
755251881Speter      const char *local_abspath;
756251881Speter      path = APR_ARRAY_IDX(paths, i, const char *);
757251881Speter
758251881Speter      svn_pool_clear(iterpool);
759251881Speter
760251881Speter      if (ctx->cancel_func)
761251881Speter        {
762251881Speter          err = ctx->cancel_func(ctx->cancel_baton);
763251881Speter          if (err)
764251881Speter            goto cleanup;
765251881Speter        }
766251881Speter
767251881Speter      err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
768251881Speter      if (err)
769251881Speter        goto cleanup;
770289180Speter      err = svn_client__update_internal(&result_rev, &sleep, local_abspath,
771251881Speter                                        revision, depth, depth_is_sticky,
772251881Speter                                        ignore_externals,
773251881Speter                                        allow_unver_obstructions,
774251881Speter                                        adds_as_modification,
775251881Speter                                        make_parents,
776289180Speter                                        FALSE, NULL, ctx,
777251881Speter                                        iterpool);
778251881Speter
779251881Speter      if (err)
780251881Speter        {
781251881Speter          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
782251881Speter            goto cleanup;
783251881Speter
784251881Speter          svn_error_clear(err);
785251881Speter          err = SVN_NO_ERROR;
786251881Speter
787251881Speter          /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
788251881Speter
789251881Speter          result_rev = SVN_INVALID_REVNUM;
790251881Speter          if (ctx->notify_func2)
791251881Speter            {
792251881Speter              svn_wc_notify_t *notify;
793251881Speter              notify = svn_wc_create_notify(path,
794251881Speter                                            svn_wc_notify_skip,
795251881Speter                                            iterpool);
796289180Speter              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
797251881Speter            }
798251881Speter        }
799289180Speter      else
800289180Speter        found_valid_target = TRUE;
801289180Speter
802251881Speter      if (result_revs)
803251881Speter        APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
804251881Speter    }
805251881Speter  svn_pool_destroy(iterpool);
806251881Speter
807251881Speter cleanup:
808289180Speter  if (!err && !found_valid_target)
809289180Speter    return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
810289180Speter                            _("None of the targets are working copies"));
811251881Speter  if (sleep)
812257936Speter    {
813257936Speter      const char *wcroot_abspath;
814251881Speter
815257936Speter      if (paths->nelts == 1)
816257936Speter        {
817257936Speter          const char *abspath;
818257936Speter
819257936Speter          /* PATH iteslf may have been removed by the update. */
820257936Speter          SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
821257936Speter          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
822257936Speter                                     pool, pool));
823257936Speter        }
824257936Speter      else
825257936Speter        wcroot_abspath = NULL;
826257936Speter
827257936Speter      svn_io_sleep_for_timestamps(wcroot_abspath, pool);
828257936Speter    }
829257936Speter
830251881Speter  return svn_error_trace(err);
831251881Speter}
832