//
// aegis - project change supervisor
// Copyright (C) 1998-2009, 2011, 2012, 2014 Peter Miller
// Copyright (C) 2006-2009 Walter Franzini
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static void
clone_usage(void)
{
const char *progname;
progname = progname_get();
fprintf(stderr, "usage: %s -CLone [ ... ]\n", progname);
fprintf(stderr, " %s -CLone -List [ ... ]\n", progname);
fprintf(stderr, " %s -CLone -Help\n", progname);
quit(1);
}
static void
clone_help(void)
{
help("aeclone", clone_usage);
}
static void
clone_list(void)
{
trace(("clone_list()\n{\n"));
arglex();
change_identifier cid;
cid.command_line_parse_rest(clone_usage);
list_changes_in_state_mask(cid, ~(1 << cstate_state_awaiting_development));
trace(("}\n"));
}
static void
clone_main(void)
{
string_ty *s;
sub_context_ty *scp;
cstate_history_ty *history_data;
project_identifier_subset_plain pid;
project_identifier_subset_branch orig_branch(pid);
change_identifier_subset orig_cid(orig_branch);
project_identifier_subset_branch dest_branch(pid);
change_identifier_subset dest_cid(dest_branch);
string_ty *devdir;
size_t j;
const char *output_filename;
trace(("clone_main()\n{\n"));
arglex();
devdir = 0;
output_filename = 0;
while (arglex_token != arglex_token_eoln)
{
switch (arglex_token)
{
default:
generic_argument(clone_usage);
continue;
case arglex_token_branch:
case arglex_token_change:
case arglex_token_grandparent:
case arglex_token_number:
case arglex_token_project:
case arglex_token_string:
case arglex_token_trunk:
trace(("mark\n"));
if (!orig_cid.is_set())
{
case arglex_token_baseline:
case arglex_token_delta:
case arglex_token_delta_date:
case arglex_token_delta_from_change:
orig_cid.command_line_parse(clone_usage);
}
else
{
dest_cid.command_line_parse(clone_usage);
}
continue;
case arglex_token_directory:
if (devdir)
duplicate_option(clone_usage);
if (arglex() != arglex_token_string)
option_needs_dir(arglex_token_directory, clone_usage);
devdir = str_from_c(arglex_value.alv_string);
break;
case arglex_token_wait:
case arglex_token_wait_not:
user_ty::lock_wait_argument(clone_usage);
break;
case arglex_token_whiteout:
case arglex_token_whiteout_not:
user_ty::whiteout_argument(clone_usage);
break;
case arglex_token_output:
if (output_filename)
duplicate_option(clone_usage);
switch (arglex())
{
default:
option_needs_file(arglex_token_output, clone_usage);
// NOTREACHED
case arglex_token_string:
output_filename = arglex_value.alv_string;
break;
case arglex_token_stdio:
output_filename = "";
break;
}
break;
}
arglex();
}
orig_cid.command_line_check(clone_usage);
dest_cid.command_line_check(clone_usage);
orig_cid.error_if_no_explicit_change_number();
if (orig_cid.is_set() && output_filename)
{
mutually_exclusive_options
(
arglex_token_change,
arglex_token_output,
clone_usage
);
}
//
// locate project data
//
//
// make sure this branch of the project is still active
//
if (!dest_cid.get_pp()->change_get()->is_a_branch())
project_fatal(dest_cid.get_pp(), 0, i18n("branch completed"));
//
// Lock the project state file.
// Block if necessary.
//
dest_cid.get_pp()->pstate_lock_prepare();
dest_cid.get_up()->ustate_lock_prepare();
lock_take();
//
// make sure they are allowed to
//
new_change_check_permission(dest_cid.get_pp(), dest_cid.get_up());
//
// locate which branch
//
//
// locate change data
// on the other branch
//
cstate_ty *orig_cstate_data = orig_cid.get_cp()->cstate_get();
//
// create a new change
//
dest_cid.create_new_change_set();
cstate_ty *dest_cstate_data = dest_cid.get_cp()->cstate_get();
//
// copy change attributes from the old change
//
assert(orig_cstate_data->description);
if (orig_cstate_data->description)
dest_cstate_data->description = str_copy(orig_cstate_data->description);
assert(orig_cstate_data->brief_description);
s = str_trim(orig_cstate_data->brief_description);
dest_cstate_data->brief_description =
str_format
(
"%s (clone of %s)",
s->str_text,
orig_cid.get_cp()->version_get().c_str()
);
str_free(s);
dest_cstate_data->cause = orig_cstate_data->cause;
dest_cstate_data->test_exempt = orig_cstate_data->test_exempt;
dest_cstate_data->test_baseline_exempt =
orig_cstate_data->test_baseline_exempt;
dest_cstate_data->regression_test_exempt =
orig_cstate_data->regression_test_exempt;
if (orig_cstate_data->architecture)
{
change_architecture_clear(dest_cid.get_cp());
for (j = 0; j < orig_cstate_data->architecture->length; ++j)
change_architecture_add(dest_cid.get_cp(),
orig_cstate_data->architecture->list[j]);
}
change_copyright_years_now(dest_cid.get_cp());
change_copyright_years_merge(dest_cid.get_cp(), orig_cid.get_cp());
//
// Copy also the user defined attributes in the new change,
// removing attributes that may be wrong on the other side.
//
if (orig_cstate_data->attribute)
dest_cstate_data->attribute =
attributes_list_clone(orig_cstate_data->attribute);
attributes_list_remove(dest_cstate_data->attribute, HISTORY_GET_COMMAND);
//
// Copy the UUID into a user defined attribute.
//
if (orig_cstate_data->uuid)
{
change_attributes_append
(
dest_cstate_data,
ORIGINAL_UUID,
orig_cstate_data->uuid->str_text
);
}
//
// add to history for change creation
//
dest_cstate_data->state = cstate_state_awaiting_development;
history_data = dest_cid.get_cp()->history_new(dest_cid.get_up());
history_data->what = cstate_history_what_new_change;
history_data->why =
str_format
(
"Cloned from change %s.",
orig_cid.get_change_version_string().c_str()
);
int the_umask = orig_cid.get_cp()->umask_get();
if (orig_cstate_data->state >= cstate_state_being_developed)
{
//
// Construct the name of the development directory.
//
// (Do this before the state advances to being developed,
// it tries to find the config file in the as-yet
// non-existant development directory.)
//
if (!devdir)
{
scp = sub_context_new();
devdir =
change_development_directory_template
(
dest_cid.get_cp(),
dest_cid.get_up()
);
sub_var_set_string(scp, "File_Name", devdir);
change_verbose
(
dest_cid.get_cp(),
scp,
i18n("development directory \"$filename\"")
);
sub_context_delete(scp);
}
dest_cid.get_cp()->development_directory_set(devdir);
//
// add to history for develop begin
//
dest_cstate_data->state = cstate_state_being_developed;
history_data = dest_cid.get_cp()->history_new(dest_cid.get_up());
history_data->what = cstate_history_what_develop_begin;
//
// Clear the build-time field.
// Clear the test-time field.
// Clear the test-baseline-time field.
// Clear the src field.
//
change_build_times_clear(dest_cid.get_cp());
//
// Assign the new change to the user.
//
dest_cid.get_up()->own_add
(
dest_cid.get_pp(),
dest_cid.get_change_number()
);
//
// Create the development directory.
//
dest_cid.get_up()->become_begin();
os_mkdir(devdir, 02755);
undo_rmdir_bg(devdir);
dest_cid.get_up()->become_end();
//
// run the develop begin early command
//
dest_cid.get_cp()->run_develop_begin_early_command(dest_cid.get_up());
//
// add all of the files to the new change
// copy the files into the development directory
//
change_verbose(dest_cid.get_cp(), 0, i18n("copy change source files"));
for (j = 0;; ++j)
{
//
// find the file
//
// There are many files we will ignore.
//
fstate_src_ty *orig_fstate_data =
change_file_nth(orig_cid.get_cp(), j, view_path_first);
if (!orig_fstate_data)
break;
switch (orig_fstate_data->action)
{
case file_action_insulate:
continue;
case file_action_transparent:
if (orig_cid.get_cp()->was_a_branch())
continue;
break;
case file_action_modify:
switch (orig_fstate_data->usage)
{
case file_usage_build:
continue;
case file_usage_source:
case file_usage_config:
case file_usage_test:
case file_usage_manual_test:
break;
}
break;
case file_action_create:
case file_action_remove:
break;
}
//
// find the file in the project
//
fstate_src_ty *orig_proj_fstate_data =
orig_cid.get_pp()->file_find
(
orig_fstate_data,
view_path_extreme
);
bool creating = false;
if (!orig_proj_fstate_data)
{
switch (orig_fstate_data->action)
{
case file_action_remove:
continue;
case file_action_transparent:
case file_action_create:
break;
case file_action_modify:
case file_action_insulate:
#ifndef DEBUG
default:
#endif
creating = true;
break;
}
}
//
// create the file in the new change
//
fstate_src_ty *dest_fstate_data =
dest_cid.get_cp()->file_new(orig_fstate_data);
dest_fstate_data->action = orig_fstate_data->action;
if (creating)
dest_fstate_data->action = file_action_create;
change_file_copy_basic_attributes
(
dest_fstate_data,
orig_fstate_data
);
if (orig_fstate_data->move)
dest_fstate_data->move = str_copy(orig_fstate_data->move);
switch (orig_fstate_data->action)
{
case file_action_remove:
//
// removed files aren't copied,
// they have whiteout instead.
//
change_file_whiteout_write
(
dest_cid.get_cp(),
orig_fstate_data->file_name,
dest_cid.get_up()
);
assert(orig_proj_fstate_data->edit);
assert(orig_proj_fstate_data->edit->revision);
dest_fstate_data->edit_origin =
history_version_copy(orig_proj_fstate_data->edit);
continue;
case file_action_transparent:
{
//
// Transparent files are copied, but orig_filename
// the project's parent, not orig_filename the other
// change.
//
// construct the paths to the files
//
string_ty *orig_filename =
project_file_path
(
orig_cid.get_pp()->parent_get(),
orig_fstate_data
);
string_ty *dest_filename =
os_path_join(devdir, dest_fstate_data->file_name);
//
// copy the file
//
dest_cid.get_up()->become_begin();
os_mkdir_between
(
devdir,
dest_fstate_data->file_name,
02755
);
if (os_exists(dest_filename))
os_unlink(dest_filename);
copy_whole_file(orig_filename, dest_filename, 0);
//
// Set the file mode.
//
int mode = 0666;
if (orig_proj_fstate_data->executable)
mode |= 0111;
mode &= ~the_umask;
os_chmod(dest_filename, mode);
dest_cid.get_up()->become_end();
//
// If possible, use the edit number origin of the project
// and copy the head revision number.
//
if (orig_proj_fstate_data && orig_proj_fstate_data->edit)
{
dest_fstate_data->edit_origin =
history_version_copy(orig_proj_fstate_data->edit);
}
str_free(orig_filename);
str_free(dest_filename);
}
//
// Go on to the next file.
//
continue;
case file_action_create:
case file_action_modify:
case file_action_insulate:
break;
}
//
// If the change has already been completed, get the
// file orig_filename history, but if it is still active, get
// the file orig_filename the old development directory.
//
if (orig_cstate_data->state == cstate_state_completed)
{
//
// We could be creating the file, orig_filename the point
// of view of this branch.
//
assert(orig_fstate_data->edit);
assert(orig_fstate_data->edit->revision);
if (orig_proj_fstate_data && orig_proj_fstate_data->edit)
{
dest_fstate_data->edit_origin =
history_version_copy(orig_fstate_data->edit);
}
else if (orig_proj_fstate_data && orig_proj_fstate_data->edit)
{
dest_fstate_data->edit_origin =
history_version_copy(orig_proj_fstate_data->edit);
}
else
dest_fstate_data->action = file_action_create;
//
// figure where to send it
//
string_ty *dest_filename =
os_path_join(devdir, dest_fstate_data->file_name);
//
// make sure there is a directory for it
//
dest_cid.get_up()->become_begin();
os_mkdir_between(devdir, dest_fstate_data->file_name, 02755);
if (os_exists(dest_filename))
os_unlink(dest_filename);
os_unlink_errok(dest_filename);
dest_cid.get_up()->become_end();
//
// get the file orig_filename history
//
change_run_history_get_command
(
orig_cid.get_cp(),
orig_fstate_data,
dest_filename,
dest_cid.get_up()
);
//
// set the file mode
//
dest_cid.get_up()->become_begin();
int mode = 0666;
if (orig_fstate_data->executable)
mode |= 0111;
mode &= ~the_umask;
os_chmod(dest_filename, mode);
dest_cid.get_up()->become_end();
//
// clean up afterwards
//
str_free(dest_filename);
}
else
{
//
// If possible, use the edit number origin of
// the change we are cloning, this gives us the
// best chance dest_filename merge correctly.
//
// Otherwise, see if the file exists in the
// project and copy the head revision number
//
if (orig_proj_fstate_data && orig_fstate_data->edit_origin)
{
dest_fstate_data->edit_origin =
history_version_copy(orig_fstate_data->edit_origin);
}
else if (orig_proj_fstate_data && orig_proj_fstate_data->edit)
{
dest_fstate_data->edit_origin =
history_version_copy(orig_proj_fstate_data->edit);
}
else
dest_fstate_data->action = file_action_create;
if (orig_proj_fstate_data && orig_fstate_data->edit_origin_new)
{
dest_fstate_data->edit_origin_new =
history_version_copy(orig_fstate_data->edit_origin_new);
}
//
// construct the paths to the files
//
string_ty *orig_filename =
orig_cid.get_cp()->file_path(orig_fstate_data->file_name);
string_ty *dest_filename =
os_path_join(devdir, dest_fstate_data->file_name);
//
// copy the file
//
dest_cid.get_up()->become_begin();
os_mkdir_between(devdir, dest_fstate_data->file_name, 02755);
if (os_exists(dest_filename))
os_unlink(dest_filename);
copy_whole_file(orig_filename, dest_filename, 0);
//
// set the file mode
//
int mode = 0666;
if (os_executable(orig_filename))
mode |= 0111;
mode &= ~the_umask;
os_chmod(dest_filename, mode);
dest_cid.get_up()->become_end();
//
// clean up afterwards
//
str_free(orig_filename);
str_free(dest_filename);
}
}
}
//
// Write out the change file.
// There is no need to lock this file
// as it does not exist yet;
// the project state file, with the number in it, is locked.
//
dest_cid.get_cp()->cstate_write();
//
// Add the change to the list of existing changes.
// Increment the next_change_number.
// and write pstate back out.
//
project_change_append(dest_cid.get_pp(), dest_cid.get_change_number(), 0);
//
// If there is an --output option,
// write the change number to the file.
//
if (output_filename)
{
string_ty *content =
str_format("%ld", magic_zero_decode(dest_cid.get_change_number()));
if (*output_filename)
{
dest_cid.get_up()->become_begin();
string_ty *fn = str_from_c(output_filename);
file_from_string(fn, content, 0644);
str_free(fn);
dest_cid.get_up()->become_end();
}
else
cat_string_to_stdout(content);
str_free(content);
}
// remember that we are about to
if (dest_cstate_data->state >= cstate_state_being_developed)
dest_cid.get_cp()->run_project_file_command_done();
//
// Write the change table row.
// Write the user table rows.
// Release advisory locks.
//
dest_cid.get_pp()->pstate_write();
dest_cid.get_up()->ustate_write();
commit();
lock_release();
if (dest_cstate_data->state >= cstate_state_being_developed)
{
//
// run the develop begin command
//
dest_cid.get_cp()->run_develop_begin_command(dest_cid.get_up());
//
// run the change file command
// and the project file command if necessary
//
string_list_ty wl_nf;
string_list_ty wl_nt;
string_list_ty wl_cp;
string_list_ty wl_rm;
string_list_ty wl_mt;
for (j = 0;; ++j)
{
fstate_src_ty *dest_fstate_data =
change_file_nth(dest_cid.get_cp(), j, view_path_first);
if (!dest_fstate_data)
break;
switch (dest_fstate_data->action)
{
case file_action_create:
switch (dest_fstate_data->usage)
{
case file_usage_test:
case file_usage_manual_test:
wl_nt.push_back(dest_fstate_data->file_name);
break;
case file_usage_source:
case file_usage_config:
case file_usage_build:
wl_nf.push_back(dest_fstate_data->file_name);
break;
}
break;
case file_action_modify:
case file_action_insulate:
wl_cp.push_back(dest_fstate_data->file_name);
break;
case file_action_transparent:
wl_mt.push_back(dest_fstate_data->file_name);
break;
case file_action_remove:
wl_rm.push_back(dest_fstate_data->file_name);
break;
}
}
if (wl_nf.size())
dest_cid.get_cp()->run_new_file_command(&wl_nf, dest_cid.get_up());
if (wl_nt.size())
dest_cid.get_cp()->run_new_test_command(&wl_nt, dest_cid.get_up());
if (wl_cp.size())
dest_cid.get_cp()->run_copy_file_command(&wl_cp, dest_cid.get_up());
if (wl_rm.size())
{
dest_cid.get_cp()->run_remove_file_command
(
&wl_rm,
dest_cid.get_up()
);
}
if (wl_mt.size())
{
dest_cid.get_cp()->run_make_transparent_command
(
&wl_mt,
dest_cid.get_up()
);
}
dest_cid.get_cp()->run_project_file_command(dest_cid.get_up());
//
// if symlinks are being used to pander to dumb DMT,
// and they are not removed after each build,
// create them now, rather than waiting for the first build.
// This will present a more uniform interface to the developer.
//
pconf_ty *dest_pconf_data = change_pconf_get(dest_cid.get_cp(), 0);
assert(dest_pconf_data->development_directory_style);
if (!dest_pconf_data->development_directory_style->during_build_only)
{
work_area_style_ty style =
*dest_pconf_data->development_directory_style;
change_create_symlinks_to_baseline
(
dest_cid.get_cp(),
dest_cid.get_up(),
style
);
}
}
//
// verbose success message
//
{
sub_context_ty sc;
sc.var_set_string("ORiginal", orig_cid.get_cp()->version_get());
sc.var_optional("ORiginal");
change_verbose(dest_cid.get_cp(), &sc, i18n("clone complete"));
}
//
// clean up and go home
//
trace(("}\n"));
}
void
aeclone(void)
{
static arglex_dispatch_ty dispatch[] =
{
{ arglex_token_help, clone_help, 0 },
{ arglex_token_list, clone_list, 0 },
};
trace(("clone()\n{\n"));
arglex_dispatch(dispatch, SIZEOF(dispatch), clone_main);
trace(("}\n"));
}
// vim: set ts=8 sw=4 et :