/* cmd-annotate.c:
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2005 Canonical Ltd.
* Authors: Robert Collins <robert.collins@canonical.com>
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/tmp-files.h"
#include "libarch/annotation-builder.h"
#include "libarch/changeset-report.h"
#include "libarch/namespace.h"
#include "libarch/project-tree.h"
#include "libarch/my.h"
#include "libarch/ancestry.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/patch-logs.h"
#include "libarch/invent.h"
#include "libarch/local-cache.h"
#include "libarch/project-tree.h"
#include "libfsutils/file-contents.h"
#include "commands/cmdutils.h"
#include "commands/annotate.h"
#include "commands/version.h"
static t_uchar * usage = N_("[options] [path ...]");
#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\n" \
"and exit.")) \
OP (opt_dir, "d", "dir DIR", 1, \
N_("cd to DIR first"))
/* OP (opt_merges, "m", "merges", 0, \
N_("follow merges to determine the original author of each change line")) */
t_uchar arch_cmd_annotate_help[] = N_("display the changesets that last modified lines in the tree\n");
enum options
{
OPTS (OPT_ENUM)
};
static struct opt_desc opts[] =
{
OPTS (OPT_DESC)
{-1, 0, 0, 0, 0}
};
/**
* \brief return the content of a file, broken at newlines
*/
static rel_table
tree_file_lines (arch_project_tree_t *tree, t_uchar const *rel_path)
{
t_uchar *path=file_name_in_vicinity (0, tree->root, rel_path);
rel_table result = file_lines (path);
lim_free (0, path);
return result;
}
/**
* \brief return the number of lines in the file
*/
static int
tree_file_line_count (arch_project_tree_t *tree, t_uchar const *rel_path)
{
rel_table lines = tree_file_lines (tree, rel_path);
int result = rel_n_records (lines);
rel_free_table (lines);
return result;
}
/**
* \brief get the contents of a patch file from disk
*
* fixes up the broken paths returned by evaluate_changeset
* \param patch_path the relpath to the file in the changeset
*
*/
static t_uchar *
patch_contents (t_uchar const * patch_path)
{
t_uchar *path=str_alloc_cat (0, patch_path, ".patch");
t_uchar *result=file_contents (path);
lim_free (0, path);
return result;
}
/**
* \brief return non zero if a file is a symlink
*/
static int
is_symlink (t_uchar const *path)
{
struct stat stat_info;
safe_lstat ((t_uchar *)path, &stat_info);
return S_ISLNK (stat_info.st_mode);
}
/**
* \brief return non zero if a file is a symlink
*/
static int
tree_is_symlink (arch_project_tree_t *tree, t_uchar const *rel_path)
{
t_uchar *path=file_name_in_vicinity (0, tree->root, rel_path);
int result = is_symlink (path);
lim_free (0, path);
return result;
}
int
arch_cmd_annotate (t_uchar * program_name, int argc, char * argv[])
{
int o;
struct opt_parsed * option;
t_uchar * dir = 0;
safe_buffer_fd (1, 0, O_WRONLY, 0);
dir = str_save (0, ".");
option = 0;
while (1)
{
o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_cmd_annotate_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_dir:
{
lim_free (0, dir);
dir = str_save (0, option->arg_string);
break;
}
}
}
{
arch_project_tree_t * tree;
t_uchar * current_revision;
arch_annotation_builder_t * builder;
rel_table source_files;
arch_archive_connection_cache cache;
arch_archive_connection_cache_init (&cache);
tree = arch_project_tree_new (talloc_context, dir);
source_files = arch_paths_from_user (NULL, "annotate", tree, argc - 1, &argv[1]);
builder = arch_annotation_builder_new (talloc_context);
/* seed the builder */
{
int position;
for (position = 0; position < rel_n_records (source_files); ++position)
{
invariant (0 < str_length (source_files[position][1]));
/* we don't keep the files in memory at this point, as that is a heavy memory burden for large trees,
* and for small trees they'll likely stay in cache
* -RBC 20050223
*/
if (!tree_is_symlink (tree, source_files[position][0]))
arch_annotation_builder_add_file (builder, source_files[position][1], tree_file_line_count(tree, source_files[position][0]));
}
}
current_revision = str_save (0, tree->fqrevision);
while (current_revision)
{
t_uchar * next_rev = NULL;
struct exception * e;
Try
{
arch_patch_id * patch_id = talloc_steal (talloc_context,
arch_patch_id_new (current_revision));
arch_patch_id * patch_result = arch_patch_ancestor (talloc_context,
patch_id, 0);
if (patch_result)
next_rev = str_save (0, arch_patch_id_patch_id (patch_result));
else
next_rev = NULL;
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
/* cannot dig deeper */
lim_free (0, current_revision);
current_revision = NULL;
next_rev = NULL;
}
{
arch_patch_id aPatch;
// t_uchar *creator;
struct arch_archive * this_arch;
arch_patch_id_init (&aPatch, current_revision);
this_arch = arch_archive_connection_cache_find_or_maybe_connect (&cache, arch_patch_id_archive(&aPatch), 1);
// creator =
arch_annotation_builder_add_patch (builder, &aPatch, "creator");
if (!this_arch)
{
/* cannot dig deeper */
lim_free (0, next_rev);
lim_free (0, current_revision);
current_revision = NULL;
continue;
}
if (arch_archive_get_revision_type(this_arch, arch_patch_id_revision (&aPatch)) == arch_import_revision)
/*
* get the file lines, and synthetically process
* as though its a patch.
*/
{
t_uchar * temp_root = arch_find_or_make_local_copy (-1, tree, NULL, NULL, arch_patch_id_archive(&aPatch),
arch_patch_id_revision (&aPatch));
arch_project_tree_t * temp_tree;
rel_table reference_source_files;
int file;
temp_tree = arch_project_tree_new (talloc_context, temp_root);
reference_source_files = arch_source_files_inventory (temp_tree, 0, 0);
for (file = 0; file < rel_n_records (reference_source_files); ++file)
{
int line;
int lines;
if (!arch_annotation_builder_has_file (builder, reference_source_files[file][1]))
continue;
if (tree_is_symlink (temp_tree, reference_source_files[file][0]))
continue;
lines = tree_file_line_count(temp_tree, reference_source_files[file][0]);
arch_annotation_builder_add_file (builder, reference_source_files[file][1], -1);
for (line=0; line < lines; ++ line)
arch_annotation_builder_add_line(builder, 0);
}
arch_project_tree_delete (temp_tree);
lim_free (0, temp_root);
}
else
{
/* get unpacked changeset */
t_uchar * tmppatch = talloc_tmp_file_name (talloc_context, tree->root, ",,annotate-patch");
struct arch_changeset_report changeset;
int file;
arch_get_patch (this_arch, &aPatch, tmppatch);
/* create memory object */
arch_changeset_report_init (&changeset);
arch_evaluate_changeset (&changeset, tmppatch);
//rel_print_table (2, changeset.patched_regular_files);
/* patched files */
for (file = 0; file < rel_n_records (changeset.patched_regular_files); ++file)
{
t_uchar *patch_content;
patch_line_changes_list changes;
if (!arch_annotation_builder_has_file (builder, changeset.patched_regular_files[file][1]))
continue;
patch_content = patch_contents (changeset.patched_regular_files[file][2]);
changes = patch_parser_parse(patch_content);
arch_annotation_builder_add_file (builder, changeset.patched_regular_files[file][1], -1);
arch_annotation_builder_process_changes(builder, changes);
ar_free_patch_line_change(&changes);
lim_free (0, patch_content);
}
rel_for_each (changeset.added_files, file)
{
int line;
int lines;
if (!arch_annotation_builder_has_file (builder, changeset.added_files[file][1]))
continue;
lines = file_line_count(changeset.added_files[file][2]);
arch_annotation_builder_add_file (builder, changeset.added_files[file][1], -1);
for (line=0; line < lines; ++ line)
arch_annotation_builder_add_line(builder, 0);
}
arch_free_changeset_report_data (&changeset);
rmrf_file(tmppatch);
talloc_free (tmppatch);
}
arch_patch_id_finalise (&aPatch);
lim_free (0, current_revision);
current_revision = next_rev;
}
}
/* print the result */
/* TODO make this callable with an fd and a list of files */
{
int position;
for (position = 0; position < rel_n_records (source_files); ++position)
{
int line;
rel_table lines;
arch_annotated_file_t *aFile;
invariant (0 < str_length (source_files[position][1]));
if (tree_is_symlink (tree, source_files[position][0]))
continue;
safe_printfmt (1, "ANNOTATIONS FOR %s\n", source_files[position][0]);
lines=tree_file_lines (tree, source_files[position][0]);
aFile = arch_annotation_builder_get_file (builder, talloc_context, source_files[position][1]);
for (line = 0; line < rel_n_records (lines); ++line)
{
if (aFile->lines[line])
safe_printfmt (1, "%s: %s\n", arch_patch_id_patch_id(arch_patch_patch_id(aFile->lines[line])), lines[line][0]);
else
safe_printfmt (1, "unknown: %s\n", lines[line][0]);
}
talloc_free (aFile);
rel_free_table (lines);
}
}
rel_free_table (source_files);
arch_archive_connection_cache_finalise (&cache, NULL);
arch_project_tree_delete (tree);
talloc_free (builder);
}
lim_free (0, dir);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1