public function updateAction($projects, $tag, $user) { $individual_file_rev_updates = array(); $individual_file_rev_projects = array(); ### Target mode $target_mode_update = false; if ($tag == 'Target') { $target_mode_update = true; $tag = 'HEAD'; ### Get the max file tag for files in more than one project $file_tags = array(); foreach ($projects as $project) { $tags = $project->get_file_tags(); foreach ($project->get_affected_files() as $file) { if (empty($file_tags[$file]) || empty($tags[$file]) && $file_tags[$file] != 'HEAD' || !empty($tags[$file]) && is_numeric($tags[$file]) && $file_tags[$file] != 'HEAD' && $file_tags[$file] < $tags[$file]) { $file_tags[$file] = isset($tags[$file]) ? $tags[$file] : 'HEAD'; } } } foreach ($file_tags as $file => $tag) { if ($tag == 'HEAD') { unset($file_tags[$file]); } } } ### Prepare for a MASS HEAD update if updating to HEAD $doing_indiv_dir_update = array(); $mass_head_update_files = array(); $mass_head_update_projects = array(); if ($tag == 'HEAD') { foreach ($projects as $project) { foreach ($project->get_affected_files() as $file) { if (is_dir($this->stage->env()->repo_base . "/{$file}") || $target_mode_update && isset($file_tags[$file])) { continue; } $mass_head_update_files[$file] = $file; $mass_head_update_projects[$project->project_name] = $project; } } ### Get Target Mode files if ($target_mode_update) { foreach ($projects as $project) { foreach ($project->get_affected_files() as $file) { if (is_dir($this->stage->env()->repo_base . "/{$file}")) { continue; } if (!empty($file_tags[$file]) && abs(floor($file_tags[$file])) == $file_tags[$file]) { $individual_file_rev_updates[$file] = array($file, $file_tags[$file]); $individual_file_rev_projects[$file][$project->project_name] = $project; } } } } } else { if (preg_match('/^RP-(\\d+)$/', $tag, $m)) { require_once dirname(__FILE__) . '/../model/RollPoint.class.php'; /// Get the rollback point $point = new Ansible__RollPoint($m[1]); if (!$point->exists()) { trigger_error("Non-existant Roll Point: " . $tag, E_USER_ERROR); } /// Check out that this rollout includes all the files from /// this rollback point if (!$point->includes_same_projects($projects)) { trigger_error("Not all selected projects are in the selected roll point: " . $tag, E_USER_ERROR); } /// Create new ROLL entry $point_roll = $point->new_roll($user); /// If we are rolling out to a 'rollout' point then auto-make a Rollback point if ($point->point_type == 'rollout') { list($tag_cmd, $tag_command_output, $rb_point) = $this->tagAction($projects, 'prod_rollback', $user); # bug('TAGGED prod_rollback!!', $tag_cmd, $tag_command_output); $point_roll->set_and_save(array('rollback_rlpt_id' => $rb_point->rlpt_id)); } foreach ($point->files as $point_file) { if (!empty($point_file->revision)) { $dir_test = $point_file->file; ### Before we do Inidividual Tag updates on files the containing dirs must exist $dirs_to_update = array(); while (!empty($dir_test) && !is_dir(dirname($this->stage->env()->repo_base . "/{$dir_test}")) && $this->stage->env()->repo_base != dirname($this->stage->env()->repo_base . "/{$dir_test}") && !array_key_exists(dirname($dir_test), $doing_indiv_dir_update)) { $dir = dirname($dir_test); $dirs_to_update[] = $dir; $doing_indiv_dir_update[$dir] = true; $dir_test = $dir; // iterate backwards } /// Need to add in parent-first order /// NOTE: we only need to do the parent one, because the in-between ones will be included if (count($dirs_to_update)) { $first_dir = array_pop($dirs_to_update); $individual_file_rev_updates[$first_dir] = array($first_dir, $point_file->revision); $individual_file_rev_projects[$first_dir] = $point->get_file_projects($first_dir); } $individual_file_rev_updates[$point_file->file] = array($point_file->file, $point_file->revision); $individual_file_rev_projects[$point_file->file] = $point->get_file_projects($point_file); } else { list($first_rev, $error) = $this->get_first_rev($point_file->file); if (empty($error)) { $rev_before_first = $first_rev - 1; $individual_file_rev_updates[$point_file->file] = array($point_file->file, $rev_before_first); $individual_file_rev_projects[$point_file->file] = $point->get_file_projects($point_file->file); } } } } else { foreach ($projects as $project) { foreach ($project->get_affected_files() as $file) { if (is_dir($this->stage->env()->repo_base . "/{$file}")) { continue; } ### Get the tag rev for this file... $sth = dbh_query_bind("SELECT revision FROM file_tag WHERE file = ? AND tag = ?", $file, $tag); $rev = $sth->fetch(PDO::FETCH_NUM); $sth->closeCursor(); if (!empty($rev)) { $dir_test = $file; ### Before we do Inidividual Tag updates on files the containing dirs must exist $dirs_to_update = array(); while (!empty($dir_test) && !is_dir(dirname($this->stage->env()->repo_base . "/{$dir_test}")) && $this->stage->env()->repo_base != dirname($this->stage->env()->repo_base . "/{$dir_test}") && !array_key_exists(dirname($dir_test), $doing_indiv_dir_update)) { $dir = dirname($dir_test); $dirs_to_update[] = $dir; $doing_indiv_dir_update[$dir] = true; $dir_test = $dir; // iterate backwards } /// Need to add in parent-first order /// NOTE: we only need to do the parent one, because the in-between ones will be included if (count($dirs_to_update)) { $individual_file_rev_updates[] = array(array_pop($dirs_to_update), $rev[0]); $individual_file_rev_projects[$file][$project->project_name] = $project; } $individual_file_rev_updates[$file] = array($file, $rev[0]); $individual_file_rev_projects[$file][$project->project_name] = $project; } else { list($first_rev, $error) = $this->get_first_rev($file); if (empty($error)) { $rev_before_first = $first_rev - 1; $individual_file_rev_updates[$file] = array($file, $rev_before_first); $individual_file_rev_projects[$file][$project->project_name] = $project; } } } } } } ### Run the MASS HEAD update (if any) if (!empty($mass_head_update_files)) { $head_update_cmd = "svn update "; foreach ($mass_head_update_files as $file) { $head_update_cmd .= ' ' . escapeshellcmd($file); } START_TIMER('REPO_CMD', PROJECT_PROJECT_TIMERS); foreach ($mass_head_update_projects as $project) { $this->log_repo_action($head_update_cmd, $project, $user); } $cmd_prefix = $this->stage->config('repo_cmd_prefix'); $command_output .= shell_exec("{$cmd_prefix}{$head_update_cmd} 2>&1 | cat -"); END_TIMER('REPO_CMD', PROJECT_PROJECT_TIMERS); $cmd .= "\n" . (strlen($cmd) ? ' ; ' : '') . $head_update_cmd; } ### File tag update if (!empty($individual_file_rev_updates)) { foreach ($individual_file_rev_updates as $update) { list($file, $rev) = $update; $indiv_update_cmd = "svn update -r{$rev} " . escapeshellcmd($file); START_TIMER('REPO_CMD', PROJECT_PROJECT_TIMERS); foreach ($individual_file_rev_projects[$file] as $project) { $this->log_repo_action($indiv_update_cmd, $project, $user); } $cmd_prefix = $this->stage->config('repo_cmd_prefix'); $command_output .= shell_exec("{$cmd_prefix}{$indiv_update_cmd} 2>&1 | cat -"); END_TIMER('REPO_CMD', PROJECT_PROJECT_TIMERS); $cmd .= "\n" . (strlen($cmd) ? ' ; ' : '') . $indiv_update_cmd; } } /// After a rollout on Production, retag PROD_TEST for other people's reference if ($this->stage->onLive()) { list($tag_cmd, $tag_command_output) = $this->tagAction($projects, 'PROD_TEST', $user); # bug('TAGGED PROD_TEST!!', $tag_cmd, $tag_command_output); } /// If this is a Roll to Point then save the command output if (isset($point_roll)) { $point_roll->set_and_save(array('cmd' => $cmd, 'cmd_output' => $command_output)); } if (empty($command_output)) { $command_output = '</xmp><i>No output</i>'; } return array($cmd, $command_output); }