/* project-tree.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/bugs/exception.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/char/str.h"
#include "libfsutils/file-contents.h"
#include "libawk/trim.h"
#include "libarch/ancestry.h"
#include "libarch/namespace.h"
#include "libarch/patch-id.h"
#include "libarch/patch-logs.h"
#include "libarch/changelogs.h"
#include "libarch/inv-ids.h"
#include "libarch/null-project-tree.h"
#include "libarch/tla-project-tree.h"
#include "libarch/pfs.h"
#include "libarch/project-tree.h"
static void arch_project_tree_check_name_ext (arch_project_tree_t * tree,
struct arch_archive ** arch,
t_uchar ** namespace,
t_uchar const * const name,
struct arch_archive * (*connect_function)(t_uchar const * const name, t_uchar **name_out));
static int arch_project_tree_destructor (void * data);
/* These must agree about the format version number.
*/
void
arch_init_tree (t_uchar const * const tree_root)
{
tla_project_tree_init_directory (tree_root);
}
/* Move to fsutils or hackerlab */
static int
is_dir (t_uchar const *path)
{
int errn;
int answer;
struct stat stat_buf;
answer = vu_lstat (&errn, (char *)path, &stat_buf);
if (answer)
{
/* couldn't stat, pretend its not a dir */
return 0;
}
return S_ISDIR(stat_buf.st_mode);
}
/**
* \brief find the tree root.
*
* finds the tree root for a tree, for any version tree
*/
t_uchar *
arch_tree_root (enum arch_tree_state * state, t_uchar const * const input_dir, int accurate)
{
return arch_tree_root_ext (NULL, state, input_dir, accurate, NULL, 0);
}
/**
* \brief find the root of a tree and provide a vtable for the found tree
* \param exact the path is the tree root, do not search, and panic if missing
*/
t_uchar *
arch_tree_root_ext (void * context, enum arch_tree_state * state,
t_uchar const * const _input_dir, int accurate,
arch_project_tree_t **object_out, int exact)
{
int here_fd = -1; /* shutup gcc, here_fd is used properly */
int errn;
t_uchar * dir;
t_uchar * answer;
t_uchar * input_dir = str_save (0, _input_dir);
answer = 0;
if (str_length(input_dir))
{
if (!is_dir (input_dir))
{
input_dir = str_replace (input_dir, file_name_directory_file (0, input_dir));
if (! input_dir)
input_dir = str_save (0, ".");
}
here_fd = safe_open (".", O_RDONLY, 0);
safe_chdir (input_dir);
}
dir = (t_uchar *)current_working_directory (&errn, 0);
if (!dir)
panic ("unable to compute current working directory");
if (input_dir)
{
safe_fchdir (here_fd);
safe_close (here_fd);
}
if (exact)
{
if (!arch_project_tree_dir_is_root (context, dir, object_out))
panic ("exact tree root is not a tree - perhaps an unsupported tree type ?");
answer = str_save (0, dir);
}
else
while (1)
{
t_uchar * next_dir;
if (arch_project_tree_dir_is_root (context, dir, object_out))
{
answer = str_save (0, dir);
break;
}
if (!str_cmp (dir, "/"))
break;
next_dir = file_name_directory_file (0, dir);
lim_free (0, dir);
dir = next_dir;
next_dir = 0;
}
if (answer && accurate)
{
t_uchar * rc_name;
t_uchar * cd_name;
t_uchar * mc_name;
rc_name = file_name_in_vicinity (0, answer, "{arch}/++resolve-conflicts");
cd_name = file_name_in_vicinity (0, answer, "{arch}/++commit-definite");
mc_name = file_name_in_vicinity (0, answer, "{arch}/++mid-commit");
if (state)
{
if (!safe_access (rc_name, F_OK))
*state = arch_tree_in_resolve_conflicts;
else if (!safe_access (cd_name, F_OK))
*state = arch_tree_in_commit_definite;
else if (!safe_access (mc_name, F_OK))
*state = arch_tree_in_mid_commit;
else
*state = arch_tree_in_ok_state;
}
}
lim_free (0, dir);
lim_free (0, input_dir);
return answer;
}
t_uchar *
arch_tree_ctl_dir (t_uchar const * tree_root)
{
return file_name_in_vicinity (0, tree_root, "{arch}");
}
void
arch_set_tree_version (t_uchar const * tree_root, arch_patch_id * patch_id)
{
t_uchar * default_version_file = 0;
t_uchar * tmp_file = 0;
int out_fd;
invariant (arch_valid_package_name (arch_patch_id_branch (patch_id), arch_req_archive, arch_req_version, 0));
tmp_file = file_name_in_vicinity (0, tree_root, "{arch}/,,set-tree-version");
default_version_file = file_name_in_vicinity (0, tree_root, "{arch}/++default-version");
out_fd = safe_open (tmp_file, O_WRONLY | O_CREAT, 0666);
safe_ftruncate (out_fd, (long)0);
safe_printfmt (out_fd, "%s\n", arch_patch_id_branch (patch_id));
safe_close (out_fd);
safe_rename (tmp_file, default_version_file);
lim_free (0, tmp_file);
lim_free (0, default_version_file);
}
t_uchar *
arch_tree_version (t_uchar const * tree_root)
{
t_uchar * default_version_file;
int in_fd;
t_uchar * file_contents;
size_t file_contents_len;
t_uchar * nl;
default_version_file = file_name_in_vicinity (0, tree_root, "{arch}/++default-version");
if (safe_access (default_version_file, F_OK))
{
lim_free (0, default_version_file);
return 0;
}
in_fd = safe_open (default_version_file, O_RDONLY, 0666);
file_contents = 0;
file_contents_len = 0;
safe_file_to_string (&file_contents, &file_contents_len, in_fd);
safe_close (in_fd);
nl = str_chr_index_n (file_contents, file_contents_len, '\n');
if (nl)
file_contents_len = nl - file_contents;
file_contents = lim_realloc (0, file_contents, file_contents_len + 1);
file_contents[file_contents_len] = 0;
invariant (arch_valid_package_name (file_contents, arch_req_archive, arch_req_version, 0));
lim_free (0, default_version_file);
return file_contents;
}
t_uchar *
arch_try_tree_version_dir (t_uchar * cmd, arch_project_tree_t * tree)
{
t_uchar * version_spec = 0;
if (!tree->root)
{
safe_printfmt (2, "%s: not in a project tree\n", cmd);
exit (2);
}
version_spec = arch_tree_version (tree->root);
if (!version_spec)
{
safe_printfmt (2, "%s: tree has no default version set\n tree: %s\n",
cmd, tree->root);
exit (2);
}
return version_spec;
}
t_uchar *
arch_try_tree_version (t_uchar * cmd)
{
t_uchar * answer;
arch_project_tree_t * tree = arch_project_tree_new (talloc_context, ".");
answer = arch_try_tree_version_dir (cmd, tree);
talloc_unlink (talloc_context, tree);
return answer;
}
void
arch_start_tree_commit (arch_project_tree_t * tree, t_uchar * log)
{
int ign;
t_uchar * mid_commit_file = 0;
t_uchar * mid_commit_tmp = 0;
int out_fd;
mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit");
mid_commit_tmp = file_name_in_vicinity (0, tree->root, "{arch}/,,mid-commit");
vu_unlink (&ign, mid_commit_tmp);
vu_unlink (&ign, mid_commit_file);
out_fd = safe_open (mid_commit_tmp, O_WRONLY | O_CREAT | O_EXCL, 0666);
safe_printfmt (out_fd, "%s", log);
safe_close (out_fd);
safe_rename (mid_commit_tmp, mid_commit_file);
lim_free (0, mid_commit_file);
}
void
arch_finish_tree_commit (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision, t_uchar * changelog_loc)
{
t_uchar * mid_commit_file = 0;
t_uchar * commit_definite_file = 0;
mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit");
commit_definite_file = file_name_in_vicinity (0, tree->root, "{arch}/++commit-definite");
safe_rename (mid_commit_file, commit_definite_file);
if (changelog_loc)
{
struct stat statb;
t_uchar * level = 0;
t_uchar * version = 0;
t_uchar * changelog_path = 0;
t_uchar * changelog_dir = 0;
t_uchar * changelog_tmp = 0;
mode_t mode;
int fd;
level = arch_parse_package_name (arch_ret_patch_level, 0, revision);
version = arch_parse_package_name (arch_ret_package_version, 0, revision);
changelog_path = file_name_in_vicinity (0, tree->root, changelog_loc);
changelog_dir = file_name_directory_file (0, changelog_path);
changelog_tmp = file_name_in_vicinity (0, changelog_dir, ",,new-changelog");
safe_stat (changelog_path, &statb);
mode = statb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
fd = safe_open (changelog_tmp, O_WRONLY | O_CREAT | O_TRUNC, mode);
safe_fchmod (fd, mode);
arch_generate_changelog (fd, tree, 0, 0, level, commit_definite_file, archive, version);
safe_close (fd);
safe_rename (changelog_tmp, changelog_path);
lim_free (0, level);
lim_free (0, version);
lim_free (0, changelog_path);
lim_free (0, changelog_dir);
lim_free (0, changelog_tmp);
}
arch_copy_to_patch_log (tree->root, archive, revision, commit_definite_file);
safe_unlink (commit_definite_file);
lim_free (0, mid_commit_file);
lim_free (0, commit_definite_file);
}
void
arch_abort_tree_commit (arch_project_tree_t * tree, t_uchar * archive, t_uchar * revision)
{
t_uchar * mid_commit_file = 0;
mid_commit_file = file_name_in_vicinity (0, tree->root, "{arch}/++mid-commit");
safe_unlink (mid_commit_file);
lim_free (0, mid_commit_file);
}
/**
* \brief common project_tree initialisation
*
* root must be set explicitly (NULL is ok) before calling this.
* \param tree the tree to init
*/
void
arch_project_tree_init (arch_project_tree_t *tree)
{
t_uchar * latest_log;
tree->archive = NULL;
tree->fqversion = NULL;
tree->fqrevision = NULL;
tree->version = NULL;
tree->revision = NULL;
tree->ancestry = NULL;
tree->local_ancestry = NULL;
{
/* these do not belong here. however layering is still tricky for
* them. so we give them arbitrary initialized values and then the
* concrete classes can override
*/
tree->untagged_is_source = 0;
tree->id_tagging_shortcut = NULL;
tree->explicit_skips = NULL;
}
arch_patch_log_dirs_init (&tree->patch_logs);
if (!tree->root)
{
/* FIXME audit for non root tree-usage, and nuke it.
* all those should become null trees
*/
tree->untagged_is_source = 1;
tree->tag_method = arch_names_id_tagging; /* ancient default */
return ;
}
tree->fqversion = arch_tree_version (tree->root);
if (tree->fqversion)
{
tree->archive = arch_parse_package_name (arch_ret_archive, 0, tree->fqversion);
tree->version = arch_parse_package_name (arch_ret_non_archive, 0, tree->fqversion);
/* tree_root should have done this right.. but lets be sure */
invariant (!!tree->archive);
invariant (!!tree->version);
latest_log = arch_highest_patch_level (tree->root, tree->archive, tree->version);
if (latest_log)
{
tree->fqrevision = str_alloc_cat_many (0, tree->archive, "/", tree->version, "--", latest_log, str_end);
tree->revision = str_alloc_cat_many (0, tree->version, "--", latest_log, str_end);
lim_free (0, latest_log);
}
}
if (tree->vtable)
tree->vtable->init (tree);
}
/**
* \brief initialize a project tree object
* \param tree_root the possible tree root
* \return an initialized tree. If no root was found, a tree with no vtable is returned.
*/
struct arch_project_tree *
arch_project_tree_new (void * context, t_uchar const * tree_root)
{
return arch_project_tree_new_ext (context, tree_root, 0, 0);
}
/**
* \brief initialize a project tree object
* \param tree_root the exact path to the tree root
* \return an initialized tree. If no root was found, a panic occurs
*/
struct arch_project_tree *
arch_project_tree_new_exact (void * context, t_uchar const * tree_root)
{
return arch_project_tree_new_ext (context, tree_root, 0, 1);
}
/**
* \brief initialize a project tree object
* \param tree_root the possible tree root
* \param null_ok if true, and no tree root was found, initialize a null tree.
* \return an initialized tree. If no root was found, a tree with no vtable is returned.
*/
struct arch_project_tree *
arch_project_tree_new_ext (void * context, t_uchar const * tree_root, int null_ok, int exact)
{
arch_project_tree_t * result;
t_uchar * root;
root = arch_tree_root_ext (context, 0, tree_root, 0, &result, exact);
if (!root && null_ok)
{
if (null_project_tree_dir_is_root (context, tree_root, &result))
root = arch_abs_path (tree_root);
}
if (!root)
{
result = talloc (context, arch_project_tree_t);
talloc_set_destructor (result, arch_project_tree_destructor);
result->vtable = NULL;
}
result->root = root;
arch_project_tree_init (result);
return result;
}
int
arch_project_tree_destructor (void * data)
{
arch_project_tree_t * tree = talloc_get_type (data, arch_project_tree_t);
if (!tree)
Throw (exception (EINVAL, "invalid tree in arch_project_tree_destructor"));
arch_project_tree_finalise (tree);
return 0;
}
void
arch_project_tree_finalise (arch_project_tree_t *tree)
{
lim_free (0, tree->root);
lim_free (0, tree->archive);
lim_free (0, tree->fqversion);
lim_free (0, tree->fqrevision);
lim_free (0, tree->version);
lim_free (0, tree->revision);
rel_free_table (tree->ancestry);
free_assoc_table (tree->local_ancestry);
arch_patch_log_dirs_finalise (&tree->patch_logs);
free_assoc_table (tree->id_tagging_shortcut);
free_assoc_table (tree->explicit_skips);
}
int
arch_project_tree_file_exists(struct arch_project_tree *tree, t_uchar const *rel_path)
{
t_uchar *path = file_name_in_vicinity (0, tree->root, rel_path);
struct stat stat_buf;
int result = stat (path, &stat_buf);
lim_free (0, path);
return result == 0;
}
t_uchar *
arch_project_tree_file_contents(struct arch_project_tree *tree, t_uchar const *rel_path)
{
t_uchar *path = file_name_in_vicinity (0, tree->root, rel_path);
t_uchar *answer = file_contents (path);
lim_free (0, path);
return answer;
}
/*
* \brief get as much of the trees ancestry as we can. Uses the archive
* to retrieve the ancestry.
* Ownership of the table is retained by the project tree struct
* \throw as per patch_ancestry
*/
rel_table /* const */
arch_project_tree_ancestry (struct arch_project_tree *tree)
{
if (tree->ancestry)
return tree->ancestry;
arch_patch_id * patch_id = arch_patch_id_new (tree->fqrevision);
tree->ancestry = patch_ancestry (tree, tree, patch_id, -1);
talloc_free (patch_id);
return tree->ancestry;
}
int
arch_project_tree_has_patch (arch_project_tree_t *tree, t_uchar const *patch_id)
{
int answer = 0;
t_uchar *log_file = arch_project_tree_patch_filename (tree, patch_id);
/* log files are never symlinks etc */
if (!safe_access (log_file, F_OK))
answer = -1;
lim_free (0, log_file);
return answer;
}
/**
* \@ brief find the filename a patch will be stored under, if it is present
* as a file in the tree
*/
t_uchar *
arch_project_tree_patch_filename (arch_project_tree_t *tree, t_uchar const *patch_id)
{
t_uchar * log_dir, *log_file;
arch_patch_id thePatch;
arch_patch_id_init (&thePatch, patch_id);
log_dir = arch_log_dir (tree->root, arch_patch_id_archive(&thePatch), arch_patch_id_version(&thePatch));
log_file = file_name_in_vicinity (0, log_dir, arch_patch_id_patchlevel(&thePatch));
lim_free (0, log_dir);
arch_patch_id_finalise (&thePatch);
return log_file;
}
t_uchar *
arch_project_tree_patch_log (arch_project_tree_t * tree, t_uchar const * fqrevision)
{
t_uchar * log_filename = NULL;
t_uchar * log_text = NULL;
if (!arch_project_tree_has_patch (tree, fqrevision))
return NULL;
log_filename = arch_project_tree_patch_filename (tree, fqrevision);
log_text = file_contents (log_filename);
lim_free (0, log_filename);
return log_text;
}
/**
* \brief get the inventory entry for a given path from tree
* \param tree duh
* \param path the tree relative path, optionally prefixed with ./
* \param known_lstat a cached stat result. if not present and the tree type requires it, a stat will be performed
*/
inventory_entry_t *
arch_project_tree_path_inventory (arch_project_tree_t * tree, t_uchar const * const path, struct stat * known_lstat)
{
int errn = 0;
inventory_entry_t *answer = tree->vtable->path_id (tree, &errn, path, known_lstat);
if (!answer && errn)
{
safe_printfmt (2, "error finding file id (%d: %s)\n path: %s\n", errn, errno_to_string(errn), path);
panic ("arch_project_tree_path_inventory");
}
return answer;
}
/**
* \brief given a filename, qualify it relative to a tree root:
* This uses the current working directory if needed - not suitable
* for use by libarch / * functions.
* \throw EINVAL if either parameter is NULL, or if filename
* while qualified is not relative to the tree root.
* \param tree - the tree object
* \param filename - the file to make relative
* \return the relative file path
*/
t_uchar *
arch_project_tree_rel_path_from_user_input (arch_project_tree_t * tree,
t_uchar const * const filename)
{
t_uchar * abs_filename;
t_uchar * qualified;
if (!tree->root || !filename)
Throw (exception (EINVAL, "arch_project_tree_rel_path_from_user_input: invalid parameters"));
abs_filename = arch_abs_path (filename);
qualified = arch_project_tree_rel_path_from_abs(tree, abs_filename);
lim_free (0, abs_filename);
return qualified;
}
/**
* \brief canonicalise a path within a project tree. this depends on the tree root and the abs path both being in canonical normal form
*
* passing in a path that isn't in the tree will cause a panic - this is deliberate, as user input never hits this directly.
* \param tree the tree
* \param abs_path the path to canonicalise
*/
t_uchar *
arch_project_tree_rel_path_from_abs (arch_project_tree_t * tree, t_uchar const * const abs_path)
{
t_uchar * result;
invariant (!!tree->root);
/* TODO canonicalise both etc etc */
if ( str_cmp_prefix (tree->root, abs_path))
{
safe_printfmt (2, "abs_path (%s) is not a subpath of tree root (%s)\n", abs_path, tree->root);
exit (2);
}
result = str_save (0, abs_path+str_length(tree->root));
return str_replace (result, str_alloc_cat (0, ".", result));
}
/**
* \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree with a connection type constraint.
*
* i.e. patch-1 -> tree-archive & tree-version
* foo--bar--0 -> tree-archive & & foo--bar--0
* foo/bar.. -> foo & bar
* on failure, *arch is null;
*/
void
arch_project_tree_check_name_ext (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name, struct arch_archive * (*connect_function)(t_uchar const * const name, t_uchar **name_out))
{
/* its a patch level : qualify with the tree version */
if (tree->fqversion && arch_valid_patch_level_name (name))
{
/* get the official name for the tree */
*arch = connect_function (tree->fqversion, namespace);
if (!*arch)
return;
/* append the patch-level */
*namespace = str_replace (*namespace, str_alloc_cat_many (0, *namespace, "--", name, str_end));
return;
}
/* its a relative branch name : qualify with the tree archive */
if (tree->archive && arch_valid_package_name (name, arch_no_archive, arch_req_package, 1))
{
/* get the official name for the tree */
*arch = connect_function (tree->archive, namespace);
if (!*arch)
return;
/* append the user input */
*namespace = str_replace (*namespace, str_alloc_cat_many (0, *namespace, "/", name, str_end));
return;
}
/* its an official name or an archive url */
*arch = connect_function (name, namespace);
}
/**
* \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree
*
* i.e. patch-1 -> tree-archive & tree-version
* foo--bar--0 -> tree-archive & & foo--bar--0
* foo/bar.. -> foo & bar
* on failure, *arch is null;
*/
void
arch_project_tree_check_name (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name)
{
arch_project_tree_check_name_ext (tree, arch, namespace, name, arch_archive_connect_branch);
}
/**
* \brief establish an archive handle (possibly lazy connecting) to a user name, in the context of a tree.
* this will be a commitable name, unless a url isprovided, in which case the url is presumed good.
*
* i.e. patch-1 -> tree-archive & tree-version
* foo--bar--0 -> tree-archive & & foo--bar--0
* foo/bar.. -> foo & bar
* on failure, *arch is null;
*/
void
arch_project_tree_check_commitable_name (arch_project_tree_t * tree, struct arch_archive ** arch, t_uchar ** namespace, t_uchar const * const name)
{
arch_project_tree_check_name_ext (tree, arch, namespace, name, arch_archive_connect_commitable_branch);
}
/**
* \brief probe wether dir is a project tree root
*/
int
arch_project_tree_dir_is_root (void * context, t_uchar const * dir, arch_project_tree_t **object_out)
{
return tla_project_tree_dir_is_root (context, dir, object_out);
}
/**
* \brief a placeholder to ensure vtable entries are added.
* always last in the vtable
*/
int ***
arch_project_tree_vtable_end (char const * const * const * foo)
{
return NULL;
}
/**
* \brief perform a changeset inventory traversal. some tree types may cache the results of this
*/
void
arch_project_tree_changeset_inventory_traversal (arch_project_tree_t * tree, struct arch_inventory_options * options, inv_callback callback, void * closure)
{
return tree->vtable->changeset_inventory_traveral (tree, options, callback, closure);
}
/**
* \brief inform a tree that files in it have been changed, to flush any caches
*
* optional method
*/
void
arch_project_tree_mutated (arch_project_tree_t * tree)
{
if (tree->vtable->mutated)
tree->vtable->mutated (tree);
}
/* tag: Tom Lord Mon May 12 10:12:38 2003 (project-tree.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1