1251881Speter/*
2251881Speter * target.c:  functions which operate on a list of targets supplied to
3251881Speter *              a subversion subcommand.
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter/* ==================================================================== */
26251881Speter
27251881Speter
28251881Speter
29251881Speter/*** Includes. ***/
30251881Speter
31251881Speter#include "svn_pools.h"
32251881Speter#include "svn_error.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter#include "svn_path.h"
35251881Speter
36251881Speter
37251881Speter/*** Code. ***/
38251881Speter
39251881Spetersvn_error_t *
40251881Spetersvn_path_condense_targets(const char **pcommon,
41251881Speter                          apr_array_header_t **pcondensed_targets,
42251881Speter                          const apr_array_header_t *targets,
43251881Speter                          svn_boolean_t remove_redundancies,
44251881Speter                          apr_pool_t *pool)
45251881Speter{
46251881Speter  int i, j, num_condensed = targets->nelts;
47251881Speter  svn_boolean_t *removed;
48251881Speter  apr_array_header_t *abs_targets;
49251881Speter  size_t basedir_len;
50251881Speter  const char *first_target;
51251881Speter  svn_boolean_t first_target_is_url;
52251881Speter
53251881Speter  /* Early exit when there's no data to work on. */
54251881Speter  if (targets->nelts <= 0)
55251881Speter    {
56251881Speter      *pcommon = NULL;
57251881Speter      if (pcondensed_targets)
58251881Speter        *pcondensed_targets = NULL;
59251881Speter      return SVN_NO_ERROR;
60251881Speter    }
61251881Speter
62251881Speter  /* Get the absolute path of the first target. */
63251881Speter  first_target = APR_ARRAY_IDX(targets, 0, const char *);
64251881Speter  first_target_is_url = svn_path_is_url(first_target);
65251881Speter  if (first_target_is_url)
66251881Speter    {
67251881Speter      first_target = apr_pstrdup(pool, first_target);
68251881Speter      *pcommon = first_target;
69251881Speter    }
70251881Speter  else
71251881Speter    SVN_ERR(svn_dirent_get_absolute(pcommon, first_target, pool));
72251881Speter
73251881Speter  /* Early exit when there's only one path to work on. */
74251881Speter  if (targets->nelts == 1)
75251881Speter    {
76251881Speter      if (pcondensed_targets)
77251881Speter        *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *));
78251881Speter      return SVN_NO_ERROR;
79251881Speter    }
80251881Speter
81251881Speter  /* Copy the targets array, but with absolute paths instead of
82251881Speter     relative.  Also, find the pcommon argument by finding what is
83251881Speter     common in all of the absolute paths. NOTE: This is not as
84251881Speter     efficient as it could be.  The calculation of the basedir could
85251881Speter     be done in the loop below, which would save some calls to
86251881Speter     svn_path_get_longest_ancestor.  I decided to do it this way
87251881Speter     because I thought it would be simpler, since this way, we don't
88251881Speter     even do the loop if we don't need to condense the targets. */
89251881Speter
90251881Speter  removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t)));
91251881Speter  abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
92251881Speter
93251881Speter  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
94251881Speter
95251881Speter  for (i = 1; i < targets->nelts; ++i)
96251881Speter    {
97251881Speter      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
98251881Speter      const char *absolute;
99251881Speter      svn_boolean_t is_url = svn_path_is_url(rel);
100251881Speter
101251881Speter      if (is_url)
102251881Speter        absolute = apr_pstrdup(pool, rel); /* ### TODO: avoid pool dup? */
103251881Speter      else
104251881Speter        SVN_ERR(svn_dirent_get_absolute(&absolute, rel, pool));
105251881Speter
106251881Speter      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
107251881Speter
108251881Speter      /* If we've not already determined that there's no common
109251881Speter         parent, then continue trying to do so. */
110251881Speter      if (*pcommon && **pcommon)
111251881Speter        {
112251881Speter          /* If the is-url-ness of this target doesn't match that of
113251881Speter             the first target, our search for a common ancestor can
114251881Speter             end right here.  Otherwise, use the appropriate
115251881Speter             get-longest-ancestor function per the path type. */
116251881Speter          if (is_url != first_target_is_url)
117251881Speter            *pcommon = "";
118251881Speter          else if (first_target_is_url)
119251881Speter            *pcommon = svn_uri_get_longest_ancestor(*pcommon, absolute, pool);
120251881Speter          else
121251881Speter            *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
122251881Speter                                                       pool);
123251881Speter        }
124251881Speter    }
125251881Speter
126251881Speter  if (pcondensed_targets != NULL)
127251881Speter    {
128251881Speter      if (remove_redundancies)
129251881Speter        {
130251881Speter          /* Find the common part of each pair of targets.  If
131251881Speter             common part is equal to one of the paths, the other
132251881Speter             is a child of it, and can be removed.  If a target is
133251881Speter             equal to *pcommon, it can also be removed. */
134251881Speter
135251881Speter          /* First pass: when one non-removed target is a child of
136251881Speter             another non-removed target, remove the child. */
137251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
138251881Speter            {
139251881Speter              if (removed[i])
140251881Speter                continue;
141251881Speter
142251881Speter              for (j = i + 1; j < abs_targets->nelts; ++j)
143251881Speter                {
144251881Speter                  const char *abs_targets_i;
145251881Speter                  const char *abs_targets_j;
146251881Speter                  svn_boolean_t i_is_url, j_is_url;
147251881Speter                  const char *ancestor;
148251881Speter
149251881Speter                  if (removed[j])
150251881Speter                    continue;
151251881Speter
152251881Speter                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
153251881Speter                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
154251881Speter                  i_is_url = svn_path_is_url(abs_targets_i);
155251881Speter                  j_is_url = svn_path_is_url(abs_targets_j);
156251881Speter
157251881Speter                  if (i_is_url != j_is_url)
158251881Speter                    continue;
159251881Speter
160251881Speter                  if (i_is_url)
161251881Speter                    ancestor = svn_uri_get_longest_ancestor(abs_targets_i,
162251881Speter                                                            abs_targets_j,
163251881Speter                                                            pool);
164251881Speter                  else
165251881Speter                    ancestor = svn_dirent_get_longest_ancestor(abs_targets_i,
166251881Speter                                                               abs_targets_j,
167251881Speter                                                               pool);
168251881Speter
169251881Speter                  if (*ancestor == '\0')
170251881Speter                    continue;
171251881Speter
172251881Speter                  if (strcmp(ancestor, abs_targets_i) == 0)
173251881Speter                    {
174251881Speter                      removed[j] = TRUE;
175251881Speter                      num_condensed--;
176251881Speter                    }
177251881Speter                  else if (strcmp(ancestor, abs_targets_j) == 0)
178251881Speter                    {
179251881Speter                      removed[i] = TRUE;
180251881Speter                      num_condensed--;
181251881Speter                    }
182251881Speter                }
183251881Speter            }
184251881Speter
185251881Speter          /* Second pass: when a target is the same as *pcommon,
186251881Speter             remove the target. */
187251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
188251881Speter            {
189251881Speter              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
190251881Speter                                                        const char *);
191251881Speter
192251881Speter              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
193251881Speter                {
194251881Speter                  removed[i] = TRUE;
195251881Speter                  num_condensed--;
196251881Speter                }
197251881Speter            }
198251881Speter        }
199251881Speter
200251881Speter      /* Now create the return array, and copy the non-removed items */
201251881Speter      basedir_len = strlen(*pcommon);
202251881Speter      *pcondensed_targets = apr_array_make(pool, num_condensed,
203251881Speter                                           sizeof(const char *));
204251881Speter
205251881Speter      for (i = 0; i < abs_targets->nelts; ++i)
206251881Speter        {
207251881Speter          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
208251881Speter
209251881Speter          /* Skip this if it's been removed. */
210251881Speter          if (removed[i])
211251881Speter            continue;
212251881Speter
213251881Speter          /* If a common prefix was found, condensed_targets are given
214251881Speter             relative to that prefix.  */
215251881Speter          if (basedir_len > 0)
216251881Speter            {
217251881Speter              /* Only advance our pointer past a path separator if
218251881Speter                 REL_ITEM isn't the same as *PCOMMON.
219251881Speter
220251881Speter                 If *PCOMMON is a root path, basedir_len will already
221251881Speter                 include the closing '/', so never advance the pointer
222251881Speter                 here.
223251881Speter                 */
224251881Speter              rel_item += basedir_len;
225251881Speter              if (rel_item[0] &&
226251881Speter                  ! svn_dirent_is_root(*pcommon, basedir_len))
227251881Speter                rel_item++;
228251881Speter            }
229251881Speter
230251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
231251881Speter            = apr_pstrdup(pool, rel_item);
232251881Speter        }
233251881Speter    }
234251881Speter
235251881Speter  return SVN_NO_ERROR;
236251881Speter}
237251881Speter
238251881Speter
239251881Spetersvn_error_t *
240251881Spetersvn_path_remove_redundancies(apr_array_header_t **pcondensed_targets,
241251881Speter                             const apr_array_header_t *targets,
242251881Speter                             apr_pool_t *pool)
243251881Speter{
244251881Speter  apr_pool_t *temp_pool;
245251881Speter  apr_array_header_t *abs_targets;
246251881Speter  apr_array_header_t *rel_targets;
247251881Speter  int i;
248251881Speter
249251881Speter  if ((targets->nelts <= 0) || (! pcondensed_targets))
250251881Speter    {
251251881Speter      /* No targets or no place to store our work means this function
252251881Speter         really has nothing to do. */
253251881Speter      if (pcondensed_targets)
254251881Speter        *pcondensed_targets = NULL;
255251881Speter      return SVN_NO_ERROR;
256251881Speter    }
257251881Speter
258251881Speter  /* Initialize our temporary pool. */
259251881Speter  temp_pool = svn_pool_create(pool);
260251881Speter
261251881Speter  /* Create our list of absolute paths for our "keepers" */
262251881Speter  abs_targets = apr_array_make(temp_pool, targets->nelts,
263251881Speter                               sizeof(const char *));
264251881Speter
265251881Speter  /* Create our list of untainted paths for our "keepers" */
266251881Speter  rel_targets = apr_array_make(pool, targets->nelts,
267251881Speter                               sizeof(const char *));
268251881Speter
269251881Speter  /* For each target in our list we do the following:
270251881Speter
271251881Speter     1.  Calculate its absolute path (ABS_PATH).
272251881Speter     2.  See if any of the keepers in ABS_TARGETS is a parent of, or
273251881Speter         is the same path as, ABS_PATH.  If so, we ignore this
274251881Speter         target.  If not, however, add this target's absolute path to
275251881Speter         ABS_TARGETS and its original path to REL_TARGETS.
276251881Speter  */
277251881Speter  for (i = 0; i < targets->nelts; i++)
278251881Speter    {
279251881Speter      const char *rel_path = APR_ARRAY_IDX(targets, i, const char *);
280251881Speter      const char *abs_path;
281251881Speter      int j;
282251881Speter      svn_boolean_t is_url, keep_me;
283251881Speter
284251881Speter      /* Get the absolute path for this target. */
285251881Speter      is_url = svn_path_is_url(rel_path);
286251881Speter      if (is_url)
287251881Speter        abs_path = rel_path;
288251881Speter      else
289251881Speter        SVN_ERR(svn_dirent_get_absolute(&abs_path, rel_path, temp_pool));
290251881Speter
291251881Speter      /* For each keeper in ABS_TARGETS, see if this target is the
292251881Speter         same as or a child of that keeper. */
293251881Speter      keep_me = TRUE;
294251881Speter      for (j = 0; j < abs_targets->nelts; j++)
295251881Speter        {
296251881Speter          const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *);
297251881Speter          svn_boolean_t keeper_is_url = svn_path_is_url(keeper);
298251881Speter          const char *child_relpath;
299251881Speter
300251881Speter          /* If KEEPER hasn't the same is-url-ness as ABS_PATH, we
301251881Speter             know they aren't equal and that one isn't the child of
302251881Speter             the other. */
303251881Speter          if (is_url != keeper_is_url)
304251881Speter            continue;
305251881Speter
306251881Speter          /* Quit here if this path is the same as or a child of one of the
307251881Speter             keepers. */
308251881Speter          if (is_url)
309251881Speter            child_relpath = svn_uri_skip_ancestor(keeper, abs_path, temp_pool);
310251881Speter          else
311251881Speter            child_relpath = svn_dirent_skip_ancestor(keeper, abs_path);
312251881Speter          if (child_relpath)
313251881Speter            {
314251881Speter              keep_me = FALSE;
315251881Speter              break;
316251881Speter            }
317251881Speter        }
318251881Speter
319251881Speter      /* If this is a new keeper, add its absolute path to ABS_TARGETS
320251881Speter         and its original path to REL_TARGETS. */
321251881Speter      if (keep_me)
322251881Speter        {
323251881Speter          APR_ARRAY_PUSH(abs_targets, const char *) = abs_path;
324251881Speter          APR_ARRAY_PUSH(rel_targets, const char *) = rel_path;
325251881Speter        }
326251881Speter    }
327251881Speter
328251881Speter  /* Destroy our temporary pool. */
329251881Speter  svn_pool_destroy(temp_pool);
330251881Speter
331251881Speter  /* Make sure we return the list of untainted keeper paths. */
332251881Speter  *pcondensed_targets = rel_targets;
333251881Speter
334251881Speter  return SVN_NO_ERROR;
335251881Speter}
336