/* Print out logs stored in the tree or the archive. 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 "Parsed_Name.hpp" #include "Patch_Log.hpp" #include "patch_level_cmp.hpp" #include "patch_level.hpp" #include "check_extra_args.hpp" #include "command_initializer.hpp" #include "arx_error.hpp" #include #include #include "list_archive_revisions.hpp" #include "list_patch_logs.hpp" #include "gvfs.hpp" #include "tree_root.hpp" #include "get_option_from_file.hpp" #include "output_changelog.hpp" #include "tree_branch.hpp" #include "Path_Logs.hpp" #include "follow_path.hpp" #include "annotate_file.hpp" #include "parse_range.hpp" #include "latest_tree_revision.hpp" using namespace std; using namespace boost; namespace fs=boost::filesystem; using fs::path; int log(list &argument_list, const command &cmd); static command_initializer log_init(command("log", "Print out revision logs", "usage: log [options] [path [range ...]]", " --formatted print a GNU-style formmatted changelog\n\ -r --reverse reverse the order of the names\n\ -B --branch BRANCH print the logs for the specified branch only\n\ --header HEADER print the specified log header\n\ --body print the log body\n\ --all print the complete patch log\n\ --remote Use the revision logs stored in the archive\n\ --dir DIR change to DIR first\n\ \n\ Prints out a short description of the revisions in a branch. If no\n\ branch is given, ArX uses the current project tree's tree-branch.\n\ \n\ The --formatted option prints out a more complete GNU-style changelog.\n\ \n\ If given a path, then ArX will print out only those logs which affected\n\ those paths. If followed by one or more numerical ranges, ArX will only\n\ print out patches which affect those line numbers. So the commands\n\ \n\ arx log foo -23\n\ arx log foo 12-23\n\ arx log foo 12-\n\ \n\ will print out the log affecting lines from the beginning of the file to\n\ line 23, lines 12 to 23, and lines 23 to the end of the file respectively.\n\ \n\ You may also specify which headers you wish to print out. In addition\n\ to any that might have been supplied by the user, the following headers\n\ are available:\n\ Revision\n\ Archive\n\ Creator\n\ Date\n\ Standard-date\n\ Summary\n\ New-files\n\ New-directories\n\ Removed-files\n\ Removed-directories\n\ Renamed-files\n\ Renamed-directories\n\ Modified-files\n\ Modified-directories\n\ New-patches\n\ Removed-patches\n\ Continuation-of\n\ Latest-continuation\n\ Tags\n\ \n\ The --all option prints out all of the headers, and --body prints out\n\ the body of the patch log.\n\ \n\ Normally, ArX will use the revision logs stored in the project tree.\n\ The --remote option will force ArX to look up the revision logs in the\n\ archive.", log,"Basic",true)); int log(list &argument_list, const command &cmd) { Command_Info info(cmd); bool reversed(false), body(false), formatted(false), all(false), simple(true), remote(false); path tree_directory(fs::current_path()); list header_list; Parsed_Name branch_name; while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="--header") { simple=false; argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --header"); } header_list.push_back(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="--branch" || *(argument_list.begin())=="-B") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -B and --branch"); } branch_name=Parsed_Name(*(argument_list.begin())); if(branch_name.branch().empty()) throw arx_error("This argument needs to specify a branch: " + branch_name.full_name()); 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())=="--reverse" || *(argument_list.begin())=="-r") { reversed=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--body") { simple=false; body=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--formatted") { simple=false; formatted=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--remote") { remote=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--all") { simple=false; all=true; argument_list.pop_front(); } else { parse_unknown_options(argument_list); break; } } /* If present, get the name of the file, annotate it, get the ranges, and construct the list of names. */ set names; if(!argument_list.empty()) { path file_path; file_path=*(argument_list.begin()); argument_list.pop_front(); /* parse the ranges if they exist */ if(!argument_list.empty()) { pair,vector > annotations(annotate_file(file_path)); while(!argument_list.empty()) { pair range =parse_range(*(argument_list.begin())); argument_list.pop_front(); for(unsigned int i=range.first; i!=range.second; ++i) if(i=default_output) { gvfs::Init(); gvfs::uri location; for(set::iterator i=names.begin(); i!=names.end(); ++i) { /* The location of the patch logs in trees and archives vary a little. */ if(remote) { location=i->archive_location()/ i->branch_path_no_archive(); } else { location=(tree_root(tree_directory) / "_arx/patch-log" / i->branch_path()).native_file_string(); } if(branch_name.revision().empty() || branch_name==(*i)) { const unsigned int rev=i->patch_number(); string log_string; if(remote) log_string=gvfs::read_file_into_string (location/patch_level(rev)/"log"); else log_string=gvfs::read_file_into_string (location/patch_level(rev)); /* Different options for output formats */ if(simple) { Patch_Log log(log_string); cout << rev << "\n\t" << log.headers["Summary"] << endl; } else if(formatted) { Patch_Log log(log_string); output_changelog(rev,log,cout); } else { Patch_Log log(log_string); if(!all) { for(list::iterator k=header_list.begin(); k!=header_list.end(); ++k) { map::iterator j=log.headers.find(*k); if(j!=log.headers.end()) cout << j->second << endl; } } else { for(map::iterator k=log.headers.begin(); k!=log.headers.end(); ++k) { cout << k->first << ": " << k->second << endl; } } if(body || all) cout << endl << log.body << endl; } } } } return 0; }