1289177Speter/*
2289177Speter * mtcc.c -- Multi Command Context implementation. This allows
3289177Speter *           performing many operations without a working copy.
4289177Speter *
5289177Speter * ====================================================================
6289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
7289177Speter *    or more contributor license agreements.  See the NOTICE file
8289177Speter *    distributed with this work for additional information
9289177Speter *    regarding copyright ownership.  The ASF licenses this file
10289177Speter *    to you under the Apache License, Version 2.0 (the
11289177Speter *    "License"); you may not use this file except in compliance
12289177Speter *    with the License.  You may obtain a copy of the License at
13289177Speter *
14289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
15289177Speter *
16289177Speter *    Unless required by applicable law or agreed to in writing,
17289177Speter *    software distributed under the License is distributed on an
18289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19289177Speter *    KIND, either express or implied.  See the License for the
20289177Speter *    specific language governing permissions and limitations
21289177Speter *    under the License.
22289177Speter * ====================================================================
23289177Speter */
24289177Speter
25289177Speter#include "svn_dirent_uri.h"
26289177Speter#include "svn_hash.h"
27289177Speter#include "svn_path.h"
28289177Speter#include "svn_props.h"
29289177Speter#include "svn_pools.h"
30289177Speter#include "svn_subst.h"
31289177Speter
32289177Speter#include "private/svn_client_mtcc.h"
33289177Speter
34289177Speter
35289177Speter#include "svn_private_config.h"
36289177Speter
37289177Speter#include "client.h"
38289177Speter
39289177Speter#include <assert.h>
40289177Speter
41289177Speter#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
42289177Speter
43289177Speter/* The kind of operation to perform in an mtcc_op_t */
44289177Spetertypedef enum mtcc_kind_t
45289177Speter{
46289177Speter  OP_OPEN_DIR,
47289177Speter  OP_OPEN_FILE,
48289177Speter  OP_ADD_DIR,
49289177Speter  OP_ADD_FILE,
50289177Speter  OP_DELETE
51289177Speter} mtcc_kind_t;
52289177Speter
53289177Spetertypedef struct mtcc_op_t
54289177Speter{
55289177Speter  const char *name;                 /* basename of operation */
56289177Speter  mtcc_kind_t kind;                 /* editor operation */
57289177Speter
58289177Speter  apr_array_header_t *children;     /* List of mtcc_op_t * */
59289177Speter
60289177Speter  const char *src_relpath;              /* For ADD_DIR, ADD_FILE */
61289177Speter  svn_revnum_t src_rev;                 /* For ADD_DIR, ADD_FILE */
62289177Speter  svn_stream_t *src_stream;             /* For ADD_FILE, OPEN_FILE */
63289177Speter  svn_checksum_t *src_checksum;         /* For ADD_FILE, OPEN_FILE */
64289177Speter  svn_stream_t *base_stream;            /* For ADD_FILE, OPEN_FILE */
65289177Speter  const svn_checksum_t *base_checksum;  /* For ADD_FILE, OPEN_FILE */
66289177Speter
67289177Speter  apr_array_header_t *prop_mods;        /* For all except DELETE
68289177Speter                                           List of svn_prop_t */
69289177Speter
70289177Speter  svn_boolean_t performed_stat;         /* Verified kind with repository */
71289177Speter} mtcc_op_t;
72289177Speter
73289177Speter/* Check if the mtcc doesn't contain any modifications yet */
74289177Speter#define MTCC_UNMODIFIED(mtcc)                                               \
75289177Speter    ((mtcc->root_op->kind == OP_OPEN_DIR                                    \
76289177Speter                            || mtcc->root_op->kind == OP_OPEN_FILE)         \
77289177Speter     && (mtcc->root_op->prop_mods == NULL                                   \
78289177Speter                            || !mtcc->root_op->prop_mods->nelts)            \
79289177Speter     && (mtcc->root_op->children == NULL                                    \
80289177Speter                            || !mtcc->root_op->children->nelts))
81289177Speter
82289177Speterstruct svn_client__mtcc_t
83289177Speter{
84289177Speter  apr_pool_t *pool;
85289177Speter  svn_revnum_t head_revision;
86289177Speter  svn_revnum_t base_revision;
87289177Speter
88289177Speter  svn_ra_session_t *ra_session;
89289177Speter  svn_client_ctx_t *ctx;
90289177Speter
91289177Speter  mtcc_op_t *root_op;
92289177Speter};
93289177Speter
94289177Speterstatic mtcc_op_t *
95289177Spetermtcc_op_create(const char *name,
96289177Speter               svn_boolean_t add,
97289177Speter               svn_boolean_t directory,
98289177Speter               apr_pool_t *result_pool)
99289177Speter{
100289177Speter  mtcc_op_t *op;
101289177Speter
102289177Speter  op = apr_pcalloc(result_pool, sizeof(*op));
103289177Speter  op->name = name ? apr_pstrdup(result_pool, name) : "";
104289177Speter
105289177Speter  if (add)
106289177Speter    op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE;
107289177Speter  else
108289177Speter    op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE;
109289177Speter
110289177Speter  if (directory)
111289177Speter    op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *));
112289177Speter
113289177Speter  op->src_rev = SVN_INVALID_REVNUM;
114289177Speter
115289177Speter  return op;
116289177Speter}
117289177Speter
118289177Speterstatic svn_error_t *
119289177Spetermtcc_op_find(mtcc_op_t **op,
120289177Speter             svn_boolean_t *created,
121289177Speter             const char *relpath,
122289177Speter             mtcc_op_t *base_op,
123289177Speter             svn_boolean_t find_existing,
124289177Speter             svn_boolean_t find_deletes,
125289177Speter             svn_boolean_t create_file,
126289177Speter             apr_pool_t *result_pool,
127289177Speter             apr_pool_t *scratch_pool)
128289177Speter{
129289177Speter  const char *name;
130289177Speter  const char *child;
131289177Speter  int i;
132289177Speter
133289177Speter  assert(svn_relpath_is_canonical(relpath));
134289177Speter  if (created)
135289177Speter    *created = FALSE;
136289177Speter
137289177Speter  if (SVN_PATH_IS_EMPTY(relpath))
138289177Speter    {
139289177Speter      if (find_existing)
140289177Speter        *op = base_op;
141289177Speter      else
142289177Speter        *op = NULL;
143289177Speter
144289177Speter      return SVN_NO_ERROR;
145289177Speter    }
146289177Speter
147289177Speter  child = strchr(relpath, '/');
148289177Speter
149289177Speter  if (child)
150289177Speter    {
151289177Speter      name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath));
152289177Speter      child++; /* Skip '/' */
153289177Speter    }
154289177Speter  else
155289177Speter    name = relpath;
156289177Speter
157289177Speter  if (!base_op->children)
158289177Speter    {
159289177Speter      if (!created)
160289177Speter        {
161289177Speter          *op = NULL;
162289177Speter           return SVN_NO_ERROR;
163289177Speter        }
164289177Speter      else
165289177Speter        return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
166289177Speter                                 _("Can't operate on '%s' because '%s' is not a "
167289177Speter                                   "directory"),
168289177Speter                                 name, base_op->name);
169289177Speter    }
170289177Speter
171289177Speter  for (i = base_op->children->nelts-1; i >= 0 ; i--)
172289177Speter    {
173289177Speter      mtcc_op_t *cop;
174289177Speter
175289177Speter      cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *);
176289177Speter
177289177Speter      if (! strcmp(cop->name, name)
178289177Speter          && (find_deletes || cop->kind != OP_DELETE))
179289177Speter        {
180289177Speter          return svn_error_trace(
181289177Speter                        mtcc_op_find(op, created, child ? child : "", cop,
182289177Speter                                     find_existing, find_deletes, create_file,
183289177Speter                                     result_pool, scratch_pool));
184289177Speter        }
185289177Speter    }
186289177Speter
187289177Speter  if (!created)
188289177Speter    {
189289177Speter      *op = NULL;
190289177Speter      return SVN_NO_ERROR;
191289177Speter    }
192289177Speter
193289177Speter  {
194289177Speter    mtcc_op_t *cop;
195289177Speter
196289177Speter    cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool);
197289177Speter
198289177Speter    APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop;
199289177Speter
200289177Speter    if (!child)
201289177Speter      {
202289177Speter        *op = cop;
203289177Speter        *created = TRUE;
204289177Speter        return SVN_NO_ERROR;
205289177Speter      }
206289177Speter
207289177Speter    return svn_error_trace(
208289177Speter                mtcc_op_find(op, created, child, cop, find_existing,
209289177Speter                             find_deletes, create_file,
210289177Speter                             result_pool, scratch_pool));
211289177Speter  }
212289177Speter}
213289177Speter
214289177Speter/* Gets the original repository location of RELPATH, checking things
215289177Speter   like copies, moves, etc.  */
216289177Speterstatic svn_error_t *
217289177Speterget_origin(svn_boolean_t *done,
218289177Speter           const char **origin_relpath,
219289177Speter           svn_revnum_t *rev,
220289177Speter           mtcc_op_t *op,
221289177Speter           const char *relpath,
222289177Speter           apr_pool_t *result_pool,
223289177Speter           apr_pool_t *scratch_pool)
224289177Speter{
225289177Speter  const char *child;
226289177Speter  const char *name;
227289177Speter  if (SVN_PATH_IS_EMPTY(relpath))
228289177Speter    {
229289177Speter      if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
230289177Speter        *done = TRUE;
231289177Speter      *origin_relpath = op->src_relpath
232289177Speter                                ? apr_pstrdup(result_pool, op->src_relpath)
233289177Speter                                : NULL;
234289177Speter      *rev = op->src_rev;
235289177Speter      return SVN_NO_ERROR;
236289177Speter    }
237289177Speter
238289177Speter  child = strchr(relpath, '/');
239289177Speter  if (child)
240289177Speter    {
241289177Speter      name = apr_pstrmemdup(scratch_pool, relpath, child-relpath);
242289177Speter      child++; /* Skip '/' */
243289177Speter    }
244289177Speter  else
245289177Speter    name = relpath;
246289177Speter
247289177Speter  if (op->children && op->children->nelts)
248289177Speter    {
249289177Speter      int i;
250289177Speter
251289177Speter      for (i = op->children->nelts-1; i >= 0; i--)
252289177Speter        {
253289177Speter           mtcc_op_t *cop;
254289177Speter
255289177Speter           cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
256289177Speter
257289177Speter           if (! strcmp(cop->name, name))
258289177Speter            {
259289177Speter              if (cop->kind == OP_DELETE)
260289177Speter                {
261289177Speter                  *done = TRUE;
262289177Speter                  return SVN_NO_ERROR;
263289177Speter                }
264289177Speter
265289177Speter              SVN_ERR(get_origin(done, origin_relpath, rev,
266289177Speter                                 cop, child ? child : "",
267289177Speter                                 result_pool, scratch_pool));
268289177Speter
269289177Speter              if (*origin_relpath || *done)
270289177Speter                return SVN_NO_ERROR;
271289177Speter
272289177Speter              break;
273289177Speter            }
274289177Speter        }
275289177Speter    }
276289177Speter
277289177Speter  if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
278289177Speter    {
279289177Speter      *done = TRUE;
280289177Speter      if (op->src_relpath)
281289177Speter        {
282289177Speter          *origin_relpath = svn_relpath_join(op->src_relpath, relpath,
283289177Speter                                             result_pool);
284289177Speter          *rev = op->src_rev;
285289177Speter        }
286289177Speter    }
287289177Speter
288289177Speter  return SVN_NO_ERROR;
289289177Speter}
290289177Speter
291289177Speter/* Obtains the original repository location for an mtcc relpath as
292289177Speter   *ORIGIN_RELPATH @ *REV, if it has one. If it has not and IGNORE_ENOENT
293289177Speter   is TRUE report *ORIGIN_RELPATH as NULL, otherwise return an error */
294289177Speterstatic svn_error_t *
295289177Spetermtcc_get_origin(const char **origin_relpath,
296289177Speter                svn_revnum_t *rev,
297289177Speter                const char *relpath,
298289177Speter                svn_boolean_t ignore_enoent,
299289177Speter                svn_client__mtcc_t *mtcc,
300289177Speter                apr_pool_t *result_pool,
301289177Speter                apr_pool_t *scratch_pool)
302289177Speter{
303289177Speter  svn_boolean_t done = FALSE;
304289177Speter
305289177Speter  *origin_relpath = NULL;
306289177Speter  *rev = SVN_INVALID_REVNUM;
307289177Speter
308289177Speter  SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath,
309289177Speter                     result_pool, scratch_pool));
310289177Speter
311289177Speter  if (!*origin_relpath && !done)
312289177Speter    {
313289177Speter      *origin_relpath = apr_pstrdup(result_pool, relpath);
314289177Speter      *rev = mtcc->base_revision;
315289177Speter    }
316289177Speter  else if (!ignore_enoent)
317289177Speter    {
318289177Speter      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
319289177Speter                               _("No origin found for node at '%s'"),
320289177Speter                               relpath);
321289177Speter    }
322289177Speter
323289177Speter  return SVN_NO_ERROR;
324289177Speter}
325289177Speter
326289177Spetersvn_error_t *
327289177Spetersvn_client__mtcc_create(svn_client__mtcc_t **mtcc,
328289177Speter                        const char *anchor_url,
329289177Speter                        svn_revnum_t base_revision,
330289177Speter                        svn_client_ctx_t *ctx,
331289177Speter                        apr_pool_t *result_pool,
332289177Speter                        apr_pool_t *scratch_pool)
333289177Speter{
334289177Speter  apr_pool_t *mtcc_pool;
335289177Speter
336289177Speter  mtcc_pool = svn_pool_create(result_pool);
337289177Speter
338289177Speter  *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc));
339289177Speter  (*mtcc)->pool = mtcc_pool;
340289177Speter
341289177Speter  (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool);
342289177Speter
343289177Speter  (*mtcc)->ctx = ctx;
344289177Speter
345289177Speter  SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url,
346289177Speter                                      NULL /* wri_abspath */, ctx,
347289177Speter                                      mtcc_pool, scratch_pool));
348289177Speter
349289177Speter  SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision,
350289177Speter                                   scratch_pool));
351289177Speter
352289177Speter  if (SVN_IS_VALID_REVNUM(base_revision))
353289177Speter    (*mtcc)->base_revision = base_revision;
354289177Speter  else
355289177Speter    (*mtcc)->base_revision = (*mtcc)->head_revision;
356289177Speter
357289177Speter  if ((*mtcc)->base_revision > (*mtcc)->head_revision)
358289177Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
359289177Speter                             _("No such revision %ld (HEAD is %ld)"),
360289177Speter                             base_revision, (*mtcc)->head_revision);
361289177Speter
362289177Speter  return SVN_NO_ERROR;
363289177Speter}
364289177Speter
365289177Speterstatic svn_error_t *
366289177Speterupdate_copy_src(mtcc_op_t *op,
367289177Speter                const char *add_relpath,
368289177Speter                apr_pool_t *result_pool)
369289177Speter{
370289177Speter  int i;
371289177Speter
372289177Speter  if (op->src_relpath)
373289177Speter    op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath,
374289177Speter                                       result_pool);
375289177Speter
376289177Speter  if (!op->children)
377289177Speter    return SVN_NO_ERROR;
378289177Speter
379289177Speter  for (i = 0; i < op->children->nelts; i++)
380289177Speter    {
381289177Speter      mtcc_op_t *cop;
382289177Speter
383289177Speter      cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
384289177Speter
385289177Speter      SVN_ERR(update_copy_src(cop, add_relpath, result_pool));
386289177Speter    }
387289177Speter
388289177Speter  return SVN_NO_ERROR;
389289177Speter}
390289177Speter
391289177Speterstatic svn_error_t *
392289177Spetermtcc_reparent(const char *new_anchor_url,
393289177Speter              svn_client__mtcc_t *mtcc,
394289177Speter              apr_pool_t *scratch_pool)
395289177Speter{
396289177Speter  const char *session_url;
397289177Speter  const char *up;
398289177Speter
399289177Speter  SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url,
400289177Speter                                 scratch_pool));
401289177Speter
402289177Speter  up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool);
403289177Speter
404289177Speter  if (! up)
405289177Speter    {
406289177Speter      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
407289177Speter                               _("'%s' is not an ancestor of  '%s'"),
408289177Speter                               new_anchor_url, session_url);
409289177Speter    }
410289177Speter  else if (!*up)
411289177Speter    {
412289177Speter      return SVN_NO_ERROR; /* Same url */
413289177Speter    }
414289177Speter
415289177Speter  /* Update copy origins recursively...:( */
416289177Speter  SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool));
417289177Speter
418289177Speter  SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool));
419289177Speter
420289177Speter  /* Create directory open operations for new ancestors */
421289177Speter  while (*up)
422289177Speter    {
423289177Speter      mtcc_op_t *root_op;
424289177Speter
425289177Speter      mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool);
426289177Speter      up = svn_relpath_dirname(up, scratch_pool);
427289177Speter
428289177Speter      root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool);
429289177Speter
430289177Speter      APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op;
431289177Speter
432289177Speter      mtcc->root_op = root_op;
433289177Speter    }
434289177Speter
435289177Speter  return SVN_NO_ERROR;
436289177Speter}
437289177Speter
438289177Speter/* Check if it is safe to create a new node at NEW_RELPATH. Return a proper
439289177Speter   error if it is not */
440289177Speterstatic svn_error_t *
441289177Spetermtcc_verify_create(svn_client__mtcc_t *mtcc,
442289177Speter                   const char *new_relpath,
443289177Speter                   apr_pool_t *scratch_pool)
444289177Speter{
445289177Speter  svn_node_kind_t kind;
446289177Speter
447289177Speter  if (*new_relpath || !MTCC_UNMODIFIED(mtcc))
448289177Speter    {
449289177Speter      mtcc_op_t *op;
450289177Speter
451289177Speter      SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE,
452289177Speter                           FALSE, mtcc->pool, scratch_pool));
453289177Speter
454289177Speter      if (op)
455289177Speter        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
456369302Sdim                                 _("Path '%s' already exists, or was created "
457369302Sdim                                   "by an earlier operation"),
458289177Speter                                 new_relpath);
459289177Speter
460289177Speter      SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE,
461289177Speter                           FALSE, mtcc->pool, scratch_pool));
462289177Speter
463289177Speter      if (op)
464289177Speter        return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */
465289177Speter    }
466289177Speter
467289177Speter  /* mod_dav_svn used to allow overwriting existing directories. Let's hide
468289177Speter     that for users of this api */
469289177Speter  SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE,
470289177Speter                                      mtcc, scratch_pool));
471289177Speter
472289177Speter  if (kind != svn_node_none)
473289177Speter    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
474289177Speter                             _("Path '%s' already exists"),
475289177Speter                             new_relpath);
476289177Speter
477289177Speter  return SVN_NO_ERROR;
478289177Speter}
479289177Speter
480289177Speter
481289177Spetersvn_error_t *
482289177Spetersvn_client__mtcc_add_add_file(const char *relpath,
483289177Speter                              svn_stream_t *src_stream,
484289177Speter                              const svn_checksum_t *src_checksum,
485289177Speter                              svn_client__mtcc_t *mtcc,
486289177Speter                              apr_pool_t *scratch_pool)
487289177Speter{
488289177Speter  mtcc_op_t *op;
489289177Speter  svn_boolean_t created;
490289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
491289177Speter
492289177Speter  SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
493289177Speter
494289177Speter  if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
495289177Speter    {
496289177Speter      /* Turn the root operation into a file addition */
497289177Speter      op = mtcc->root_op;
498289177Speter    }
499289177Speter  else
500289177Speter    {
501289177Speter      SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
502289177Speter                           TRUE, mtcc->pool, scratch_pool));
503289177Speter
504289177Speter      if (!op || !created)
505289177Speter        {
506289177Speter          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
507289177Speter                                   _("Can't add file at '%s'"),
508289177Speter                                   relpath);
509289177Speter        }
510289177Speter    }
511289177Speter
512289177Speter  op->kind = OP_ADD_FILE;
513289177Speter  op->src_stream = src_stream;
514289177Speter  op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
515289177Speter                                  : NULL;
516289177Speter
517289177Speter  return SVN_NO_ERROR;
518289177Speter}
519289177Speter
520289177Spetersvn_error_t *
521289177Spetersvn_client__mtcc_add_copy(const char *src_relpath,
522289177Speter                          svn_revnum_t revision,
523289177Speter                          const char *dst_relpath,
524289177Speter                          svn_client__mtcc_t *mtcc,
525289177Speter                          apr_pool_t *scratch_pool)
526289177Speter{
527289177Speter  mtcc_op_t *op;
528289177Speter  svn_boolean_t created;
529289177Speter  svn_node_kind_t kind;
530289177Speter
531289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)
532289177Speter                 && svn_relpath_is_canonical(dst_relpath));
533289177Speter
534289177Speter  if (! SVN_IS_VALID_REVNUM(revision))
535289177Speter    revision = mtcc->head_revision;
536289177Speter  else if (revision > mtcc->head_revision)
537289177Speter    {
538289177Speter      return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
539289177Speter                               _("No such revision %ld"), revision);
540289177Speter    }
541289177Speter
542289177Speter  SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool));
543289177Speter
544289177Speter  /* Subversion requires the kind of a copy */
545289177Speter  SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind,
546289177Speter                            scratch_pool));
547289177Speter
548289177Speter  if (kind != svn_node_dir && kind != svn_node_file)
549289177Speter    {
550289177Speter      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
551289177Speter                               _("Path '%s' not found in revision %ld"),
552289177Speter                               src_relpath, revision);
553289177Speter    }
554289177Speter
555289177Speter  SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE,
556289177Speter                       (kind == svn_node_file), mtcc->pool, scratch_pool));
557289177Speter
558289177Speter  if (!op || !created)
559289177Speter    {
560289177Speter      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
561289177Speter                               _("Can't add node at '%s'"),
562289177Speter                               dst_relpath);
563289177Speter    }
564289177Speter
565289177Speter  op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR;
566289177Speter  op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath);
567289177Speter  op->src_rev = revision;
568289177Speter
569289177Speter  return SVN_NO_ERROR;
570289177Speter}
571289177Speter
572362181Sdim/* Check if this operation contains at least one change that is not a
573362181Sdim   plain delete */
574362181Sdimstatic svn_boolean_t
575362181Sdimmtcc_op_contains_non_delete(const mtcc_op_t *op)
576289177Speter{
577362181Sdim  if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE
578362181Sdim      && op->kind != OP_DELETE)
579362181Sdim    {
580362181Sdim      return TRUE;
581362181Sdim    }
582362181Sdim
583362181Sdim  if (op->prop_mods && op->prop_mods->nelts)
584362181Sdim    return TRUE;
585362181Sdim
586362181Sdim  if (op->src_stream)
587362181Sdim    return TRUE;
588362181Sdim
589362181Sdim  if (op->children)
590362181Sdim    {
591362181Sdim      int i;
592362181Sdim
593362181Sdim      for (i = 0; i < op->children->nelts; i++)
594362181Sdim        {
595362181Sdim          const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i,
596362181Sdim                                                const mtcc_op_t *);
597362181Sdim
598362181Sdim          if (mtcc_op_contains_non_delete(c_op))
599362181Sdim            return TRUE;
600362181Sdim        }
601362181Sdim    }
602362181Sdim  return FALSE;
603362181Sdim}
604362181Sdim
605362181Sdimstatic svn_error_t *
606362181Sdimmtcc_add_delete(const char *relpath,
607362181Sdim                svn_boolean_t for_move,
608362181Sdim                svn_client__mtcc_t *mtcc,
609362181Sdim                apr_pool_t *scratch_pool)
610362181Sdim{
611289177Speter  mtcc_op_t *op;
612289177Speter  svn_boolean_t created;
613289177Speter  svn_node_kind_t kind;
614289177Speter
615289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
616289177Speter
617289177Speter  SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
618289177Speter                                      mtcc, scratch_pool));
619289177Speter
620289177Speter  if (kind == svn_node_none)
621289177Speter    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
622289177Speter                             _("Can't delete node at '%s' as it "
623289177Speter                                "does not exist"),
624289177Speter                             relpath);
625289177Speter
626289177Speter  if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
627289177Speter    {
628289177Speter      /* Turn root operation into delete */
629289177Speter      op = mtcc->root_op;
630289177Speter    }
631289177Speter  else
632289177Speter    {
633289177Speter      SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE,
634289177Speter                           TRUE, mtcc->pool, scratch_pool));
635289177Speter
636362181Sdim      if (!for_move && !op && !created)
637362181Sdim        {
638362181Sdim          /* Allow deleting directories, that are unmodified except for
639362181Sdim              one or more deleted descendants */
640362181Sdim
641362181Sdim          SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE,
642362181Sdim                  FALSE, FALSE, mtcc->pool, scratch_pool));
643362181Sdim
644362181Sdim          if (op && mtcc_op_contains_non_delete(op))
645362181Sdim            op = NULL;
646362181Sdim          else
647362181Sdim            created = TRUE;
648362181Sdim        }
649362181Sdim
650289177Speter      if (!op || !created)
651289177Speter        {
652289177Speter          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
653289177Speter                                   _("Can't delete node at '%s'"),
654289177Speter                                   relpath);
655289177Speter        }
656289177Speter    }
657289177Speter
658289177Speter  op->kind = OP_DELETE;
659289177Speter  op->children = NULL;
660289177Speter  op->prop_mods = NULL;
661289177Speter
662289177Speter  return SVN_NO_ERROR;
663289177Speter}
664289177Speter
665289177Spetersvn_error_t *
666362181Sdimsvn_client__mtcc_add_delete(const char *relpath,
667362181Sdim                            svn_client__mtcc_t *mtcc,
668362181Sdim                            apr_pool_t *scratch_pool)
669362181Sdim{
670362181Sdim  return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool));
671362181Sdim}
672362181Sdim
673362181Sdimsvn_error_t *
674289177Spetersvn_client__mtcc_add_mkdir(const char *relpath,
675289177Speter                           svn_client__mtcc_t *mtcc,
676289177Speter                           apr_pool_t *scratch_pool)
677289177Speter{
678289177Speter  mtcc_op_t *op;
679289177Speter  svn_boolean_t created;
680289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
681289177Speter
682289177Speter  SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool));
683289177Speter
684289177Speter  if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
685289177Speter    {
686289177Speter      /* Turn the root of the operation in an MKDIR */
687289177Speter      mtcc->root_op->kind = OP_ADD_DIR;
688289177Speter
689289177Speter      return SVN_NO_ERROR;
690289177Speter    }
691289177Speter
692289177Speter  SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE,
693289177Speter                       FALSE, mtcc->pool, scratch_pool));
694289177Speter
695289177Speter  if (!op || !created)
696289177Speter    {
697289177Speter      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
698289177Speter                               _("Can't create directory at '%s'"),
699289177Speter                               relpath);
700289177Speter    }
701289177Speter
702289177Speter  op->kind = OP_ADD_DIR;
703289177Speter
704289177Speter  return SVN_NO_ERROR;
705289177Speter}
706289177Speter
707289177Spetersvn_error_t *
708289177Spetersvn_client__mtcc_add_move(const char *src_relpath,
709289177Speter                          const char *dst_relpath,
710289177Speter                          svn_client__mtcc_t *mtcc,
711289177Speter                          apr_pool_t *scratch_pool)
712289177Speter{
713289177Speter  const char *origin_relpath;
714289177Speter  svn_revnum_t origin_rev;
715289177Speter
716289177Speter  SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
717289177Speter                          src_relpath, FALSE, mtcc,
718289177Speter                          scratch_pool, scratch_pool));
719289177Speter
720289177Speter  SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision,
721289177Speter                                    dst_relpath, mtcc, scratch_pool));
722362181Sdim  SVN_ERR(mtcc_add_delete(src_relpath, TRUE, mtcc, scratch_pool));
723289177Speter
724289177Speter  return SVN_NO_ERROR;
725289177Speter}
726289177Speter
727289177Speter/* Baton for mtcc_prop_getter */
728289177Speterstruct mtcc_prop_get_baton
729289177Speter{
730289177Speter  svn_client__mtcc_t *mtcc;
731289177Speter  const char *relpath;
732289177Speter  svn_cancel_func_t cancel_func;
733289177Speter  void *cancel_baton;
734289177Speter};
735289177Speter
736289177Speter/* Implements svn_wc_canonicalize_svn_prop_get_file_t */
737289177Speterstatic svn_error_t *
738289177Spetermtcc_prop_getter(const svn_string_t **mime_type,
739289177Speter                 svn_stream_t *stream,
740289177Speter                 void *baton,
741289177Speter                 apr_pool_t *pool)
742289177Speter{
743289177Speter  struct mtcc_prop_get_baton *mpgb = baton;
744289177Speter  const char *origin_relpath;
745289177Speter  svn_revnum_t origin_rev;
746289177Speter  apr_hash_t *props = NULL;
747289177Speter
748289177Speter  mtcc_op_t *op;
749289177Speter
750289177Speter  if (mime_type)
751289177Speter    *mime_type = NULL;
752289177Speter
753289177Speter  /* Check if we have the information locally */
754289177Speter  SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE,
755289177Speter                       FALSE, FALSE, pool, pool));
756289177Speter
757289177Speter  if (op)
758289177Speter    {
759289177Speter      if (mime_type)
760289177Speter        {
761289177Speter          int i;
762289177Speter
763289177Speter          for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++)
764289177Speter            {
765289177Speter              const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i,
766289177Speter                                                     svn_prop_t);
767289177Speter
768289177Speter              if (! strcmp(mod->name, SVN_PROP_MIME_TYPE))
769289177Speter                {
770289177Speter                  *mime_type = svn_string_dup(mod->value, pool);
771289177Speter                  mime_type = NULL;
772362181Sdim                  break;
773289177Speter                }
774289177Speter            }
775289177Speter        }
776289177Speter
777289177Speter      if (stream && op->src_stream)
778289177Speter        {
779289177Speter          svn_stream_mark_t *mark;
780289177Speter          svn_error_t *err;
781289177Speter
782289177Speter          /* Is the source stream capable of being read multiple times? */
783289177Speter          err = svn_stream_mark(op->src_stream, &mark, pool);
784289177Speter
785289177Speter          if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED)
786289177Speter            return svn_error_trace(err);
787289177Speter          svn_error_clear(err);
788289177Speter
789289177Speter          if (!err)
790289177Speter            {
791289177Speter              err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool),
792289177Speter                                     svn_stream_disown(stream, pool),
793289177Speter                                     mpgb->cancel_func, mpgb->cancel_baton,
794289177Speter                                     pool);
795289177Speter
796289177Speter              SVN_ERR(svn_error_compose_create(
797289177Speter                            err,
798289177Speter                            svn_stream_seek(op->src_stream, mark)));
799289177Speter            }
800289177Speter          /* else: ### Create tempfile? */
801289177Speter
802289177Speter          stream = NULL; /* Stream is handled */
803289177Speter        }
804289177Speter    }
805289177Speter
806289177Speter  if (!stream && !mime_type)
807289177Speter    return SVN_NO_ERROR;
808289177Speter
809289177Speter  SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE,
810289177Speter                          mpgb->mtcc, pool, pool));
811289177Speter
812289177Speter  if (!origin_relpath)
813289177Speter    return SVN_NO_ERROR; /* Nothing to fetch at repository */
814289177Speter
815289177Speter  SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev,
816289177Speter                          stream, NULL, mime_type ? &props : NULL, pool));
817289177Speter
818289177Speter  if (mime_type && props)
819289177Speter    *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
820289177Speter
821289177Speter  return SVN_NO_ERROR;
822289177Speter}
823289177Speter
824289177Spetersvn_error_t *
825289177Spetersvn_client__mtcc_add_propset(const char *relpath,
826289177Speter                             const char *propname,
827289177Speter                             const svn_string_t *propval,
828289177Speter                             svn_boolean_t skip_checks,
829289177Speter                             svn_client__mtcc_t *mtcc,
830289177Speter                             apr_pool_t *scratch_pool)
831289177Speter{
832289177Speter  mtcc_op_t *op;
833289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
834289177Speter
835289177Speter  if (! svn_prop_name_is_valid(propname))
836289177Speter    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
837289177Speter                             _("Bad property name: '%s'"), propname);
838289177Speter
839289177Speter  if (svn_prop_is_known_svn_rev_prop(propname))
840289177Speter    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
841289177Speter                             _("Revision property '%s' not allowed "
842289177Speter                               "in this context"), propname);
843289177Speter
844289177Speter  if (svn_property_kind2(propname) == svn_prop_wc_kind)
845289177Speter    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
846289177Speter                             _("'%s' is a wcprop, thus not accessible "
847289177Speter                               "to clients"), propname);
848289177Speter
849289177Speter  if (!skip_checks && svn_prop_needs_translation(propname))
850289177Speter    {
851289177Speter      svn_string_t *translated_value;
852289177Speter      SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
853289177Speter                                            NULL, propval,
854289177Speter                                            NULL, FALSE,
855289177Speter                                            scratch_pool, scratch_pool),
856289177Speter                _("Error normalizing property value"));
857289177Speter
858289177Speter      propval = translated_value;
859289177Speter    }
860289177Speter
861289177Speter  if (propval && svn_prop_is_svn_prop(propname))
862289177Speter    {
863289177Speter      struct mtcc_prop_get_baton mpbg;
864289177Speter      svn_node_kind_t kind;
865289177Speter      SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc,
866289177Speter                                          scratch_pool));
867289177Speter
868289177Speter      mpbg.mtcc = mtcc;
869289177Speter      mpbg.relpath = relpath;
870289177Speter      mpbg.cancel_func = mtcc->ctx->cancel_func;
871289177Speter      mpbg.cancel_baton = mtcc->ctx->cancel_baton;
872289177Speter
873289177Speter      SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval,
874289177Speter                                           relpath, kind, skip_checks,
875289177Speter                                           mtcc_prop_getter, &mpbg,
876289177Speter                                           scratch_pool));
877289177Speter    }
878289177Speter
879289177Speter  if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc))
880289177Speter    {
881289177Speter      svn_node_kind_t kind;
882289177Speter
883289177Speter      /* Probing the node for an unmodified root will fix the node type to
884289177Speter         a file if necessary */
885289177Speter
886289177Speter      SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
887289177Speter                                          mtcc, scratch_pool));
888289177Speter
889289177Speter      if (kind == svn_node_none)
890289177Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
891289177Speter                                 _("Can't set properties at not existing '%s'"),
892289177Speter                                   relpath);
893289177Speter
894289177Speter      op = mtcc->root_op;
895289177Speter    }
896289177Speter  else
897289177Speter    {
898289177Speter      SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
899289177Speter                           FALSE, mtcc->pool, scratch_pool));
900289177Speter
901289177Speter      if (!op)
902289177Speter        {
903289177Speter          svn_node_kind_t kind;
904289177Speter          svn_boolean_t created;
905289177Speter
906289177Speter          /* ### TODO: Check if this node is within a newly copied directory,
907289177Speter                       and update origin values accordingly */
908289177Speter
909289177Speter          SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
910289177Speter                                              mtcc, scratch_pool));
911289177Speter
912289177Speter          if (kind == svn_node_none)
913289177Speter            return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
914289177Speter                                     _("Can't set properties at not existing '%s'"),
915289177Speter                                     relpath);
916289177Speter
917289177Speter          SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
918289177Speter                               (kind != svn_node_dir),
919289177Speter                               mtcc->pool, scratch_pool));
920289177Speter
921289177Speter          SVN_ERR_ASSERT(op != NULL);
922289177Speter        }
923289177Speter    }
924289177Speter
925289177Speter  if (!op->prop_mods)
926289177Speter      op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t));
927289177Speter
928289177Speter  {
929289177Speter    svn_prop_t propchange;
930289177Speter    propchange.name = apr_pstrdup(mtcc->pool, propname);
931289177Speter
932289177Speter    if (propval)
933289177Speter      propchange.value = svn_string_dup(propval, mtcc->pool);
934289177Speter    else
935289177Speter      propchange.value = NULL;
936289177Speter
937289177Speter    APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange;
938289177Speter  }
939289177Speter
940289177Speter  return SVN_NO_ERROR;
941289177Speter}
942289177Speter
943289177Spetersvn_error_t *
944289177Spetersvn_client__mtcc_add_update_file(const char *relpath,
945289177Speter                                 svn_stream_t *src_stream,
946289177Speter                                 const svn_checksum_t *src_checksum,
947289177Speter                                 svn_stream_t *base_stream,
948289177Speter                                 const svn_checksum_t *base_checksum,
949289177Speter                                 svn_client__mtcc_t *mtcc,
950289177Speter                                 apr_pool_t *scratch_pool)
951289177Speter{
952289177Speter  mtcc_op_t *op;
953289177Speter  svn_boolean_t created;
954289177Speter  svn_node_kind_t kind;
955289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream);
956289177Speter
957289177Speter  SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE,
958289177Speter                                      mtcc, scratch_pool));
959289177Speter
960289177Speter  if (kind != svn_node_file)
961289177Speter    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
962289177Speter                             _("Can't update '%s' because it is not a file"),
963289177Speter                             relpath);
964289177Speter
965289177Speter  SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE,
966289177Speter                       TRUE, mtcc->pool, scratch_pool));
967289177Speter
968289177Speter  if (!op
969289177Speter      || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE)
970289177Speter      || (op->src_stream != NULL))
971289177Speter    {
972289177Speter      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
973289177Speter                               _("Can't update file at '%s'"), relpath);
974289177Speter    }
975289177Speter
976289177Speter  op->src_stream = src_stream;
977289177Speter  op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool)
978289177Speter                                  : NULL;
979289177Speter
980289177Speter  op->base_stream = base_stream;
981289177Speter  op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum,
982289177Speter                                                       mtcc->pool)
983289177Speter                                    : NULL;
984289177Speter
985289177Speter  return SVN_NO_ERROR;
986289177Speter}
987289177Speter
988289177Spetersvn_error_t *
989289177Spetersvn_client__mtcc_check_path(svn_node_kind_t *kind,
990289177Speter                            const char *relpath,
991289177Speter                            svn_boolean_t check_repository,
992289177Speter                            svn_client__mtcc_t *mtcc,
993289177Speter                            apr_pool_t *scratch_pool)
994289177Speter{
995289177Speter  const char *origin_relpath;
996289177Speter  svn_revnum_t origin_rev;
997289177Speter  mtcc_op_t *op;
998289177Speter
999289177Speter  SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
1000289177Speter
1001289177Speter  if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)
1002289177Speter      && !mtcc->root_op->performed_stat)
1003289177Speter    {
1004289177Speter      /* We know nothing about the root. Perhaps it is a file? */
1005289177Speter      SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision,
1006289177Speter                                kind, scratch_pool));
1007289177Speter
1008289177Speter      mtcc->root_op->performed_stat = TRUE;
1009289177Speter      if (*kind == svn_node_file)
1010289177Speter        {
1011289177Speter          mtcc->root_op->kind = OP_OPEN_FILE;
1012289177Speter          mtcc->root_op->children = NULL;
1013289177Speter        }
1014289177Speter      return SVN_NO_ERROR;
1015289177Speter    }
1016289177Speter
1017289177Speter  SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE,
1018289177Speter                       FALSE, mtcc->pool, scratch_pool));
1019289177Speter
1020289177Speter  if (!op || (check_repository && !op->performed_stat))
1021289177Speter    {
1022289177Speter      SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev,
1023289177Speter                              relpath, TRUE, mtcc,
1024289177Speter                              scratch_pool, scratch_pool));
1025289177Speter
1026289177Speter      if (!origin_relpath)
1027289177Speter        *kind = svn_node_none;
1028289177Speter      else
1029289177Speter        SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath,
1030289177Speter                                  origin_rev, kind, scratch_pool));
1031289177Speter
1032289177Speter      if (op && *kind == svn_node_dir)
1033289177Speter        {
1034289177Speter          if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1035289177Speter            op->performed_stat = TRUE;
1036289177Speter          else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1037289177Speter            return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1038289177Speter                                     _("Can't perform file operation "
1039289177Speter                                       "on '%s' as it is not a file"),
1040289177Speter                                     relpath);
1041289177Speter        }
1042289177Speter      else if (op && *kind == svn_node_file)
1043289177Speter        {
1044289177Speter          if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1045289177Speter            op->performed_stat = TRUE;
1046289177Speter          else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1047289177Speter            return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1048289177Speter                                     _("Can't perform directory operation "
1049289177Speter                                       "on '%s' as it is not a directory"),
1050289177Speter                                     relpath);
1051289177Speter        }
1052289177Speter      else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE))
1053289177Speter        {
1054289177Speter          return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1055289177Speter                                   _("Can't open '%s' as it does not exist"),
1056289177Speter                                   relpath);
1057289177Speter        }
1058289177Speter
1059289177Speter      return SVN_NO_ERROR;
1060289177Speter    }
1061289177Speter
1062289177Speter  /* op != NULL */
1063289177Speter  if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1064289177Speter    {
1065289177Speter      *kind = svn_node_dir;
1066289177Speter      return SVN_NO_ERROR;
1067289177Speter    }
1068289177Speter  else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1069289177Speter    {
1070289177Speter      *kind = svn_node_file;
1071289177Speter      return SVN_NO_ERROR;
1072289177Speter    }
1073289177Speter  SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */
1074289177Speter}
1075289177Speter
1076289177Speterstatic svn_error_t *
1077289177Spetercommit_properties(const svn_delta_editor_t *editor,
1078289177Speter                  const mtcc_op_t *op,
1079289177Speter                  void *node_baton,
1080289177Speter                  apr_pool_t *scratch_pool)
1081289177Speter{
1082289177Speter  int i;
1083289177Speter  apr_pool_t *iterpool;
1084289177Speter
1085289177Speter  if (!op->prop_mods || op->prop_mods->nelts == 0)
1086289177Speter    return SVN_NO_ERROR;
1087289177Speter
1088289177Speter  iterpool = svn_pool_create(scratch_pool);
1089289177Speter  for (i = 0; i < op->prop_mods->nelts; i++)
1090289177Speter    {
1091289177Speter      const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t);
1092289177Speter
1093289177Speter      svn_pool_clear(iterpool);
1094289177Speter
1095289177Speter      if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR)
1096289177Speter        SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value,
1097289177Speter                                        iterpool));
1098289177Speter      else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE)
1099289177Speter        SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value,
1100289177Speter                                         iterpool));
1101289177Speter    }
1102289177Speter
1103289177Speter  svn_pool_destroy(iterpool);
1104289177Speter  return SVN_NO_ERROR;
1105289177Speter}
1106289177Speter
1107289177Speter/* Handles updating a file to a delta editor and then closes it */
1108289177Speterstatic svn_error_t *
1109289177Spetercommit_file(const svn_delta_editor_t *editor,
1110289177Speter            mtcc_op_t *op,
1111289177Speter            void *file_baton,
1112289177Speter            const char *session_url,
1113289177Speter            const char *relpath,
1114289177Speter            svn_client_ctx_t *ctx,
1115289177Speter            apr_pool_t *scratch_pool)
1116289177Speter{
1117289177Speter  const char *text_checksum = NULL;
1118289177Speter  svn_checksum_t *src_checksum = op->src_checksum;
1119289177Speter  SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool));
1120289177Speter
1121289177Speter  if (op->src_stream)
1122289177Speter    {
1123289177Speter      const char *base_checksum = NULL;
1124289177Speter      apr_pool_t *txdelta_pool = scratch_pool;
1125289177Speter      svn_txdelta_window_handler_t window_handler;
1126289177Speter      void *handler_baton;
1127289177Speter      svn_stream_t *src_stream = op->src_stream;
1128289177Speter
1129289177Speter      if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5)
1130289177Speter        base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool);
1131289177Speter
1132289177Speter      /* ### TODO: Future enhancement: Allocate in special pool and send
1133289177Speter                   files after the true edit operation, like a wc commit */
1134289177Speter      SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool,
1135289177Speter                                      &window_handler, &handler_baton));
1136289177Speter
1137289177Speter      if (ctx->notify_func2)
1138289177Speter        {
1139289177Speter          svn_wc_notify_t *notify;
1140289177Speter
1141289177Speter          notify = svn_wc_create_notify_url(
1142289177Speter                            svn_path_url_add_component2(session_url, relpath,
1143289177Speter                                                        scratch_pool),
1144289177Speter                            svn_wc_notify_commit_postfix_txdelta,
1145289177Speter                            scratch_pool);
1146289177Speter
1147289177Speter          notify->path = relpath;
1148289177Speter          notify->kind = svn_node_file;
1149289177Speter
1150289177Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
1151289177Speter        }
1152289177Speter
1153289177Speter      if (window_handler != svn_delta_noop_window_handler)
1154289177Speter        {
1155289177Speter          if (!src_checksum || src_checksum->kind != svn_checksum_md5)
1156289177Speter            src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL,
1157289177Speter                                                 svn_checksum_md5,
1158289177Speter                                                 TRUE, scratch_pool);
1159289177Speter
1160289177Speter          if (!op->base_stream)
1161289177Speter            SVN_ERR(svn_txdelta_send_stream(src_stream,
1162289177Speter                                            window_handler, handler_baton, NULL,
1163289177Speter                                            scratch_pool));
1164289177Speter          else
1165289177Speter            SVN_ERR(svn_txdelta_run(op->base_stream, src_stream,
1166289177Speter                                    window_handler, handler_baton,
1167289177Speter                                    svn_checksum_md5, NULL,
1168289177Speter                                    ctx->cancel_func, ctx->cancel_baton,
1169289177Speter                                    scratch_pool, scratch_pool));
1170289177Speter        }
1171289177Speter
1172289177Speter      SVN_ERR(svn_stream_close(src_stream));
1173289177Speter      if (op->base_stream)
1174289177Speter        SVN_ERR(svn_stream_close(op->base_stream));
1175289177Speter    }
1176289177Speter
1177289177Speter  if (src_checksum && src_checksum->kind == svn_checksum_md5)
1178289177Speter    text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool);
1179289177Speter
1180289177Speter  return svn_error_trace(editor->close_file(file_baton, text_checksum,
1181289177Speter                                            scratch_pool));
1182289177Speter}
1183289177Speter
1184289177Speter/* Handles updating a directory to a delta editor and then closes it */
1185289177Speterstatic svn_error_t *
1186289177Spetercommit_directory(const svn_delta_editor_t *editor,
1187289177Speter                 mtcc_op_t *op,
1188289177Speter                 const char *relpath,
1189289177Speter                 svn_revnum_t base_rev,
1190289177Speter                 void *dir_baton,
1191289177Speter                 const char *session_url,
1192289177Speter                 svn_client_ctx_t *ctx,
1193289177Speter                 apr_pool_t *scratch_pool)
1194289177Speter{
1195289177Speter  SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool));
1196289177Speter
1197289177Speter  if (op->children && op->children->nelts > 0)
1198289177Speter    {
1199289177Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1200289177Speter      int i;
1201289177Speter
1202289177Speter      for (i = 0; i < op->children->nelts; i++)
1203289177Speter        {
1204289177Speter          mtcc_op_t *cop;
1205289177Speter          const char * child_relpath;
1206289177Speter          void *child_baton;
1207289177Speter
1208289177Speter          cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1209289177Speter
1210289177Speter          svn_pool_clear(iterpool);
1211289177Speter
1212289177Speter          if (ctx->cancel_func)
1213289177Speter            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1214289177Speter
1215289177Speter          child_relpath = svn_relpath_join(relpath, cop->name, iterpool);
1216289177Speter
1217289177Speter          switch (cop->kind)
1218289177Speter            {
1219289177Speter              case OP_DELETE:
1220289177Speter                SVN_ERR(editor->delete_entry(child_relpath, base_rev,
1221289177Speter                                             dir_baton, iterpool));
1222289177Speter                break;
1223289177Speter
1224289177Speter              case OP_ADD_DIR:
1225289177Speter                SVN_ERR(editor->add_directory(child_relpath, dir_baton,
1226289177Speter                                              cop->src_relpath
1227289177Speter                                                ? svn_path_url_add_component2(
1228289177Speter                                                              session_url,
1229289177Speter                                                              cop->src_relpath,
1230289177Speter                                                              iterpool)
1231289177Speter                                                : NULL,
1232289177Speter                                              cop->src_rev,
1233289177Speter                                              iterpool, &child_baton));
1234289177Speter                SVN_ERR(commit_directory(editor, cop, child_relpath,
1235289177Speter                                         SVN_INVALID_REVNUM, child_baton,
1236289177Speter                                         session_url, ctx, iterpool));
1237289177Speter                break;
1238289177Speter              case OP_OPEN_DIR:
1239289177Speter                SVN_ERR(editor->open_directory(child_relpath, dir_baton,
1240289177Speter                                               base_rev, iterpool, &child_baton));
1241289177Speter                SVN_ERR(commit_directory(editor, cop, child_relpath,
1242289177Speter                                         base_rev, child_baton,
1243289177Speter                                         session_url, ctx, iterpool));
1244289177Speter                break;
1245289177Speter
1246289177Speter              case OP_ADD_FILE:
1247289177Speter                SVN_ERR(editor->add_file(child_relpath, dir_baton,
1248289177Speter                                         cop->src_relpath
1249289177Speter                                            ? svn_path_url_add_component2(
1250289177Speter                                                            session_url,
1251289177Speter                                                            cop->src_relpath,
1252289177Speter                                                            iterpool)
1253289177Speter                                            : NULL,
1254289177Speter                                         cop->src_rev,
1255289177Speter                                         iterpool, &child_baton));
1256289177Speter                SVN_ERR(commit_file(editor, cop, child_baton,
1257289177Speter                                    session_url, child_relpath, ctx, iterpool));
1258289177Speter                break;
1259289177Speter              case OP_OPEN_FILE:
1260289177Speter                SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev,
1261289177Speter                                          iterpool, &child_baton));
1262289177Speter                SVN_ERR(commit_file(editor, cop, child_baton,
1263289177Speter                                    session_url, child_relpath, ctx, iterpool));
1264289177Speter                break;
1265289177Speter
1266289177Speter              default:
1267289177Speter                SVN_ERR_MALFUNCTION();
1268289177Speter            }
1269289177Speter        }
1270289177Speter    }
1271289177Speter
1272289177Speter  return svn_error_trace(editor->close_directory(dir_baton, scratch_pool));
1273289177Speter}
1274289177Speter
1275289177Speter
1276289177Speter/* Helper function to recursively create svn_client_commit_item3_t items
1277289177Speter   to provide to the log message callback */
1278289177Speterstatic svn_error_t *
1279289177Speteradd_commit_items(mtcc_op_t *op,
1280289177Speter                 const char *session_url,
1281289177Speter                 const char *url,
1282289177Speter                 apr_array_header_t *commit_items,
1283289177Speter                 apr_pool_t *result_pool,
1284289177Speter                 apr_pool_t *scratch_pool)
1285289177Speter{
1286289177Speter  if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE)
1287289177Speter      || (op->prop_mods && op->prop_mods->nelts)
1288289177Speter      || (op->src_stream))
1289289177Speter    {
1290289177Speter      svn_client_commit_item3_t *item;
1291289177Speter
1292289177Speter      item = svn_client_commit_item3_create(result_pool);
1293289177Speter
1294289177Speter      item->path = NULL;
1295289177Speter      if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR)
1296289177Speter        item->kind = svn_node_dir;
1297289177Speter      else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE)
1298289177Speter        item->kind = svn_node_file;
1299289177Speter      else
1300289177Speter        item->kind = svn_node_unknown;
1301289177Speter
1302289177Speter      item->url = apr_pstrdup(result_pool, url);
1303289177Speter      item->session_relpath = svn_uri_skip_ancestor(session_url, item->url,
1304289177Speter                                                    result_pool);
1305289177Speter
1306289177Speter      if (op->src_relpath)
1307289177Speter        {
1308289177Speter          item->copyfrom_url = svn_path_url_add_component2(session_url,
1309289177Speter                                                           op->src_relpath,
1310289177Speter                                                           result_pool);
1311289177Speter          item->copyfrom_rev = op->src_rev;
1312289177Speter          item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1313289177Speter        }
1314289177Speter      else
1315289177Speter        item->copyfrom_rev = SVN_INVALID_REVNUM;
1316289177Speter
1317289177Speter      if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE)
1318289177Speter        item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1319289177Speter      else if (op->kind == OP_DELETE)
1320289177Speter        item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1321289177Speter      /* else item->state_flags = 0; */
1322289177Speter
1323289177Speter      if (op->prop_mods && op->prop_mods->nelts)
1324289177Speter        item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1325289177Speter
1326289177Speter      if (op->src_stream)
1327289177Speter        item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
1328289177Speter
1329289177Speter      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1330289177Speter    }
1331289177Speter
1332289177Speter  if (op->children && op->children->nelts)
1333289177Speter    {
1334289177Speter      int i;
1335289177Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1336289177Speter
1337289177Speter      for (i = 0; i < op->children->nelts; i++)
1338289177Speter        {
1339289177Speter          mtcc_op_t *cop;
1340289177Speter          const char * child_url;
1341289177Speter
1342289177Speter          cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *);
1343289177Speter
1344289177Speter          svn_pool_clear(iterpool);
1345289177Speter
1346289177Speter          child_url = svn_path_url_add_component2(url, cop->name, iterpool);
1347289177Speter
1348289177Speter          SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items,
1349289177Speter                                   result_pool, iterpool));
1350289177Speter        }
1351289177Speter
1352289177Speter      svn_pool_destroy(iterpool);
1353289177Speter    }
1354289177Speter
1355289177Speter  return SVN_NO_ERROR;
1356289177Speter}
1357289177Speter
1358289177Spetersvn_error_t *
1359289177Spetersvn_client__mtcc_commit(apr_hash_t *revprop_table,
1360289177Speter                        svn_commit_callback2_t commit_callback,
1361289177Speter                        void *commit_baton,
1362289177Speter                        svn_client__mtcc_t *mtcc,
1363289177Speter                        apr_pool_t *scratch_pool)
1364289177Speter{
1365289177Speter  const svn_delta_editor_t *editor;
1366289177Speter  void *edit_baton;
1367289177Speter  void *root_baton;
1368289177Speter  apr_hash_t *commit_revprops;
1369289177Speter  svn_node_kind_t kind;
1370289177Speter  svn_error_t *err;
1371289177Speter  const char *session_url;
1372289177Speter  const char *log_msg;
1373289177Speter
1374289177Speter  if (MTCC_UNMODIFIED(mtcc))
1375289177Speter    {
1376289177Speter      /* No changes -> no revision. Easy out */
1377289177Speter      svn_pool_destroy(mtcc->pool);
1378289177Speter      return SVN_NO_ERROR;
1379289177Speter    }
1380289177Speter
1381289177Speter  SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool));
1382289177Speter
1383289177Speter  if (mtcc->root_op->kind != OP_OPEN_DIR)
1384289177Speter    {
1385289177Speter      const char *name;
1386289177Speter
1387289177Speter      svn_uri_split(&session_url, &name, session_url, scratch_pool);
1388289177Speter
1389289177Speter      if (*name)
1390289177Speter        {
1391289177Speter          SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool));
1392289177Speter
1393289177Speter          SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool));
1394289177Speter        }
1395289177Speter    }
1396289177Speter
1397289177Speter    /* Create new commit items and add them to the array. */
1398289177Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx))
1399289177Speter    {
1400289177Speter      svn_client_commit_item3_t *item;
1401289177Speter      const char *tmp_file;
1402289177Speter      apr_array_header_t *commit_items
1403289177Speter                = apr_array_make(scratch_pool, 32, sizeof(item));
1404289177Speter
1405289177Speter      SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url,
1406289177Speter                               commit_items, scratch_pool, scratch_pool));
1407289177Speter
1408289177Speter      SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1409289177Speter                                      mtcc->ctx, scratch_pool));
1410289177Speter
1411289177Speter      if (! log_msg)
1412289177Speter        return SVN_NO_ERROR;
1413289177Speter    }
1414289177Speter  else
1415289177Speter    log_msg = "";
1416289177Speter
1417289177Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1418289177Speter                                           log_msg, mtcc->ctx, scratch_pool));
1419289177Speter
1420289177Speter  /* Ugly corner case: The ra session might have died while we were waiting
1421289177Speter     for the callback */
1422289177Speter
1423289177Speter  err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind,
1424289177Speter                          scratch_pool);
1425289177Speter
1426289177Speter  if (err)
1427289177Speter    {
1428289177Speter      svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session,
1429289177Speter                                                      session_url,
1430289177Speter                                                      NULL, mtcc->ctx,
1431289177Speter                                                      mtcc->pool,
1432289177Speter                                                      scratch_pool);
1433289177Speter
1434289177Speter      if (err2)
1435289177Speter        {
1436289177Speter          svn_pool_destroy(mtcc->pool);
1437289177Speter          return svn_error_trace(svn_error_compose_create(err, err2));
1438289177Speter        }
1439289177Speter      svn_error_clear(err);
1440289177Speter
1441289177Speter      SVN_ERR(svn_ra_check_path(mtcc->ra_session, "",
1442289177Speter                                mtcc->base_revision, &kind, scratch_pool));
1443289177Speter    }
1444289177Speter
1445289177Speter  if (kind != svn_node_dir)
1446289177Speter    return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1447289177Speter                             _("Can't commit to '%s' because it "
1448289177Speter                               "is not a directory"),
1449289177Speter                             session_url);
1450289177Speter
1451289177Speter  /* Beware that the editor object must not live longer than the MTCC.
1452289177Speter     Otherwise, txn objects etc. in EDITOR may live longer than their
1453289177Speter     respective FS objects.  So, we can't use SCRATCH_POOL here. */
1454289177Speter  SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton,
1455289177Speter                                    commit_revprops,
1456289177Speter                                    commit_callback, commit_baton,
1457289177Speter                                    NULL /* lock_tokens */,
1458289177Speter                                    FALSE /* keep_locks */,
1459289177Speter                                    mtcc->pool));
1460289177Speter
1461289177Speter  err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton);
1462289177Speter
1463289177Speter  if (!err)
1464289177Speter    err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision,
1465289177Speter                           root_baton, session_url, mtcc->ctx, scratch_pool);
1466289177Speter
1467289177Speter  if (!err)
1468289177Speter    {
1469289177Speter      if (mtcc->ctx->notify_func2)
1470289177Speter        {
1471289177Speter          svn_wc_notify_t *notify;
1472289177Speter          notify = svn_wc_create_notify_url(session_url,
1473289177Speter                                            svn_wc_notify_commit_finalizing,
1474289177Speter                                            scratch_pool);
1475289177Speter          mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify,
1476289177Speter                                  scratch_pool);
1477289177Speter        }
1478289177Speter      SVN_ERR(editor->close_edit(edit_baton, scratch_pool));
1479289177Speter    }
1480289177Speter  else
1481289177Speter    err = svn_error_compose_create(err,
1482289177Speter                                   editor->abort_edit(edit_baton, scratch_pool));
1483289177Speter
1484289177Speter  svn_pool_destroy(mtcc->pool);
1485289177Speter
1486289177Speter  return svn_error_trace(err);
1487289177Speter}
1488