blob: d06060f2bb0216d83d310367d9f37f1e03ca5381 [file] [log] [blame]
/*
* 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:
*/