/* commit.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/exception.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/os/time.h" #include "hackerlab/char/str.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/vu/safe.h" #include "libdate/date-string.h" #include "libfsutils/string-files.h" #include "libfsutils/copy-file.h" #include "libfsutils/tmp-files.h" #include "libfsutils/rmrf.h" #include "libarch/ancestry.h" #include "libarch/archive-cache.h" #include "libarch/diffs.h" #include "libarch/patch-logs.h" #include "libarch/invent.h" #include "libarch/my.h" #include "libarch/hooks.h" #include "libarch/namespace.h" #include "libarch/pristines.h" #include "libarch/project-tree.h" #include "libarch/make-changeset-files.h" #include "libarch/make-changeset.h" #include "libarch/changeset-report.h" #include "libarch/local-cache.h" #include "libarch/project-tree.h" #include "libarch/inv-ids.h" #include "libarch/changelogs.h" #include "libarch/apply-changeset.h" #include "libarch/inode-sig.h" #include "libarch/chatter.h" #include "libarch/arch-cache.h" #include "libarch/commit.h" /* __STDC__ prototypes for static functions */ static t_uchar * arch_prepare_commit_changeset (t_uchar ** changelog_loc_ret, int chatter_fd, t_uchar ** cooked_log_ret, arch_project_tree_t * tree, t_uchar * raw_log, struct arch_archive * arch, t_uchar * revision, t_uchar * prev_level, rel_table file_list, int escape_classes); static rel_table pick_patch_logs (rel_table table); static void arch_recycle_pristine (int chatter_fd, arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, t_uchar * changeset, t_uchar * changelog_loc, int escape_classes); static void arch_commit_failed (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision, t_uchar * changeset); static void commit_make_changeset_callback (void * vfd, char * fmt, va_list ap); int arch_commit (int chatter_fd, struct arch_archive * arch, t_uchar * revision, arch_project_tree_t * tree, t_uchar * raw_log, rel_table file_list, int just_commit, int escape_classes) { t_uchar * errstr; t_uchar * version = 0; t_uchar * this_level = 0; arch_patch_id * prev_revision; t_uchar * changelog_loc = 0; t_uchar * changeset_path = 0; t_uchar * cooked_log = 0; t_uchar * my_uid = 0; t_uchar * txn_id = 0; int error; int result = 0; int do_cacherev = 0; arch_patch_id * patch_id = arch_patch_id_new_archive (arch->official_name, revision); invariant (!!raw_log); version = arch_parse_package_name (arch_ret_package_version, 0, revision); this_level = arch_parse_package_name (arch_ret_patch_level, 0, revision); prev_revision = arch_previous_revision (arch, patch_id); changeset_path = arch_prepare_commit_changeset (&changelog_loc, chatter_fd, &cooked_log, tree, raw_log, arch, revision, arch_patch_id_patchlevel (prev_revision), file_list, escape_classes); invariant (!!arch_patch_id_patchlevel (prev_revision)); /* Check the error return code for the "precommit" hook and exit if non-zero. */ error = arch_run_hook ("precommit", "ARCH_ARCHIVE", arch->official_name, "ARCH_REVISION", revision, "ARCH_TREE_ROOT", tree->root, 0) ; if (error) { safe_printfmt (2, "arch_commit: precommit hook function failed with error (%d)\n commit cancelled.\n", error); exit (2); } my_uid = arch_my_id_uid (); txn_id = arch_generate_txn_id (); if (arch_archive_lock_revision (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id, this_level)) { safe_printfmt (2, "arch_commit: unable to acquire revision lock (%s)\n tree: %s\n revision: %s/%s\n url: %s\n", errstr, tree->root, arch->official_name, revision, arch->location); exit (2); } if (arch_archive_put_log (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id, cooked_log)) { safe_printfmt (2, "arch_commit: unable to send log message to archive (%s)\n tree: %s\n revision: %s/%s\n url: %s\n", errstr, tree->root, arch->official_name, version, arch->location); exit (2); } if (arch_archive_put_changeset (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id, this_level, changeset_path)) { safe_printfmt (2, "arch_commit: unable to send changeset to archive (%s)\n tree: %s\n revision: %s/%s\n url: %s\n", errstr, tree->root, arch->official_name, revision, arch->location); exit (2); } if (arch_revision_ready (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id, this_level)) { safe_printfmt (2, "arch_commit: error sending revision to archive (%s)\n tree: %s\n revision: %s/%s\n url: %s\n", errstr, tree->root, arch->official_name, revision, arch->location); /* FIXME: detail unlock failures to the user gracefully */ invariant (!arch_archive_break_revision_lock (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id)); result = -1; goto error_exit; } arch_start_tree_commit (tree, cooked_log); if (arch_archive_finish_revision (&errstr, arch, version, arch_patch_id_patchlevel (prev_revision), my_uid, txn_id, this_level)) { arch_commit_failed (tree, arch->official_name, version, changeset_path); safe_printfmt (2, "arch_commit: unable to complete commit transaction (%s)\n tree: %s\n revision: %s/%s\n url: %s\n", errstr, tree->root, arch->official_name, version, arch->location); exit (2); } if (!just_commit) { struct exception * e; /* FIXME upload limited history and update a current dir in the archive */ ancestry_upload_patch (arch, patch_id, -1); Try { /* pull in the tree ancestry */ /* bah again with the needing policy stuff */ rel_table ancestry = patch_ancestry (talloc_context, tree, patch_id, -1); do_cacherev = rel_n_records (ancestry) % MAGIC_CACHEREV_INTERVAL == 2; rel_free_table (ancestry); } Catch (e) { /* invariants */ if (e->code < 0) Throw (e); talloc_free (e); } } arch_maybe_cache_commit (arch, revision, arch->official_name, arch_patch_id_revision (prev_revision), changeset_path); arch_finish_tree_commit (tree, arch->official_name, revision, changelog_loc); arch_recycle_pristine (chatter_fd, tree, arch->official_name, arch_patch_id_revision (prev_revision), revision, changeset_path, changelog_loc, escape_classes); arch_run_hook ("commit", "ARCH_ARCHIVE", arch->official_name, "ARCH_REVISION", revision, "ARCH_TREE_ROOT", tree->root, 0); if (rel_n_records (file_list) == 0) arch_snap_inode_sig_not_reference (tree, arch->official_name, revision); else { arch_snap_inode_sig_not_reference_files(tree, arch->official_name, arch_patch_id_revision (prev_revision), revision, file_list); } if (do_cacherev) arch_archive_cache (2, arch, arch->official_name, revision, tree); error_exit: lim_free (0, version); lim_free (0, this_level); talloc_free (prev_revision); lim_free (0, changeset_path); lim_free (0, cooked_log); lim_free (0, changelog_loc); lim_free (0, my_uid); lim_free (0, txn_id); talloc_free (patch_id); return result; } static t_uchar * arch_prepare_commit_changeset (t_uchar ** changelog_loc_ret, int chatter_fd, t_uchar ** cooked_log_ret, arch_project_tree_t * tree, t_uchar * raw_log, struct arch_archive * arch, t_uchar * revision, t_uchar * prev_level, rel_table file_list, int escape_classes) { t_uchar * archive = arch->official_name; t_uchar * tmp_stem = 0; t_uchar * tmp_path = 0; t_uchar * changeset_basename = 0; t_uchar * changeset_path = 0; /* return value */ t_uchar * version = 0; t_uchar * level = 0; t_uchar * prev_revision = 0; t_uchar * prev_rev_path = 0; arch_project_tree_t * prev_rev_tree; struct arch_make_changeset_report make_report = {0, }; /* should have a chatter callback here */ t_uchar * changelog_id_suffix = 0; t_uchar * changelog_x_id = 0; t_uchar * changelog_i_id = 0; t_uchar * changelog_id = 0; t_uchar * changelog_orig_loc = 0; t_uchar * changelog_mod_loc = 0; struct arch_changeset_report csr = {0, }; int changelog_diffs_fd = -1; int changelog_add_fd = -1; t_uchar * new_log_id = 0; t_uchar * new_log_loc = 0; int new_log_fd = -1; t_uchar * new_log_path = 0; t_uchar * cooked_log = 0; t_uchar * new_changelog_path = 0; /**************************************************************** * double check that we were handed a valid log message, if any */ invariant (arch_valid_log_file (raw_log)); /**************************************************************** * make a temp dir for the changeset */ tmp_stem = str_alloc_cat_many (0, ",,commit.", revision, "--", archive, str_end); tmp_path = talloc_tmp_file_name (talloc_context, tree->root, tmp_stem); rmrf_file (tmp_path); safe_mkdir (tmp_path, 0777); changeset_basename = str_alloc_cat (0, revision, ".patches"); changeset_path = file_name_in_vicinity (0, tmp_path, changeset_basename); /**************************************************************** * Compute the raw changeset * * The changeset computed here does _not_ include an add of the new log * message and does _not_ include updates to automatic changelogs. */ version = arch_parse_package_name (arch_ret_package_version, 0, revision); level = arch_parse_package_name (arch_ret_patch_level, 0, revision); prev_revision = str_alloc_cat_many (0, version, "--", prev_level, str_end); prev_rev_path = arch_find_or_make_local_copy (chatter_fd, tree, 0, arch, archive, prev_revision); prev_rev_tree = arch_project_tree_new (talloc_context, prev_rev_path); if (chatter_fd >= 0) { make_report.callback = commit_make_changeset_callback; make_report.thunk = (void *)(long)chatter_fd; } if (!file_list) { assoc_table inode_shortcut = 0; arch_read_inode_sig_ids (0, &inode_shortcut, tree->root, archive, prev_revision); arch_make_changeset (&make_report, prev_rev_tree, tree, changeset_path, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, inode_shortcut, 0, escape_classes); free_assoc_table (inode_shortcut); } else arch_make_files_changeset (&make_report, changeset_path, file_list, prev_rev_tree, tree, arch_unspecified_id_tagging, arch_inventory_unrecognized, escape_classes); /**************************************************************** * Look for a Changelog for the version we're committing to. */ changelog_id_suffix = str_alloc_cat_many (0, "_automatic-ChangeLog--", archive, "/", version, str_end); changelog_x_id = str_alloc_cat (0, "x", changelog_id_suffix); changelog_i_id = str_alloc_cat (0, "i", changelog_id_suffix); changelog_mod_loc = assoc_ref (make_report.mod_file_loc_of, changelog_x_id); if (changelog_mod_loc) { changelog_mod_loc = str_save (0, changelog_mod_loc); changelog_id = str_save (0, changelog_x_id); } else { changelog_mod_loc = assoc_ref (make_report.mod_file_loc_of, changelog_i_id); if (changelog_mod_loc) { changelog_mod_loc = str_save (0, changelog_mod_loc); changelog_id = str_save (0, changelog_i_id); } } if (changelog_mod_loc) { if (changelog_loc_ret) { *changelog_loc_ret = str_save (0, changelog_mod_loc); } changelog_orig_loc = assoc_ref (make_report.orig_file_loc_of, changelog_id); if (changelog_orig_loc) changelog_orig_loc = str_save (0, changelog_orig_loc); } /* post-condition: * * changelog_id, changelog_mod_loc: 0 if no MOD tree changelog * set if there is a changelog * * changelog_orig_loc: 0 if no MOD or no ORIG changelog, set if both */ /**************************************************************** * Get a report about this changeset */ arch_evaluate_changeset (&csr, changeset_path); /**************************************************************** * Update the changeset indexes to reflect the patch log and changelog * * The changelog for this version, if any, is going to change * as a result of the new patch log for this commit. * Usually (though we don't count on it) it didn't change between * pristine and project tree. * * So, we have to touch up the changeset to reflect a diff to that * changelog. At this stage, only the file and dir indexes are * modified to reflect that (we haven't generated the new log entry * yet, so we can't produce the actual changelog diffs). * * Also, we know we're adding a new patch log, so the indexes should * reflect that, too. * * The reason to touch up the indexes early is that the patch log * is derived, in part, from those indexes. */ if (changelog_mod_loc && changelog_orig_loc) { changelog_diffs_fd = arch_changeset_add_diffs (&csr, &make_report, changeset_path, changelog_orig_loc, changelog_mod_loc, changelog_id); } else if (changelog_mod_loc) { changelog_add_fd = arch_changeset_add_file (&new_changelog_path, &csr, &make_report, changeset_path, changelog_mod_loc, changelog_id); } new_log_loc = arch_log_file (".", archive, revision); new_log_id = arch_log_file_id (archive, revision); new_log_fd = arch_changeset_add_file (&new_log_path, &csr, &make_report, changeset_path, new_log_loc, new_log_id); arch_changeset_rewrite_indexes (changeset_path, &csr); /**************************************************************** * Generate the Cooked Log Entry */ { t_uchar * my_id = 0; time_t now; t_uchar * std_date = 0; t_uchar * human_date = 0; int log_fd; my_id = arch_my_id (); now = time (0); std_date = standard_date (now); human_date = pretty_date (now); log_fd = make_output_to_string_fd (); safe_printfmt (log_fd, "Revision: %s\n", revision); safe_printfmt (log_fd, "Archive: %s\n", archive); safe_printfmt (log_fd, "Creator: %s\n", my_id); safe_printfmt (log_fd, "Date: %s\n", human_date); safe_printfmt (log_fd, "Standard-date: %s\n", std_date); /******************************** * automatic headers for various file and patch lists */ { rel_table new_files = 0; rel_table removed_files = 0; rel_table new_directories = 0; rel_table removed_directories = 0; rel_table modified_files = 0; rel_table new_patches = 0; rel_table removed_patches = 0; new_files = pick_non_control (csr.added_files); rel_append_x (&new_files, csr.added_symlinks); rel_sort_table_by_field (0, new_files, 0); removed_files = pick_non_control (csr.removed_files); rel_append_x (&removed_files, csr.removed_symlinks); rel_sort_table_by_field (0, removed_files, 0); new_directories = pick_non_control (csr.added_dirs); removed_directories = pick_non_control (csr.removed_dirs); modified_files = rel_copy_table (csr.patched_regular_files); rel_append_x (&modified_files, csr.patched_symlinks); rel_append_x (&modified_files, csr.patched_binaries); rel_append_x (&modified_files, csr.file_metadata_changed); rel_append_x (&modified_files, csr.symlink_to_file); rel_append_x (&modified_files, csr.file_to_symlink); rel_sort_table_by_field (0, modified_files, 0); rel_uniq_by_field (&modified_files, 0); new_patches = pick_patch_logs (csr.added_files); removed_patches = pick_patch_logs (csr.removed_files); arch_print_log_list_header (log_fd, "New-files", new_files, 0); arch_print_log_list_header (log_fd, "New-directories", new_directories, 0); arch_print_log_list_header (log_fd, "Removed-files", removed_files, 0); arch_print_log_list_header (log_fd, "Removed-directories", removed_directories, 0); arch_print_log_pairs_header (log_fd, "Renamed-files", csr.renamed_files, 0, 1); arch_print_log_pairs_header (log_fd, "Renamed-directories", csr.renamed_dirs, 0, 1); arch_print_log_list_header (log_fd, "Modified-files", modified_files, 0); arch_print_log_list_header (log_fd, "Modified-directories", csr.dir_metadata_changed, 0); arch_print_log_list_header (log_fd, "New-patches", new_patches, 0); arch_print_log_list_header (log_fd, "Removed-patches", removed_patches, 0); rel_free_table (new_files); rel_free_table (removed_files); rel_free_table (new_directories); rel_free_table (removed_directories); rel_free_table (modified_files); rel_free_table (new_patches); rel_free_table (removed_patches); } /******************************** * copy the user headers, and copy or generate the log body */ { t_uchar * eoh = raw_log; while (1) { eoh = str_chr_index (eoh, '\n'); if (!eoh || (eoh[1] == '\n') || (!eoh[1])) break; else ++eoh; } if (eoh) { eoh = eoh + 1; safe_printfmt (log_fd, "%.*s", (int)(eoh - raw_log), raw_log); } if (eoh && *eoh) { safe_printfmt (log_fd, "%s", eoh); } else { safe_printfmt (log_fd, "\n\n"); } } /******************************** * oh... did i mention we were writing * the log to a string? */ cooked_log = string_fd_close (log_fd); lim_free (0, my_id); lim_free (0, std_date); lim_free (0, human_date); } /**************************************************************** * Write the log into the changeset */ safe_printfmt (new_log_fd, "%s", cooked_log); safe_close (new_log_fd); /**************************************************************** * Give a copy of the log to the caller. */ if (cooked_log_ret) *cooked_log_ret = str_save (0, cooked_log); /**************************************************************** * Generate the new changelog. */ if (changelog_add_fd > 0) { invariant (changelog_add_fd >= 0); invariant (changelog_diffs_fd < 0); arch_generate_changelog (changelog_add_fd, tree, 0, 0, level, new_log_path, archive, version); /* new_changelog_path already set. */ safe_close (changelog_add_fd); } else if (changelog_diffs_fd > 0) { int fd = -1; t_uchar * changelog_orig_path = 0; new_changelog_path = file_name_in_vicinity (0, changeset_path, ",,changelog"); fd = safe_open (new_changelog_path, O_WRONLY | O_CREAT | O_EXCL, 0444); arch_generate_changelog (fd, tree, 0, 0, level, new_log_path, archive, version); safe_close (fd); changelog_orig_path = file_name_in_vicinity (0, prev_rev_tree->root, changelog_orig_loc); invariant (changelog_diffs_fd >= 0); invariant (changelog_add_fd < 0); arch_invoke_diff (changelog_diffs_fd, changelog_orig_path, changelog_orig_loc, new_changelog_path, changelog_mod_loc, 0, 0); safe_close (changelog_diffs_fd); rmrf_file (new_changelog_path); lim_free (0, changelog_orig_path); } lim_free (0, tmp_stem); talloc_free (tmp_path); lim_free (0, changeset_basename); lim_free (0, version); lim_free (0, level); lim_free (0, prev_revision); lim_free (0, prev_rev_path); arch_project_tree_delete (prev_rev_tree); arch_free_make_changeset_report_data (&make_report); lim_free (0, changelog_id_suffix); lim_free (0, changelog_x_id); lim_free (0, changelog_i_id); lim_free (0, changelog_id); lim_free (0, changelog_orig_loc); lim_free (0, changelog_mod_loc); arch_free_changeset_report_data (&csr); lim_free (0, new_log_id); lim_free (0, new_log_loc); lim_free (0, new_log_path); lim_free (0, cooked_log); lim_free (0, new_changelog_path); /**************************************************************** * Give the user the path to the pristine tree for base-0. * It's up tot he caller to stash this in the archive. */ return changeset_path; } static rel_table pick_patch_logs (rel_table table) { int x; rel_table answer = 0; for (x = 0; x < rel_n_records (table); ++x) { t_uchar * f = 0; t_uchar * t = 0; t_uchar * d = 0; t_uchar * level = 0; t_uchar * archive = 0; t_uchar * version = 0; t_uchar * revision = 0; t_uchar * fqrev = 0; f = str_save (0, table[x][0]); t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (arch_valid_patch_level_name (t)) { level = str_save (0, t); lim_free (0, f); f = d; t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (!str_cmp ("patch-log", t)) { lim_free (0, f); f = d; t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (arch_valid_archive_name (t)) { archive = str_save (0, t); lim_free (0, f); f = d; t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (arch_valid_package_name (t, arch_no_archive, arch_req_version, 0)) { version = str_save (0, t); lim_free (0, f); f = d; t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (arch_valid_package_name (t, arch_no_archive, arch_req_package, 0)) { lim_free (0, f); f = d; t = file_name_tail (0, f); d = file_name_directory_file (0, f); if (arch_valid_package_name (t, arch_no_archive, arch_req_category, 0)) { if (!str_cmp ("./{arch}", d)) { revision = str_alloc_cat_many (0, version, "--", level, str_end); fqrev = arch_fully_qualify (archive, revision); rel_add_records (&answer, rel_make_record (fqrev, table[x][1], table[x][2], 0), 0); } } } } } } lim_free (0, f); lim_free (0, t); lim_free (0, d); lim_free (0, level); lim_free (0, archive); lim_free (0, version); lim_free (0, revision); lim_free (0, fqrev); } } arch_sort_table_by_name_field (0, answer, 0); return answer; } static void arch_recycle_pristine (int chatter_fd, arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, t_uchar * changeset, t_uchar * changelog_loc, int escape_classes) { t_uchar * dir = 0; t_uchar * dir_tail = 0; arch_project_tree_t * recycled_pristine_src = 0; dir = file_name_directory_file (0, changeset); dir_tail = file_name_tail (0, dir); invariant (!str_cmp_prefix (",,", dir_tail)); { arch_patch_id * prev = arch_patch_id_new_archive (archive, prev_revision); recycled_pristine_src = arch_find_pristine (tree, prev, arch_tree_pristine_search); talloc_free (prev); } if (recycled_pristine_src) { t_uchar * inventory_path = 0; struct arch_apply_changeset_report * report; arch_project_tree_t * recycled_tree; t_uchar * recycled_pristine_tmp = 0; arch_chatter (chatter_fd, "* update pristine tree (%s/%s => %s)\n", archive, prev_revision, revision); recycled_pristine_tmp = talloc_tmp_file_name (talloc_context, dir, ",,pristine"); safe_rename (recycled_pristine_src->root, recycled_pristine_tmp); inventory_path = file_name_in_vicinity (0, recycled_pristine_tmp, ",,index"); rmrf_file (inventory_path); recycled_tree = arch_project_tree_new (talloc_context, recycled_pristine_tmp); report = arch_apply_changeset (changeset, talloc_context, recycled_tree, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, 0, escape_classes, NULL, NULL); invariant (!arch_conflicts_occurred (report)); talloc_free (report); arch_install_pristine (tree, archive, revision, recycled_pristine_tmp); arch_project_tree_delete (recycled_tree); lim_free (0, inventory_path); talloc_free (recycled_pristine_tmp); } rmrf_file (dir); lim_free (0, dir); lim_free (0, dir_tail); arch_project_tree_delete (recycled_pristine_src); } static void arch_commit_failed (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision, t_uchar * changeset) { t_uchar * dir = 0; t_uchar * dir_tail = 0; dir = file_name_directory_file (0, changeset); dir_tail = file_name_tail (0, dir); invariant (!str_cmp_prefix (",,", dir_tail)); arch_abort_tree_commit (tree, archive, revision); rmrf_file (dir); lim_free (0, dir); lim_free (0, dir_tail); } static void commit_make_changeset_callback (void * vfd, char * fmt, va_list ap) { int fd; fd = (int)(t_ulong)vfd; safe_printfmt_va_list (fd, fmt, ap); safe_flush (1); } /* tag: Tom Lord Mon May 26 12:05:54 2003 (commit.c) */