summaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/target.c
blob: 10b737a85bcf27621e25b73be4d95512d0ec39d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
 * target.c:  functions which operate on a list of targets supplied to
 *              a subversion subcommand.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

/* ==================================================================== */



/*** Includes. ***/

#include "svn_pools.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"


/*** Code. ***/

svn_error_t *
svn_path_condense_targets(const char **pcommon,
                          apr_array_header_t **pcondensed_targets,
                          const apr_array_header_t *targets,
                          svn_boolean_t remove_redundancies,
                          apr_pool_t *pool)
{
  int i, j, num_condensed = targets->nelts;
  svn_boolean_t *removed;
  apr_array_header_t *abs_targets;
  int basedir_len;
  const char *first_target;
  svn_boolean_t first_target_is_url;

  /* Early exit when there's no data to work on. */
  if (targets->nelts <= 0)
    {
      *pcommon = NULL;
      if (pcondensed_targets)
        *pcondensed_targets = NULL;
      return SVN_NO_ERROR;
    }

  /* Get the absolute path of the first target. */
  first_target = APR_ARRAY_IDX(targets, 0, const char *);
  first_target_is_url = svn_path_is_url(first_target);
  if (first_target_is_url)
    {
      first_target = apr_pstrdup(pool, first_target);
      *pcommon = first_target;
    }
  else
    SVN_ERR(svn_dirent_get_absolute(pcommon, first_target, pool));

  /* Early exit when there's only one path to work on. */
  if (targets->nelts == 1)
    {
      if (pcondensed_targets)
        *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *));
      return SVN_NO_ERROR;
    }

  /* Copy the targets array, but with absolute paths instead of
     relative.  Also, find the pcommon argument by finding what is
     common in all of the absolute paths. NOTE: This is not as
     efficient as it could be.  The calculation of the basedir could
     be done in the loop below, which would save some calls to
     svn_path_get_longest_ancestor.  I decided to do it this way
     because I thought it would be simpler, since this way, we don't
     even do the loop if we don't need to condense the targets. */

  removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t)));
  abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));

  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;

  for (i = 1; i < targets->nelts; ++i)
    {
      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
      const char *absolute;
      svn_boolean_t is_url = svn_path_is_url(rel);

      if (is_url)
        absolute = apr_pstrdup(pool, rel); /* ### TODO: avoid pool dup? */
      else
        SVN_ERR(svn_dirent_get_absolute(&absolute, rel, pool));

      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;

      /* If we've not already determined that there's no common
         parent, then continue trying to do so. */
      if (*pcommon && **pcommon)
        {
          /* If the is-url-ness of this target doesn't match that of
             the first target, our search for a common ancestor can
             end right here.  Otherwise, use the appropriate
             get-longest-ancestor function per the path type. */
          if (is_url != first_target_is_url)
            *pcommon = "";
          else if (first_target_is_url)
            *pcommon = svn_uri_get_longest_ancestor(*pcommon, absolute, pool);
          else
            *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
                                                       pool);
        }
    }

  if (pcondensed_targets != NULL)
    {
      if (remove_redundancies)
        {
          /* Find the common part of each pair of targets.  If
             common part is equal to one of the paths, the other
             is a child of it, and can be removed.  If a target is
             equal to *pcommon, it can also be removed. */

          /* First pass: when one non-removed target is a child of
             another non-removed target, remove the child. */
          for (i = 0; i < abs_targets->nelts; ++i)
            {
              if (removed[i])
                continue;

              for (j = i + 1; j < abs_targets->nelts; ++j)
                {
                  const char *abs_targets_i;
                  const char *abs_targets_j;
                  svn_boolean_t i_is_url, j_is_url;
                  const char *ancestor;

                  if (removed[j])
                    continue;

                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
                  i_is_url = svn_path_is_url(abs_targets_i);
                  j_is_url = svn_path_is_url(abs_targets_j);

                  if (i_is_url != j_is_url)
                    continue;

                  if (i_is_url)
                    ancestor = svn_uri_get_longest_ancestor(abs_targets_i,
                                                            abs_targets_j,
                                                            pool);
                  else
                    ancestor = svn_dirent_get_longest_ancestor(abs_targets_i,
                                                               abs_targets_j,
                                                               pool);

                  if (*ancestor == '\0')
                    continue;

                  if (strcmp(ancestor, abs_targets_i) == 0)
                    {
                      removed[j] = TRUE;
                      num_condensed--;
                    }
                  else if (strcmp(ancestor, abs_targets_j) == 0)
                    {
                      removed[i] = TRUE;
                      num_condensed--;
                    }
                }
            }

          /* Second pass: when a target is the same as *pcommon,
             remove the target. */
          for (i = 0; i < abs_targets->nelts; ++i)
            {
              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
                                                        const char *);

              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
                {
                  removed[i] = TRUE;
                  num_condensed--;
                }
            }
        }

      /* Now create the return array, and copy the non-removed items */
      basedir_len = strlen(*pcommon);
      *pcondensed_targets = apr_array_make(pool, num_condensed,
                                           sizeof(const char *));

      for (i = 0; i < abs_targets->nelts; ++i)
        {
          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);

          /* Skip this if it's been removed. */
          if (removed[i])
            continue;

          /* If a common prefix was found, condensed_targets are given
             relative to that prefix.  */
          if (basedir_len > 0)
            {
              /* Only advance our pointer past a path separator if
                 REL_ITEM isn't the same as *PCOMMON.

                 If *PCOMMON is a root path, basedir_len will already
                 include the closing '/', so never advance the pointer
                 here.
                 */
              rel_item += basedir_len;
              if (rel_item[0] &&
                  ! svn_dirent_is_root(*pcommon, basedir_len))
                rel_item++;
            }

          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
            = apr_pstrdup(pool, rel_item);
        }
    }

  return SVN_NO_ERROR;
}


