/* cmd-log.c
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2005 Canonocal Ltd.
* Original Authors Robert Collins and James Blackwell
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
/* FIXME: SEE THIS USING STDLIB FOR ATOI. THIS NEEDS TO BE IN HACKERLAB */
#include <stdlib.h>
/* FIXME: SEE THIS USING STDLIB FOR ATOI. THIS NEEDS TO BE IN HACKERLAB */
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "libarch/ancestry.h"
#include "libarch/merge.h"
#include "libarch/patch-logs.h"
#include "libarch/project-tree.h"
#include "libarch/namespace.h"
#include "commands/log.h"
#include "commands/version.h"
enum log_types {
LOG_BEGIN,
LOG_END
};
static int get_logs(rel_table * output, arch_project_tree_t * tree);
static rel_record record_for_patch (arch_patch_id * patch_id, arch_project_tree_t *tree);
static int qualify_revision (arch_project_tree_t * tree,
t_uchar * source,
t_uchar ** qualed,
int log_type);
static int limit_logs_by_range (rel_table * output, rel_table input,
t_uchar * start, t_uchar * end);
static int split_range (t_uchar * range_input,
t_uchar ** start,
t_uchar **end);
static t_uchar * log_format_short (t_uchar * revision,
t_uchar * date,
t_uchar * creator,
t_uchar * log);
static t_uchar * usage = N_("[-f|--full] [-r start[:end]]");
#define OPTS(OP) \
OP (opt_help_msg, "h", "help", 0, \
N_("Display a help message and exit.")) \
OP (opt_long_help, "H", 0, 0, \
N_("Display a verbose help message and exit.")) \
OP (opt_version, "V", "version", 0, \
N_("Display a release identifier string and exit.")) \
OP (opt_range, "r", "range", 1, \
N_("-r start[:end], where start+end are PATCH or PACKAGE")) \
OP (opt_full_logs, "f", "full", 0, \
N_("show the entire log message rather than the summary"))
t_uchar arch_cmd_log_help[] = N_(
"Show the log messages for the working directory or given\n"
"files/directories, since the beginning of time (crossing\n"
"copies/renames) or since/between the revisions/dates \n"
"given. Can also be run out of a working directory to\n"
" interrogate an archive instead of a working tree->\n"
"\n"
"Examples:\n"
"Show me the changes made to scripts/dpkg-source.pl in the \n"
"archive:\n"
" %% baz log -r dpkg--devel--1.13 scripts/dpkg-source.pl\n"
"\n"
"Show me the changes between a different branch's \n"
"patch-4, my branches patch-2:\n"
" %% baz log -r dpkg--devel--1.13--patch-4:patch-2\n"
"\n"
"Show me the changes in all revisions of the tree-version\n"
"after and including patch-123.\n"
" %% baz log -r patch-123:\n"
"\n"
" Show me the last ten changes:\n"
" %% baz log -r 10\n");
enum options
{
OPTS (OPT_ENUM)
};
static struct opt_desc opts[] =
{
OPTS (OPT_DESC)
{-1, 0, 0, 0, 0}
};
int
arch_cmd_log (t_uchar * program_name, int argc, char * argv[])
{
int o;
t_uchar * log_range_start = 0;
t_uchar * log_range_end = 0;
int show_full_logs = 0;
struct opt_parsed * option;
safe_buffer_fd (1, 0, O_WRONLY, 0);
option = 0;
while (1)
{
o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_cmd_log_help, opt_help_msg, opt_long_help, opt_version);
if (o == opt_none)
break;
switch (o)
{
default:
safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
panic ("internal error parsing arguments");
usage_error:
opt_usage (2, argv[0], program_name, usage, 1);
exit (1);
/* bogus_arg: */
safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
goto usage_error;
case opt_range :
{
if ( log_range_start || log_range_end )
panic(N_("More than exactly one range provided."));
if (! split_range (option->arg_string,
&log_range_start,
&log_range_end))
{
panic(N_("Invalid range given"));
}
break;
}
case opt_full_logs:
{
show_full_logs = 1;
break;
}
}
}
{
arch_project_tree_t * tree;
rel_table unlimited_logs = 0;
rel_table range_limited_logs = 0;
t_uchar * latest_revision;
t_uchar * fqrvn;
t_uchar * revision_start = 0;
t_uchar * revision_end = 0;
int status;
int loop;
/* Before we can do much, we have to figure out where we are */
tree = arch_project_tree_new (talloc_context, ".");
latest_revision = arch_tree_latest_revision (tree->root);
fqrvn = str_alloc_cat_many (0, tree->archive, "/", latest_revision, str_end);
if (get_logs ( & unlimited_logs , tree))
panic (N_("Unable to get information about previous revisions\n"));
/* Now, lets pound our user input into something useful */
if (argc > 1)
panic (N_("Unhandled arguments. Did you forget to use -r or -d?"));
/* First, get the start revision right */
if (log_range_start)
if (0 != qualify_revision (tree, log_range_start,
&revision_start, LOG_BEGIN))
panic(N_("Unable to parse range start."));
/* Now, get the end revision right */
if (log_range_end)
if (0 != qualify_revision (tree, log_range_end, &revision_end, LOG_END))
panic (N_("Unable to parse range end."));
/* Now lint out the stuff the user told us he cared about */
status = limit_logs_by_range (&range_limited_logs, unlimited_logs,
revision_start, revision_end);
/* All that work, just to print... sigh */
for (loop = 0; loop < rel_n_records (range_limited_logs); loop++)
{
if (show_full_logs)
{
t_uchar * log = 0;
log = arch_log_contents (tree->root,
tree->archive,
range_limited_logs[loop][0]);
safe_printfmt(1, "%s\n", log);
safe_printfmt(1, " ================================"
"==================================\n\n");
lim_free (0, log);
}
else
{
t_uchar * output = log_format_short (range_limited_logs[loop][0],
range_limited_logs[loop][1],
range_limited_logs[loop][2],
range_limited_logs[loop][3]);
safe_printfmt(1, "%s", output);
lim_free (0, output);
}
}
arch_project_tree_delete (tree);
rel_free_table (unlimited_logs);
lim_free (0, fqrvn);
lim_free (0, latest_revision);
}
return 0;
}
t_uchar *
log_format_short (t_uchar * revision,
t_uchar * date,
t_uchar * creator,
t_uchar * log)
{
t_uchar * output = 0;
output = str_alloc_cat_many (0, revision, "\n",
" ", date, "\n",
" ", creator, "\n",
" ", log, "\n\n",
str_end);
return output;
}
int
split_range (t_uchar * range_input, t_uchar ** start, t_uchar **end)
{
t_uchar * colon;
if (! range_input)
return 1;
colon = str_chr_index(range_input, ':');
if ( !colon)
*start = str_save (0, range_input);
else
{
*start = str_save_n (0, range_input, colon - range_input);
*end = str_save (0, colon+1);
}
return 1;
}
int
qualify_revision (arch_project_tree_t * tree, t_uchar * source,
t_uchar ** qualed, int log_type)
{
t_uchar * with_archive = 0;
/* Check to see if we're already done */
if ( arch_valid_package_name ( source, arch_maybe_archive,
arch_req_patch_level, 0))
{
return 0;
}
if ( 0 != atoi(source))
{
*qualed = str_save (0, source);
return 0;
}
/* If given patchlevel, we need to qualify it and return */
if (arch_valid_patch_level_name (source))
{
*qualed = str_alloc_cat_many (0, tree->fqversion, "--",
source, str_end);
return 0;
}
/* Now it get harder. First, make sure its fully qualified */
if (str_chr_index (source, '/'))
with_archive = str_save (0, source);
else
with_archive = str_alloc_cat_many (0, tree->archive, "/",
source, str_end );
/* Now, check to ee if we were given a version. The patchlevel
* we want is determined by whether we're doing the begin, or
* the end
*/
if ( arch_valid_package_name (source, arch_req_archive,
arch_req_version,0))
{
if (log_type == LOG_END)
*qualed = str_save (0, with_archive);
else /* LOG_BEGIN */
*qualed = str_alloc_cat_many (0, with_archive, "--base-0", str_end);
}
return 0;
}
int
limit_logs_by_range (rel_table * output,
rel_table input,
t_uchar * input_start,
t_uchar * input_end)
{
int loop;
t_uchar * start = 0;
t_uchar * end = 0;
int start_offset = 0;
int loop_end=-1;
int end_offset;
int start_found = 0;
int end_found = 0;
/* First, we setup our start offset and our start. We can't
* have both, so either one is 0, or they're both 0 */
if (! str_length(input_start))
{
start_offset = 0;
start = 0;
}
else
{
if ( atoi(input_start))
{
start_offset = atoi(input_start);
start = 0;
}
else
{
start_offset = 0;
start = str_save(0, input_start);
}
}
/* First, we setup our end offset and our end. We can't
* have both, so either one is 0, or they're both 0 */
if (! str_length(input_end))
{
end_offset = 0;
end = 0;
}
else
{
if ( atoi(input_end))
{
end_offset = atoi(input_end);
end = 0;
}
else
{
end_offset = 0;
end = str_save(0, input_end);
}
}
/* If there's no end, then we've already found it, and we need
* to start counting the number of logs we added
*/
if ( !end )
{
end_found = 1;
if (start_offset)
loop_end = start_offset;
}
for (loop = end_offset;!start_found && loop < rel_n_records(input); loop++)
{
/* Keep skipping until we find the end */
if (! end_found && str_cmp_prefix (end, input[loop][0]))
continue;
/* As soon as we find the end, we need to calculate where the
* number of hops for where the start is
*/
if (! end_found)
{
if (start_offset)
loop_end = loop + start_offset;
end_found = 1;
}
if ( loop == loop_end)
start_found = 1;
/* Is the the starting revision we're looking for */
if ( start && 0 == str_cmp_prefix (start, input[loop][0]))
start_found = 1;
rel_add_records (output, rel_copy_record (input[loop]), 0);
}
return !(!start || start_found) && (!end || end_found);
}
/**
* \brief retrieve the logs we will output according to the users requests
* the output rel table has the results appended with the revision in 0, the
* date in 1 and the summary in 2.
* if a non fatal error occurs, the result is non zero.
*/
int
get_logs(rel_table * output, arch_project_tree_t *tree)
{
int result = 0;
/* The idea behind this is that we're keeping our interface to the other
* (libarch) api as simple as possible. If necessary, between this api and
* the libarch api, we write a conversion function, an "adaptor" that works
* as a shim between the two. Some day,if you ahve control over the other
* api, you could potentially refactor it out, converting other callers to
* the new interface - assuming its more sensible for them.
* Otherwise, that's a sign that the api leaning towards 97 pound
* weakling, and needs to support both( or more) apis.
*/
struct exception * e;
Try
{
arch_patch_id * current_revision = talloc_steal (talloc_context,
arch_patch_id_new (tree->fqrevision));
while (current_revision)
{
rel_add_records ( output, record_for_patch( current_revision, tree), 0);
arch_patch_id * next = arch_patch_ancestor (talloc_context, current_revision, 0);
talloc_free (current_revision);
current_revision = next;
}
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
}
return result;
}
/**
* \brief retrieve the logs we will output according to the users requests
* the output rel table has the results appended with the revision in 0, the date in 1 and the
* summary in 2.
* if a non fatal error occurs, the result is non zero.
*/
rel_record
record_for_patch (arch_patch_id * patch_id, arch_project_tree_t *tree)
{
rel_record output = 0;
assoc_table headers = 0;
t_uchar * summary = "";
t_uchar * log;
log = arch_project_tree_patch_log (tree, arch_patch_id_patch_id (patch_id));
if (log)
{
arch_parse_log (0, &headers, 0, log);
if (!assoc_ref (headers, "summary"))
summary = "";
else
summary = assoc_ref(headers, "summary");
output = rel_make_record ( arch_patch_id_patch_id (patch_id),
assoc_ref (headers, "date"),
assoc_ref (headers, "creator"),
summary, 0);
}
else
{
output = rel_make_record (arch_patch_id_patch_id (patch_id),
N_("UNKNOWN DATE"),
N_("UNKNOWN CREATOR"),
N_("LOG MISSING"), 0);
}
return output;
}
/* tag: James Blackwell Sun Feb 6 18:21:45 EST 2005 (log.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1