/* Create symbolic name for a revision. 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 "boost/filesystem/fstream.hpp" #include "boost/filesystem/convenience.hpp" #include "boost/filesystem/exception.hpp" #include "boost/date_time/posix_time/posix_time.hpp" #include "parse_common_options.hpp" #include "parse_unknown_options.hpp" #include "Parsed_Name.hpp" #include "check_extra_args.hpp" #include "command_initializer.hpp" #include "arx_error.hpp" #include #include "tree_root.hpp" #include "patch_level.hpp" #include "get_revision.hpp" #include "get_config_option.hpp" #include "Revision_List.hpp" #include "list_archive_revisions.hpp" #include "latest_archive_revision.hpp" #include "Temp_Directory.hpp" #include #include "parse_rfc822.hpp" #include "ensure_branch_exists_in_archive.hpp" #include "tar_and_archive_patch.hpp" #include "valid_package_name.hpp" #include "set_option_in_file.hpp" #include "boost/archive/text_oarchive.hpp" #include "boost/serialization/map.hpp" #include "boost/serialization/list.hpp" #include "gvfs.hpp" #include "sha256.hpp" #include "Patch_Log.hpp" #include "tempdir.hpp" using namespace std; using namespace boost; using namespace boost::posix_time; namespace fs=boost::filesystem; using fs::path; namespace { void remove_header(map &headers, const string &field) { map::iterator i=headers.find(field); if(i!=headers.end()) headers.erase(i); } } int tag(list &argument_list, const command &cmd); static command_initializer tag_init(command("tag", "Create a symbolic name for a revision or group of revisions", "usage: tag [options] tag-name head [sub-project sub-dir ...]\n\ tag -f FILE", " -L --log-file file specify the name of the log-file\n\ -s log-message use LOG-MESSAGE as the description\n\ --key KEY sign the revision with KEY\n\ -f FILE read the tag information from FILE\n\ --float make a floating tag that always points to\n\ the latest revision\n\ \n\ Create a symbolic name for a revision or group of revisions. For example,\n\ suppose that you had a branch \"myproduct\", and your marketing\n\ experts wanted to name the next version \"v0.0.1\". Then \n\ \n\ arx tag myproduct.v0.0.1 myproduct\n\ \n\ will create a revision that you can get with\n\ \n\ arx get myproduct.v0.0.1\n\ \n\ You can also use tag to mark a collection of projects. So if you have the\n\ directory structure\n\ \n\ foo ----> Contains the project foo.main\n\ foo/bar ----> Contains the project bar.main\n\ \n\ then you can mark the whole collection of projects with\n\ \n\ arx tag foo.collection foo.main bar.main bar\n\ \n\ That is, you first specify the head project (foo.main). For the tail\n\ projects, you specify the project name (bar.main), and the subdirectory\n\ that it goes into (bar). Then\n\ \n\ arx get foo.collection\n\ \n\ will download both foo.main and bar.main, and put bar.main into the bar\n\ subdirectory of foo.main.\n\ \n\ You can have as many sub-projects as you wish. For large, complicated\n\ projects, you can read in a list of projects from a file. The format is\n\ \n\ tag-name\n\ head-project\n\ sub-project sub-dir\n\ sub-project sub-dir\n\ ...\n\ \n\ Then you read it in with\n\ arx tag -f FILE\n\ \n\ By default, ArX will create a tag that points to particular revisions.\n\ For example, if you only specify a branch, as in\n\ \n\ arx tag foo-tag foo\n\ \n\ then ArX will tag the latest archive revision of foo. So whenever you\n\ get that particular revision of foo-tag, you will get that particular\n\ revision of foo. If you want that revision of foo-tag to be a floating\n\ marker which just means to get the latest revision of foo (for example, if\n\ you want to make it easy to get the latest versions of a collection of\n\ projects), then use the --float option.\n\ \n\ Note that tags will show up as ordinary branches in \"arx browse\", but they\n\ have a few restrictions. In particular, tree-cache, replay,\n\ fork, file-diff, file-orig, file-undo, and get-patch will not work with\n\ tags. archive-cache, get, make-dist, missing, and merge will work.\n\ So \"get\", gets all of the different projects, \"merge\" acts on all\n\ of the subdirectories, etc.", tag,"Branching and Merging",true)); int tag(list &argument_list, const command &cmd) { Command_Info info(cmd); path log_name, tag_path; string log_message, gpg_key(get_config_option("gpg-key")); bool floating(false); Patch_Log log; while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="-L" || *(argument_list.begin())=="--log-file") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -L and --log-file"); } log_name=path(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="-f") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -f"); } tag_path=path(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="--float") { argument_list.pop_front(); floating=true; } else if(*(argument_list.begin())=="-s") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -s"); } log_message=*(argument_list.begin()); argument_list.pop_front(); } else if(*(argument_list.begin())=="--key") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --key"); } gpg_key=*(argument_list.begin()); argument_list.pop_front(); } else if(*(argument_list.begin())=="--date") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --date"); } log.headers["Standard-date"]=log.headers["Date"]= *(argument_list.begin()); argument_list.pop_front(); } else if(*(argument_list.begin())=="--author") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --author"); } log.headers["Creator"]=*(argument_list.begin()); argument_list.pop_front(); } else { parse_unknown_options(argument_list); break; } } if(!log_message.empty() && !log_name.empty()) throw arx_error("Can't specify both -s and -L"); Parsed_Name tag_name; list > tag_list; if(!tag_path.empty()) { string temp; fs::ifstream tag_file(tag_path); tag_file >> temp; tag_name=Parsed_Name(temp); tag_file >> temp; tag_list.push_back(make_pair(Parsed_Name(temp),path("."))); if(!tag_file) throw arx_error("Problem reading the tag specifications from\n\t" + tag_path.native_file_string()); while(tag_file) { Parsed_Name project; path subdir; tag_file >> temp; project=Parsed_Name(temp); tag_file >> temp; subdir=path(temp); if(tag_file) tag_list.push_back(make_pair(project,subdir)); } } else { if(argument_list.size()<2) throw arx_error("Not enough arguments"); tag_name=(*(argument_list.begin())); argument_list.pop_front(); tag_list.push_back(make_pair(Parsed_Name(*(argument_list.begin())), path("."))); argument_list.pop_front(); while(!argument_list.empty()) { if(argument_list.size()<2) throw arx_error("Wrong number of arguments. You must have a sub-directory for every\nsub-project."); Parsed_Name project(*(argument_list.begin())); argument_list.pop_front(); path subdir(*(argument_list.begin())); argument_list.pop_front(); tag_list.push_back(make_pair(project,subdir)); } } check_extra_args(argument_list,info); if(!floating) for(list >::iterator i=tag_list.begin(); i!=tag_list.end(); ++i) { if(i->first.revision().empty()) i->first=latest_archive_revision(i->first); } valid_package_name(tag_name,Branch); if(!tag_name.revision().empty()) throw arx_error("You may not specify a revision\n\t" + tag_name.full_name()); ensure_branch_exists_in_archive(tag_name); Revision_List temp(list_archive_revisions(tag_name)); if(temp.empty()) { tag_name.set_revision(0); } else { tag_name.set_revision(*(temp.rbegin())+1); } /* Create the new patch log */ if(!log_name.empty()) { parse_rfc822(log_name,log.headers,log.body,"\n\t"); if(log.headers.find("Summary")==log.headers.end()) throw arx_error("The log file must have a Summary: line"); /* Remove any of these headers that might be present in the log file since ArX will compute what should go into these things. */ remove_header(log.headers,"Removed-files"); remove_header(log.headers,"Removed-directories"); remove_header(log.headers,"New-files"); remove_header(log.headers,"New-directories"); remove_header(log.headers,"Modified-files"); remove_header(log.headers,"Modified-directories"); remove_header(log.headers,"Renamed-files"); remove_header(log.headers,"Renamed-directories"); remove_header(log.headers,"New-patches"); remove_header(log.headers,"Continuation-of"); } else { if(!log_message.empty()) { log.headers["Summary"]=log_message; } else { log.headers["Summary"]="tag of"; for(list >::iterator i=tag_list.begin(); i!=tag_list.end(); ++i) { log.headers["Summary"]+=" " + i->first.complete_name(); } log.body="(automatically generated log message)"; } } log.headers["Revision"]=tag_name.revision(); log.headers["Archive"]=tag_name.archive(); if(log.headers.find("Creator")==log.headers.end()) log.headers["Creator"]=get_config_option("id"); if(log.headers.find("Date")==log.headers.end()) log.headers["Date"]=to_simple_string(second_clock::local_time()); if(log.headers.find("Standard-date")==log.headers.end()) log.headers["Standard-Date"]= to_simple_string(second_clock::universal_time()); list > tag_list_string, checksums; for(list >::iterator i=tag_list.begin(); i!=tag_list.end(); ++i) { tag_list_string.push_back(make_pair(i->first.complete_name(), i->second.string())); if(!floating) { gvfs::Init(); gvfs::uri location(i->first.archive_location() / i->first.revision_path_no_archive() / "sha256"); checksums.push_back(make_pair(i->first.complete_revision(), read_file_into_string(location).substr(0,64))); } } log.rename_lists["Tags"]=tag_list_string; if(!floating) log.rename_lists["Checksums"]=checksums; Temp_Directory tag_dir(tempdir(),",,tag"); { fs::ofstream new_log(tag_dir.path/"log"); archive::text_oarchive new_archive(new_log); new_archive << log.headers << log.header_lists << log.rename_lists << log.body; } { sha256 log_checksum(tag_dir.path/"log"); fs::ofstream log_sha256(tag_dir.path/"sha256"); log_sha256 << log_checksum << endl; } tar_and_archive_patch(tag_dir.path,tag_name,tag_name,false, fs::current_path(),gpg_key,true); return 0; }