| /* |
| * status.c: construct a status structure from an entry structure |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <string.h> |
| #include <apr_pools.h> |
| #include <apr_file_io.h> |
| #include <apr_hash.h> |
| #include <apr_time.h> |
| #include <apr_fnmatch.h> |
| #include "svn_pools.h" |
| #include "svn_types.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_wc.h" |
| |
| #include "wc.h" |
| #include "props.h" |
| |
| |
| static void add_default_ignores (apr_array_header_t *patterns) |
| { |
| static const char * const ignores[] = |
| { |
| "*.o", "*.lo", "*.la", "#*#", "*.rej", "*~", ".#*", |
| /* what else? */ |
| NULL |
| }; |
| int i; |
| |
| for (i = 0; ignores[i] != NULL; i++) |
| { |
| const char **ent = apr_array_push(patterns); |
| *ent = ignores[i]; |
| } |
| |
| } |
| |
| |
| /* Helper routine: add to a *PATTERNS list patterns from the value of |
| the SVN_PROP_IGNORE property set on DIRPATH. If there is no such |
| property, or the property contains no patterns, do nothing. |
| Otherwise, add to *PATTERNS a list of (const char *) patterns to |
| match. */ |
| static svn_error_t * |
| add_ignore_patterns (const char *dirpath, |
| apr_array_header_t *patterns, |
| apr_pool_t *pool) |
| { |
| const svn_string_t *value; |
| |
| /* Try to load the SVN_PROP_IGNORE property. */ |
| SVN_ERR (svn_wc_prop_get (&value, SVN_PROP_IGNORE, dirpath, pool)); |
| |
| if (value != NULL) |
| { |
| static const char sep[3] = "\n\r"; |
| char *last; |
| char *pats = apr_pstrdup (pool, value->data); |
| char *p = apr_strtok (pats, sep, &last); |
| |
| while (p) |
| { |
| if (p[0] != '\0') |
| { |
| (*((const char **) apr_array_push (patterns))) = p; |
| } |
| p = apr_strtok (NULL, sep, &last); |
| } |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Fill in *STATUS for PATH, whose entry data is in ENTRY. Allocate |
| *STATUS in POOL. |
| |
| ENTRY may be null, for non-versioned entities. In this case, we |
| will assemble a special status structure item which implies a |
| non-versioned thing. |
| |
| Else, ENTRY's pool must not be shorter-lived than STATUS's, since |
| ENTRY will be stored directly, not copied. |
| |
| If GET_ALL is zero, and ENTRY is not locally modified, then *STATUS |
| will be set to NULL. If GET_ALL is non-zero, then *STATUS will be |
| allocated and returned no matter what. |
| |
| */ |
| static svn_error_t * |
| assemble_status (svn_wc_status_t **status, |
| svn_stringbuf_t *path, |
| svn_wc_entry_t *entry, |
| svn_boolean_t get_all, |
| svn_boolean_t strict, |
| apr_pool_t *pool) |
| { |
| svn_wc_status_t *stat; |
| enum svn_node_kind path_kind; |
| svn_boolean_t has_props; |
| svn_boolean_t text_modified_p = FALSE; |
| svn_boolean_t prop_modified_p = FALSE; |
| svn_boolean_t locked_p = FALSE; |
| |
| /* Defaults for two main variables. */ |
| enum svn_wc_status_kind final_text_status = svn_wc_status_normal; |
| enum svn_wc_status_kind final_prop_status = svn_wc_status_none; |
| |
| /* Check the path kind for PATH. */ |
| SVN_ERR( svn_io_check_path (path->data, &path_kind, pool)); |
| |
| if (! entry) |
| { |
| /* return a blank structure. */ |
| stat = apr_pcalloc (pool, sizeof(*stat)); |
| stat->entry = NULL; |
| stat->text_status = svn_wc_status_none; |
| stat->prop_status = svn_wc_status_none; |
| stat->repos_text_status = svn_wc_status_none; |
| stat->repos_prop_status = svn_wc_status_none; |
| stat->locked = FALSE; |
| stat->copied = FALSE; |
| |
| /* If this path has no entry, but IS present on disk, it's |
| unversioned. */ |
| if (path_kind != svn_node_none) |
| stat->text_status = svn_wc_status_unversioned; |
| |
| /* If we're in strict mode and we encounter a path that doesn't |
| exist in the wc, then we return an error*/ |
| if (strict && (path_kind == svn_node_none)) |
| return svn_error_createf (APR_ENOENT, 0, NULL, pool, |
| "assemble_status: " |
| "%s: No such file or directory", |
| path->data); |
| |
| *status = stat; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implement predecence rules: */ |
| |
| /* 1. Set the two main variables to "discovered" values first (M, C). |
| Together, these two stati are of lowest precedence, and C has |
| precedence over M. */ |
| |
| /* Does the entry have props? */ |
| SVN_ERR (svn_wc__has_props (&has_props, path, pool)); |
| if (has_props) |
| final_prop_status = svn_wc_status_normal; |
| |
| /* If the entry has a property file, see if it has local changes. */ |
| SVN_ERR (svn_wc_props_modified_p (&prop_modified_p, path, pool)); |
| |
| /* If the entry is a file, check for textual modifications */ |
| if (entry->kind == svn_node_file) |
| SVN_ERR (svn_wc_text_modified_p (&text_modified_p, path, pool)); |
| |
| if (text_modified_p) |
| final_text_status = svn_wc_status_modified; |
| |
| if (prop_modified_p) |
| final_prop_status = svn_wc_status_modified; |
| |
| if (entry->prejfile || entry->conflict_old || |
| entry->conflict_new || entry->conflict_wrk) |
| { |
| svn_boolean_t text_conflict_p, prop_conflict_p; |
| svn_stringbuf_t *parent_dir; |
| |
| if (entry->kind == svn_node_dir) |
| parent_dir = path; |
| else /* non-directory, that's all we need to know */ |
| { |
| parent_dir = svn_stringbuf_dup (path, pool); |
| svn_path_remove_component (parent_dir); |
| } |
| |
| SVN_ERR (svn_wc_conflicted_p (&text_conflict_p, &prop_conflict_p, |
| parent_dir, entry, pool)); |
| |
| if (text_conflict_p) |
| final_text_status = svn_wc_status_conflicted; |
| if (prop_conflict_p) |
| final_prop_status = svn_wc_status_conflicted; |
| } |
| |
| /* 2. Possibly overwrite the the text_status variable with |
| "scheduled" states from the entry (A, D, R). As a group, |
| these states are of medium precedence. They also override any |
| C or M that may be in the prop_status field at this point.*/ |
| |
| if (entry->schedule == svn_wc_schedule_add) |
| { |
| final_text_status = svn_wc_status_added; |
| final_prop_status = svn_wc_status_none; |
| } |
| |
| else if (entry->schedule == svn_wc_schedule_replace) |
| { |
| final_text_status = svn_wc_status_replaced; |
| final_prop_status = svn_wc_status_none; |
| } |
| |
| else if (entry->schedule == svn_wc_schedule_delete) |
| { |
| final_text_status = svn_wc_status_deleted; |
| final_prop_status = svn_wc_status_none; |
| } |
| |
| |
| /* 3. Highest precedence: check to see if file or dir is just |
| missing. This overrides every possible state *except* |
| deletion. (If something is deleted or scheduled for it, we |
| don't care if the working file exists.) */ |
| |
| if ((path_kind == svn_node_none) |
| && (final_text_status != svn_wc_status_deleted)) |
| final_text_status = svn_wc_status_absent; |
| |
| |
| /* 4. Check for locked directory. */ |
| |
| if (entry->kind == svn_node_dir) |
| SVN_ERR (svn_wc_locked (&locked_p, path, pool)); |
| |
| |
| /* 5. Easy out: unless we're fetching -every- entry, don't bother |
| to allocate a struct for an uninteresting entry. */ |
| |
| if (! get_all) |
| if (((final_text_status == svn_wc_status_none) |
| || (final_text_status == svn_wc_status_normal)) |
| && ((final_prop_status == svn_wc_status_none) |
| || (final_prop_status == svn_wc_status_normal)) |
| && (! locked_p)) |
| { |
| *status = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* 6. Build and return a status structure. */ |
| |
| stat = apr_pcalloc (pool, sizeof(**status)); |
| stat->entry = svn_wc_entry_dup (entry, pool); |
| stat->text_status = final_text_status; |
| stat->prop_status = final_prop_status; |
| stat->repos_text_status = svn_wc_status_none; /* default */ |
| stat->repos_prop_status = svn_wc_status_none; /* default */ |
| stat->locked = locked_p; |
| stat->copied = entry->copied; |
| |
| *status = stat; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Given an ENTRY object representing PATH, build a status structure |
| and store it in STATUSHASH. */ |
| static svn_error_t * |
| add_status_structure (apr_hash_t *statushash, |
| svn_stringbuf_t *path, |
| svn_wc_entry_t *entry, |
| svn_boolean_t get_all, |
| svn_boolean_t strict, |
| apr_pool_t *pool) |
| { |
| svn_wc_status_t *statstruct; |
| |
| SVN_ERR (assemble_status (&statstruct, path, entry, |
| get_all, strict, pool)); |
| if (statstruct) |
| apr_hash_set (statushash, path->data, path->len, statstruct); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Add all items that are NOT in ENTRIES (which is a list of PATH's |
| versioned things) to the STATUSHASH as unversioned items, |
| allocating everything in POOL. */ |
| static svn_error_t * |
| add_unversioned_items (svn_stringbuf_t *path, |
| apr_hash_t *entries, |
| apr_hash_t *statushash, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| apr_hash_t *dirents; |
| apr_hash_index_t *hi; |
| apr_array_header_t *patterns; |
| |
| /* Read PATH's dirents. */ |
| SVN_ERR (svn_io_get_dirents (&dirents, path, subpool)); |
| |
| /* Try to load any '.svnignore' file that may be present. */ |
| patterns = apr_array_make (subpool, 1, sizeof(const char *)); |
| add_default_ignores (patterns); |
| SVN_ERR (add_ignore_patterns (path->data, patterns, subpool)); |
| |
| /* Add empty status structures for each of the unversioned things. */ |
| for (hi = apr_hash_first (subpool, dirents); hi; hi = apr_hash_next (hi)) |
| { |
| const void *key; |
| apr_ssize_t klen; |
| void *val; |
| const char *keystring; |
| int i; |
| int ignore_me; |
| svn_stringbuf_t *printable_path; |
| |
| apr_hash_this (hi, &key, &klen, &val); |
| keystring = (const char *) key; |
| |
| /* If the dirent isn't in `.svn/entries'... */ |
| if (apr_hash_get (entries, key, klen)) |
| continue; |
| |
| /* and we're not looking at .svn... */ |
| if (! strcmp (keystring, SVN_WC_ADM_DIR_NAME)) |
| continue; |
| |
| ignore_me = 0; |
| |
| /* See if any of the ignore patterns we have matches our |
| keystring. */ |
| for (i = 0; i < patterns->nelts; i++) |
| { |
| const char *pat = (((const char **) (patterns)->elts))[i]; |
| |
| /* Try to match current_entry_name to pat. */ |
| if (APR_SUCCESS == apr_fnmatch (pat, keystring, FNM_PERIOD)) |
| { |
| ignore_me = 1; |
| break; |
| } |
| } |
| |
| /* If we aren't ignoring it, add a status structure for this |
| dirent. */ |
| if (! ignore_me) |
| { |
| /* Reset our base string... */ |
| printable_path = svn_stringbuf_dup (path, pool); |
| /* ...and append the current entry. */ |
| svn_path_add_component_nts (printable_path, keystring); |
| |
| /* Add this item to the status hash. */ |
| SVN_ERR (add_status_structure (statushash, |
| printable_path, |
| NULL, /* no entry */ |
| FALSE, |
| FALSE, |
| pool)); |
| } |
| } |
| |
| svn_pool_destroy (subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_status (svn_wc_status_t **status, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| svn_wc_status_t *s; |
| svn_wc_entry_t *entry = NULL; |
| |
| /* Don't check for error; PATH may be unversioned, or nonexistent |
| (in the case of 'svn st -u' being told about as-yet-unknnown |
| paths), and either condition will cause svn_wc_entry to return an |
| error. If this routine returns error, then a NULL entry will be |
| passed to assemble_status() below, which is fine -- a blank |
| status structure will be returned with either 'unversioned' or |
| 'absent' status filled in. */ |
| svn_wc_entry (&entry, path, pool); |
| |
| SVN_ERR (assemble_status (&s, path, entry, TRUE, FALSE, pool)); |
| *status = s; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_wc_statuses (apr_hash_t *statushash, |
| svn_stringbuf_t *path, |
| svn_boolean_t descend, |
| svn_boolean_t get_all, |
| svn_boolean_t strict, |
| apr_pool_t *pool) |
| { |
| enum svn_node_kind kind; |
| svn_wc_entry_t *entry; |
| |
| /* Is PATH a directory or file? */ |
| SVN_ERR (svn_io_check_path (path->data, &kind, pool)); |
| |
| /* kff todo: this has to deal with the case of a type-changing edit, |
| i.e., someone removed a file under vc and replaced it with a dir, |
| or vice versa. In such a case, when you ask for the status, you |
| should get mostly information about the now-vanished entity, plus |
| some information about what happened to it. The same situation |
| is handled in entries.c:svn_wc_entry. */ |
| |
| /* Read the appropriate entries file */ |
| |
| /* If path points to just one file, or at least to just one |
| non-directory, store just one status structure in the |
| STATUSHASH and return. */ |
| if ((kind == svn_node_file) || (kind == svn_node_none)) |
| { |
| /* Get the entry for this file. Place it into the specified pool since |
| we're going to return it in statushash. */ |
| SVN_ERR (svn_wc_entry (&entry, path, pool)); |
| |
| /* Convert the entry into a status structure, store in the hash. |
| |
| ### Notice that because we're getting one specific file, |
| we're ignoring the GET_ALL flag and unconditionally fetching |
| the status structure. */ |
| SVN_ERR (add_status_structure (statushash, path, entry, |
| TRUE, strict, pool)); |
| } |
| |
| |
| /* Fill the hash with a status structure for *each* entry in PATH */ |
| else if (kind == svn_node_dir) |
| { |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| |
| /* Load entries file for the directory into the requested pool. */ |
| SVN_ERR (svn_wc_entries_read (&entries, path, pool)); |
| |
| /* Add the unversioned items to the status output. */ |
| SVN_ERR (add_unversioned_items (path, entries, statushash, pool)); |
| |
| /* Loop over entries hash */ |
| for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi)) |
| { |
| const void *key; |
| void *val; |
| const char *basename; |
| apr_ssize_t keylen; |
| |
| /* Put fullpath into the request pool since it becomes a key |
| in the output statushash hash table. */ |
| svn_stringbuf_t *fullpath = svn_stringbuf_dup (path, pool); |
| |
| /* Get the next dirent */ |
| apr_hash_this (hi, &key, &keylen, &val); |
| basename = (const char *) key; |
| if (strcmp (basename, SVN_WC_ENTRY_THIS_DIR) != 0) |
| { |
| svn_path_add_component_nts (fullpath, basename); |
| } |
| |
| entry = (svn_wc_entry_t *) val; |
| |
| SVN_ERR (svn_io_check_path (fullpath->data, &kind, pool)); |
| |
| /* In deciding whether or not to descend, we use the actual |
| kind of the entity, not the kind claimed by the entries |
| file. The two are usually the same, but where they are |
| not, its usually because some directory got moved, and |
| one would still want a status report on its contents. |
| kff todo: However, must handle mixed working copies. |
| What if the subdir is not under revision control, or is |
| from another repository? */ |
| |
| /* Do *not* store THIS_DIR in the statushash, unless this |
| path has never been seen before. We don't want to add |
| the path key twice. */ |
| if (! strcmp (basename, SVN_WC_ENTRY_THIS_DIR)) |
| { |
| svn_wc_status_t *s = apr_hash_get (statushash, |
| fullpath->data, |
| fullpath->len); |
| if (! s) |
| SVN_ERR (add_status_structure (statushash, fullpath, |
| entry, get_all, |
| strict, pool)); |
| } |
| else |
| { |
| if (kind == svn_node_dir && descend) |
| { |
| /* Directory entries are incomplete. We must get |
| their full entry from their own THIS_DIR entry. |
| svn_wc_entry does this for us if it can. */ |
| svn_wc_entry_t *subdir; |
| |
| SVN_ERR (svn_wc_entry (&subdir, fullpath, pool)); |
| SVN_ERR (add_status_structure (statushash, fullpath, |
| subdir, get_all, |
| strict, pool)); |
| SVN_ERR (svn_wc_statuses (statushash, fullpath, |
| descend, get_all, strict, pool)); |
| } |
| else if ((kind == svn_node_file) || (kind == svn_node_none)) |
| { |
| /* File entries are ... just fine! */ |
| SVN_ERR (add_status_structure (statushash, fullpath, |
| entry, get_all, |
| strict, pool)); |
| } |
| } |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |