/* Mirror one archive to another. 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 "parse_common_options.hpp" #include "parse_unknown_options.hpp" #include "check_extra_args.hpp" #include "command_initializer.hpp" #include "arx_error.hpp" #include #include #include "Parsed_Name.hpp" #include "gvfs.hpp" #include "invoke_hook.hpp" #include "Branch_Lock.hpp" #include "Revision_List.hpp" #include "recursive_browse.hpp" #include "remote_recursive_browse.hpp" #include "match_uri_fragment.hpp" #include "update_listing.hpp" using namespace std; class copy_remote_mirror: public remote_recursive_browse { const bool readmes, patches, cached; gvfs::uri target_uri; public: gvfs::uri source_uri; copy_remote_mirror(const Parsed_Name &Name, const bool &Readmes, const bool &Patches, const bool &Cached, const string &Source_URI, const string &Target_URI): remote_recursive_browse(Name), readmes(Readmes), patches(Patches), cached(Cached) { /* Expand the source and target uri's */ source_uri=match_uri_fragment(Source_URI,Name); target_uri=match_uri_fragment(Target_URI,Name); /* Make sure that the target is a mirror of the source */ if(!exists(target_uri/",meta-info/mirror")) throw arx_error("Destination archive is not a mirror\n\t" + target_uri.string()); } void branch_hook(const Parsed_Name &Name, const gvfs::uri &location) { if(!gvfs::exists(target_uri/Name.branch_path_no_archive())) { if(Command_Info::verbosity>=default_output) cout << "Mirroring branch: " << Name.branch() << endl; invoke_hook(Parsed_Name(), target_uri.string() + "/" + Name.branch(),true); make_directory(target_uri/Name.branch_path_no_archive()); invoke_hook(Parsed_Name(), target_uri.string() + "/" + Name.branch(),false); if(gvfs::exists(target_uri/",meta-info/update-listing")) { update_listing(target_uri.string() + "/" + Name.parent_branch().branch()); update_listing(target_uri.string() + "/" + Name.branch()); } } if(readmes && gvfs::exists(location/",README")) { gvfs::copy(location/",README", target_uri/Name.branch_path_no_archive()/",README"); } if(cached && gvfs::exists(location/",cache")) { gvfs::uri cache_uri(target_uri/Name.branch_path_no_archive()/",cache"); if(!gvfs::exists(cache_uri)) gvfs::make_directory(cache_uri); list cache_list; gvfs::fill_directory_list(cache_list,location/",cache"); for(list::iterator i=cache_list.begin(); i!=cache_list.end(); ++i) { if(i->size()<8) throw arx_error("Bad entry\n\t" + *i + "\n\t" + cache_uri.string()); if(!gvfs::exists(cache_uri/(*i))) { if(Command_Info::verbosity>=default_output) cout << "Mirroring cached revision: " << i->substr(0,i->size()-7) << endl; gvfs::copy(location/",cache"/(*i),cache_uri/(*i)); if(gvfs::exists(target_uri/",meta-info/update-listing")) { update_listing((target_uri/Name.branch()).string()); } } } } } void revision_hook(const Parsed_Name &branch, const Revision_List &rev_list, const gvfs::uri &location) { if(patches) for(Revision_List::const_iterator i=rev_list.begin(); i!=rev_list.end(); ++i) { if(!exists(target_uri/branch.branch_path_no_archive() /patch_level(*i))) { list revision_items; gvfs::fill_directory_list(revision_items, location/patch_level(*i)); Branch_Lock rev_lock(target_uri.string() + "/" + branch.branch()); for(list::iterator items=revision_items.begin(); items!=revision_items.end(); ++items) { gvfs::copy(location/patch_level(*i)/(*items), rev_lock.uri()/(*items)); } if(Command_Info::verbosity>=default_output) cout << "Mirroring revision: " << branch.branch() << patch_level(*i) << endl; invoke_hook(Parsed_Name(), target_uri.string() + "/" + branch.branch() + patch_level(*i),true); gvfs::rename(rev_lock.uri(), target_uri/branch.branch_path_no_archive() /patch_level(*i)); invoke_hook(Parsed_Name(), target_uri.string() + "/" + branch.branch() + patch_level(*i),false); if(gvfs::exists(target_uri/",meta-info/update-listing")) update_listing(target_uri.string() + "/" + branch.branch()); } } } }; int mirror(list &argument_list, const command &cmd); static command_initializer mirror_init(command("mirror", "mirror one archive to another", "usage: mirror [options] archive source destination", " --readmes Copy the README files\n\ --patches Copy the patches\n\ --cached Copy the cached revisions\n\ \n\ Mirror the contents of a source archive to the destination archive.\n\ The destination archive must have been created using the --mirror option to\n\ make-archive.\n\ \n\ The default is to copy over the patches and any cached revisions. The\n\ README files can change at any time, so they are not copied by default.\n\ The --patches, --cached, and --readme options will only copy over those\n\ types.\n\ \n\ The SOURCE and DESTINATION arguments must be uris complete enough to\n\ uniquely determine them. For example, if you have an archive which\n\ \"arx archives\" shows as:\n\ \n\ foo@bar\n\ file:///home/foo/bar\n\ http://foo.com/bar\n\ \n\ then\n\ \n\ arx mirror foo@bar http file\n\ \n\ is sufficient to mirror the remote http archive to the local copy.\n\ \n\ The ARCHIVE argument can be an archive, or it can also specify a branch\n\ or revision, in which case ArX will mirror only that branch or revision.\n\ \n\ Note that .listing files for http access are only updated if you have run\n\ \"arx update-listing -a\" on the mirror location.", mirror,"Administrative",true)); int mirror(list &argument_list, const command &cmd) { Command_Info info(cmd); bool copy_readmes(false), copy_patches(false), copy_cached(false); while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="--patches") { copy_patches=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--cached") { copy_cached=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--readmes") { copy_readmes=true; argument_list.pop_front(); } parse_unknown_options(argument_list); break; } /* If nothing is specified, copy patches and pristines */ if(!copy_patches && !copy_cached && !copy_readmes) copy_patches=copy_cached=true; if(argument_list.size()!=3) throw arx_error("Not enough arguments. Need an archive, source, and destination."); Parsed_Name limit(*(argument_list.begin())); argument_list.pop_front(); string source=*(argument_list.begin()); argument_list.pop_front(); string destination=*(argument_list.begin()); argument_list.pop_front(); check_extra_args(argument_list,info); /* Now do the actual mirroring. */ gvfs::Init(); copy_remote_mirror remote(limit,copy_readmes,copy_patches,copy_cached, source,destination); recursive_browse(remote.source_uri/limit.branch_path_no_archive(), remote.limit_name,remote); return 0; }