/* Merge a project tree with a sibling. Most of the work is done in merge_branches. Copyright (C) 2001, 2002, 2003 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 "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 "get_revision.hpp" #include "Parsed_Name.hpp" #include "get_option_from_file.hpp" #include "Temp_Directory.hpp" #include "latest_tree_revision.hpp" #include "latest_archive_revision.hpp" #include "merge_branches.hpp" #include "get_config_option.hpp" #include "patch_level.hpp" #include "Revision_List.hpp" #include "add_tree_cache.hpp" #include "list_tree_cached_revisions.hpp" #include "tree_branch.hpp" #include "recursively_add_tags.hpp" #include "is_a_tag.hpp" #include "inventory_directory.hpp" #include "read_checksums.hpp" #include "Conflicts.hpp" using namespace std; using namespace boost; namespace fs=boost::filesystem; using fs::path; int merge(list &argument_list, const command &cmd); static command_initializer merge_init(command("merge", "Merge a project tree with a sibling branch", "usage: merge [options] [sibling]", " --dir DIR cd to DIR first\n\ --delete-removed remove all deleted files\n\ -R --recursive merge nested trees as well\n\ -u --update update pristine trees and set the tree branch\n\ --algo ALGO use the ALGO algorithm for merging\n\ --ancestor ANCESTOR use ANCESTOR for merging instead of computing it\n\ \n\ Merge the current project tree with a sibling. ArX can use two different\n\ algorithms when merging. They both start by finding the least common\n\ ancestor of the project tree with the sibling branch. ArX can then either\n\ use a three-way merge to resolve differences, or simply compute a patch\n\ between the ancestor and the sibling, and apply that patch to the project\n\ tree.\n\ \n\ ArX is fairly sophisticated in just applying patches, so the real\n\ difference comes down to whether you prefer to resolve conflicts with\n\ in-line markers and three-way merges, or .rej files.\n\ \n\ The default is three-way merge, although you can select it by specifying\n\ either \"3\" or \"patch\" as the merging algorithm. So\n\ \n\ arx merge --algo patch\n\ \n\ will use a big patch merge.\n\ \n\ If no sibling branch is given, ArX performs a simple update of the\n\ tree (merging with itself).\n\ \n\ ArX finds the ancestor revision by looking at the patch logs present\n\ in the mainline and the current project tree, so if any patch logs\n\ have been removed with \"arx history --delete\", results may be inconsistent.\n\ For example, suppose there are three lines of developent, A, B, and C,\n\ with B and C branching off of A:\n\ \n\ B\n\ --------------->\n\ / /\n\ / /\n\ A ---1--2---3--------------------->\n\ \\ \\\n\ \\ \\\n\ ------------>\n\ C\n\ \n\ If you want to merge branch B with branch C, then ArX will notice that\n\ both B and C have merged with the mainline A. Branches B and C have\n\ points 1, 2, and 3 as common ancestors, but 3 is a descendant of 1 and 2.\n\ So ArX chooses 3 as the most recent common ancestor.\n\ \n\ To be concrete, if you are in a tree of branch B, then to merge that tree\n\ with branch C, you would type\n\ \n\ arx merge C\n\ \n\ Then \"commit\" will commit the merge to B.\n\ \n\ If the merge involves conflicts, ArX prints a warning message and tries to\n\ invoke ~/.arx/merge3. The script is invoked with four arguments:\n\ \n\ 1) The conflicted path with inline conflict markers\n\ 2) A copy of the original file in the project tree\n\ 3) A copy of the ancestors file\n\ 4) A copy of the merged branch's file\n\ \n\ As an example, use this script to invoke Meld on conflicts\n\ \n\ rm \"$4\"\n\ mv \"$1\" \"$4\"\n\ meld \"$2\" \"$4\" \"$3\"\n\ \n\ Be sure to make the script executable.\n\ \n\ With the --update option, ArX will change the project tree's branch and\n\ update the tree cache. If you are merging with the same branch, this is\n\ just a simple update. When merging with a different branch, this is more\n\ like you are switching to that branch.\n\ \n\ You may also merge with tags, in which case ArX will update all of the\n\ branches listed in the tag. The locations of the branches listed in the\n\ tag must match up with projects in the tree.\n\ \n\ Finally, the --recursive option will update the current directory and\n\ all subdirectories. You may not specify a sibling with --recursive. It\n\ differs from merging with a tag in that 1) all subdirectories are updated,\n\ not just those listed in the tag, and 2) it will not not update a\n\ subdirectory to a new branch, while using a tag might.", merge,"Branching and Merging",true)); int merge(list &argument_list, const command &cmd) { Command_Info info(cmd); bool delete_removed(false), recursive(false), update(false); path tree_directory(fs::current_path()); Parsed_Name ancestor; Merge_Algo merge_algo(three_way); while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="--delete-removed") { delete_removed=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--recursive" || *(argument_list.begin())=="-R") { recursive=true; argument_list.pop_front(); } else 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())=="--update" || *(argument_list.begin())=="-u") { update=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--algo") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --algo"); } string merge_algo_string(*(argument_list.begin())); if (merge_algo_string=="3") merge_algo=three_way; else if (merge_algo_string=="patch") merge_algo=big_patch; else throw arx_error("Bad argument for --algo. Valid arguments are 2, 3, or patch.\n\t" + merge_algo_string); argument_list.pop_front(); } else if(*(argument_list.begin())=="--ancestor") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --ancestor"); } ancestor=Parsed_Name(*(argument_list.begin())); argument_list.pop_front(); } else { parse_unknown_options(argument_list); break; } } Parsed_Name tree_tag(latest_tree_revision(tree_directory)), sibling_tag; if(argument_list.empty()) { sibling_tag=latest_archive_revision(tree_tag.complete_branch()); } else { if(recursive) throw arx_error("You may not specify a sibling with the --recursive option"); sibling_tag=latest_archive_revision(*(argument_list.begin())); argument_list.pop_front(); } check_extra_args(argument_list,info); list > tree_list, sibling_list; if(recursive) { tree_list.push_back(make_pair(tree_tag,tree_directory)); sibling_list.push_back(make_pair(sibling_tag,tree_directory)); const path root(tree_root(tree_directory)); Inventory dir_inventory; Inventory_Flags flags; flags.trees=flags.nested=flags.directories=true; Checksums checksums; read_checksums(root,checksums); inventory_directory(root,dir_inventory,flags,checksums); for(Inventory::iterator i=dir_inventory(nested_tree,arx_dir).begin(); i!=dir_inventory(nested_tree,arx_dir).end(); ++i) { Parsed_Name tree_name(latest_tree_revision(i->file_path)); tree_list.push_back(make_pair(tree_name,i->file_path)); sibling_list.push_back(make_pair(latest_archive_revision (tree_name.complete_branch()), i->file_path)); } } else if(!is_a_tag(sibling_tag)) { tree_list.push_back(make_pair(tree_tag,tree_directory)); sibling_list.push_back(make_pair(sibling_tag,tree_directory)); } else { recursively_add_tags(sibling_list,sibling_tag,tree_directory); for(list >::iterator i=sibling_list.begin(); i!=sibling_list.end(); ++i) { if(tree_root(i->second)!=system_complete(i->second)) throw arx_error("Can not merge with this tag. This directory is not a nested tree.\n\t" + i->second.native_file_string()); tree_list.push_back(make_pair(latest_tree_revision(i->second), i->second)); } } list >::iterator i=tree_list.begin(), j=sibling_list.begin(); bool global_conflicts(false); for(;i!=tree_list.end();++i,++j) { const path root(tree_root(i->second)); const Parsed_Name tree(i->first), sibling(j->first); Conflicts conflicts=merge_branches(latest_tree_revision(root,tree), latest_archive_revision(sibling), ancestor,root,merge_algo, delete_removed,update); global_conflicts|=!conflicts.empty(); if(Command_Info::verbosity>=quiet) cerr << conflicts; } return global_conflicts ? 1 : 0; }