/* cmd-annotate.c: * * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2 **************************************************************** * Copyright (C) 2005 Canonical Ltd. * Authors: Robert Collins * * 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; }