1d25dac7peter/*
2d25dac7peter * target.c:  functions which operate on a list of targets supplied to
3d25dac7peter *              a subversion subcommand.
4d25dac7peter *
5d25dac7peter * ====================================================================
6d25dac7peter *    Licensed to the Apache Software Foundation (ASF) under one
7d25dac7peter *    or more contributor license agreements.  See the NOTICE file
8d25dac7peter *    distributed with this work for additional information
9d25dac7peter *    regarding copyright ownership.  The ASF licenses this file
10d25dac7peter *    to you under the Apache License, Version 2.0 (the
11d25dac7peter *    "License"); you may not use this file except in compliance
12d25dac7peter *    with the License.  You may obtain a copy of the License at
13d25dac7peter *
14d25dac7peter *      http://www.apache.org/licenses/LICENSE-2.0
15d25dac7peter *
16d25dac7peter *    Unless required by applicable law or agreed to in writing,
17d25dac7peter *    software distributed under the License is distributed on an
18d25dac7peter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19d25dac7peter *    KIND, either express or implied.  See the License for the
20d25dac7peter *    specific language governing permissions and limitations
21d25dac7peter *    under the License.
22d25dac7peter * ====================================================================
23d25dac7peter */
24d25dac7peter
25d25dac7peter/* ==================================================================== */
26d25dac7peter
27d25dac7peter
28d25dac7peter
29d25dac7peter/*** Includes. ***/
30d25dac7peter
31d25dac7peter#include "svn_pools.h"
32d25dac7peter#include "svn_error.h"
33d25dac7peter#include "svn_dirent_uri.h"
34d25dac7peter#include "svn_path.h"
35d25dac7peter
36d25dac7peter
37d25dac7peter/*** Code. ***/
38d25dac7peter
39d25dac7petersvn_error_t *
40d25dac7petersvn_path_condense_targets(const char **pcommon,
41d25dac7peter                          apr_array_header_t **pcondensed_targets,
42d25dac7peter                          const apr_array_header_t *targets,
43d25dac7peter                          svn_boolean_t remove_redundancies,
44d25dac7peter                          apr_pool_t *pool)
45d25dac7peter{
46d25dac7peter  int i, j, num_condensed = targets->nelts;
47d25dac7peter  svn_boolean_t *removed;
48d25dac7peter  apr_array_header_t *abs_targets;
49d25dac7peter  size_t basedir_len;
50d25dac7peter  const char *first_target;
51d25dac7peter  svn_boolean_t first_target_is_url;
52d25dac7peter
53d25dac7peter  /* Early exit when there's no data to work on. */
54d25dac7peter  if (targets->nelts <= 0)
55d25dac7peter    {
56d25dac7peter      *pcommon = NULL;
57d25dac7peter      if (pcondensed_targets)
58d25dac7peter        *pcondensed_targets = NULL;
59d25dac7peter      return SVN_NO_ERROR;
60d25dac7peter    }
61d25dac7peter
62d25dac7peter  /* Get the absolute path of the first target. */
63d25dac7peter  first_target = APR_ARRAY_IDX(targets, 0, const char *);
64d25dac7peter  first_target_is_url = svn_path_is_url(first_target);
65d25dac7peter  if (first_target_is_url)
66d25dac7peter    {
67d25dac7peter      first_target = apr_pstrdup(pool, first_target);
68d25dac7peter      *pcommon = first_target;
69d25dac7peter    }
70d25dac7peter  else
71d25dac7peter    SVN_ERR(svn_dirent_get_absolute(pcommon, first_target, pool));
72d25dac7peter
73d25dac7peter  /* Early exit when there's only one path to work on. */
74d25dac7peter  if (targets->nelts == 1)
75d25dac7peter    {
76d25dac7peter      if (pcondensed_targets)
77d25dac7peter        *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *));
78d25dac7peter      return SVN_NO_ERROR;
79d25dac7peter    }
80d25dac7peter
81d25dac7peter  /* Copy the targets array, but with absolute paths instead of
82d25dac7peter     relative.  Also, find the pcommon argument by finding what is
83d25dac7peter     common in all of the absolute paths. NOTE: This is not as
84d25dac7peter     efficient as it could be.  The calculation of the basedir could
85d25dac7peter     be done in the loop below, which would save some calls to
86d25dac7peter     svn_path_get_longest_ancestor.  I decided to do it this way
87d25dac7peter     because I thought it would be simpler, since this way, we don't
88d25dac7peter     even do the loop if we don't need to condense the targets. */
89d25dac7peter
90d25dac7peter  removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t)));
91d25dac7peter  abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
92d25dac7peter
93d25dac7peter  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
94d25dac7peter
95d25dac7peter  for (i = 1; i < targets->nelts; ++i)
96d25dac7peter    {
97d25dac7peter      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
98d25dac7peter      const char *absolute;
99d25dac7peter      svn_boolean_t is_url = svn_path_is_url(rel);
100d25dac7peter
101d25dac7peter      if (is_url)
102d25dac7peter        absolute = apr_pstrdup(pool, rel); /* ### TODO: avoid pool dup? */
103d25dac7peter      else
104d25dac7peter        SVN_ERR(svn_dirent_get_absolute(&absolute, rel, pool));
105d25dac7peter
106d25dac7peter      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
107d25dac7peter
108d25dac7peter      /* If we've not already determined that there's no common
109d25dac7peter         parent, then continue trying to do so. */
110d25dac7peter      if (*pcommon && **pcommon)
111d25dac7peter        {
112d25dac7peter          /* If the is-url-ness of this target doesn't match that of
113d25dac7peter             the first target, our search for a common ancestor can
114d25dac7peter             end right here.  Otherwise, use the appropriate
115d25dac7peter             get-longest-ancestor function per the path type. */
116d25dac7peter          if (is_url != first_target_is_url)
117d25dac7peter            *pcommon = "";
118d25dac7peter          else if (first_target_is_url)
119d25dac7peter            *pcommon = svn_uri_get_longest_ancestor(*pcommon, absolute, pool);
120d25dac7peter          else
121d25dac7peter            *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
122d25dac7peter                                                       pool);
123d25dac7peter        }
124d25dac7peter    }
125d25dac7peter
126d25dac7peter  if (pcondensed_targets != NULL)
127d25dac7peter    {
128d25dac7peter      if (remove_redundancies)
129d25dac7peter        {
130d25dac7peter          /* Find the common part of each pair of targets.  If
131d25dac7peter             common part is equal to one of the paths, the other
132d25dac7peter             is a child of it, and can be removed.  If a target is
133d25dac7peter             equal to *pcommon, it can also be removed. */
134d25dac7peter
135d25dac7peter          /* First pass: when one non-removed target is a child of
136d25dac7peter             another non-removed target, remove the child. */
137d25dac7peter          for (i = 0; i < abs_targets->nelts; ++i)
138d25dac7peter            {
139d25dac7peter              if (removed[i])
140d25dac7peter                continue;
141d25dac7peter
142d25dac7peter              for (j = i + 1; j < abs_targets->nelts; ++j)
143d25dac7peter                {
144d25dac7peter                  const char *abs_targets_i;
145d25dac7peter                  const char *abs_targets_j;
146d25dac7peter                  svn_boolean_t i_is_url, j_is_url;
147d25dac7peter                  const char *ancestor;
148d25dac7peter
149d25dac7peter                  if (removed[j])
150d25dac7peter                    continue;
151d25dac7peter
152d25dac7peter                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
153d25dac7peter                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
154d25dac7peter                  i_is_url = svn_path_is_url(abs_targets_i);
155d25dac7peter                  j_is_url = svn_path_is_url(abs_targets_j);
156d25dac7peter
157d25dac7peter                  if (i_is_url != j_is_url)
158d25dac7peter                    continue;
159d25dac7peter
160d25dac7peter                  if (i_is_url)
161d25dac7peter                    ancestor = svn_uri_get_longest_ancestor(abs_targets_i,
162d25dac7peter                                                            abs_targets_j,
163d25dac7peter                                                            pool);
164d25dac7peter                  else
165d25dac7peter                    ancestor = svn_dirent_get_longest_ancestor(abs_targets_i,
166d25dac7peter                                                               abs_targets_j,
167d25dac7peter                                                               pool);
168d25dac7peter
169d25dac7peter                  if (*ancestor == '\0')
170d25dac7peter                    continue;
171d25dac7peter
172d25dac7peter                  if (strcmp(ancestor, abs_targets_i) == 0)
173d25dac7peter                    {
174d25dac7peter                      removed[j] = TRUE;
175d25dac7peter                      num_condensed--;
176d25dac7peter                    }
177d25dac7peter                  else if (strcmp(ancestor, abs_targets_j) == 0)
178d25dac7peter                    {
179d25dac7peter                      removed[i] = TRUE;
180d25dac7peter                      num_condensed--;
181d25dac7peter                    }
182d25dac7peter                }
183d25dac7peter            }
184d25dac7peter
185d25dac7peter          /* Second pass: when a target is the same as *pcommon,
186d25dac7peter             remove the target. */
187d25dac7peter          for (i = 0; i < abs_targets->nelts; ++i)
188d25dac7peter            {
189d25dac7peter              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
190d25dac7peter                                                        const char *);
191d25dac7peter
192d25dac7peter              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
193d25dac7peter                {
194d25dac7peter                  removed[i] = TRUE;
195d25dac7peter                  num_condensed--;
196d25dac7peter                }
197d25dac7peter            }
198d25dac7peter        }
199d25dac7peter
200d25dac7peter      /* Now create the return array, and copy the non-removed items */
201d25dac7peter      basedir_len = strlen(*pcommon);
202d25dac7peter      *pcondensed_targets = apr_array_make(pool, num_condensed,
203d25dac7peter                                           sizeof(const char *));
204d25dac7peter
205d25dac7peter      for (i = 0; i < abs_targets->nelts; ++i)
206d25dac7peter        {
207d25dac7peter          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
208d25dac7peter
209d25dac7peter          /* Skip this if it's been removed. */
210d25dac7peter          if (removed[i])
211d25dac7peter            continue;
212d25dac7peter
213d25dac7peter          /* If a common prefix was found, condensed_targets are given
214d25dac7peter             relative to that prefix.  */
215d25dac7peter          if (basedir_len > 0)
216d25dac7peter            {
217d25dac7peter              /* Only advance our pointer past a path separator if
218d25dac7peter                 REL_ITEM isn't the same as *PCOMMON.
219d25dac7peter
220d25dac7peter                 If *PCOMMON is a root path, basedir_len will already
221d25dac7peter                 include the closing '/', so never advance the pointer
222d25dac7peter                 here.
223d25dac7peter                 */
224d25dac7peter              rel_item += basedir_len;
225d25dac7peter              if (rel_item[0] &&
226d25dac7peter                  ! svn_dirent_is_root(*pcommon, basedir_len))
227d25dac7peter                rel_item++;
228d25dac7peter            }
229d25dac7peter
230d25dac7peter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
231d25dac7peter            = apr_pstrdup(pool, rel_item);
232d25dac7peter        }
233d25dac7peter    }
234d25dac7peter
235d25dac7peter  return SVN_NO_ERROR;
236d25dac7peter}
237d25dac7peter
238d25dac7peter
239d25dac7petersvn_error_t *
240d25dac7petersvn_path_remove_redundancies(apr_array_header_t **pcondensed_targets,
241d25dac7peter                             const apr_array_header_t *targets,
242d25dac7peter                             apr_pool_t *pool)
243d25dac7peter{
244d25dac7peter  apr_pool_t *temp_pool;
245d25dac7peter  apr_array_header_t *abs_targets;
246d25dac7peter  apr_array_header_t *rel_targets;
247d25dac7peter  int i;
248d25dac7peter
249d25dac7peter  if ((targets->nelts <= 0) || (! pcondensed_targets))
250d25dac7peter    {
251d25dac7peter      /* No targets or no place to store our work means this function
252d25dac7peter         really has nothing to do. */
253d25dac7peter      if (pcondensed_targets)
254d25dac7peter        *pcondensed_targets = NULL;
255d25dac7peter      return SVN_NO_ERROR;
256d25dac7peter    }
257d25dac7peter
258d25dac7peter  /* Initialize our temporary pool. */
259d25dac7peter  temp_pool = svn_pool_create(pool);
260d25dac7peter
261d25dac7peter  /* Create our list of absolute paths for our "keepers" */
262d25dac7peter  abs_targets = apr_array_make(temp_pool, targets->nelts,
263d25dac7peter                               sizeof(const char *));
264d25dac7peter
265d25dac7peter  /* Create our list of untainted paths for our "keepers" */
266d25dac7peter  rel_targets = apr_array_make(pool, targets->nelts,
267d25dac7peter                               sizeof(const char *));
268d25dac7peter
269d25dac7peter  /* For each target in our list we do the following:
270d25dac7peter
271d25dac7peter     1.  Calculate its absolute path (ABS_PATH).
272d25dac7peter     2.  See if any of the keepers in ABS_TARGETS is a parent of, or
273d25dac7peter         is the same path as, ABS_PATH.  If so, we ignore this
274d25dac7peter         target.  If not, however, add this target's absolute path to
275d25dac7peter         ABS_TARGETS and its original path to REL_TARGETS.
276d25dac7peter  */
277d25dac7peter  for (i = 0; i < targets->nelts; i++)
278d25dac7peter    {
279d25dac7peter      const char *rel_path = APR_ARRAY_IDX(targets, i, const char *);
280d25dac7peter      const char *abs_path;
281d25dac7peter      int j;
282d25dac7peter      svn_boolean_t is_url, keep_me;
283d25dac7peter
284d25dac7peter      /* Get the absolute path for this target. */
285d25dac7peter      is_url = svn_path_is_url(rel_path);
286d25dac7peter      if (is_url)
287d25dac7peter        abs_path = rel_path;
288d25dac7peter      else
289d25dac7peter        SVN_ERR(svn_dirent_get_absolute(&abs_path, rel_path, temp_pool));
290d25dac7peter
291d25dac7peter      /* For each keeper in ABS_TARGETS, see if this target is the
292d25dac7peter         same as or a child of that keeper. */
293d25dac7peter      keep_me = TRUE;
294d25dac7peter      for (j = 0; j < abs_targets->nelts; j++)
295d25dac7peter        {
296d25dac7peter          const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *);
297d25dac7peter          svn_boolean_t keeper_is_url = svn_path_is_url(keeper);
298d25dac7peter          const char *child_relpath;
299d25dac7peter
300d25dac7peter          /* If KEEPER hasn't the same is-url-ness as ABS_PATH, we
301d25dac7peter             know they aren't equal and that one isn't the child of
302d25dac7peter             the other. */
303d25dac7peter          if (is_url != keeper_is_url)
304d25dac7peter            continue;
305d25dac7peter
306d25dac7peter          /* Quit here if this path is the same as or a child of one of the
307d25dac7peter             keepers. */
308d25dac7peter          if (is_url)
309d25dac7peter            child_relpath = svn_uri_skip_ancestor(keeper, abs_path, temp_pool);
310d25dac7peter          else
311d25dac7peter            child_relpath = svn_dirent_skip_ancestor(keeper, abs_path);
312d25dac7peter          if (child_relpath)
313d25dac7peter            {
314d25dac7peter              keep_me = FALSE;
315d25dac7peter              break;
316d25dac7peter            }
317d25dac7peter        }
318d25dac7peter
319d25dac7peter      /* If this is a new keeper, add its absolute path to ABS_TARGETS
320d25dac7peter         and its original path to REL_TARGETS. */
321d25dac7peter      if (keep_me)
322d25dac7peter        {
323d25dac7peter          APR_ARRAY_PUSH(abs_targets, const char *) = abs_path;
324d25dac7peter          APR_ARRAY_PUSH(rel_targets, const char *) = rel_path;
325d25dac7peter        }
326d25dac7peter    }
327d25dac7peter
328d25dac7peter  /* Destroy our temporary pool. */
329d25dac7peter  svn_pool_destroy(temp_pool);
330d25dac7peter
331d25dac7peter  /* Make sure we return the list of untainted keeper paths. */
332d25dac7peter  *pcondensed_targets = rel_targets;
333d25dac7peter
334d25dac7peter  return SVN_NO_ERROR;
335d25dac7peter}
336