/* Print all of the branches that went into making the current project tree. Eventually, it should be expanded to print out merges. Copyright (C) 2001, 2002 Tom Lord Copyright (C) 2002, 2003 Walter Landry and the Regents of the University of California Copyright (C) 2004 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 "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 "boost/filesystem/operations.hpp" #include "boost/filesystem/exception.hpp" #include "boost/filesystem/convenience.hpp" #include #include "Component_Regexes.hpp" #include "Parsed_Name.hpp" #include "Temp_Directory.hpp" #include "get_revision.hpp" #include "list_tree_branches.hpp" #include "push_revision_on_list.hpp" #include "recursive_browse.hpp" #include "file_attributes.hpp" #include "add_path_and_checksum.hpp" #include "tempdir.hpp" using namespace std; using namespace boost; namespace fs=boost::filesystem; using fs::path; inline bool boost_paths_string_cmp(path a, path b) { return a.string() < b.string(); } int history(list &argument_list, const command &cmd); static command_initializer history_init(command("history", "Query, add or delete the patches in a project tree", "usage: history [options]", " --dir DIR change to DIR first\n\ --limit LIMIT list only branches from LIMIT\n\ -a --add REVISION add the revision to the history\n\ -d --delete REVISION remove the revision from the history\n\ \n\ Print all of the branches that went into making a project tree.\n\ With the --limit option, it will only print the branches from that\n\ limit. LIMIT can be a branch or revision.\n\ \n\ With the --add option, add a record of those revisions to the project\n\ tree. It does not actually apply patches. It works by looking at\n\ what patches have gone into making the indicated revision, and adding\n\ everything not in the project tree. If only a branch is specified,\n\ then it uses the latest revision of that branch. If you only want to\n\ add a record for a particular branch or revision, use the --limit option.\n\ \n\ With the --delete option, remove the record of the tree having those\n\ patches. Use with care, as it erases history. It can make it\n\ difficult to merge with other branches that incorporate what has been\n\ deleted.\n\ \n\ For detailed information about what is in a patch, see \"arx log\".", history,"Miscellaneous Advanced",true)); int history(list &argument_list, const command &cmd) { Command_Info info(cmd); bool add(false), del(false); Parsed_Name limit, add_delete; path tree_directory(fs::current_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())=="--limit") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --limit"); } limit=Parsed_Name(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="-a" || *(argument_list.begin())=="--add") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -a and --add"); } add_delete=Parsed_Name(*(argument_list.begin())); if(add_delete.branch().empty()) throw arx_error("You must specify a branch or revision for -a and --add\n\t" + add_delete.full_name()); argument_list.pop_front(); add=true; } else if(*(argument_list.begin())=="-d" || *(argument_list.begin())=="--delete") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -d and --delete"); } add_delete=Parsed_Name(*(argument_list.begin())); if(add_delete.branch().empty()) throw arx_error("You must specify a branch\n\t" + add_delete.full_name()); argument_list.pop_front(); del=true; } else { parse_unknown_options(argument_list); break; } } if(add && del) throw arx_error("Can't add and delete at the same time"); check_extra_args(argument_list,info); if(add) { Temp_Directory history_dir(tempdir(),",,history"); path revision_dir(history_dir.path/"revision"); get_revision(add_delete,revision_dir,tree_directory); /* Find where the logs are. */ Component_Regexes component_regex; path patch_root(tree_root(tree_directory)/"_arx/patch-log"); const path source(revision_dir/"_arx/patch-log"); list archive_dir ((fs::directory_iterator(source)),fs::directory_iterator()); archive_dir.sort(boost_paths_string_cmp); list result; for(list::iterator l=archive_dir.begin(); l!=archive_dir.end(); ++l) { if(limit.archive().empty() || limit.archive()==l->leaf()) { Parsed_Name local_limit(limit); if(local_limit.empty()) local_limit=Parsed_Name(l->leaf() + "/"); push_revision_on_list push(result,local_limit); recursive_browse(*l/local_limit.branch_path_no_archive(), local_limit,push); } } for(list::iterator i=result.begin(); i!=result.end(); ++i) for(Revision_List::iterator j=i->begin(); j!=i->end(); ++j) { const path rev_path(i->name.branch_path() + "/" + patch_level(*j)); if(!lexists(patch_root/rev_path)) { create_directories((patch_root/rev_path).branch_path()); copy(source/rev_path,patch_root/rev_path); file_attributes f; f.file_path=path("_arx/patch-log")/rev_path; f.inventory_id=f.file_path.string(); add_path_and_checksum(tree_directory,f,sha256("0")); } } } else if(del) { if((limit.archive().empty() || limit.archive()==add_delete.archive()) && add_delete.is_subbranch(limit)) { path patch_root(tree_root(tree_directory)/"_arx/patch-log"); if(!limit.revision().empty() || !add_delete.revision().empty()) { if(add_delete.revision().empty()) { add_delete=limit; } /* Remove a patch log for one revision where both add_delete and limit agree. */ if(limit.revision().empty() || limit==add_delete) { remove_all(patch_root/add_delete.revision_path()); } } else { /* Remove all patch logs for a version. */ remove_all(patch_root/add_delete.branch_path()); } } } else { list branch_list(list_tree_branches(tree_directory, limit)); branch_list.sort(Parsed_Name::Branch_less); for(list::iterator i=branch_list.begin(); i!=branch_list.end(); ++i) { if(Command_Info::verbosity>=default_output) cout << i->complete_branch() << endl; } } return 0; }