svn_error_t *
svn_path_remove_redundancies(apr_array_header_t **pcondensed_targets,
                             const apr_array_header_t *targets,
                             apr_pool_t *pool)
{
  apr_pool_t *temp_pool;
  apr_array_header_t *abs_targets;
  apr_array_header_t *rel_targets;
  int i;

  if ((targets->nelts <= 0) || (! pcondensed_targets))
    {
      /* No targets or no place to store our work means this function
         really has nothing to do. */
      if (pcondensed_targets)
        *pcondensed_targets = NULL;
      return SVN_NO_ERROR;
    }

  /* Initialize our temporary pool. */
  temp_pool = svn_pool_create(pool);

  /* Create our list of absolute paths for our "keepers" */
  abs_targets = apr_array_make(temp_pool, targets->nelts,
                               sizeof(const char *));

  /* Create our list of untainted paths for our "keepers" */
  rel_targets = apr_array_make(pool, targets->nelts,
                               sizeof(const char *));

  /* For each target in our list we do the following:

     1.  Calculate its absolute path (ABS_PATH).
     2.  See if any of the keepers in ABS_TARGETS is a parent of, or
         is the same path as, ABS_PATH.  If so, we ignore this
         target.  If not, however, add this target's absolute path to
         ABS_TARGETS and its original path to REL_TARGETS.
  */
  for (i = 0; i < targets->nelts; i++)
    {
      const char *rel_path = APR_ARRAY_IDX(targets, i, const char *);
      const char *abs_path;
      int j;
      svn_boolean_t is_url, keep_me;

      /* Get the absolute path for this target. */
      is_url = svn_path_is_url(rel_path);
      if (is_url)
        abs_path = rel_path;
      else
        SVN_ERR(svn_dirent_get_absolute(&abs_path, rel_path, temp_pool));

      /* For each keeper in ABS_TARGETS, see if this target is the
         same as or a child of that keeper. */
      keep_me = TRUE;
      for (j = 0; j < abs_targets->nelts; j++)
        {
          const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *);
          svn_boolean_t keeper_is_url = svn_path_is_url(keeper);
          const char *child_relpath;

          /* If KEEPER hasn't the same is-url-ness as ABS_PATH, we
             know they aren't equal and that one isn't the child of
             the other. */
          if (is_url != keeper_is_url)
            continue;

          /* Quit here if we find this path already in the keepers. */
          if (strcmp(keeper, abs_path) == 0)
            {
              keep_me = FALSE;
              break;
            }

          /* Quit here if this path is a child of one of the keepers. */
          if (is_url)
            child_relpath = svn_uri__is_child(keeper, abs_path, temp_pool);
          else
            child_relpath = svn_dirent_is_child(keeper, abs_path, temp_pool);
          if (child_relpath)
            {
              keep_me = FALSE;
              break;
            }
        }

      /* If this is a new keeper, add its absolute path to ABS_TARGETS
         and its original path to REL_TARGETS. */
      if (keep_me)
        {
          APR_ARRAY_PUSH(abs_targets, const char *) = abs_path;
          APR_ARRAY_PUSH(rel_targets, const char *) = rel_path;
        }
    }

  /* Destroy our temporary pool. */
  svn_pool_destroy(temp_pool);

  /* Make sure we return the list of untainted keeper paths. */
  *pcondensed_targets = rel_targets;

  return SVN_NO_ERROR;
}