/* make-changeset.c: * * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2 **************************************************************** * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/mem.h" #include "hackerlab/char/str.h" #include "hackerlab/char/char-class.h" #include "hackerlab/arrays/ar.h" #include "hackerlab/sort/qsort.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/fs/cwd.h" #include "hackerlab/vu/safe.h" #include "hackerlab/char/pika-escaping-utils.h" #include "libfsutils/ensure-dir.h" #include "libfsutils/dir-as-cwd.h" #include "libfsutils/copy-file.h" #include "libfsutils/link-target.h" #include "libawk/associative.h" #include "po/gettext.h" #include "libarch/arch.h" #include "libarch/debug.h" #include "libarch/diffs.h" #include "libarch/invent.h" #include "libarch/project-tree.h" #include "libarch/make-changeset.h" typedef struct changeset_maker_ { rel_table * orig_index_names_output; rel_table * mod_index_names_output; struct arch_make_changeset_report * report; int escape_classes; t_uchar * patches; } changeset_maker; /* This struct isn't strictly used by write_changeset, but contains lots of auxiliary data * that might be of use elsewhere */ struct changeset_vars_ids { rel_table orig_dir_ids; rel_table mod_dir_ids; rel_table original_only_dir_ids; rel_table modified_only_dir_ids; rel_table dir_ids_in_both; rel_table orig_file_ids; rel_table mod_file_ids; rel_table original_only_file_ids; rel_table modified_only_file_ids; rel_table file_ids_in_both; }; struct changeset_vars { /* Original and modified trees */ arch_project_tree_t * orig_tree; arch_project_tree_t * mod_tree; /* Lists of names of items changed in some way, or common between both */ rel_table original_only_dirs; rel_table modified_only_dirs; rel_table original_only_files; rel_table modified_only_files; rel_table files_in_both; rel_table dirs_in_both; rel_table renamed_dirs; rel_table renamed_files; rel_table orig_dirs_full_index_by_name; rel_table orig_files_full_index_by_name; rel_table mod_dirs_full_index_by_name; rel_table mod_files_full_index_by_name; /* Extra data */ rel_table orig_full_index; rel_table mod_full_index; rel_table mod_full_index_by_name; rel_table orig_full_index_by_name; assoc_table orig_ids_seen; assoc_table mod_ids_seen; struct changeset_vars_ids ids; }; #if !defined(__GNUC__) # undef __attribute__ # define __attribute__(X) #endif static void __attribute__((format (printf, 2, 3))) invoke_report_callback (struct arch_make_changeset_report * report, char * fmt, ...); static void compute_renames (rel_table * out, assoc_table loc_of_orig_X_id, assoc_table mod_dir_id_of, assoc_table orig_container_dir_id_of_X_id, rel_table X_index); static void write_dir_metadata (t_uchar * file, t_uchar * tree_root, rel_table dirs); static void copy_files_and_symlinks (t_uchar * dest, t_uchar * tree_root, rel_table loc_list); static void emit_dir_patches (rel_table * orig_index_names_output, rel_table * mod_index_names_output, struct arch_make_changeset_report * report, t_uchar * patches, t_uchar * orig, t_uchar * orig_loc, t_uchar * mod, t_uchar * mod_loc, int escape_classes); static void emit_file_or_symlink_patches (rel_table * orig_index_names_output, rel_table * mod_index_names_output, struct arch_make_changeset_report * report, t_uchar * patches, t_uchar * orig, t_uchar * orig_loc, t_uchar * mod, t_uchar * mod_loc, t_uchar * id, assoc_table inode_sig_shortcuts_of_mod, int link_same, int escape_classes); static void compute_parent_dir_closure (rel_table * orig_newname_dirs, rel_table * mod_newname_dirs, rel_table orig_newname_files, rel_table mod_newname_files, assoc_table orig_dir_loc_of, assoc_table orig_dir_id_of, assoc_table orig_container_dir_id_of_dir_id, assoc_table orig_container_dir_id_of_file_id, assoc_table mod_dir_loc_of, assoc_table mod_dir_id_of, assoc_table mod_container_dir_id_of_dir_id, assoc_table mod_container_dir_id_of_file_id, assoc_table orig_file_id_of, assoc_table mod_file_id_of); static void schedule_missing_containers (rel_table * added_orig_container_ids, assoc_table * orig_has_dir_id, rel_table * added_mod_container_ids, assoc_table * mod_has_dir_id, rel_table locs_in, assoc_table id_of_dir_loc, assoc_table first_container_id_of_id, assoc_table container_id_of_dir_id, assoc_table orig_dir_loc_of, assoc_table mod_dir_loc_of); static void container_closure (rel_table * locs_out, assoc_table * has_ids, t_uchar * deep_id, assoc_table dir_loc_of, assoc_table container_dir_id_of_dir_id); static void link_force (t_uchar *old_path, t_uchar *new_path); static void emit_symlink_patch (changeset_maker *self, t_uchar * orig_loc, t_uchar * orig_target, t_uchar * mod_loc, t_uchar * mod_target); static void note_change (changeset_maker *self, t_uchar * orig_loc, t_uchar * mod_loc); static void rel_merge_to_assoc_unique (rel_table from, assoc_table *to, t_uchar const *error_message); static rel_table canonicalise_limits (rel_table limits_spec, t_uchar const * mod_root); static void make_changeset_inventory (struct arch_changeset_inventory * index, arch_project_tree_t *tree, enum arch_id_tagging_method method, enum arch_inventory_category untagged_source_category, int escape_classes, rel_table *orig_full_index, rel_table *orig_full_index_by_name); void arch_free_make_changeset_report_data (struct arch_make_changeset_report * report) { arch_free_changeset_inventory_data (&report->orig_index); arch_free_changeset_inventory_data (&report->mod_index); free_assoc_table (report->orig_dir_id_of); free_assoc_table (report->orig_dir_loc_of); free_assoc_table (report->orig_file_id_of); free_assoc_table (report->orig_file_loc_of); free_assoc_table (report->mod_dir_id_of); free_assoc_table (report->mod_dir_loc_of); free_assoc_table (report->mod_file_id_of); free_assoc_table (report->mod_file_loc_of); free_assoc_table (report->orig_container_dir_id_of_dir_id); free_assoc_table (report->orig_container_dir_id_of_file_id); free_assoc_table (report->mod_container_dir_id_of_dir_id); free_assoc_table (report->mod_container_dir_id_of_file_id); rel_free_table (report->renamed_dirs); rel_free_table (report->renamed_files); rel_free_table (report->modified_files); rel_free_table (report->perms_changed_files); rel_free_table (report->perms_changed_dirs); rel_free_table (report->added_files); rel_free_table (report->added_dirs); rel_free_table (report->removed_files); rel_free_table (report->removed_dirs); } void invoke_report_callback (struct arch_make_changeset_report * report, char * fmt, ...) { va_list ap; va_start (ap, fmt); report->callback (report->thunk, fmt, ap); va_end (ap); } void rel_merge_to_assoc_unique (rel_table from, assoc_table *to, t_uchar const *error_message) { int lim = rel_n_records (from); int position; for (position = 0; position < lim; ++position) { if (assoc_ref (*to, from[position][1])) panic (error_message); assoc_set (to, from[position][1], from[position][0]); } } /** * \brief convert user supplied limit paths into in-tree paths, eliminating limits outside the tree or in sub-projects. */ rel_table canonicalise_limits (rel_table limits_spec, t_uchar const * mod_root) { rel_table limits = NULL; rel_table lim_spec_locs = 0; int mod_root_length; int l; if (!limits_spec) return NULL; mod_root_length = str_length (mod_root); for (l = 0; l < rel_n_records (limits_spec); ++l) { t_uchar * spec = 0; t_uchar * spec_dir = 0; t_uchar * spec_tail = 0; t_uchar * spec_dir_abs = 0; t_uchar * spec_loc = 0; spec = limits_spec[l][0]; spec_dir = file_name_directory_file (0, spec); if (!spec_dir) spec_dir = str_save (0, "."); spec_tail = file_name_tail (0, spec); spec_dir_abs = directory_as_cwd (spec_dir); if (str_cmp_prefix (mod_root, spec_dir_abs) || (spec_dir_abs[mod_root_length] && (spec_dir_abs[mod_root_length] != '/'))) { safe_printfmt (2, "make-changeset: limit %s is not in tree %s\n", spec, mod_root); exit (2); } spec_loc = str_alloc_cat_many (0, ".", spec_dir_abs + mod_root_length, "/", spec_tail, str_end); rel_add_records (&lim_spec_locs, rel_make_record (spec_loc, 0), 0); lim_free (0, spec); lim_free (0, spec_dir); lim_free (0, spec_tail); lim_free (0, spec_dir_abs); lim_free (0, spec_loc); } for (l = 0; l < rel_n_records (lim_spec_locs); ++l) { int m; int dont_want = 0; for (m = 0; (!dont_want && (m < rel_n_records (limits))); ++m) { if (file_name_is_path_prefix (limits[m][0], lim_spec_locs[l][0])) { dont_want = 1; } else if (file_name_is_path_prefix (lim_spec_locs[l][0], limits[m][0])) { rel_replace_record (limits, m, rel_make_record (lim_spec_locs[l][0], NULL)); dont_want = 1; } } if (!dont_want) rel_add_records (&limits, rel_make_record (lim_spec_locs[l][0], 0), 0); } rel_sort_table_by_field (0, limits, 0); rel_free_table (lim_spec_locs); return limits; } void make_changeset_inventory (struct arch_changeset_inventory * index, arch_project_tree_t * tree, enum arch_id_tagging_method method, enum arch_inventory_category untagged_source_category, int escape_classes, rel_table *full_index, rel_table *full_index_by_name) { mem_set0 ((t_uchar *)index, sizeof (*index)); arch_changeset_inventory (index, tree, method, untagged_source_category, escape_classes); *full_index = rel_copy_table (index->dirs); rel_append_x (full_index, index->files); rel_add_records (full_index, rel_make_record (".", "?_.", 0), 0); rel_sort_table_by_field (0, *full_index, 1); *full_index_by_name = rel_copy_table (*full_index); rel_sort_table_by_field (0, *full_index_by_name, 0); } static void arch_free_changeset_vars_ids(struct changeset_vars_ids * csi) { rel_free_table (csi->orig_dir_ids); rel_free_table (csi->mod_dir_ids); rel_free_table (csi->original_only_dir_ids); rel_free_table (csi->modified_only_dir_ids); rel_free_table (csi->dir_ids_in_both); rel_free_table (csi->orig_file_ids); rel_free_table (csi->mod_file_ids); rel_free_table (csi->original_only_file_ids); rel_free_table (csi->modified_only_file_ids); rel_free_table (csi->file_ids_in_both); } static void arch_free_changeset_vars(struct changeset_vars * cs) { rel_free_table (cs->original_only_dirs); rel_free_table (cs->modified_only_dirs); rel_free_table (cs->original_only_files); rel_free_table (cs->modified_only_files); rel_free_table (cs->files_in_both); rel_free_table (cs->dirs_in_both); rel_free_table (cs->renamed_dirs); rel_free_table (cs->renamed_files); rel_free_table (cs->orig_dirs_full_index_by_name); rel_free_table (cs->orig_files_full_index_by_name); rel_free_table (cs->mod_dirs_full_index_by_name); rel_free_table (cs->mod_files_full_index_by_name); rel_free_table (cs->orig_full_index); rel_free_table (cs->mod_full_index); rel_free_table (cs->mod_full_index_by_name); rel_free_table (cs->orig_full_index_by_name); free_assoc_table (cs->orig_ids_seen); free_assoc_table (cs->mod_ids_seen); arch_free_changeset_vars_ids(&cs->ids); } static void arch_detect_changeset (struct arch_make_changeset_report * report, struct changeset_vars * cs, arch_project_tree_t * orig_tree, arch_project_tree_t * mod_tree, enum arch_id_tagging_method method, enum arch_inventory_category untagged_source_category, rel_table limits_spec, int escape_classes) { int here_fd; rel_table limits; /* For array iteration in for() loops */ int lim; int x; here_fd = safe_open (".", O_RDONLY, 0); limits = canonicalise_limits (limits_spec, mod_tree->root); /* inventory orig and mod */ make_changeset_inventory (&report->orig_index, orig_tree, method, untagged_source_category, escape_classes, &cs->orig_full_index, &cs->orig_full_index_by_name); make_changeset_inventory (&report->mod_index, mod_tree, method, untagged_source_category, escape_classes, &cs->mod_full_index, &cs->mod_full_index_by_name); /* if limits were specified, smudge the MOD index and load-up the translation table */ if (limits) { /* The goal here is to create a fake mod index -- the index of the * mod tree as it would be after we ignore changes outside the limits. * * If there were apparently renames across the limit boundaries, we have to * fail. * * Additionally, we need a "mod-path-translation" -- a mapping from fake-inventory * locs to actual mod locs to use when diffing. If we want to pretend/presume * that some item hasn't been modified, it's left out of this translation. */ /* Three cases of items to deal with in the fake inventory: * * 1) Items common to both ORIG and MOD * 2) Items unique to ORIG * 3) Items unique to MOD * * * Little lemma: for each (canonicalized) limit $PREFIX/$TAIL, * the item in MOD with loc $PREFIX must exist in ORIG. Because, * otherwise, the limit wants to add files to dirs that have not been * added to ORIG (a user error). * */ /* currently, limits is just [0] == mod_path */ safe_printfmt (2, "limits in make_changeset is current unimplemented\n"); exit (2); } cs->orig_dirs_full_index_by_name = rel_copy_table (report->orig_index.dirs); rel_sort_table_by_field (0, cs->orig_dirs_full_index_by_name, 0); cs->mod_dirs_full_index_by_name = rel_copy_table (report->mod_index.dirs); rel_sort_table_by_field (0, cs->mod_dirs_full_index_by_name, 0); { rel_cut_spec spec = rel_cut_list (1, -1); cs->ids.orig_dir_ids = rel_cut (spec, report->orig_index.dirs); cs->ids.mod_dir_ids = rel_cut (spec, report->mod_index.dirs); rel_cut_spec_finalise (&spec); } cs->ids.original_only_dir_ids = rel_join (1, rel_join_output (1,0, -1), 0, 0, cs->ids.orig_dir_ids, cs->ids.mod_dir_ids); cs->ids.modified_only_dir_ids = rel_join (2, rel_join_output (2,0, -1), 0, 0, cs->ids.orig_dir_ids, cs->ids.mod_dir_ids); cs->ids.dir_ids_in_both = rel_join (-1, rel_join_output (0,0, -1), 0, 0, cs->ids.orig_dir_ids, cs->ids.mod_dir_ids); cs->original_only_dirs = rel_join (-1, rel_join_output (2,0, -1), 0, 1, cs->ids.original_only_dir_ids, report->orig_index.dirs); rel_sort_table_by_field (0, cs->original_only_dirs, 0); cs->modified_only_dirs = rel_join (-1, rel_join_output (2,0, -1), 0, 1, cs->ids.modified_only_dir_ids, report->mod_index.dirs); rel_sort_table_by_field (0, cs->modified_only_dirs, 0); /**************************************************************** * build associative tables to and from ids and locs */ lim = rel_n_records (report->orig_index.dirs); for (x = 0; x < lim; ++x) { assoc_set (&report->orig_dir_id_of, report->orig_index.dirs[x][0], report->orig_index.dirs[x][1]); assoc_set (&report->orig_dir_loc_of, report->orig_index.dirs[x][1], report->orig_index.dirs[x][0]); } lim = rel_n_records (report->orig_index.files); for (x = 0; x < lim; ++x) { assoc_set (&report->orig_file_id_of, report->orig_index.files[x][0], report->orig_index.files[x][1]); assoc_set (&report->orig_file_loc_of, report->orig_index.files[x][1], report->orig_index.files[x][0]); } lim = rel_n_records (report->mod_index.dirs); for (x = 0; x < lim; ++x) { assoc_set (&report->mod_dir_id_of, report->mod_index.dirs[x][0], report->mod_index.dirs[x][1]); assoc_set (&report->mod_dir_loc_of, report->mod_index.dirs[x][1], report->mod_index.dirs[x][0]); } lim = rel_n_records (report->mod_index.files); for (x = 0; x < lim; ++x) { assoc_set (&report->mod_file_id_of, report->mod_index.files[x][0], report->mod_index.files[x][1]); assoc_set (&report->mod_file_loc_of, report->mod_index.files[x][1], report->mod_index.files[x][0]); } assoc_set (&report->orig_dir_id_of, ".", "?_."); assoc_set (&report->mod_dir_id_of, ".", "?_."); assoc_set (&report->orig_dir_loc_of, "?_.", "."); assoc_set (&report->mod_dir_loc_of, "?_.", "."); arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_dir_id, report->orig_dir_id_of, report->orig_index.dirs); arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_file_id, report->orig_dir_id_of, report->orig_index.files); arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_dir_id, report->mod_dir_id_of, report->mod_index.dirs); arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_file_id, report->mod_dir_id_of, report->mod_index.files); /**************************************************************** * figure out which directories have been renamed. */ compute_renames (&cs->renamed_dirs, report->orig_dir_loc_of, report->mod_dir_id_of, report->orig_container_dir_id_of_dir_id, report->mod_index.dirs); /**************************************************************** * file lists */ cs->orig_files_full_index_by_name = rel_copy_table (report->orig_index.files); rel_sort_table_by_field (0, cs->orig_files_full_index_by_name, 0); cs->mod_files_full_index_by_name = rel_copy_table (report->mod_index.files); rel_sort_table_by_field (0, cs->mod_files_full_index_by_name, 0); { rel_cut_spec spec = rel_cut_list (1, -1); cs->ids.orig_file_ids = rel_cut (spec, report->orig_index.files); cs->ids.mod_file_ids = rel_cut (spec, report->mod_index.files); rel_cut_spec_finalise (&spec); } cs->ids.original_only_file_ids = rel_join (1, rel_join_output (1,0, -1), 0, 0, cs->ids.orig_file_ids, cs->ids.mod_file_ids); cs->ids.modified_only_file_ids = rel_join (2, rel_join_output (2,0, -1), 0, 0, cs->ids.orig_file_ids, cs->ids.mod_file_ids); cs->ids.file_ids_in_both = rel_join (-1, rel_join_output (1,0, -1), 0, 0, cs->ids.orig_file_ids, cs->ids.mod_file_ids); cs->original_only_files = rel_join (-1, rel_join_output (2,0, -1), 0, 1, cs->ids.original_only_file_ids, report->orig_index.files); rel_sort_table_by_field (0, cs->original_only_files, 0); cs->modified_only_files = rel_join (-1, rel_join_output (2,0, -1), 0, 1, cs->ids.modified_only_file_ids, report->mod_index.files); rel_sort_table_by_field (0, cs->modified_only_files, 0); compute_renames (&cs->renamed_files, report->orig_file_loc_of, report->mod_dir_id_of, report->orig_container_dir_id_of_file_id, report->mod_index.files); /*************************************************************** * {files,dir}_in_both [0] orig_loc [1] mod_loc */ { rel_table tmp_table; tmp_table = rel_join (-1, rel_join_output (1,0, 2,0, -1), 0, 1, cs->ids.file_ids_in_both, report->mod_index.files); cs->files_in_both = rel_join (-1, rel_join_output (2,0, 1,1, 1,0, -1), 0, 1, tmp_table, report->orig_index.files); rel_free_table (tmp_table); } { rel_table tmp_table; tmp_table = rel_join (-1, rel_join_output (1,0, 2,0, -1), 0, 1, cs->ids.dir_ids_in_both, report->mod_index.dirs); cs->dirs_in_both = rel_join (-1, rel_join_output (2,0, 1,1, -1), 0, 1, tmp_table, report->orig_index.dirs); rel_free_table (tmp_table); } /**************************************************************** * Check for Duplicate Inventory Ids */ rel_merge_to_assoc_unique (report->orig_index.dirs, &cs->orig_ids_seen, _("duplicate ids in ORIG tree, try status --lint")); rel_merge_to_assoc_unique (report->orig_index.files, &cs->orig_ids_seen, _("duplicate ids in ORIG tree, try status --lint")); rel_merge_to_assoc_unique (report->mod_index.dirs, &cs->mod_ids_seen, _("duplicate ids in MOD tree, try status --lint")); rel_merge_to_assoc_unique (report->mod_index.files, &cs->mod_ids_seen, _("duplicate ids in MOD tree, try status --lint")); safe_fchdir (here_fd); safe_close (here_fd); rel_free_table (limits); } static void arch_write_changeset(struct arch_make_changeset_report * report, struct changeset_vars * cs, t_uchar * dest_spec, assoc_table inode_sig_shortcuts_of_mod, int link_same, int escape_classes) { int here_fd; t_uchar * dest; t_uchar * original_only_dir_metadata_file = 0; t_uchar * modified_only_dir_metadata_file = 0; t_uchar * removed_files_archive_dir = 0; t_uchar * new_files_archive_dir = 0; t_uchar * patches_dir = 0; rel_table orig_files_index_names_output = 0; rel_table mod_files_index_names_output = 0; rel_table orig_dirs_index_names_output = 0; rel_table mod_dirs_index_names_output = 0; t_uchar * orig_files_index_file = 0; t_uchar * orig_dirs_index_file = 0; t_uchar * mod_files_index_file = 0; t_uchar * mod_dirs_index_file = 0; /* Useful for iterating over arrays in for() loops */ int lim; int x; here_fd = safe_open (".", O_RDONLY, 0); /* create the output directory */ safe_mkdir (dest_spec, 0777); safe_chdir (dest_spec); dest = safe_current_working_directory (); safe_fchdir (here_fd); /**************************************************************** * Capture the Permissions of Removed and New Directories */ original_only_dir_metadata_file = file_name_in_vicinity (0, dest, "original-only-dir-metadata"); modified_only_dir_metadata_file = file_name_in_vicinity (0, dest, "modified-only-dir-metadata"); write_dir_metadata (original_only_dir_metadata_file, cs->orig_tree->root, cs->original_only_dirs); write_dir_metadata (modified_only_dir_metadata_file, cs->mod_tree->root, cs->modified_only_dirs); if (report) { report->removed_dirs = rel_copy_table (cs->original_only_dirs); report->added_dirs = rel_copy_table (cs->modified_only_dirs); if (report->callback) { lim = rel_n_records (report->removed_dirs); for (x = 0; x < lim; ++x) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->removed_dirs[x][0]); invoke_report_callback (report, "D/ %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } lim = rel_n_records (report->added_dirs); for (x = 0; x < lim; ++x) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->added_dirs[x][0]); invoke_report_callback (report, "A/ %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } } /**************************************************************** * Save Copies of new and removed files. */ removed_files_archive_dir = file_name_in_vicinity (0, dest, "removed-files-archive"); new_files_archive_dir = file_name_in_vicinity (0, dest, "new-files-archive"); safe_mkdir (removed_files_archive_dir, 0777); safe_mkdir (new_files_archive_dir, 0777); copy_files_and_symlinks (removed_files_archive_dir, cs->orig_tree->root, cs->original_only_files); copy_files_and_symlinks (new_files_archive_dir, cs->mod_tree->root, cs->modified_only_files); if (report) { report->removed_files = rel_copy_table (cs->original_only_files); report->added_files = rel_copy_table (cs->modified_only_files); if (report->callback) { lim = rel_n_records (report->removed_files); for (x = 0; x < lim; ++x) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->removed_files[x][0]); invoke_report_callback (report, "D %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } lim = rel_n_records (report->added_files); for (x = 0; x < lim; ++x) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->added_files[x][0]); invoke_report_callback (report, "A %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } } /**************************************************************** * Save patches for common files and directories. */ patches_dir = file_name_in_vicinity (0, dest, "patches"); safe_mkdir (patches_dir, 0777); lim = rel_n_records (cs->files_in_both); for (x = 0; x < lim; ++x) { emit_file_or_symlink_patches (&orig_files_index_names_output, &mod_files_index_names_output, report, patches_dir, cs->orig_tree->root, cs->files_in_both[x][0], cs->mod_tree->root, cs->files_in_both[x][1], cs->files_in_both[x][2], inode_sig_shortcuts_of_mod, link_same, escape_classes); } lim = rel_n_records (cs->dirs_in_both); for (x = 0; x < lim; ++x) { emit_dir_patches (&orig_dirs_index_names_output, &mod_dirs_index_names_output, report, patches_dir, cs->orig_tree->root, cs->dirs_in_both[x][0], cs->mod_tree->root, cs->dirs_in_both[x][1], escape_classes); } /**************************************************************** * Write the file indexes. */ orig_files_index_file = file_name_in_vicinity (0, dest, "orig-files-index"); orig_dirs_index_file = file_name_in_vicinity (0, dest, "orig-dirs-index"); mod_files_index_file = file_name_in_vicinity (0, dest, "mod-files-index"); mod_dirs_index_file = file_name_in_vicinity (0, dest, "mod-dirs-index"); { rel_table orig_newname_dir_names = 0; rel_table orig_newname_file_names = 0; rel_table mod_newname_dir_names = 0; rel_table mod_newname_file_names = 0; rel_table orig_dirs_index_final = 0; rel_table orig_files_index_final = 0; rel_table mod_dirs_index_final = 0; rel_table mod_files_index_final = 0; int orig_dirs_index_fd; int orig_files_index_fd; int mod_dirs_index_fd; int mod_files_index_fd; { rel_cut_spec spec = rel_cut_list (0, -1); orig_newname_dir_names = rel_cut (spec, cs->renamed_dirs); orig_newname_file_names = rel_cut (spec, cs->renamed_files); rel_cut_spec_finalise (&spec); spec = rel_cut_list (1, -1); mod_newname_dir_names = rel_cut (spec, cs->renamed_dirs); mod_newname_file_names = rel_cut (spec, cs->renamed_files); rel_cut_spec_finalise (&spec); } rel_append_x (&orig_newname_dir_names, cs->original_only_dirs); rel_append_x (&orig_newname_file_names, cs->original_only_files); rel_append_x (&mod_newname_dir_names, cs->modified_only_dirs); rel_append_x (&mod_newname_file_names, cs->modified_only_files); compute_parent_dir_closure (&orig_newname_dir_names, &mod_newname_dir_names, orig_newname_file_names, mod_newname_file_names, report->orig_dir_loc_of, report->orig_dir_id_of, report->orig_container_dir_id_of_dir_id, report->orig_container_dir_id_of_file_id, report->mod_dir_loc_of, report->mod_dir_id_of, report->mod_container_dir_id_of_dir_id, report->mod_container_dir_id_of_file_id, report->orig_file_id_of, report->mod_file_id_of); rel_append_x (&orig_dirs_index_names_output, orig_newname_dir_names); rel_append_x (&orig_files_index_names_output, orig_newname_file_names); rel_append_x (&mod_dirs_index_names_output, mod_newname_dir_names); rel_append_x (&mod_files_index_names_output, mod_newname_file_names); rel_sort_table_by_field (0, orig_dirs_index_names_output, 0); rel_sort_table_by_field (0, orig_files_index_names_output, 0); rel_sort_table_by_field (0, mod_dirs_index_names_output, 0); rel_sort_table_by_field (0, mod_files_index_names_output, 0); rel_uniq_by_field (&orig_dirs_index_names_output, 0); rel_uniq_by_field (&orig_files_index_names_output, 0); rel_uniq_by_field (&mod_dirs_index_names_output, 0); rel_uniq_by_field (&mod_files_index_names_output, 0); orig_dirs_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, orig_dirs_index_names_output, cs->orig_dirs_full_index_by_name); orig_files_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, orig_files_index_names_output, cs->orig_files_full_index_by_name); mod_dirs_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, mod_dirs_index_names_output, cs->mod_dirs_full_index_by_name); mod_files_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, mod_files_index_names_output, cs->mod_files_full_index_by_name); rel_sort_table_by_field (0, orig_dirs_index_final, 1); rel_sort_table_by_field (0, orig_files_index_final, 1); rel_sort_table_by_field (0, mod_dirs_index_final, 1); rel_sort_table_by_field (0, mod_files_index_final, 1); orig_dirs_index_fd = safe_open (orig_dirs_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666); orig_files_index_fd = safe_open (orig_files_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666); mod_dirs_index_fd = safe_open (mod_dirs_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666); mod_files_index_fd = safe_open (mod_files_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666); rel_print_pika_escape_iso8859_1_table_sp (orig_dirs_index_fd, arch_escape_classes, orig_dirs_index_final); rel_print_pika_escape_iso8859_1_table_sp (orig_files_index_fd, arch_escape_classes, orig_files_index_final); rel_print_pika_escape_iso8859_1_table_sp (mod_dirs_index_fd, arch_escape_classes, mod_dirs_index_final); rel_print_pika_escape_iso8859_1_table_sp (mod_files_index_fd, arch_escape_classes, mod_files_index_final); rel_free_table (orig_dirs_index_final); rel_free_table (orig_files_index_final); rel_free_table (mod_dirs_index_final); rel_free_table (mod_files_index_final); safe_close (orig_dirs_index_fd); safe_close (orig_files_index_fd); safe_close (mod_dirs_index_fd); safe_close (mod_files_index_fd); rel_free_table (orig_newname_dir_names); rel_free_table (orig_newname_file_names); rel_free_table (mod_newname_dir_names); rel_free_table (mod_newname_file_names); if (report) { lim = rel_n_records (cs->renamed_dirs); for (x = 0; x < lim; ++x) { rel_add_records (&report->renamed_dirs, rel_copy_record (cs->renamed_dirs[x]), 0); if (report->callback) { t_uchar * escape_tmp0; t_uchar * escape_tmp1; escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->renamed_dirs[x][0]); escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->renamed_dirs[x][1]); invoke_report_callback (report, "/> %s\t%s\n", no_dot (escape_tmp0), no_dot (escape_tmp1)); lim_free (0, escape_tmp1); lim_free (0, escape_tmp0); } } lim = rel_n_records (cs->renamed_files); for (x = 0; x < lim; ++x) { rel_add_records (&report->renamed_files, rel_copy_record (cs->renamed_files[x]), 0); if (report->callback) { t_uchar * escape_tmp0; t_uchar * escape_tmp1; escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->renamed_files[x][0]); escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, report->renamed_files[x][1]); invoke_report_callback (report, "=> %s\t%s\n", no_dot (escape_tmp0), no_dot (escape_tmp1)); lim_free (0, escape_tmp1); lim_free (0, escape_tmp0); } } } } safe_fchdir (here_fd); safe_close (here_fd); lim_free (0, dest); lim_free (0, original_only_dir_metadata_file); lim_free (0, modified_only_dir_metadata_file); lim_free (0, removed_files_archive_dir); lim_free (0, new_files_archive_dir); lim_free (0, patches_dir); lim_free (0, orig_files_index_file); lim_free (0, orig_dirs_index_file); lim_free (0, mod_files_index_file); lim_free (0, mod_dirs_index_file); rel_free_table (orig_files_index_names_output); rel_free_table (orig_dirs_index_names_output); rel_free_table (mod_files_index_names_output); rel_free_table (mod_dirs_index_names_output); } /* both orig and dest_spec must point to arch trees, */ void arch_make_changeset (struct arch_make_changeset_report * report, arch_project_tree_t * orig_tree, arch_project_tree_t * mod_tree, t_uchar * dest_spec, enum arch_id_tagging_method method, enum arch_inventory_category untagged_source_category, rel_table limits_spec, assoc_table inode_sig_shortcuts_of_mod, int link_same, int escape_classes) { /* The changeset variables themselves */ struct changeset_vars cs = { 0 }; /* Initialise all pointers to NULL */ debug(dbg_diff, 6, "Detecting changeset...\n"); cs.orig_tree = orig_tree; cs.mod_tree = mod_tree; arch_detect_changeset (report, &cs, orig_tree, mod_tree, method, untagged_source_category, limits_spec, escape_classes); debug(dbg_diff, 6, "Writing changeset...\n"); arch_write_changeset (report, &cs, dest_spec, inode_sig_shortcuts_of_mod, link_same, escape_classes); arch_free_changeset_vars (&cs); } void arch_make_empty_changeset (struct arch_make_changeset_report * make_report, struct arch_changeset_report * report, t_uchar * changeset_path) { t_uchar * new_files_archive = 0; t_uchar * removed_files_archive = 0; t_uchar * patches = 0; t_uchar * original_only_dir_metadata = 0; t_uchar * modified_only_dir_metadata = 0; t_uchar * orig_files_index = 0; t_uchar * orig_dirs_index = 0; t_uchar * mod_files_index = 0; t_uchar * mod_dirs_index = 0; int fd; new_files_archive = file_name_in_vicinity (0, changeset_path, "new-files-archive"); removed_files_archive = file_name_in_vicinity (0, changeset_path, "removed-files-archive"); patches = file_name_in_vicinity (0, changeset_path, "patches"); original_only_dir_metadata = file_name_in_vicinity (0, changeset_path, "original-only-dir-metadata"); modified_only_dir_metadata = file_name_in_vicinity (0, changeset_path, "modified-only-dir-metadata"); orig_files_index = file_name_in_vicinity (0, changeset_path, "orig-files-index"); orig_dirs_index = file_name_in_vicinity (0, changeset_path, "orig-dirs-index"); mod_files_index = file_name_in_vicinity (0, changeset_path, "mod-files-index"); mod_dirs_index = file_name_in_vicinity (0, changeset_path, "mod-dirs-index"); safe_mkdir (changeset_path, 0777); safe_mkdir (patches, 0777); safe_mkdir (new_files_archive, 0777); safe_mkdir (removed_files_archive, 0777); fd = safe_open (original_only_dir_metadata, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); fd = safe_open (modified_only_dir_metadata, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); fd = safe_open (orig_files_index, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); fd = safe_open (orig_dirs_index, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); fd = safe_open (mod_files_index, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); fd = safe_open (mod_dirs_index, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_close (fd); assoc_set (&make_report->orig_dir_id_of, ".", "?_."); assoc_set (&make_report->mod_dir_id_of, ".", "?_."); assoc_set (&make_report->orig_dir_loc_of, "?_.", "."); assoc_set (&make_report->mod_dir_loc_of, "?_.", "."); lim_free (0, new_files_archive); lim_free (0, removed_files_archive); lim_free (0, patches); lim_free (0, original_only_dir_metadata); lim_free (0, modified_only_dir_metadata); lim_free (0, orig_files_index); lim_free (0, orig_dirs_index); lim_free (0, mod_files_index); lim_free (0, mod_dirs_index); } void arch_changeset_rewrite_indexes (t_uchar * changeset_path, struct arch_changeset_report * report) { int ign; t_uchar * orig_dirs_index_path = 0; t_uchar * orig_files_index_path = 0; t_uchar * mod_dirs_index_path = 0; t_uchar * mod_files_index_path = 0; int orig_dirs_index_fd = -1; int orig_files_index_fd = -1; int mod_dirs_index_fd = -1; int mod_files_index_fd = -1; orig_dirs_index_path = file_name_in_vicinity (0, changeset_path, "orig-dirs-index"); orig_files_index_path = file_name_in_vicinity (0, changeset_path, "orig-files-index"); mod_dirs_index_path = file_name_in_vicinity (0, changeset_path, "mod-dirs-index"); mod_files_index_path = file_name_in_vicinity (0, changeset_path, "mod-files-index"); vu_unlink (&ign, orig_dirs_index_path); orig_dirs_index_fd = safe_open (orig_dirs_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444); vu_unlink (&ign, orig_files_index_path); orig_files_index_fd = safe_open (orig_files_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444); vu_unlink (&ign, mod_dirs_index_path); mod_dirs_index_fd = safe_open (mod_dirs_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444); vu_unlink (&ign, mod_files_index_path); mod_files_index_fd = safe_open (mod_files_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444); rel_sort_table_by_field (0, report->orig_dirs_index, 1); rel_sort_table_by_field (0, report->orig_files_index, 1); rel_sort_table_by_field (0, report->mod_dirs_index, 1); rel_sort_table_by_field (0, report->mod_files_index, 1); rel_print_pika_escape_iso8859_1_table_sp (orig_dirs_index_fd, arch_escape_classes, report->orig_dirs_index); rel_print_pika_escape_iso8859_1_table_sp (orig_files_index_fd, arch_escape_classes, report->orig_files_index); rel_print_pika_escape_iso8859_1_table_sp (mod_dirs_index_fd, arch_escape_classes, report->mod_dirs_index); rel_print_pika_escape_iso8859_1_table_sp (mod_files_index_fd, arch_escape_classes, report->mod_files_index); safe_close (orig_dirs_index_fd); safe_close (orig_files_index_fd); safe_close (mod_dirs_index_fd); safe_close (mod_files_index_fd); lim_free (0, orig_dirs_index_path); lim_free (0, orig_files_index_path); lim_free (0, mod_dirs_index_path); lim_free (0, mod_files_index_path); } int arch_changeset_add_file (t_uchar ** path_ret, struct arch_changeset_report * report, struct arch_make_changeset_report * make_report, t_uchar * changeset_path, t_uchar * mod_loc, t_uchar * id) { int ign; t_uchar * new_file_archive_path = 0; t_uchar * new_file_path = 0; t_uchar * new_file_dir_path = 0; t_uchar * loc_dir = 0; t_uchar * loc_dir_id; int dest_fd = -1; new_file_archive_path = file_name_in_vicinity (0, changeset_path, "new-files-archive"); new_file_path = file_name_in_vicinity (0, new_file_archive_path, mod_loc); if (path_ret) *path_ret = str_save (0, new_file_path); new_file_dir_path = file_name_directory_file (0, new_file_path); ensure_directory_exists (new_file_dir_path); vu_unlink (&ign, new_file_path); dest_fd = safe_open (new_file_path, O_WRONLY | O_CREAT | O_EXCL, 0666); /* update the changeset report. */ rel_add_records (&report->mod_files_index, rel_make_record (mod_loc, id, 0), 0); rel_sort_table_by_field (0, report->mod_files_index, 0); rel_uniq_by_field (&report->mod_files_index, 0); rel_add_records (&report->added_files, rel_make_record (mod_loc, id, new_file_path, 0), 0); rel_sort_table_by_field (0, report->added_files, 0); rel_uniq_by_field (&report->added_files, 0); /* update the make-changeset report. */ assoc_set (&make_report->mod_file_id_of, mod_loc, id); assoc_set (&make_report->mod_file_loc_of, id, mod_loc); rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, id, 0), 0); rel_sort_table_by_field (0, make_report->mod_index.files, 1); rel_uniq_by_field (&make_report->mod_index.files, 1); rel_add_records (&make_report->added_files, rel_make_record (mod_loc, 0), 0); rel_sort_table_by_field (0, make_report->added_files, 0); rel_uniq_by_field (&make_report->added_files, 0); loc_dir = file_name_directory_file (0, mod_loc); loc_dir_id = assoc_ref (make_report->mod_dir_id_of, loc_dir); if (loc_dir_id) assoc_set (&make_report->mod_container_dir_id_of_file_id, id, loc_dir_id); lim_free (0, new_file_archive_path); lim_free (0, new_file_path); lim_free (0, new_file_dir_path); lim_free (0, loc_dir); return dest_fd; } int arch_changeset_add_diffs (struct arch_changeset_report * report, struct arch_make_changeset_report * make_report, t_uchar * changeset_path, t_uchar * orig_loc, t_uchar * mod_loc, t_uchar * id) { int ign; t_uchar * patches_path = 0; t_uchar * patch_path = 0; t_uchar * patch_dir_path = 0; t_uchar * orig_loc_dir = 0; t_uchar * orig_loc_dir_id; t_uchar * mod_loc_dir = 0; t_uchar * mod_loc_dir_id; int dest_fd = -1; patches_path = file_name_in_vicinity (0, changeset_path, "patches"); patch_path = file_name_in_vicinity (0, patches_path, mod_loc); patch_path = str_realloc_cat (0, patch_path, ".patch"); patch_dir_path = file_name_directory_file (0, patch_path); ensure_directory_exists (patch_dir_path); vu_unlink (&ign, patch_path); dest_fd = safe_open (patch_path, O_WRONLY | O_CREAT | O_EXCL, 0666); /* update the changeset report. */ if (report) { rel_add_records (&report->mod_files_index, rel_make_record (mod_loc, id, 0), 0); rel_sort_table_by_field (0, report->mod_files_index, 0); rel_uniq_by_field (&report->mod_files_index, 0); rel_add_records (&report->orig_files_index, rel_make_record (orig_loc, id, 0), 0); rel_sort_table_by_field (0, report->orig_files_index, 0); rel_uniq_by_field (&report->orig_files_index, 0); rel_add_records (&report->patched_regular_files, rel_make_record (mod_loc, id, patch_path, 0), 0); rel_sort_table_by_field (0, report->patched_regular_files, 0); rel_uniq_by_field (&report->patched_regular_files, 0); } /* update the make-changeset report. */ assoc_set (&make_report->mod_file_id_of, mod_loc, id); assoc_set (&make_report->mod_file_loc_of, id, mod_loc); assoc_set (&make_report->orig_file_id_of, orig_loc, id); assoc_set (&make_report->orig_file_loc_of, id, orig_loc); rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, id, 0), 0); rel_sort_table_by_field (0, make_report->mod_index.files, 1); rel_uniq_by_field (&make_report->mod_index.files, 1); rel_add_records (&make_report->orig_index.files, rel_make_record (orig_loc, id, 0), 0); rel_sort_table_by_field (0, make_report->orig_index.files, 1); rel_uniq_by_field (&make_report->orig_index.files, 1); rel_add_records (&make_report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); rel_sort_table_by_field (0, make_report->modified_files, 0); rel_uniq_by_field (&make_report->modified_files, 0); mod_loc_dir = file_name_directory_file (0, mod_loc); mod_loc_dir_id = assoc_ref (make_report->mod_dir_id_of, mod_loc_dir); if (mod_loc_dir_id) assoc_set (&make_report->mod_container_dir_id_of_file_id, id, mod_loc_dir_id); orig_loc_dir = file_name_directory_file (0, orig_loc); orig_loc_dir_id = assoc_ref (make_report->orig_dir_id_of, orig_loc_dir); if (orig_loc_dir_id) assoc_set (&make_report->orig_container_dir_id_of_file_id, id, orig_loc_dir_id); lim_free (0, patches_path); lim_free (0, patch_path); lim_free (0, patch_dir_path); lim_free (0, orig_loc_dir); lim_free (0, mod_loc_dir); return dest_fd; } void arch_make_changeset_compute_container_map (assoc_table * out, assoc_table dir_id_of, rel_table index) { int lim; int x; lim = rel_n_records (index); for (x = 0; x < lim; ++x) { t_uchar * item; t_uchar * id; t_uchar * dir_as_file; t_uchar * container_id; item = index[x][0]; id = index[x][1]; dir_as_file = file_name_directory_file (0, item); container_id = assoc_ref (dir_id_of, dir_as_file); invariant (!!container_id); assoc_set (out, id, container_id); lim_free (0, dir_as_file); } } static void compute_renames (rel_table * out, assoc_table loc_of_orig_X_id, assoc_table mod_dir_id_of, assoc_table orig_container_dir_id_of_X_id, rel_table X_index) { int lim; int x; lim = rel_n_records (X_index); for (x = 0; x < lim; ++x) { t_uchar * container; t_uchar * tail; t_uchar * id; t_uchar * orig; t_uchar * orig_tail; t_uchar * mod_container_id; t_uchar * orig_container_id; container = file_name_directory_file (0, X_index[x][0]); tail = file_name_tail (0, X_index[x][0]); id = X_index[x][1]; orig = assoc_ref (loc_of_orig_X_id, id); orig_tail = (orig ? file_name_tail (0, orig) : 0); mod_container_id = assoc_ref (mod_dir_id_of, container); orig_container_id = assoc_ref (orig_container_dir_id_of_X_id, id); if (orig && (str_cmp (orig_tail, tail) || (!mod_container_id && str_cmp (X_index[x][0], orig)) || str_cmp (mod_container_id, orig_container_id))) { rel_add_records (out, rel_make_record (orig, X_index[x][0], 0), 0); } lim_free (0, container); lim_free (0, tail); lim_free (0, orig_tail); } rel_sort_table_by_field (0, *out, 1); } static void write_dir_metadata (t_uchar * file, t_uchar * tree_root, rel_table dirs) { int out_fd; int lim; int x; out_fd = safe_open (file, O_WRONLY | O_CREAT | O_EXCL, 0666); lim = rel_n_records (dirs); for (x = 0; x < lim; ++x) { t_uchar * path; t_uchar * escape_tmp; struct stat stat_buf; path = file_name_in_vicinity (0, tree_root, dirs[x][0]); safe_stat (path, &stat_buf); escape_tmp = pika_save_escape_iso8859_1 (0, 0, arch_escape_classes, dirs[x][0]); safe_printfmt (out_fd, "--permissions %lo\t%s\n", (unsigned long)(stat_buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)), escape_tmp); lim_free (0, escape_tmp); lim_free (0, path); } safe_close (out_fd); } static void copy_files_and_symlinks (t_uchar * dest, t_uchar * tree_root, rel_table loc_list) { int lim; int x; lim = rel_n_records (loc_list); for (x = 0; x < lim; ++x) { t_uchar * loc; t_uchar * source_name; t_uchar * dest_name; t_uchar * dest_dir; loc = loc_list[x][0]; source_name = file_name_in_vicinity (0, tree_root, loc); dest_name = file_name_in_vicinity (0, dest, loc); dest_dir = file_name_directory_file (0, dest_name); ensure_directory_exists (dest_dir); copy_file_or_symlink (source_name, dest_name); copy_permissions (source_name, dest_name); lim_free (0, source_name); lim_free (0, dest_name); lim_free (0, dest_dir); } } static void emit_dir_patches (rel_table * orig_index_names_output, rel_table * mod_index_names_output, struct arch_make_changeset_report * report, t_uchar * patches, t_uchar * orig, t_uchar * orig_loc, t_uchar * mod, t_uchar * mod_loc, int escape_classes) { t_uchar * orig_path; t_uchar * mod_path; struct stat orig_stat; struct stat mod_stat; t_ulong orig_mode; t_ulong mod_mode; orig_path = file_name_in_vicinity (0, orig, orig_loc); mod_path = file_name_in_vicinity (0, mod, mod_loc); safe_stat (orig_path, &orig_stat); safe_stat (mod_path, &mod_stat); orig_mode = (t_ulong)(orig_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); mod_mode = (t_ulong)(mod_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); if (orig_mode != mod_mode) { t_uchar * patches_dir; t_uchar * orig_patch; t_uchar * mod_patch; int orig_patch_fd; int mod_patch_fd; rel_add_records (orig_index_names_output, rel_make_record (orig_loc, 0), 0); rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0); if (report) { rel_add_records (&report->perms_changed_dirs, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "-/ %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } patches_dir = file_name_in_vicinity (0, patches, mod_loc); orig_patch = file_name_in_vicinity (0, patches_dir, "=dir-meta-orig"); mod_patch = file_name_in_vicinity (0, patches_dir, "=dir-meta-mod"); ensure_directory_exists (patches_dir); orig_patch_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); mod_patch_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_printfmt (orig_patch_fd, "--permissions %lo\n", orig_mode); safe_printfmt (mod_patch_fd, "--permissions %lo\n", mod_mode); safe_close (orig_patch_fd); safe_close (mod_patch_fd); lim_free (0, patches_dir); lim_free (0, orig_patch); lim_free (0, mod_patch); } lim_free (0, orig_path); lim_free (0, mod_path); } void emit_symlink_patch (changeset_maker * self, t_uchar * orig_loc, t_uchar * orig_target, t_uchar * mod_loc, t_uchar * mod_target) { t_uchar * patch_basename_path; t_uchar * orig_patch; t_uchar * mod_patch; t_uchar * patch_dir; int orig_out_fd; int mod_out_fd; note_change (self, orig_loc, mod_loc); if (self->report) { rel_add_records (&self->report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (self->report->callback) { /* report the symlink itself renamed */ t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, self->escape_classes, mod_loc); invoke_report_callback (self->report, "-> %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } patch_basename_path = file_name_in_vicinity (0, self->patches, mod_loc); orig_patch = str_alloc_cat (0, patch_basename_path, ".link-orig"); mod_patch = str_alloc_cat (0, patch_basename_path, ".link-mod"); patch_dir = file_name_directory_file (0, patch_basename_path); ensure_directory_exists (patch_dir); orig_out_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); mod_out_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_printfmt (orig_out_fd, "%s\n", orig_target); safe_printfmt (mod_out_fd, "%s\n", mod_target); lim_free (0, patch_basename_path); lim_free (0, orig_patch); lim_free (0, mod_patch); lim_free (0, patch_dir); } void note_change (changeset_maker *self, t_uchar * orig_loc, t_uchar * mod_loc) { rel_add_records (self->orig_index_names_output, rel_make_record (orig_loc, 0), 0); rel_add_records (self->mod_index_names_output, rel_make_record (mod_loc, 0), 0); } /** * \brief Check for changes for a file * * \param orig_index_names_output FIXME docstring * \param mod_index_names_output FIXME docstring * \param report return a description of the changeset in this * parameter * \param patches the directory where patches will be stored * \param orig root of the ORIG tree * \param orig_loc path to the file in ORIG tree (relative from orig) * \param mod root of the MOD tree * \param mod_loc path to the file in MOD tree (relative from mod) * \param id the id of the file considered * \param inode_sig_shortcuts_of_mod inode signature table for mod. * If non NULL, a file matching its inode signature will be * considered unmodified. If NULL, the inode signature will not * be checked. * \param link_same Whether unmodified files should be hardlinked to * their reference. * \param escape_classes how file names should be escaped. */ void emit_file_or_symlink_patches (rel_table * orig_index_names_output, rel_table * mod_index_names_output, struct arch_make_changeset_report * report, t_uchar * patches, t_uchar * orig, t_uchar * orig_loc, t_uchar * mod, t_uchar * mod_loc, t_uchar * id, assoc_table inode_sig_shortcuts_of_mod, int link_same, int escape_classes) { t_uchar * orig_path; t_uchar * mod_path; int orig_is_symlink; int mod_is_symlink; changeset_maker self = {orig_index_names_output, mod_index_names_output, report, escape_classes, patches}; struct stat *orig_stat = &(cset_map_find (report->orig_index.entries, id)->stat_buf); struct stat *mod_stat = &(cset_map_find (report->mod_index.entries, id)->stat_buf); orig_path = file_name_in_vicinity (0, orig, orig_loc); mod_path = file_name_in_vicinity (0, mod, mod_loc); orig_is_symlink = S_ISLNK (orig_stat->st_mode); mod_is_symlink = S_ISLNK (mod_stat->st_mode); if (orig_is_symlink && mod_is_symlink) { t_uchar * orig_target = link_target (orig_path); t_uchar * mod_target = link_target (mod_path); if (arch_symlinks_differ (orig_path, mod_path)) emit_symlink_patch (&self, orig_loc, orig_target, mod_loc, mod_target); lim_free (0, orig_target); lim_free (0, mod_target); } else if (orig_is_symlink) { t_uchar * orig_target; t_uchar * patch_basename_path; t_uchar * orig_patch; t_uchar * mod_patch; t_uchar * patch_dir; int orig_out_fd; note_change (&self, orig_loc, mod_loc); if (report) { rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "lf %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } orig_target = link_target (orig_path); patch_basename_path = file_name_in_vicinity (0, patches, mod_loc); orig_patch = str_alloc_cat (0, patch_basename_path, ".link-orig"); mod_patch = str_alloc_cat (0, patch_basename_path, ".modified"); patch_dir = file_name_directory_file (0, patch_basename_path); ensure_directory_exists (patch_dir); orig_out_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_printfmt (orig_out_fd, "%s\n", orig_target); copy_file (mod_path, mod_patch); copy_permissions (mod_path, mod_patch); lim_free (0, orig_target); lim_free (0, patch_basename_path); lim_free (0, orig_patch); lim_free (0, mod_patch); lim_free (0, patch_dir); } else if (mod_is_symlink) { t_uchar * mod_target; t_uchar * patch_basename_path; t_uchar * orig_patch; t_uchar * mod_patch; t_uchar * patch_dir; int mod_out_fd; note_change (&self, orig_loc, mod_loc); if (report) { rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "fl %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } mod_target = link_target (mod_path); patch_basename_path = file_name_in_vicinity (0, patches, mod_loc); orig_patch = str_alloc_cat (0, patch_basename_path, ".original"); mod_patch = str_alloc_cat (0, patch_basename_path, ".link-mod"); patch_dir = file_name_directory_file (0, patch_basename_path); ensure_directory_exists (patch_dir); mod_out_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666); safe_printfmt (mod_out_fd, "%s\n", mod_target); copy_file (orig_path, orig_patch); copy_permissions (orig_path, orig_patch); lim_free (0, mod_target); lim_free (0, patch_basename_path); lim_free (0, orig_patch); lim_free (0, mod_patch); lim_free (0, patch_dir); } else { int ign; t_uchar * patch_base_file; t_uchar * patch_file_dir; mode_t orig_mode; mode_t mod_mode; t_uchar * diff_file; int diff_fd; int diff_stat; int is_changed; is_changed = 0; if (arch_file_stats_differ (orig_stat, mod_stat) == 0) goto path_free; patch_base_file = file_name_in_vicinity (0, patches, mod_loc); patch_file_dir = file_name_directory_file (0, patch_base_file); orig_mode = orig_stat->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); mod_mode = mod_stat->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); if (orig_mode != mod_mode) { t_uchar * orig_patch_meta_file; t_uchar * mod_patch_meta_file; int orig_meta_fd; int mod_meta_fd; note_change (&self, orig_loc, mod_loc); if (report) { rel_add_records (&report->perms_changed_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "-- %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } orig_patch_meta_file = str_alloc_cat (0, patch_base_file, ".meta-orig"); mod_patch_meta_file = str_alloc_cat (0, patch_base_file, ".meta-mod"); ensure_directory_exists (patch_file_dir); orig_meta_fd = safe_open (orig_patch_meta_file, O_WRONLY | O_EXCL | O_CREAT, 0666); mod_meta_fd = safe_open (mod_patch_meta_file, O_WRONLY | O_EXCL | O_CREAT, 0666); safe_printfmt (orig_meta_fd, "--permissions %lo\n", (t_ulong)orig_mode); safe_printfmt (mod_meta_fd, "--permissions %lo\n", (t_ulong)mod_mode); safe_close (orig_meta_fd); safe_close (mod_meta_fd); lim_free (0, orig_patch_meta_file); lim_free (0, mod_patch_meta_file); is_changed = 1; } /* FIXME: performance tuning: * arch_filename_contents_differ involves reading the entire files, * more efficient to just spawn patch and use the result. */ if ((arch_inode_sigs_differ (mod_stat, id, inode_sig_shortcuts_of_mod) == 0) || (arch_filename_contents_differ (orig_path, mod_path) == 0)) { if (orig_stat->st_dev == mod_stat->st_dev && link_same) link_force (orig_path, mod_path); goto file_path_free; } ensure_directory_exists (patch_file_dir); diff_file = str_alloc_cat (0, patch_base_file, ".patch"); vu_unlink (&ign, diff_file); diff_fd = safe_open (diff_file, O_RDWR | O_EXCL | O_CREAT, 0666); diff_stat = arch_really_invoke_diff (diff_fd, orig_path, orig_loc, mod_path, mod_loc, NULL); invariant (diff_stat!=0); if (diff_stat == 1) { t_uchar diff_pseudo_magic[sizeof ("binary files ")]; long amt; /* diff might have just said "binary files differ" or something * similar. */ safe_lseek (diff_fd, (off_t)0, SEEK_SET); amt = safe_read (diff_fd, diff_pseudo_magic, sizeof (diff_pseudo_magic) - 1); if (amt > 0) { diff_pseudo_magic[amt] = 0; if (!str_casecmp (diff_pseudo_magic, "binary files ")) diff_stat = 2; } } if (diff_stat == 2) safe_unlink (diff_file); /* diff_stat: 0 == same, 1 == patch in diff_fd, 2 == differing binary files */ switch (diff_stat) { case 1: { note_change (&self, orig_loc, mod_loc); if (report) { rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "M %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } is_changed = 1; lim_free (0, diff_file); break; } case 2: { t_uchar * orig_copy; t_uchar * mod_copy; note_change (&self, orig_loc, mod_loc); if (report) { rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0); if (report->callback) { t_uchar * escape_tmp; escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc); invoke_report_callback (report, "Mb %s\n", no_dot (escape_tmp)); lim_free (0, escape_tmp); } } orig_copy = str_alloc_cat (0, patch_base_file, ".original"); mod_copy = str_alloc_cat (0, patch_base_file, ".modified"); copy_file (orig_path, orig_copy); copy_permissions (orig_path, orig_copy); copy_file (mod_path, mod_copy); copy_permissions (mod_path, mod_copy); lim_free (0, orig_copy); lim_free (0, mod_copy); is_changed = 1; break; } default: { break; } } #if 0 if (is_changed) { *(int *)ar_push ((void **)&map.changed_files[0], 0, sizeof (int)) = map.common_files[0][x]; *(int *)ar_push ((void **)&map.changed_files[1], 0, sizeof (int)) = map.common_files[1][x]; } #endif safe_close (diff_fd); file_path_free: lim_free (0, patch_base_file); lim_free (0, patch_file_dir); } path_free: lim_free (0, orig_path); lim_free (0, mod_path); } static void compute_parent_dir_closure (rel_table * orig_newname_dirs, rel_table * mod_newname_dirs, rel_table orig_newname_files, rel_table mod_newname_files, assoc_table orig_dir_loc_of, assoc_table orig_dir_id_of, assoc_table orig_container_dir_id_of_dir_id, assoc_table orig_container_dir_id_of_file_id, assoc_table mod_dir_loc_of, assoc_table mod_dir_id_of, assoc_table mod_container_dir_id_of_dir_id, assoc_table mod_container_dir_id_of_file_id, assoc_table orig_file_id_of, assoc_table mod_file_id_of) { assoc_table orig_has_dir_id = 0; assoc_table mod_has_dir_id = 0; rel_table added_orig_container_ids = 0; /* just the deepest new id */ rel_table added_mod_container_ids = 0; /* just the deepest new id */ int lim; int x; /* Note what dir ids are already in the * final indexes. */ lim = rel_n_records (*orig_newname_dirs); for (x = 0; x < lim; ++x) { t_uchar * loc; t_uchar * id; loc = (*orig_newname_dirs)[x][0]; id = assoc_ref (orig_dir_id_of, loc); assoc_set (&orig_has_dir_id, id, loc); } lim = rel_n_records (*mod_newname_dirs); for (x = 0; x < lim; ++x) { t_uchar * loc; t_uchar * id; loc = (*mod_newname_dirs)[x][0]; id = assoc_ref (mod_dir_id_of, loc); assoc_set (&mod_has_dir_id, id, loc); } /* For each newname dir in one tree, * find the deepest container in that * tree that is present in each tree * but not in that tree's index, * and slate it for addition to the index. */ schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id, &added_mod_container_ids, &mod_has_dir_id, *orig_newname_dirs, orig_dir_id_of, orig_container_dir_id_of_dir_id, orig_container_dir_id_of_dir_id, orig_dir_loc_of, mod_dir_loc_of); schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id, &added_mod_container_ids, &mod_has_dir_id, orig_newname_files, orig_dir_id_of, orig_container_dir_id_of_file_id, orig_container_dir_id_of_dir_id, orig_dir_loc_of, mod_dir_loc_of); schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id, &added_mod_container_ids, &mod_has_dir_id, *mod_newname_dirs, mod_dir_id_of, mod_container_dir_id_of_dir_id, mod_container_dir_id_of_dir_id, orig_dir_loc_of, mod_dir_loc_of); schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id, &added_mod_container_ids, &mod_has_dir_id, mod_newname_files, mod_dir_id_of, mod_container_dir_id_of_file_id, mod_container_dir_id_of_dir_id, orig_dir_loc_of, mod_dir_loc_of); /* Add the closure of container paths of added containers * to the output lists. */ lim = rel_n_records (added_orig_container_ids); for (x = 0; x < lim; ++x) { container_closure (orig_newname_dirs, &orig_has_dir_id, added_orig_container_ids[x][0], orig_dir_loc_of, orig_container_dir_id_of_dir_id); } lim = rel_n_records (added_mod_container_ids); for (x = 0; x < lim; ++x) { container_closure (mod_newname_dirs, &mod_has_dir_id, added_mod_container_ids[x][0], mod_dir_loc_of, mod_container_dir_id_of_dir_id); } free_assoc_table (orig_has_dir_id); free_assoc_table (mod_has_dir_id); rel_free_table (added_orig_container_ids); rel_free_table (added_mod_container_ids); } static void schedule_missing_containers (rel_table * added_orig_container_ids, assoc_table * orig_has_dir_id, rel_table * added_mod_container_ids, assoc_table * mod_has_dir_id, rel_table locs_in, assoc_table id_of_dir_loc, assoc_table first_container_id_of_id, assoc_table container_id_of_dir_id, assoc_table orig_dir_loc_of, assoc_table mod_dir_loc_of) { int lim; int x; lim = rel_n_records (locs_in); for (x = 0; x < lim; ++x) { t_uchar * loc = 0; t_uchar * id; t_uchar * container_id; int added_orig; int added_mod; loc = str_save (0, locs_in[x][0]); id = assoc_ref (id_of_dir_loc, loc); container_id = assoc_ref (first_container_id_of_id, id); added_orig = 0; added_mod = 0; while ((!added_orig || !added_mod) && container_id && str_cmp (id, "?_.")) { t_uchar * t = 0; if (!added_orig && !assoc_ref (*orig_has_dir_id, container_id) && assoc_ref (orig_dir_loc_of, container_id)) { rel_add_records (added_orig_container_ids, rel_make_record (container_id, 0), 0); /* assoc_set (orig_has_dir_id, container_id, assoc_ref (orig_dir_loc_of, container_id)); */ added_orig = 1; } if (!added_mod && !assoc_ref (*mod_has_dir_id, container_id) && assoc_ref (mod_dir_loc_of, container_id)) { rel_add_records (added_mod_container_ids, rel_make_record (container_id, 0), 0); /* assoc_set (mod_has_dir_id, container_id, assoc_ref (mod_dir_loc_of, container_id)); */ added_mod = 1; } t = loc; loc = file_name_directory_file (0, loc); id = assoc_ref (id_of_dir_loc, loc); container_id = assoc_ref (container_id_of_dir_id, id); lim_free (0, t); } lim_free (0, loc); } } static void container_closure (rel_table * locs_out, assoc_table * has_ids, t_uchar * deep_id, assoc_table dir_loc_of, assoc_table container_dir_id_of_dir_id) { while (deep_id && str_cmp (deep_id, "?_.")) { if (!assoc_ref (*has_ids, deep_id)) { t_uchar * deep_loc; deep_loc = assoc_ref (dir_loc_of, deep_id); rel_add_records (locs_out, rel_make_record (deep_loc, 0), 0); assoc_set (has_ids, deep_id, deep_loc); } deep_id = assoc_ref (container_dir_id_of_dir_id, deep_id); } } static void link_force (t_uchar *old_path, t_uchar *new_path) { t_uchar *dir; t_uchar *tmp_nam; dir = file_name_directory (0, new_path); tmp_nam = file_name_in_vicinity (0, dir, ",,tmp"); safe_link (old_path, tmp_nam); safe_rename (tmp_nam, new_path); lim_free (0, dir); lim_free (0, tmp_nam); } /* tag: Tom Lord Wed May 14 18:53:05 2003 (make-changeset.c) */