/* Update a project tree by replaying individual patches Copyright (C) 2001, 2002 Tom Lord Copyright (C) 2002, 2003 Walter Landry and the Regents of the University of California Copyright (C) 2004, 2005 Walter Landry 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; version 2 dated June, 1991. 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "boost/filesystem/operations.hpp" #include "boost/filesystem/fstream.hpp" #include "parse_common_options.hpp" #include "parse_unknown_options.hpp" #include "check_extra_args.hpp" #include "command_initializer.hpp" #include "arx_error.hpp" #include #include "tree_root.hpp" #include "patch_level.hpp" #include "patch_number.hpp" #include "get_revision_patch.hpp" #include "Revision_List.hpp" #include "Parsed_Name.hpp" #include "list_patch_logs.hpp" #include "get_option_from_file.hpp" #include "Temp_Directory.hpp" #include "do_patch.hpp" #include "copy_tree.hpp" #include #include "valid_package_name.hpp" #include "compute_latest_continuation.hpp" #include "tree_branch.hpp" #include "is_a_tag.hpp" #include "latest_archive_revision.hpp" using namespace std; using namespace boost; namespace fs=boost::filesystem; using fs::path; int replay(list &argument_list, const command &cmd); static command_initializer replay_init(command("replay", "Update a project tree by replaying individual patches", "usage: replay [options] [branch]", " --dir DIR cd to DIR first\n\ --exact apply just this one revision patch\n\ --list FILE apply patches listed in a file\n\ -r --reverse apply patches in reverse\n\ --delete-removed remove all deleted files\n\ \n\ Apply all of the patches in BRANCH that are missing from the current\n\ project tree. If a revision is given, then all missing patches up to\n\ that revision are applied. If no branch is given, ArX uses the current\n\ tree's branch. In that case, ArX acts like an update.\n\ \n\ So if a project tree has patches foo.bar.1.0,0 through\n\ foo.bar.1.0,12, and the archive has up to patch 17, this will\n\ end up applying five different patches in succession: -13 to -17.\n\ \n\ The --exact option applies exactly one patch. The --list option applies\n\ the patches listed in a file.\n\ \n\ If any of the patches involves conflicts, a warning message is printed,\n\ and the new project tree will contain \".rej\" files. Once the conflicts\n\ are resolved, you can complete by re-running the command on the new tree.\n\ \n\ Patches that have already been applied will not be reapplied.\n\ \n\ See also \"arx merge --help\".", replay,"Branching and Merging",true)); int replay(list &argument_list, const command &cmd) { Command_Info info(cmd); bool reverse(false), exact(false), delete_removed(false); path tree_directory(fs::current_path()); list input_patch_list; path list_path; while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="--dir") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --dir"); } tree_directory=path(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="-r" || *(argument_list.begin())=="--reverse") { reverse=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--exact") { exact=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--delete-removed") { delete_removed=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--list") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --list"); } list_path=path(*(argument_list.begin())); argument_list.pop_front(); } else { parse_unknown_options(argument_list); break; } } Parsed_Name name; if(!argument_list.empty()) { input_patch_list.push_back(*(argument_list.begin())); argument_list.pop_front(); } check_extra_args(argument_list,info); /* Fill patches from a file */ if(!list_path.empty()) { if(!exists(list_path)) throw arx_error("List file does not exist\n\t" + list_path.native_file_string()); fs::ifstream list_file(list_path); string patch; while(getline(list_file,patch)) { input_patch_list.push_back(patch); } } if(exact && input_patch_list.empty()) throw arx_error("Need to specify a revision with --exact"); path root(tree_root(tree_directory)); if(input_patch_list.empty()) input_patch_list.push_back(tree_branch(root)); list patch_list; if(exact) { patch_list=input_patch_list; } else { for(list::iterator i=input_patch_list.begin(); i!=input_patch_list.end();++i) { Parsed_Name name(*i); /* Revision not specified, get the latest from the archive. */ if(name.revision().empty()) name=latest_archive_revision(name); Revision_List tree_revs(list_patch_logs(tree_directory, name.complete_branch())); unsigned int begin_patch(0); if(!tree_revs.empty()) begin_patch=*(tree_revs.rbegin())+1; const unsigned int end_patch(name.patch_number()); if(static_cast (atoi(compute_latest_continuation(root,name).c_str()))>begin_patch) throw arx_error("There is a continuation revision between the tree's current revision\n\t" + Parsed_Name(name.complete_branch()) .set_revision(begin_patch-1).complete_name() + "\n\t" + "and the latest revision in the archive\n\t" + name.complete_branch() + "\n" + "at patch " + compute_latest_continuation(root,name) + ".\nYou probably don't want to apply the patches through the continuation.\nIf that _is_ what you want to do, you must explicitly specify the\nrevisions on the command line."); if(begin_patch>end_patch+1) throw arx_error("INTERNAL ERROR: The patch level present in the tree is greater than in the archive.\n\tTree " + patch_level(begin_patch-1) + "\n\tArchive " + patch_level(end_patch)); for(unsigned int i=begin_patch; i<=end_patch; ++i) patch_list.push_back(name.complete_branch() + patch_level(i)); } } /* Make sure that the patch names specify revisions. */ for(list::iterator i=patch_list.begin(); i!=patch_list.end();++i) valid_package_name(*i,Revision); /* Get the patches and start applying them */ if(!patch_list.empty()) { /* Make sure we don't have any tags */ for(list::iterator i=patch_list.begin(); i!=patch_list.end(); ++i) if(is_a_tag(*i)) throw arx_error("Replay does not handle tags\n\t" + i->complete_name()); Temp_Directory replay_dir(tree_directory,",,replay"); for(list::iterator i=patch_list.begin(); i!=patch_list.end(); ++i) { /* Make sure that we aren't applying a patch that has already been applied. */ if(list_patch_logs(tree_directory,*i).empty()) { if(Command_Info::verbosity>=report) cout << "Applying: " << i->complete_revision() << endl; get_revision_patch(*i,replay_dir.path/"patch"); /* Stop if there is a patching conflict. */ Conflicts conflicts(do_patch(replay_dir.path/"patch", tree_directory,reverse, delete_removed)); if(!conflicts.empty()) { if(Command_Info::verbosity>=quiet) cerr << conflicts; break; } remove_all(replay_dir.path/"patch"); } else if(Command_Info::verbosity>=default_output) { cerr << "Not applying this patch because it is already in the tree\n\t" << i->complete_revision() << endl; } } } else { if(Command_Info::verbosity>=default_output) cerr << "Tree is already up to date." << endl; } return 0; }