2 * list.c: list local and remote directory entries.
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
24 #include "svn_client.h"
26 #include "svn_pools.h"
28 #include "svn_sorts.h"
30 #include "svn_private_config.h"
32 /* Get the directory entries of DIR at REV (relative to the root of
33 RA_SESSION), getting at least the fields specified by DIRENT_FIELDS.
34 Use the cancellation function/baton of CTX to check for cancellation.
36 If DEPTH is svn_depth_empty, return immediately. If DEPTH is
37 svn_depth_files, invoke LIST_FUNC on the file entries with BATON;
38 if svn_depth_immediates, invoke it on file and directory entries;
39 if svn_depth_infinity, invoke it on file and directory entries and
40 recurse into the directory entries with the same depth.
42 LOCKS, if non-NULL, is a hash mapping const char * paths to svn_lock_t
43 objects and FS_PATH is the absolute filesystem path of the RA session.
44 Use POOL for temporary allocations.
47 get_dir_contents(apr_uint32_t dirent_fields
,
50 svn_ra_session_t
*ra_session
,
54 svn_client_ctx_t
*ctx
,
55 svn_client_list_func_t list_func
,
59 apr_hash_t
*tmpdirents
;
60 apr_pool_t
*iterpool
= svn_pool_create(pool
);
61 apr_array_header_t
*array
;
64 if (depth
== svn_depth_empty
)
67 /* Get the directory's entries, but not its props. */
68 SVN_ERR(svn_ra_get_dir2(ra_session
, &tmpdirents
, NULL
, NULL
,
69 dir
, rev
, dirent_fields
, pool
));
72 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
74 /* Sort the hash, so we can call the callback in a "deterministic" order. */
75 array
= svn_sort__hash(tmpdirents
, svn_sort_compare_items_lexically
, pool
);
76 for (i
= 0; i
< array
->nelts
; ++i
)
78 svn_sort__item_t
*item
= &APR_ARRAY_IDX(array
, i
, svn_sort__item_t
);
80 svn_dirent_t
*the_ent
= apr_hash_get(tmpdirents
, item
->key
, item
->klen
);
83 svn_pool_clear(iterpool
);
85 path
= svn_path_join(dir
, item
->key
, iterpool
);
89 const char *abs_path
= svn_path_join(fs_path
, path
, iterpool
);
90 lock
= apr_hash_get(locks
, abs_path
, APR_HASH_KEY_STRING
);
95 if (the_ent
->kind
== svn_node_file
96 || depth
== svn_depth_immediates
97 || depth
== svn_depth_infinity
)
98 SVN_ERR(list_func(baton
, path
, the_ent
, lock
, fs_path
, iterpool
));
100 if (depth
== svn_depth_infinity
&& the_ent
->kind
== svn_node_dir
)
101 SVN_ERR(get_dir_contents(dirent_fields
, path
, rev
,
102 ra_session
, locks
, fs_path
, depth
, ctx
,
103 list_func
, baton
, iterpool
));
106 svn_pool_destroy(iterpool
);
111 svn_client_list2(const char *path_or_url
,
112 const svn_opt_revision_t
*peg_revision
,
113 const svn_opt_revision_t
*revision
,
115 apr_uint32_t dirent_fields
,
116 svn_boolean_t fetch_locks
,
117 svn_client_list_func_t list_func
,
119 svn_client_ctx_t
*ctx
,
122 svn_ra_session_t
*ra_session
;
124 svn_dirent_t
*dirent
;
126 const char *repos_root
;
131 /* We use the kind field to determine if we should recurse, so we
133 dirent_fields
|= SVN_DIRENT_KIND
;
135 /* Get an RA plugin for this filesystem object. */
136 SVN_ERR(svn_client__ra_session_from_path(&ra_session
, &rev
,
137 &url
, path_or_url
, NULL
,
139 revision
, ctx
, pool
));
141 SVN_ERR(svn_ra_get_repos_root2(ra_session
, &repos_root
, pool
));
143 SVN_ERR(svn_client__path_relative_to_root(&fs_path
, url
, repos_root
,
144 TRUE
, ra_session
, NULL
, pool
));
146 err
= svn_ra_stat(ra_session
, "", rev
, &dirent
, pool
);
148 /* svnserve before 1.2 doesn't support the above, so fall back on
149 a less efficient method. */
150 if (err
&& err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
152 svn_node_kind_t kind
;
154 svn_error_clear(err
);
156 SVN_ERR(svn_ra_check_path(ra_session
, "", rev
, &kind
, pool
));
158 if (kind
!= svn_node_none
)
160 if (strcmp(url
, repos_root
) != 0)
162 svn_ra_session_t
*parent_session
;
163 apr_hash_t
*parent_ents
;
164 const char *parent_url
, *base_name
;
166 /* Open another session to the path's parent. This server
167 doesn't support svn_ra_reparent anyway, so don't try it. */
168 svn_path_split(url
, &parent_url
, &base_name
, pool
);
170 /* 'base_name' is now the last component of an URL, but we want
171 to use it as a plain file name. Therefore, we must URI-decode
173 base_name
= svn_path_uri_decode(base_name
, pool
);
175 SVN_ERR(svn_client__open_ra_session_internal(&parent_session
,
180 /* Get all parent's entries, no props. */
181 SVN_ERR(svn_ra_get_dir2(parent_session
, &parent_ents
, NULL
,
182 NULL
, "", rev
, dirent_fields
, pool
));
184 /* Get the relevant entry. */
185 dirent
= apr_hash_get(parent_ents
, base_name
,
186 APR_HASH_KEY_STRING
);
190 /* We can't get the directory entry for the repository root,
191 but we can still get the information we want.
192 The created-rev of the repository root must, by definition,
194 dirent
= apr_palloc(pool
, sizeof(*dirent
));
197 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
200 SVN_ERR(svn_ra_get_dir2(ra_session
, NULL
, NULL
, &props
,
201 "", rev
, 0 /* no dirent fields */,
203 dirent
->has_props
= (apr_hash_count(props
) != 0);
205 dirent
->created_rev
= rev
;
206 if (dirent_fields
& (SVN_DIRENT_TIME
| SVN_DIRENT_LAST_AUTHOR
))
211 SVN_ERR(svn_ra_rev_proplist(ra_session
, rev
, &props
,
213 val
= apr_hash_get(props
, SVN_PROP_REVISION_DATE
,
214 APR_HASH_KEY_STRING
);
216 SVN_ERR(svn_time_from_cstring(&dirent
->time
, val
->data
,
221 val
= apr_hash_get(props
, SVN_PROP_REVISION_AUTHOR
,
222 APR_HASH_KEY_STRING
);
223 dirent
->last_author
= val
? val
->data
: NULL
;
234 return svn_error_createf(SVN_ERR_FS_NOT_FOUND
, NULL
,
235 _("URL '%s' non-existent in that revision"),
238 /* Maybe get all locks under url. */
241 /* IMPORTANT: If locks are stored in a more temporary pool, we need
242 to fix store_dirent below to duplicate the locks. */
243 err
= svn_ra_get_locks(ra_session
, &locks
, "", pool
);
245 if (err
&& err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
247 svn_error_clear(err
);
256 /* Report the dirent for the target. */
257 SVN_ERR(list_func(baton
, "", dirent
, locks
258 ? (apr_hash_get(locks
, fs_path
,
259 APR_HASH_KEY_STRING
))
260 : NULL
, fs_path
, pool
));
262 if (dirent
->kind
== svn_node_dir
263 && (depth
== svn_depth_files
264 || depth
== svn_depth_immediates
265 || depth
== svn_depth_infinity
))
266 SVN_ERR(get_dir_contents(dirent_fields
, "", rev
, ra_session
, locks
,
267 fs_path
, depth
, ctx
, list_func
, baton
, pool
));
273 svn_client_list(const char *path_or_url
,
274 const svn_opt_revision_t
*peg_revision
,
275 const svn_opt_revision_t
*revision
,
276 svn_boolean_t recurse
,
277 apr_uint32_t dirent_fields
,
278 svn_boolean_t fetch_locks
,
279 svn_client_list_func_t list_func
,
281 svn_client_ctx_t
*ctx
,
284 return svn_client_list2(path_or_url
,
287 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse
),
296 /* Baton used by compatibility wrapper svn_client_ls3. */
303 /* This implements svn_client_list_func_t. */
305 store_dirent(void *baton
, const char *path
, const svn_dirent_t
*dirent
,
306 const svn_lock_t
*lock
, const char *abs_path
, apr_pool_t
*pool
)
308 struct ls_baton
*lb
= baton
;
310 /* The dirent is allocated in a temporary pool, so duplicate it into the
311 correct pool. Note, however, that the locks are stored in the correct
313 dirent
= svn_dirent_dup(dirent
, lb
->pool
);
315 /* An empty path means we are called for the target of the operation.
316 For compatibility, we only store the target if it is a file, and we
317 store it under the basename of the URL. Note that this makes it
318 impossible to differentiate between the target being a directory with a
319 child with the same basename as the target and the target being a file,
320 but that's how it was implemented. */
323 if (dirent
->kind
== svn_node_file
)
325 const char *base_name
= svn_path_basename(abs_path
, lb
->pool
);
326 apr_hash_set(lb
->dirents
, base_name
, APR_HASH_KEY_STRING
, dirent
);
328 apr_hash_set(lb
->locks
, base_name
, APR_HASH_KEY_STRING
, lock
);
333 path
= apr_pstrdup(lb
->pool
, path
);
334 apr_hash_set(lb
->dirents
, path
, APR_HASH_KEY_STRING
, dirent
);
336 apr_hash_set(lb
->locks
, path
, APR_HASH_KEY_STRING
, lock
);
343 svn_client_ls3(apr_hash_t
**dirents
,
345 const char *path_or_url
,
346 const svn_opt_revision_t
*peg_revision
,
347 const svn_opt_revision_t
*revision
,
348 svn_boolean_t recurse
,
349 svn_client_ctx_t
*ctx
,
354 *dirents
= lb
.dirents
= apr_hash_make(pool
);
356 *locks
= lb
.locks
= apr_hash_make(pool
);
359 return svn_client_list(path_or_url
, peg_revision
, revision
, recurse
,
360 SVN_DIRENT_ALL
, locks
!= NULL
,
361 store_dirent
, &lb
, ctx
, pool
);
365 svn_client_ls2(apr_hash_t
**dirents
,
366 const char *path_or_url
,
367 const svn_opt_revision_t
*peg_revision
,
368 const svn_opt_revision_t
*revision
,
369 svn_boolean_t recurse
,
370 svn_client_ctx_t
*ctx
,
374 return svn_client_ls3(dirents
, NULL
, path_or_url
, peg_revision
,
375 revision
, recurse
, ctx
, pool
);
380 svn_client_ls(apr_hash_t
**dirents
,
381 const char *path_or_url
,
382 svn_opt_revision_t
*revision
,
383 svn_boolean_t recurse
,
384 svn_client_ctx_t
*ctx
,
387 return svn_client_ls2(dirents
, path_or_url
, revision
,
388 revision
, recurse
, ctx
, pool
);