2 * status.c: return the status of a working copy dirent
4 * ====================================================================
5 * Copyright (c) 2000-2007 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 /* ==================================================================== */
25 #include <apr_strings.h>
26 #include <apr_pools.h>
28 #include "svn_pools.h"
32 #include "svn_delta.h"
33 #include "svn_client.h"
34 #include "svn_error.h"
37 #include "svn_private_config.h"
38 #include "private/svn_wc_private.h"
41 /*** Getting update information ***/
43 /* Baton for tweak_status. It wraps a bit of extra functionality
44 around the received status func/baton, so we can remember if the
45 target was deleted in HEAD and tweak incoming status structures
49 svn_boolean_t deleted_in_repos
; /* target is deleted in repos */
50 apr_hash_t
*changelist_hash
; /* keys are changelist names */
51 svn_wc_status_func2_t real_status_func
; /* real status function */
52 void *real_status_baton
; /* real status baton */
55 /* A status callback function which wraps the *real* status
56 function/baton. This sucker takes care of any status tweaks we
57 need to make (such as noting that the target of the status is
58 missing from HEAD in the repository).
60 This implements the 'svn_wc_status_func2_t' function type. */
62 tweak_status(void *baton
,
64 svn_wc_status2_t
*status
)
66 struct status_baton
*sb
= baton
;
68 /* If we know that the target was deleted in HEAD of the repository,
69 we need to note that fact in all the status structures that come
71 if (sb
->deleted_in_repos
)
72 status
->repos_text_status
= svn_wc_status_deleted
;
74 /* If the status item has an entry, but doesn't belong to one of the
75 changelists our caller is interested in, we filter our this status
77 if (! SVN_WC__CL_MATCH(sb
->changelist_hash
, status
->entry
))
80 /* Call the real status function/baton. */
81 sb
->real_status_func(sb
->real_status_baton
, path
, status
);
84 /* A baton for our reporter that is used to collect locks. */
85 typedef struct report_baton_t
{
86 const svn_ra_reporter3_t
* wrapped_reporter
;
87 void *wrapped_report_baton
;
88 /* The common ancestor URL of all paths included in the report. */
90 void *set_locks_baton
;
91 svn_client_ctx_t
*ctx
;
92 /* Pool to store locks in. */
96 /* Implements svn_ra_reporter3_t->set_path. */
98 reporter_set_path(void *report_baton
, const char *path
,
99 svn_revnum_t revision
, svn_depth_t depth
,
100 svn_boolean_t start_empty
, const char *lock_token
,
103 report_baton_t
*rb
= report_baton
;
105 return rb
->wrapped_reporter
->set_path(rb
->wrapped_report_baton
, path
,
106 revision
, depth
, start_empty
,
110 /* Implements svn_ra_reporter3_t->delete_path. */
112 reporter_delete_path(void *report_baton
, const char *path
, apr_pool_t
*pool
)
114 report_baton_t
*rb
= report_baton
;
116 return rb
->wrapped_reporter
->delete_path(rb
->wrapped_report_baton
, path
,
120 /* Implements svn_ra_reporter3_t->link_path. */
122 reporter_link_path(void *report_baton
, const char *path
, const char *url
,
123 svn_revnum_t revision
, svn_depth_t depth
,
124 svn_boolean_t start_empty
,
125 const char *lock_token
, apr_pool_t
*pool
)
127 report_baton_t
*rb
= report_baton
;
128 const char *ancestor
;
131 ancestor
= svn_path_get_longest_ancestor(url
, rb
->ancestor
, pool
);
133 /* If we got a shorter ancestor, truncate our current ancestor.
134 Note that svn_path_get_longest_ancestor will allocate its return
135 value even if it identical to one of its arguments. */
136 len
= strlen(ancestor
);
137 if (len
< strlen(rb
->ancestor
))
138 rb
->ancestor
[len
] = '\0';
140 return rb
->wrapped_reporter
->link_path(rb
->wrapped_report_baton
, path
, url
,
141 revision
, depth
, start_empty
,
145 /* Implements svn_ra_reporter3_t->finish_report. */
147 reporter_finish_report(void *report_baton
, apr_pool_t
*pool
)
149 report_baton_t
*rb
= report_baton
;
150 svn_ra_session_t
*ras
;
152 const char *repos_root
;
153 apr_pool_t
*subpool
= svn_pool_create(pool
);
154 svn_error_t
*err
= SVN_NO_ERROR
;
156 /* Open an RA session to our common ancestor and grab the locks under it.
158 SVN_ERR(svn_client__open_ra_session_internal(&ras
, rb
->ancestor
, NULL
,
159 NULL
, NULL
, FALSE
, TRUE
,
162 /* The locks need to live throughout the edit. Note that if the
163 server doesn't support lock discovery, we'll just not do locky
165 err
= svn_ra_get_locks(ras
, &locks
, "", rb
->pool
);
166 if (err
&& ((err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
167 || (err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)))
169 svn_error_clear(err
);
171 locks
= apr_hash_make(rb
->pool
);
175 SVN_ERR(svn_ra_get_repos_root2(ras
, &repos_root
, rb
->pool
));
177 /* Close the RA session. */
178 svn_pool_destroy(subpool
);
180 SVN_ERR(svn_wc_status_set_repos_locks(rb
->set_locks_baton
, locks
,
181 repos_root
, rb
->pool
));
183 return rb
->wrapped_reporter
->finish_report(rb
->wrapped_report_baton
, pool
);
186 /* Implements svn_ra_reporter3_t->abort_report. */
188 reporter_abort_report(void *report_baton
, apr_pool_t
*pool
)
190 report_baton_t
*rb
= report_baton
;
192 return rb
->wrapped_reporter
->abort_report(rb
->wrapped_report_baton
, pool
);
195 /* A reporter that keeps track of the common URL ancestor of all paths in
196 the WC and fetches repository locks for all paths under this ancestor. */
197 static svn_ra_reporter3_t lock_fetch_reporter
= {
199 reporter_delete_path
,
201 reporter_finish_report
,
202 reporter_abort_report
206 /*** Public Interface. ***/
210 svn_client_status3(svn_revnum_t
*result_rev
,
212 const svn_opt_revision_t
*revision
,
213 svn_wc_status_func2_t status_func
,
216 svn_boolean_t get_all
,
217 svn_boolean_t update
,
218 svn_boolean_t no_ignore
,
219 svn_boolean_t ignore_externals
,
220 const apr_array_header_t
*changelists
,
221 svn_client_ctx_t
*ctx
,
224 svn_wc_adm_access_t
*anchor_access
, *target_access
;
225 svn_wc_traversal_info_t
*traversal_info
= svn_wc_init_traversal_info(pool
);
226 const char *anchor
, *target
;
227 const svn_delta_editor_t
*editor
;
228 void *edit_baton
, *set_locks_baton
;
229 const svn_wc_entry_t
*entry
= NULL
;
230 struct status_baton sb
;
231 apr_array_header_t
*ignores
;
233 apr_hash_t
*changelist_hash
= NULL
;
234 svn_revnum_t edit_revision
= SVN_INVALID_REVNUM
;
236 if (changelists
&& changelists
->nelts
)
237 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash
, changelists
, pool
));
239 sb
.real_status_func
= status_func
;
240 sb
.real_status_baton
= status_baton
;
241 sb
.deleted_in_repos
= FALSE
;
242 sb
.changelist_hash
= changelist_hash
;
244 /* Try to open the target directory. If the target is a file or an
245 unversioned directory, open the parent directory instead */
246 err
= svn_wc_adm_open3(&anchor_access
, NULL
, path
, FALSE
,
247 SVN_DEPTH_IS_RECURSIVE(depth
) ? -1 : 1,
248 ctx
->cancel_func
, ctx
->cancel_baton
,
250 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
252 svn_error_clear(err
);
253 SVN_ERR(svn_wc_adm_open_anchor(&anchor_access
, &target_access
, &target
,
255 SVN_DEPTH_IS_RECURSIVE(depth
) ? -1 : 1,
256 ctx
->cancel_func
, ctx
->cancel_baton
,
262 target_access
= anchor_access
;
267 anchor
= svn_wc_adm_access_path(anchor_access
);
269 /* Get the status edit, and use our wrapping status function/baton
270 as the callback pair. */
271 SVN_ERR(svn_wc_get_default_ignores(&ignores
, ctx
->config
, pool
));
272 SVN_ERR(svn_wc_get_status_editor3(&editor
, &edit_baton
, &set_locks_baton
,
273 &edit_revision
, anchor_access
, target
,
274 depth
, get_all
, no_ignore
, ignores
,
275 tweak_status
, &sb
, ctx
->cancel_func
,
276 ctx
->cancel_baton
, traversal_info
,
279 /* If we want to know about out-of-dateness, we crawl the working copy and
280 let the RA layer drive the editor for real. Otherwise, we just close the
284 svn_ra_session_t
*ra_session
;
286 svn_node_kind_t kind
;
287 svn_boolean_t server_supports_depth
;
289 /* Get full URL from the ANCHOR. */
291 SVN_ERR(svn_wc__entry_versioned(&entry
, anchor
, anchor_access
, FALSE
,
294 return svn_error_createf
295 (SVN_ERR_ENTRY_MISSING_URL
, NULL
,
296 _("Entry '%s' has no URL"),
297 svn_path_local_style(anchor
, pool
));
298 URL
= apr_pstrdup(pool
, entry
->url
);
300 /* Open a repository session to the URL. */
301 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, URL
, anchor
,
306 /* Verify that URL exists in HEAD. If it doesn't, this can save
307 us a whole lot of hassle; if it does, the cost of this
308 request should be minimal compared to the size of getting
309 back the average amount of "out-of-date" information. */
310 SVN_ERR(svn_ra_check_path(ra_session
, "", SVN_INVALID_REVNUM
,
312 if (kind
== svn_node_none
)
314 /* Our status target does not exist in HEAD of the
315 repository. If we're just adding this thing, that's
316 fine. But if it was previously versioned, then it must
317 have been deleted from the repository. */
318 if (entry
->schedule
!= svn_wc_schedule_add
)
319 sb
.deleted_in_repos
= TRUE
;
321 /* And now close the edit. */
322 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
329 if (revision
->kind
== svn_opt_revision_head
)
331 /* Cause the revision number to be omitted from the request,
332 which implies HEAD. */
333 revnum
= SVN_INVALID_REVNUM
;
337 /* Get a revision number for our status operation. */
338 SVN_ERR(svn_client__get_revision_number
339 (&revnum
, NULL
, ra_session
, revision
, target
, pool
));
342 /* Do the deed. Let the RA layer drive the status editor. */
343 SVN_ERR(svn_ra_do_status2(ra_session
, &rb
.wrapped_reporter
,
344 &rb
.wrapped_report_baton
,
345 target
, revnum
, depth
, editor
,
348 /* Init the report baton. */
349 rb
.ancestor
= apr_pstrdup(pool
, URL
);
350 rb
.set_locks_baton
= set_locks_baton
;
354 SVN_ERR(svn_ra_has_capability(ra_session
, &server_supports_depth
,
355 SVN_RA_CAPABILITY_DEPTH
, pool
));
357 /* Drive the reporter structure, describing the revisions
358 within PATH. When we call reporter->finish_report,
359 EDITOR will be driven to describe differences between our
360 working copy and HEAD. */
361 SVN_ERR(svn_wc_crawl_revisions3(path
, target_access
,
362 &lock_fetch_reporter
, &rb
, FALSE
,
363 depth
, (! server_supports_depth
),
364 FALSE
, NULL
, NULL
, NULL
, pool
));
369 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
372 if (ctx
->notify_func2
&& update
)
374 svn_wc_notify_t
*notify
375 = svn_wc_create_notify(path
, svn_wc_notify_status_completed
, pool
);
376 notify
->revision
= edit_revision
;
377 (ctx
->notify_func2
)(ctx
->notify_baton2
, notify
, pool
);
380 /* If the caller wants the result revision, give it to them. */
382 *result_rev
= edit_revision
;
384 /* Close the access baton here, as svn_client__do_external_status()
385 calls back into this function and thus will be re-opening the
387 SVN_ERR(svn_wc_adm_close(anchor_access
));
389 /* If there are svn:externals set, we don't want those to show up as
390 unversioned or unrecognized, so patch up the hash. If caller wants
391 all the statuses, we will change unversioned status items that
392 are interesting to an svn:externals property to
393 svn_wc_status_unversioned, otherwise we'll just remove the status
396 We only descend into an external if depth is svn_depth_infinity or
397 svn_depth_unknown. However, there are conceivable behaviors that
398 would involve descending under other circumstances; thus, we pass
399 depth anyway, so the code will DTRT if we change the conditional
402 if (SVN_DEPTH_IS_RECURSIVE(depth
) && (! ignore_externals
))
403 SVN_ERR(svn_client__do_external_status(traversal_info
, status_func
,
404 status_baton
, depth
, get_all
,
405 update
, no_ignore
, ctx
, pool
));
411 svn_client_status2(svn_revnum_t
*result_rev
,
413 const svn_opt_revision_t
*revision
,
414 svn_wc_status_func2_t status_func
,
416 svn_boolean_t recurse
,
417 svn_boolean_t get_all
,
418 svn_boolean_t update
,
419 svn_boolean_t no_ignore
,
420 svn_boolean_t ignore_externals
,
421 svn_client_ctx_t
*ctx
,
424 return svn_client_status3(result_rev
, path
, revision
,
425 status_func
, status_baton
,
426 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse
),
427 get_all
, update
, no_ignore
, ignore_externals
, NULL
,
432 /* Baton for old_status_func_cb; does what you think it does. */
433 struct old_status_func_cb_baton
435 svn_wc_status_func_t original_func
;
436 void *original_baton
;
439 /* Help svn_client_status() accept an old-style status func and baton,
440 by wrapping them before passing along to svn_client_status2().
442 This implements the 'svn_wc_status_func2_t' function type. */
443 static void old_status_func_cb(void *baton
,
445 svn_wc_status2_t
*status
)
447 struct old_status_func_cb_baton
*b
= baton
;
448 svn_wc_status_t
*stat
= (svn_wc_status_t
*) status
;
450 b
->original_func(b
->original_baton
, path
, stat
);
455 svn_client_status(svn_revnum_t
*result_rev
,
457 svn_opt_revision_t
*revision
,
458 svn_wc_status_func_t status_func
,
460 svn_boolean_t recurse
,
461 svn_boolean_t get_all
,
462 svn_boolean_t update
,
463 svn_boolean_t no_ignore
,
464 svn_client_ctx_t
*ctx
,
467 struct old_status_func_cb_baton
*b
= apr_pcalloc(pool
, sizeof(*b
));
468 b
->original_func
= status_func
;
469 b
->original_baton
= status_baton
;
471 return svn_client_status2(result_rev
, path
, revision
,
472 old_status_func_cb
, b
,
473 recurse
, get_all
, update
, no_ignore
, FALSE
,