/* 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 /* 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) */