| /* |
| * main.c: Subversion server inspection tool. |
| * |
| * ==================================================================== |
| * 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 <apr_general.h> |
| #include <apr_pools.h> |
| #include <apr_time.h> |
| #include <apr_thread_proc.h> |
| #include <apr_file_io.h> |
| |
| #define APR_WANT_STDIO |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_repos.h" |
| #include "svn_fs.h" |
| #include "svn_repos.h" |
| #include "svn_time.h" |
| |
| |
| /*** Some convenience macros and types. ***/ |
| |
| /* Temporary subdirectory created for use by svnlook. ### todo: we |
| need to either find a way to query APR for a suitable (that is, |
| writable) temporary directory, or add this #define to the |
| svn_private_config.h stuffs (with a default of perhaps "/tmp/.svnlook" */ |
| #define SVNLOOK_TMPDIR ".svnlook" |
| |
| typedef enum svnlook_cmd_t |
| { |
| svnlook_cmd_default = 0, |
| |
| svnlook_cmd_author, |
| svnlook_cmd_changed, |
| svnlook_cmd_date, |
| svnlook_cmd_diff, |
| svnlook_cmd_dirs_changed, |
| svnlook_cmd_ids, |
| svnlook_cmd_info, |
| svnlook_cmd_log, |
| svnlook_cmd_tree, |
| |
| } svnlook_cmd_t; |
| |
| |
| typedef struct svnlook_ctxt_t |
| { |
| svn_repos_t *repos; |
| svn_fs_t *fs; |
| svn_boolean_t is_revision; |
| svn_revnum_t rev_id; |
| svn_fs_txn_t *txn; |
| char *txn_name; |
| |
| } svnlook_ctxt_t; |
| |
| |
| |
| /*** Helper functions. ***/ |
| static svn_error_t * |
| get_property (svn_string_t **prop_value, |
| svnlook_ctxt_t *c, |
| const char *prop_name, |
| apr_pool_t *pool) |
| { |
| /* Fetch transaction property... */ |
| if (! c->is_revision) |
| return svn_fs_txn_prop (prop_value, c->txn, prop_name, pool); |
| |
| /* ...or revision property -- it's your call. */ |
| return svn_fs_revision_prop (prop_value, c->fs, c->rev_id, prop_name, pool); |
| } |
| |
| |
| static svn_error_t * |
| get_root (svn_fs_root_t **root, |
| svnlook_ctxt_t *c, |
| apr_pool_t *pool) |
| { |
| /* Open up the appropriate root (revision or transaction). */ |
| if (c->is_revision) |
| { |
| /* If we didn't get a valid revision number, we'll look at the |
| youngest revision. */ |
| if (! SVN_IS_VALID_REVNUM (c->rev_id)) |
| SVN_ERR (svn_fs_youngest_rev (&(c->rev_id), c->fs, pool)); |
| |
| SVN_ERR (svn_fs_revision_root (root, c->fs, c->rev_id, pool)); |
| } |
| else |
| { |
| SVN_ERR (svn_fs_txn_root (root, c->txn, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Tree Routines ***/ |
| |
| /* Generate a generic delta tree. */ |
| static svn_error_t * |
| generate_delta_tree (svn_repos_node_t **tree, |
| svn_repos_t *repos, |
| svn_fs_root_t *root, |
| svn_revnum_t base_rev, |
| apr_pool_t *pool) |
| { |
| svn_fs_root_t *base_root; |
| const svn_delta_edit_fns_t *editor; |
| void *edit_baton; |
| apr_hash_t *src_revs = apr_hash_make (pool); |
| apr_pool_t *edit_pool = svn_pool_create (pool); |
| svn_fs_t *fs = svn_repos_fs (repos); |
| |
| /* Get the current root. */ |
| apr_hash_set (src_revs, "", APR_HASH_KEY_STRING, &base_rev); |
| |
| /* Get the base root. */ |
| SVN_ERR (svn_fs_revision_root (&base_root, fs, base_rev, pool)); |
| |
| /* Request our editor. */ |
| SVN_ERR (svn_repos_node_editor (&editor, &edit_baton, repos, |
| base_root, root, pool, edit_pool)); |
| |
| /* Drive our editor. */ |
| SVN_ERR (svn_repos_dir_delta (base_root, "", NULL, src_revs, root, "", |
| editor, edit_baton, FALSE, TRUE, FALSE, |
| edit_pool)); |
| |
| /* Return the tree we just built. */ |
| *tree = svn_repos_node_from_baton (edit_baton); |
| svn_pool_destroy (edit_pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Tree Printing Routines ***/ |
| |
| /* Recursively print only directory nodes that either a) have property |
| mods, or b) contains files that have changed. */ |
| static void |
| print_dirs_changed_tree (svn_repos_node_t *node, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| svn_repos_node_t *tmp_node; |
| int print_me = 0; |
| svn_stringbuf_t *full_path; |
| |
| if (! node) |
| return; |
| |
| /* Not a directory? We're not interested. */ |
| if (node->kind != svn_node_dir) |
| return; |
| |
| /* Got prop mods? Excellent. */ |
| if (node->prop_mod) |
| print_me = 1; |
| |
| if (! print_me) |
| { |
| /* Fly through the list of children, checking for modified files. */ |
| tmp_node = node->child; |
| if (tmp_node) |
| { |
| if ((tmp_node->kind == svn_node_file) |
| || (tmp_node->text_mod) |
| || (tmp_node->action == 'A') |
| || (tmp_node->action == 'D')) |
| { |
| print_me = 1; |
| } |
| while (tmp_node->sibling && (! print_me )) |
| { |
| tmp_node = tmp_node->sibling; |
| if ((tmp_node->kind == svn_node_file) |
| || (tmp_node->text_mod) |
| || (tmp_node->action == 'A') |
| || (tmp_node->action == 'D')) |
| { |
| print_me = 1; |
| } |
| } |
| } |
| } |
| |
| /* Print the node if it qualifies. */ |
| if (print_me) |
| { |
| printf ("%s/\n", path->data); |
| } |
| |
| /* Recursively handle the node's children. */ |
| tmp_node = node->child; |
| if (! tmp_node) |
| return; |
| |
| full_path = svn_stringbuf_dup (path, pool); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_dirs_changed_tree (tmp_node, full_path, pool); |
| while (tmp_node->sibling) |
| { |
| tmp_node = tmp_node->sibling; |
| svn_stringbuf_set (full_path, path->data); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_dirs_changed_tree (tmp_node, full_path, pool); |
| } |
| |
| return; |
| } |
| |
| |
| /* Recursively print all nodes in the tree that have been modified |
| (do not include directories affected only by "bubble-up"). */ |
| static void |
| print_changed_tree (svn_repos_node_t *node, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| svn_repos_node_t *tmp_node; |
| svn_stringbuf_t *full_path; |
| char status[3] = "_ "; |
| int print_me = 1; |
| |
| if (! node) |
| return; |
| |
| /* Print the node. */ |
| tmp_node = node; |
| if (tmp_node->action == 'A') |
| status[0] = 'A'; |
| else if (tmp_node->action == 'D') |
| status[0] = 'D'; |
| else if (tmp_node->action == 'R') |
| { |
| if ((! tmp_node->text_mod) && (! tmp_node->prop_mod)) |
| print_me = 0; |
| if (tmp_node->text_mod) |
| status[0] = 'U'; |
| if (tmp_node->prop_mod) |
| status[1] = 'U'; |
| } |
| else |
| print_me = 0; |
| |
| /* Print this node unless told to skip it. */ |
| if (print_me) |
| printf ("%s %s%s\n", |
| status, |
| path->data, |
| tmp_node->kind == svn_node_dir ? "/" : ""); |
| |
| /* Return here if the node has no children. */ |
| tmp_node = tmp_node->child; |
| if (! tmp_node) |
| return; |
| |
| /* Recursively handle the node's children. */ |
| full_path = svn_stringbuf_dup (path, pool); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_changed_tree (tmp_node, full_path, pool); |
| while (tmp_node->sibling) |
| { |
| tmp_node = tmp_node->sibling; |
| svn_stringbuf_set (full_path, path->data); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_changed_tree (tmp_node, full_path, pool); |
| } |
| |
| return; |
| } |
| |
| |
| static svn_error_t * |
| open_writable_binary_file (apr_file_t **fh, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *path_pieces; |
| apr_status_t apr_err; |
| int i; |
| svn_stringbuf_t *full_path, *dir, *basename; |
| |
| /* Try the easy way to open the file. */ |
| apr_err = apr_file_open (fh, path->data, |
| APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, |
| APR_OS_DEFAULT, pool); |
| if (! apr_err) |
| return SVN_NO_ERROR; |
| |
| svn_path_split (path, &dir, &basename, pool); |
| |
| /* If the file path has no parent, then we've already tried to open |
| it as best as we care to try above. */ |
| if (svn_path_is_empty (dir)) |
| return svn_error_createf (apr_err, 0, NULL, pool, |
| "Error opening writable file %s", path->data); |
| |
| path_pieces = svn_path_decompose (dir, pool); |
| if (! path_pieces->nelts) |
| return APR_SUCCESS; |
| |
| full_path = svn_stringbuf_create ("", pool); |
| for (i = 0; i < path_pieces->nelts; i++) |
| { |
| enum svn_node_kind kind; |
| svn_stringbuf_t *piece = ((svn_stringbuf_t **) (path_pieces->elts))[i]; |
| svn_path_add_component (full_path, piece); |
| SVN_ERR (svn_io_check_path (full_path->data, &kind, pool)); |
| |
| /* Does this path component exist at all? */ |
| if (kind == svn_node_none) |
| { |
| apr_err = apr_dir_make (full_path->data, APR_OS_DEFAULT, pool); |
| if (apr_err) |
| return svn_error_createf (apr_err, 0, NULL, pool, |
| "Error creating dir %s", |
| full_path->data); |
| } |
| else if (kind != svn_node_dir) |
| { |
| if (apr_err) |
| return svn_error_createf (apr_err, 0, NULL, pool, |
| "Error creating dir %s (path exists)", |
| full_path->data); |
| } |
| } |
| |
| /* Now that we are ensured that the parent path for this file |
| exists, try once more to open it. */ |
| apr_err = apr_file_open (fh, path->data, |
| APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY, |
| APR_OS_DEFAULT, pool); |
| if (apr_err) |
| return svn_error_createf (apr_err, 0, NULL, pool, |
| "Error opening writable file %s", path->data); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| dump_contents (apr_file_t *fh, |
| svn_fs_root_t *root, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| apr_status_t apr_err; |
| apr_size_t len, len2; |
| svn_stream_t *stream; |
| unsigned char buffer[1024]; |
| |
| /* Get a stream to the current file's contents. */ |
| SVN_ERR (svn_fs_file_contents (&stream, root, path->data, pool)); |
| |
| /* Now, route that data into our temporary file. */ |
| while (1) |
| { |
| len = sizeof (buffer); |
| SVN_ERR (svn_stream_read (stream, buffer, &len)); |
| len2 = len; |
| apr_err = apr_file_write (fh, buffer, &len2); |
| if ((apr_err) || (len2 != len)) |
| return svn_error_createf |
| (apr_err ? apr_err : SVN_ERR_INCOMPLETE_DATA, 0, NULL, pool, |
| "Error writing contents of %s", path->data); |
| if (len != sizeof (buffer)) |
| break; |
| } |
| |
| /* And close the file. */ |
| apr_file_close (fh); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Recursively print all nodes in the tree that have been modified |
| (do not include directories affected only by "bubble-up"). */ |
| static svn_error_t * |
| print_diff_tree (svn_fs_root_t *root, |
| svn_fs_root_t *base_root, |
| svn_repos_node_t *node, |
| svn_stringbuf_t *path, |
| apr_pool_t *pool) |
| { |
| svn_repos_node_t *tmp_node; |
| svn_stringbuf_t *full_path; |
| svn_stringbuf_t *orig_path = NULL, *new_path = NULL; |
| apr_file_t *fh1, *fh2; |
| |
| if (! node) |
| return SVN_NO_ERROR; |
| |
| /* Print the node. */ |
| tmp_node = node; |
| |
| /* First, we'll just print file content diffs. */ |
| if (tmp_node->kind == svn_node_file) |
| { |
| /* Here's the generalized way we do our diffs: |
| |
| - First, dump the contents of the new version of the file |
| into the svnlook temporary directory, building out the |
| actual directories that need to be created in order to |
| fully represent the filesystem path inside the tmp |
| directory. |
| |
| - Then, dump the contents of the old version of the file into |
| the top level of the svnlook temporary directory using a |
| unique temporary file name (we do this *after* putting the |
| new version of the file there in case something actually |
| versioned has a name that looks like one of our unique |
| identifiers). |
| |
| - Next, we run 'diff', passing the repository path as the |
| label. |
| |
| - Finally, we delete the temporary files (but leave the |
| built-out directories in place until after all diff |
| handling has been finished). */ |
| if ((tmp_node->action == 'R') && (tmp_node->text_mod)) |
| { |
| new_path = svn_stringbuf_create (SVNLOOK_TMPDIR, pool); |
| svn_path_add_component (new_path, path); |
| SVN_ERR (open_writable_binary_file (&fh1, new_path, pool)); |
| SVN_ERR (dump_contents (fh1, root, path, pool)); |
| apr_file_close (fh1); |
| |
| SVN_ERR (svn_io_open_unique_file (&fh2, &orig_path, new_path->data, |
| NULL, FALSE, pool)); |
| SVN_ERR (dump_contents (fh2, base_root, path, pool)); |
| apr_file_close (fh2); |
| } |
| if (tmp_node->action == 'A') |
| { |
| new_path = svn_stringbuf_create (SVNLOOK_TMPDIR, pool); |
| svn_path_add_component (new_path, path); |
| SVN_ERR (open_writable_binary_file (&fh1, new_path, pool)); |
| SVN_ERR (dump_contents (fh1, root, path, pool)); |
| apr_file_close (fh1); |
| |
| SVN_ERR (svn_io_open_unique_file (&fh2, &orig_path, new_path->data, |
| NULL, FALSE, pool)); |
| apr_file_close (fh2); |
| } |
| if (tmp_node->action == 'D') |
| { |
| new_path = svn_stringbuf_create (SVNLOOK_TMPDIR, pool); |
| svn_path_add_component (new_path, path); |
| SVN_ERR (open_writable_binary_file (&fh1, new_path, pool)); |
| apr_file_close (fh1); |
| |
| SVN_ERR (svn_io_open_unique_file (&fh2, &orig_path, new_path->data, |
| NULL, FALSE, pool)); |
| SVN_ERR (dump_contents (fh2, base_root, path, pool)); |
| apr_file_close (fh2); |
| } |
| } |
| |
| if (orig_path && new_path) |
| { |
| apr_file_t *outhandle; |
| apr_status_t apr_err; |
| const char *label; |
| svn_stringbuf_t *abs_path; |
| int exitcode; |
| |
| printf ("%s: %s\n", |
| ((tmp_node->action == 'A') ? "Added" : |
| ((tmp_node->action == 'D') ? "Deleted" : |
| ((tmp_node->action == 'R') ? "Modified" : "Index"))), |
| path->data); |
| printf ("===============================================================\ |
| ===============\n"); |
| fflush (stdout); |
| |
| /* Get an apr_file_t representing stdout, which is where we'll have |
| the diff program print to. */ |
| apr_err = apr_file_open_stdout (&outhandle, pool); |
| if (apr_err) |
| return svn_error_create |
| (apr_err, 0, NULL, pool, |
| "print_diff_tree: can't open handle to stdout"); |
| |
| label = apr_psprintf (pool, "%s\t(original)", path->data); |
| SVN_ERR (svn_path_get_absolute (&abs_path, orig_path, pool)); |
| SVN_ERR (svn_io_run_diff (SVNLOOK_TMPDIR, NULL, 0, label, |
| abs_path->data, path->data, |
| &exitcode, outhandle, NULL, pool)); |
| |
| /* TODO: Handle exit code == 2 (i.e. diff error) here. */ |
| |
| printf ("\n"); |
| fflush (stdout); |
| } |
| |
| /* Now, delete any temporary files. */ |
| if (orig_path) |
| apr_file_remove (orig_path->data, pool); |
| if (new_path) |
| apr_file_remove (new_path->data, pool); |
| |
| /* Return here if the node has no children. */ |
| tmp_node = tmp_node->child; |
| if (! tmp_node) |
| return SVN_NO_ERROR; |
| |
| /* Recursively handle the node's children. */ |
| full_path = svn_stringbuf_dup (path, pool); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| SVN_ERR (print_diff_tree (root, base_root, tmp_node, full_path, pool)); |
| while (tmp_node->sibling) |
| { |
| tmp_node = tmp_node->sibling; |
| svn_stringbuf_set (full_path, path->data); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| SVN_ERR (print_diff_tree (root, base_root, tmp_node, full_path, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Recursively print all nodes in the tree. |
| * |
| * ### todo: I'd like to write a more descriptive doc string for this |
| * function, but I wasn't able to figure out in a finite amount of |
| * time why it even takes the arguments it takes, or why it does what |
| * it does with them. See issue #540. -kff |
| */ |
| static void |
| print_ids_tree (svn_repos_node_t *node, |
| svn_fs_root_t *root, |
| svn_stringbuf_t *path, |
| int indentation, |
| apr_pool_t *pool) |
| { |
| svn_stringbuf_t *full_path; |
| svn_repos_node_t *tmp_node; |
| int i; |
| svn_fs_id_t *id; |
| svn_stringbuf_t *unparsed_id = NULL; |
| apr_pool_t *subpool; |
| |
| if (! node) |
| return; |
| |
| /* Print the indentation. */ |
| for (i = 0; i < indentation; i++) |
| { |
| printf (" "); |
| } |
| |
| /* Get the node's ID */ |
| tmp_node = node; |
| svn_fs_node_id (&id, root, path->data, pool); |
| if (id) |
| unparsed_id = svn_fs_unparse_id (id, pool); |
| |
| /* Print the node. */ |
| printf ("%s%s <%s>\n", |
| tmp_node->name, |
| tmp_node->kind == svn_node_dir ? "/" : "", |
| unparsed_id ? unparsed_id->data : "unknown"); |
| |
| /* Return here if the node has no children. */ |
| tmp_node = tmp_node->child; |
| if (! tmp_node) |
| return; |
| |
| /* Recursively handle the node's children. */ |
| subpool = svn_pool_create (pool); |
| full_path = svn_stringbuf_dup (path, subpool); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_ids_tree (tmp_node, root, full_path, indentation + 1, subpool); |
| while (tmp_node->sibling) |
| { |
| tmp_node = tmp_node->sibling; |
| svn_stringbuf_set (full_path, path->data); |
| svn_path_add_component_nts (full_path, tmp_node->name); |
| print_ids_tree (tmp_node, root, full_path, indentation + 1, subpool); |
| } |
| svn_pool_destroy (subpool); |
| |
| return; |
| } |
| |
| |
| /* Recursively print all nodes in the tree. If SHOW_IDS is non-zero, |
| print the id of each node next to its name. */ |
| static void |
| print_tree (svn_repos_node_t *node, |
| int indentation) |
| { |
| svn_repos_node_t *tmp_node; |
| int i; |
| |
| if (! node) |
| return; |
| |
| /* Print the indentation. */ |
| for (i = 0; i < indentation; i++) |
| { |
| printf (" "); |
| } |
| |
| /* Print the node. */ |
| tmp_node = node; |
| printf ("%s%s \n", |
| tmp_node->name, |
| tmp_node->kind == svn_node_dir ? "/" : ""); |
| |
| /* Return here if the node has no children. */ |
| tmp_node = tmp_node->child; |
| if (! tmp_node) |
| return; |
| |
| /* Recursively handle the node's children. */ |
| print_tree (tmp_node, indentation + 1); |
| while (tmp_node->sibling) |
| { |
| tmp_node = tmp_node->sibling; |
| print_tree (tmp_node, indentation + 1); |
| } |
| |
| return; |
| } |
| |
| |
| |
| |
| /*** Subcommand handlers. ***/ |
| |
| /* Print the revision's log message to stdout, followed by a newline. */ |
| static svn_error_t * |
| do_log (svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool) |
| { |
| svn_string_t *prop_value; |
| |
| SVN_ERR (get_property (&prop_value, c, SVN_PROP_REVISION_LOG, pool)); |
| |
| if (prop_value && prop_value->data) |
| { |
| if (print_size) |
| { |
| printf ("%lu\n", (unsigned long)prop_value->len); |
| } |
| |
| printf ("%s", prop_value->data); |
| } |
| else if (print_size) |
| { |
| printf ("0"); |
| } |
| |
| printf ("\n"); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print the timestamp of the commit (in the revision case) or the |
| empty string (in the transaction case) to stdout, followed by a |
| newline. */ |
| static svn_error_t * |
| do_date (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| if (c->is_revision) |
| { |
| svn_string_t *prop_value; |
| |
| SVN_ERR (get_property (&prop_value, c, SVN_PROP_REVISION_DATE, pool)); |
| |
| if (prop_value && prop_value->data) |
| { |
| /* The date stored in the repository is in a really complex |
| and precise format...and we don't care. Let's convert |
| that to just "YYYY-MM-DD hh:mm". |
| |
| ### todo: Right now, "svn dates" are not GMT, but the |
| results of svn_time_from_string are. This sucks. */ |
| apr_time_exp_t extime; |
| apr_time_t aprtime; |
| apr_status_t apr_err; |
| |
| aprtime = svn_time_from_nts (prop_value->data); |
| apr_err = apr_explode_time (&extime, aprtime, 0); |
| if (apr_err) |
| return svn_error_create (apr_err, 0, NULL, pool, |
| "do_date: error exploding time"); |
| |
| printf ("%04lu-%02lu-%02lu %02lu:%02lu GMT", |
| (unsigned long)(extime.tm_year + 1900), |
| (unsigned long)(extime.tm_mon + 1), |
| (unsigned long)(extime.tm_mday), |
| (unsigned long)(extime.tm_hour), |
| (unsigned long)(extime.tm_min)); |
| } |
| } |
| |
| printf ("\n"); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print the author of the commit to stdout, followed by a newline. */ |
| static svn_error_t * |
| do_author (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| svn_string_t *prop_value; |
| |
| SVN_ERR (get_property (&prop_value, c, SVN_PROP_REVISION_AUTHOR, pool)); |
| |
| if (prop_value && prop_value->data) |
| printf ("%s", prop_value->data); |
| |
| printf ("\n"); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print a list of all directories in which files, or directory |
| properties, have been modified. */ |
| static svn_error_t * |
| do_dirs_changed (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| svn_fs_root_t *root; |
| svn_revnum_t base_rev_id; |
| svn_repos_node_t *tree; |
| |
| SVN_ERR (get_root (&root, c, pool)); |
| if (c->is_revision) |
| base_rev_id = c->rev_id - 1; |
| else |
| base_rev_id = svn_fs_txn_base_revision (c->txn); |
| |
| if (! SVN_IS_VALID_REVNUM (base_rev_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_NO_SUCH_REVISION, 0, NULL, pool, |
| "Transaction '%s' is not based on a revision. How odd.", |
| c->txn_name); |
| |
| SVN_ERR (generate_delta_tree (&tree, c->repos, root, base_rev_id, pool)); |
| if (tree) |
| print_dirs_changed_tree (tree, svn_stringbuf_create ("", pool), pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print a list of all paths modified in a format compatible with `svn |
| update'. */ |
| static svn_error_t * |
| do_changed (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| svn_fs_root_t *root; |
| svn_revnum_t base_rev_id; |
| svn_repos_node_t *tree; |
| |
| SVN_ERR (get_root (&root, c, pool)); |
| if (c->is_revision) |
| base_rev_id = c->rev_id - 1; |
| else |
| base_rev_id = svn_fs_txn_base_revision (c->txn); |
| |
| if (! SVN_IS_VALID_REVNUM (base_rev_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_NO_SUCH_REVISION, 0, NULL, pool, |
| "Transaction '%s' is not based on a revision. How odd.", |
| c->txn_name); |
| |
| SVN_ERR (generate_delta_tree (&tree, c->repos, root, base_rev_id, pool)); |
| if (tree) |
| print_changed_tree (tree, svn_stringbuf_create ("", pool), pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print some diff-y stuff in a TBD way. :-) */ |
| static svn_error_t * |
| do_diff (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| svn_fs_root_t *root, *base_root; |
| svn_revnum_t base_rev_id; |
| svn_repos_node_t *tree; |
| |
| SVN_ERR (get_root (&root, c, pool)); |
| if (c->is_revision) |
| base_rev_id = c->rev_id - 1; |
| else |
| base_rev_id = svn_fs_txn_base_revision (c->txn); |
| |
| if (! SVN_IS_VALID_REVNUM (base_rev_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_NO_SUCH_REVISION, 0, NULL, pool, |
| "Transaction '%s' is not based on a revision. How odd.", |
| c->txn_name); |
| |
| SVN_ERR (generate_delta_tree (&tree, c->repos, root, base_rev_id, pool)); |
| if (tree) |
| { |
| SVN_ERR (svn_fs_revision_root (&base_root, c->fs, base_rev_id, pool)); |
| SVN_ERR (print_diff_tree |
| (root, base_root, tree, svn_stringbuf_create ("", pool), pool)); |
| apr_dir_remove_recursively (SVNLOOK_TMPDIR, pool); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print the diff between revision 0 and our our root. */ |
| static svn_error_t * |
| do_tree (svnlook_ctxt_t *c, svn_boolean_t show_ids, apr_pool_t *pool) |
| { |
| svn_fs_root_t *root; |
| svn_repos_node_t *tree; |
| |
| SVN_ERR (get_root (&root, c, pool)); |
| SVN_ERR (generate_delta_tree (&tree, c->repos, root, 0, pool)); |
| if (tree) |
| { |
| if (show_ids) |
| { |
| print_ids_tree (tree, root, |
| svn_stringbuf_create ("", pool), 0, pool); |
| } |
| else |
| { |
| print_tree (tree, 0); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print author, date, log-size, and log associated with the given |
| revision or transaction. */ |
| static svn_error_t * |
| do_info (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| SVN_ERR (do_author (c, pool)); |
| SVN_ERR (do_date (c, pool)); |
| SVN_ERR (do_log (c, TRUE, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Print author, date, log-size, log, and the tree associated with the |
| given revision or transaction. */ |
| static svn_error_t * |
| do_default (svnlook_ctxt_t *c, apr_pool_t *pool) |
| { |
| SVN_ERR (do_info (c,pool)); |
| SVN_ERR (do_tree (c, FALSE, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| /*** Argument parsing and usage. ***/ |
| static void |
| do_usage (const char *progname, int exit_code) |
| { |
| fprintf |
| (exit_code ? stderr : stdout, |
| "usage: %s REPOS_PATH rev REV [COMMAND] - inspect revision REV\n" |
| " %s REPOS_PATH txn TXN [COMMAND] - inspect transaction TXN\n" |
| " %s REPOS_PATH [COMMAND] - inspect the youngest revision\n" |
| "\n" |
| "REV is a revision number > 0.\n" |
| "TXN is a transaction name.\n" |
| "\n" |
| "If no command is given, the default output (which is the same as\n" |
| "running the subcommands `info' then `tree') will be printed.\n" |
| "\n" |
| "COMMAND can be one of: \n" |
| "\n" |
| " author: print author.\n" |
| " changed: print full change summary: all dirs & files changed.\n" |
| " date: print the timestamp (revisions only).\n" |
| " diff: print GNU-style diffs of changed files and props.\n" |
| " dirs-changed: print changed directories.\n" |
| " ids: print the tree, with nodes ids.\n" |
| " info: print the author, data, log_size, and log message.\n" |
| " log: print log message.\n" |
| " tree: print the tree.\n" |
| "\n", |
| progname, |
| progname, |
| progname); |
| |
| exit (exit_code); |
| } |
| |
| |
| |
| /*** Main. ***/ |
| |
| #define INT_ERR(expr) \ |
| do { \ |
| svn_error_t *svn_err__temp = (expr); \ |
| if (svn_err__temp) { \ |
| svn_handle_error (svn_err__temp, stderr, 0); \ |
| goto cleanup; } \ |
| } while (0) |
| |
| |
| int |
| main (int argc, const char * const *argv) |
| { |
| apr_pool_t *pool; |
| const char *repos_path = NULL; |
| int cmd_offset = 4; |
| svnlook_cmd_t command; |
| svnlook_ctxt_t c; |
| |
| /* Initialize context variable. */ |
| c.fs = NULL; |
| c.rev_id = SVN_INVALID_REVNUM; |
| c.is_revision = FALSE; |
| c.txn = NULL; |
| |
| /* We require at least 1 arguments. */ |
| if (argc < 2) |
| { |
| do_usage (argv[0], 1); |
| return EXIT_FAILURE; |
| } |
| |
| /* Argument 1 is the repository path. */ |
| repos_path = argv[1]; |
| |
| /* Argument 2 could be "rev" or "txn". If "rev", Argument 3 is a |
| numerical revision number. If "txn", Argument 3 is a transaction |
| name string. If neither, this is an inspection of the youngest |
| revision. */ |
| if (argc > 3) |
| { |
| if (! strcmp (argv[2], "txn")) /* transaction */ |
| { |
| c.is_revision = FALSE; |
| c.txn_name = (char *)argv[3]; |
| } |
| else if (! strcmp (argv[2], "rev")) /* revision */ |
| { |
| c.is_revision = TRUE; |
| c.rev_id = SVN_STR_TO_REV (argv[3]); |
| if (c.rev_id < 1) |
| { |
| do_usage (argv[0], 1); |
| return EXIT_FAILURE; |
| } |
| } |
| else |
| { |
| c.is_revision = TRUE; |
| cmd_offset = 2; |
| } |
| } |
| else |
| { |
| c.is_revision = TRUE; |
| cmd_offset = 2; |
| } |
| |
| /* If there is a subcommand, parse it. */ |
| if (argc > cmd_offset) |
| { |
| if (! strcmp (argv[cmd_offset], "author")) |
| command = svnlook_cmd_author; |
| else if (! strcmp (argv[cmd_offset], "changed")) |
| command = svnlook_cmd_changed; |
| else if (! strcmp (argv[cmd_offset], "date")) |
| command = svnlook_cmd_date; |
| else if (! strcmp (argv[cmd_offset], "diff")) |
| command = svnlook_cmd_diff; |
| else if (! strcmp (argv[cmd_offset], "dirs-changed")) |
| command = svnlook_cmd_dirs_changed; |
| else if (! strcmp (argv[cmd_offset], "ids")) |
| command = svnlook_cmd_ids; |
| else if (! strcmp (argv[cmd_offset], "info")) |
| command = svnlook_cmd_info; |
| else if (! strcmp (argv[cmd_offset], "log")) |
| command = svnlook_cmd_log; |
| else if (! strcmp (argv[cmd_offset], "tree")) |
| command = svnlook_cmd_tree; |
| else |
| { |
| do_usage (argv[0], 2); |
| return EXIT_FAILURE; |
| } |
| } |
| else |
| { |
| command = svnlook_cmd_default; |
| } |
| |
| /* Now, let's begin processing. */ |
| |
| /* Initialize APR and our top-level pool. */ |
| apr_initialize (); |
| pool = svn_pool_create (NULL); |
| |
| /* Open the repository with the given path. */ |
| INT_ERR (svn_repos_open (&(c.repos), repos_path, pool)); |
| c.fs = svn_repos_fs (c.repos); |
| |
| /* If this is a transaction, open the transaction. */ |
| if (! c.is_revision) |
| INT_ERR (svn_fs_open_txn (&(c.txn), c.fs, c.txn_name, pool)); |
| |
| /* If this is a revision with an invalid revision number, just use |
| the head revision. */ |
| if (c.is_revision && (! SVN_IS_VALID_REVNUM (c.rev_id))) |
| INT_ERR (svn_fs_youngest_rev (&(c.rev_id), c.fs, pool)); |
| |
| /* Now, out context variable is full of all the stuff we might need |
| to know. Get to work. */ |
| switch (command) |
| { |
| case svnlook_cmd_author: |
| INT_ERR (do_author (&c, pool)); |
| break; |
| |
| case svnlook_cmd_changed: |
| INT_ERR (do_changed (&c, pool)); |
| break; |
| |
| case svnlook_cmd_date: |
| INT_ERR (do_date (&c, pool)); |
| break; |
| |
| case svnlook_cmd_diff: |
| INT_ERR (do_diff (&c, pool)); |
| break; |
| |
| case svnlook_cmd_dirs_changed: |
| INT_ERR (do_dirs_changed (&c, pool)); |
| break; |
| |
| case svnlook_cmd_ids: |
| INT_ERR (do_tree (&c, TRUE, pool)); |
| break; |
| |
| case svnlook_cmd_info: |
| INT_ERR (do_info (&c, pool)); |
| break; |
| |
| case svnlook_cmd_log: |
| INT_ERR (do_log (&c, FALSE, pool)); |
| break; |
| |
| case svnlook_cmd_tree: |
| INT_ERR (do_tree (&c, FALSE, pool)); |
| break; |
| |
| case svnlook_cmd_default: |
| default: |
| INT_ERR (do_default (&c, pool)); |
| break; |
| } |
| |
| cleanup: /* Cleanup after ourselves. */ |
| if (c.txn && (! c.is_revision)) |
| svn_fs_close_txn (c.txn); |
| |
| if (c.repos) |
| svn_repos_close (c.repos); |
| |
| svn_pool_destroy (pool); |
| apr_terminate (); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |