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"
36 #include "svn_private_config.h"
37 #include "private/svn_wc_private.h"
40 /*** Getting update information ***/
42 /* Baton for tweak_status. It wraps a bit of extra functionality
43 around the received status func/baton, so we can remember if the
44 target was deleted in HEAD and tweak incoming status structures
48 svn_boolean_t deleted_in_repos
; /* target is deleted in repos */
49 svn_wc_status_func2_t real_status_func
; /* real status function */
50 void *real_status_baton
; /* real status baton */
53 /* A status callback function which wraps the *real* status
54 function/baton. This sucker takes care of any status tweaks we
55 need to make (such as noting that the target of the status is
56 missing from HEAD in the repository).
58 This implements the 'svn_wc_status_func2_t' function type. */
60 tweak_status(void *baton
,
62 svn_wc_status2_t
*status
)
64 struct status_baton
*sb
= baton
;
66 /* If we know that the target was deleted in HEAD of the repository,
67 we need to note that fact in all the status structures that come
69 if (sb
->deleted_in_repos
)
70 status
->repos_text_status
= svn_wc_status_deleted
;
72 /* Call the real status function/baton. */
73 sb
->real_status_func(sb
->real_status_baton
, path
, status
);
76 /* A baton for our reporter that is used to collect locks. */
77 typedef struct report_baton_t
{
78 const svn_ra_reporter3_t
* wrapped_reporter
;
79 void *wrapped_report_baton
;
80 /* The common ancestor URL of all paths included in the report. */
82 void *set_locks_baton
;
83 svn_client_ctx_t
*ctx
;
84 /* Pool to store locks in. */
88 /* Implements svn_ra_reporter3_t->set_path. */
90 reporter_set_path(void *report_baton
, const char *path
,
91 svn_revnum_t revision
, svn_depth_t depth
,
92 svn_boolean_t start_empty
, const char *lock_token
,
95 report_baton_t
*rb
= report_baton
;
97 return rb
->wrapped_reporter
->set_path(rb
->wrapped_report_baton
, path
,
98 revision
, depth
, start_empty
,
102 /* Implements svn_ra_reporter3_t->delete_path. */
104 reporter_delete_path(void *report_baton
, const char *path
, apr_pool_t
*pool
)
106 report_baton_t
*rb
= report_baton
;
108 return rb
->wrapped_reporter
->delete_path(rb
->wrapped_report_baton
, path
,
112 /* Implements svn_ra_reporter3_t->link_path. */
114 reporter_link_path(void *report_baton
, const char *path
, const char *url
,
115 svn_revnum_t revision
, svn_depth_t depth
,
116 svn_boolean_t start_empty
,
117 const char *lock_token
, apr_pool_t
*pool
)
119 report_baton_t
*rb
= report_baton
;
120 const char *ancestor
;
123 ancestor
= svn_path_get_longest_ancestor(url
, rb
->ancestor
, pool
);
125 /* If we got a shorter ancestor, truncate our current ancestor.
126 Note that svn_path_get_longest_ancestor will allocate its return
127 value even if it identical to one of its arguments. */
128 len
= strlen(ancestor
);
129 if (len
< strlen(rb
->ancestor
))
130 rb
->ancestor
[len
] = '\0';
132 return rb
->wrapped_reporter
->link_path(rb
->wrapped_report_baton
, path
, url
,
133 revision
, depth
, start_empty
,
137 /* Implements svn_ra_reporter3_t->finish_report. */
139 reporter_finish_report(void *report_baton
, apr_pool_t
*pool
)
141 report_baton_t
*rb
= report_baton
;
142 svn_ra_session_t
*ras
;
144 const char *repos_root
;
145 apr_pool_t
*subpool
= svn_pool_create(pool
);
146 svn_error_t
*err
= SVN_NO_ERROR
;
148 /* Open an RA session to our common ancestor and grab the locks under it.
150 SVN_ERR(svn_client__open_ra_session_internal(&ras
, rb
->ancestor
, NULL
,
151 NULL
, NULL
, FALSE
, TRUE
,
154 /* The locks need to live throughout the edit. Note that if the
155 server doesn't support lock discovery, we'll just not do locky
157 err
= svn_ra_get_locks(ras
, &locks
, "", rb
->pool
);
158 if (err
&& ((err
->apr_err
== SVN_ERR_RA_NOT_IMPLEMENTED
)
159 || (err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)))
161 svn_error_clear(err
);
163 locks
= apr_hash_make(rb
->pool
);
167 SVN_ERR(svn_ra_get_repos_root(ras
, &repos_root
, subpool
));
168 repos_root
= apr_pstrdup(rb
->pool
, repos_root
);
170 /* Close the RA session. */
171 svn_pool_destroy(subpool
);
173 SVN_ERR(svn_wc_status_set_repos_locks(rb
->set_locks_baton
, locks
,
174 repos_root
, rb
->pool
));
176 return rb
->wrapped_reporter
->finish_report(rb
->wrapped_report_baton
, pool
);
179 /* Implements svn_ra_reporter3_t->abort_report. */
181 reporter_abort_report(void *report_baton
, apr_pool_t
*pool
)
183 report_baton_t
*rb
= report_baton
;
185 return rb
->wrapped_reporter
->abort_report(rb
->wrapped_report_baton
, pool
);
188 /* A reporter that keeps track of the common URL ancestor of all paths in
189 the WC and fetches repository locks for all paths under this ancestor. */
190 static svn_ra_reporter3_t lock_fetch_reporter
= {
192 reporter_delete_path
,
194 reporter_finish_report
,
195 reporter_abort_report
199 /*** Public Interface. ***/
203 svn_client_status3(svn_revnum_t
*result_rev
,
205 const svn_opt_revision_t
*revision
,
206 svn_wc_status_func2_t status_func
,
209 svn_boolean_t get_all
,
210 svn_boolean_t update
,
211 svn_boolean_t no_ignore
,
212 svn_boolean_t ignore_externals
,
213 svn_client_ctx_t
*ctx
,
216 svn_wc_adm_access_t
*anchor_access
, *target_access
;
217 svn_wc_traversal_info_t
*traversal_info
= svn_wc_init_traversal_info(pool
);
218 const char *anchor
, *target
;
219 const svn_delta_editor_t
*editor
;
220 void *edit_baton
, *set_locks_baton
;
221 const svn_wc_entry_t
*entry
= NULL
;
222 struct status_baton sb
;
223 apr_array_header_t
*ignores
;
226 svn_revnum_t edit_revision
= SVN_INVALID_REVNUM
;
228 sb
.real_status_func
= status_func
;
229 sb
.real_status_baton
= status_baton
;
230 sb
.deleted_in_repos
= FALSE
;
232 /* Try to open the target directory. If the target is a file or an
233 unversioned directory, open the parent directory instead */
234 err
= svn_wc_adm_open3(&anchor_access
, NULL
, path
, FALSE
,
235 SVN_DEPTH_IS_RECURSIVE(depth
) ? -1 : 1,
236 ctx
->cancel_func
, ctx
->cancel_baton
,
238 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
240 svn_error_clear(err
);
241 SVN_ERR(svn_wc_adm_open_anchor(&anchor_access
, &target_access
, &target
,
243 SVN_DEPTH_IS_RECURSIVE(depth
) ? -1 : 1,
244 ctx
->cancel_func
, ctx
->cancel_baton
,
250 target_access
= anchor_access
;
255 anchor
= svn_wc_adm_access_path(anchor_access
);
257 /* Get the status edit, and use our wrapping status function/baton
258 as the callback pair. */
259 SVN_ERR(svn_wc_get_default_ignores(&ignores
, ctx
->config
, pool
));
260 SVN_ERR(svn_wc_get_status_editor3(&editor
, &edit_baton
, &set_locks_baton
,
261 &edit_revision
, anchor_access
, target
,
262 depth
, get_all
, no_ignore
, ignores
,
263 tweak_status
, &sb
, ctx
->cancel_func
,
264 ctx
->cancel_baton
, traversal_info
,
267 /* If we want to know about out-of-dateness, we crawl the working copy and
268 let the RA layer drive the editor for real. Otherwise, we just close the
272 svn_ra_session_t
*ra_session
;
274 svn_node_kind_t kind
;
275 svn_boolean_t server_supports_depth
;
277 /* Get full URL from the ANCHOR. */
279 SVN_ERR(svn_wc__entry_versioned(&entry
, anchor
, anchor_access
, FALSE
,
282 return svn_error_createf
283 (SVN_ERR_ENTRY_MISSING_URL
, NULL
,
284 _("Entry '%s' has no URL"),
285 svn_path_local_style(anchor
, pool
));
286 URL
= apr_pstrdup(pool
, entry
->url
);
288 /* Open a repository session to the URL. */
289 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, URL
, anchor
,
294 /* Verify that URL exists in HEAD. If it doesn't, this can save
295 us a whole lot of hassle; if it does, the cost of this
296 request should be minimal compared to the size of getting
297 back the average amount of "out-of-date" information. */
298 SVN_ERR(svn_ra_check_path(ra_session
, "", SVN_INVALID_REVNUM
,
300 if (kind
== svn_node_none
)
302 /* Our status target does not exist in HEAD of the
303 repository. If we're just adding this thing, that's
304 fine. But if it was previously versioned, then it must
305 have been deleted from the repository. */
306 if (entry
->schedule
!= svn_wc_schedule_add
)
307 sb
.deleted_in_repos
= TRUE
;
309 /* And now close the edit. */
310 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
317 if (revision
->kind
== svn_opt_revision_head
)
319 /* Cause the revision number to be omitted from the request,
320 which implies HEAD. */
321 revnum
= SVN_INVALID_REVNUM
;
325 /* Get a revision number for our status operation. */
326 SVN_ERR(svn_client__get_revision_number
327 (&revnum
, NULL
, ra_session
, revision
, target
, pool
));
330 /* Do the deed. Let the RA layer drive the status editor. */
331 SVN_ERR(svn_ra_do_status2(ra_session
, &rb
.wrapped_reporter
,
332 &rb
.wrapped_report_baton
,
333 target
, revnum
, depth
, editor
,
336 /* Init the report baton. */
337 rb
.ancestor
= apr_pstrdup(pool
, URL
);
338 rb
.set_locks_baton
= set_locks_baton
;
342 SVN_ERR(svn_ra_has_capability(ra_session
, &server_supports_depth
,
343 SVN_RA_CAPABILITY_DEPTH
, pool
));
345 /* Drive the reporter structure, describing the revisions
346 within PATH. When we call reporter->finish_report,
347 EDITOR will be driven to describe differences between our
348 working copy and HEAD. */
349 SVN_ERR(svn_wc_crawl_revisions3(path
, target_access
,
350 &lock_fetch_reporter
, &rb
, FALSE
,
351 depth
, (! server_supports_depth
),
352 FALSE
, NULL
, NULL
, NULL
, pool
));
357 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
360 if (ctx
->notify_func2
&& update
)
362 svn_wc_notify_t
*notify
363 = svn_wc_create_notify(path
, svn_wc_notify_status_completed
, pool
);
364 notify
->revision
= edit_revision
;
365 (ctx
->notify_func2
)(ctx
->notify_baton2
, notify
, pool
);
368 /* If the caller wants the result revision, give it to them. */
370 *result_rev
= edit_revision
;
372 /* Close the access baton here, as svn_client__do_external_status()
373 calls back into this function and thus will be re-opening the
375 SVN_ERR(svn_wc_adm_close(anchor_access
));
377 /* If there are svn:externals set, we don't want those to show up as
378 unversioned or unrecognized, so patch up the hash. If caller wants
379 all the statuses, we will change unversioned status items that
380 are interesting to an svn:externals property to
381 svn_wc_status_unversioned, otherwise we'll just remove the status
384 We only descend into an external if depth is svn_depth_infinity or
385 svn_depth_unknown. However, there are conceivable behaviors that
386 would involve descending under other circumstances; thus, we pass
387 depth anyway, so the code will DTRT if we change the conditional
390 if (SVN_DEPTH_IS_RECURSIVE(depth
) && (! ignore_externals
))
391 SVN_ERR(svn_client__do_external_status(traversal_info
, status_func
,
392 status_baton
, depth
, get_all
,
393 update
, no_ignore
, ctx
, pool
));
399 svn_client_status2(svn_revnum_t
*result_rev
,
401 const svn_opt_revision_t
*revision
,
402 svn_wc_status_func2_t status_func
,
404 svn_boolean_t recurse
,
405 svn_boolean_t get_all
,
406 svn_boolean_t update
,
407 svn_boolean_t no_ignore
,
408 svn_boolean_t ignore_externals
,
409 svn_client_ctx_t
*ctx
,
412 return svn_client_status3(result_rev
, path
, revision
,
413 status_func
, status_baton
,
414 SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse
),
416 no_ignore
, ignore_externals
,
421 /* Baton for old_status_func_cb; does what you think it does. */
422 struct old_status_func_cb_baton
424 svn_wc_status_func_t original_func
;
425 void *original_baton
;
428 /* Help svn_client_status() accept an old-style status func and baton,
429 by wrapping them before passing along to svn_client_status2().
431 This implements the 'svn_wc_status_func2_t' function type. */
432 static void old_status_func_cb(void *baton
,
434 svn_wc_status2_t
*status
)
436 struct old_status_func_cb_baton
*b
= baton
;
437 svn_wc_status_t
*stat
= (svn_wc_status_t
*) status
;
439 b
->original_func(b
->original_baton
, path
, stat
);
444 svn_client_status(svn_revnum_t
*result_rev
,
446 svn_opt_revision_t
*revision
,
447 svn_wc_status_func_t status_func
,
449 svn_boolean_t recurse
,
450 svn_boolean_t get_all
,
451 svn_boolean_t update
,
452 svn_boolean_t no_ignore
,
453 svn_client_ctx_t
*ctx
,
456 struct old_status_func_cb_baton
*b
= apr_pcalloc(pool
, sizeof(*b
));
457 b
->original_func
= status_func
;
458 b
->original_baton
= status_baton
;
460 return svn_client_status2(result_rev
, path
, revision
,
461 old_status_func_cb
, b
,
462 recurse
, get_all
, update
, no_ignore
, FALSE
,