/* Compare a project tree against a pristine revision. Copyright (C) 2001, 2002 Tom Lord Modifications 2002 Alexander Deruwe Copyright (C) 2002, 2003 Walter Landry and the Regents of the University of California 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 "boost/filesystem/operations.hpp" #include "boost/filesystem/fstream.hpp" #include #include "Patch_Info.hpp" #include "Parsed_Name.hpp" #include "find_or_make_pristine.hpp" #include "patch_level_cmp.hpp" #include #include "get_config_option.hpp" #include "make_patch.hpp" #include "latest_archive_revision.hpp" #include "tree_root.hpp" #include "fill_path_list.hpp" #include "get_option_from_file.hpp" #include "Parsed_Name.hpp" #include "Spawn.hpp" #include "valid_package_name.hpp" #include "Temp_Directory.hpp" #include "tree_branch.hpp" #include "inventory_directory.hpp" #include "read_checksums.hpp" #include "tempdir.hpp" using namespace std; using namespace boost; namespace fs=boost::filesystem; using fs::path; int diff(list &argument_list, const command &cmd); static command_initializer diff_init(command("diff", "Compare a project tree against a pristine revision", "usage: diff [options] [path ...]", " -R --recursive Include nested trees in the diff\n\ --dir DIR Change to DIR first\n\ -o --output OUTDIR Put the patch in OUTDIR\n\ --revision REVISION compare against REVISION\n\ --diffs include diffs in the output\n\ --control include control files in the output\n\ --paths-file PATHS-FILE only compare paths in PATHS-FILE\n\ --keep-patch-dir Don't delete the patch directory\n\ --html generate html output\n\ --uri only print out a URI\n\ --new-browser show output in a new browser window\n\ --new-tab show output in a new browser tab\n\ \n\ Generate a patch between the project tree containing DIR (or the\n\ current directory) and the default revision. Then, generate a patch report\n\ describing the patch. Unless the --keep-patch-dir option is specified,\n\ the patch directory is then deleted.\n\ \n\ With extra paths on the command line or the --paths-file option, only\n\ compute the differences to those paths. See \"arx commit --help\" for\n\ more information on these options.\n\ \n\ With --revision, generate a patch report against REVISION. If only a\n\ branch, rather than a full revision, is specified, then the\n\ default patch level for a given branch is the latest level for\n\ which the project tree has a patch.\n\ \n\ The patch is normally stored in the root of the project tree in the file\n\ \n\ ,,diff.REVISION--ARCHIVE\n\ \n\ but that can be overriden with -o (--output). Using --output\n\ implies --keep-patch-dir.\n\ \n\ Ordinary output is an outline-format patch report (sans diffs).\n\ The option --diffs causes diffs to be included in the outline.\n\ With --html, generate the report in HTML format.\n\ \n\ With --uri, instead of printing out the report, save the report\n\ in a file in the output directory under the name REPORT or index.html \n\ and print a \"file:\" URI for the report. --uri implies --keep-patch-dir.\n\ \n\ With --new-browser or --new-tab, open the \"file:\" URI in a new\n\ browser window or browser tab (if supported).\n\ \n\ With --recursive, ArX also includes nested trees in the patch report.\n\ This is useful if you have a project composed out of several subprojects.\n\ The --recursive option may not be combined with --revision or --output options.\n\ \n\ This command returns 0 if there are no differences, 1 if there are,\n\ and 2 if there was an error.", diff,"Basic",true)); int diff(list &argument_list, const command &cmd) { Command_Info info(cmd); bool recursive_changed(false), control(false); /* Wrap everything in a try block so that we can translate return values to 2 instead of the default 1 */ try { bool diffs(false), html(false), uri(false), new_browser(false), new_tab(false), keep_patch_dir(false), recursive(false); path tree_directory(fs::current_path()), output_directory; string paths_file; Parsed_Name input_revision; while(!argument_list.empty()) if(!parse_common_options(argument_list,info)) { if(*(argument_list.begin())=="--diffs") { diffs=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--control") { control=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())=="--paths-file") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --paths-file",2); } paths_file=*(argument_list.begin()); argument_list.pop_front(); } else if(*(argument_list.begin())=="--keep-patch-dir") { keep_patch_dir=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--revision") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for --revision",2); } input_revision=Parsed_Name(*(argument_list.begin())); argument_list.pop_front(); } else if(*(argument_list.begin())=="--html") { html=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--uri") { uri=true; keep_patch_dir=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--new-browser") { new_browser=true; uri=true; html=true; keep_patch_dir=true; argument_list.pop_front(); } else if(*(argument_list.begin())=="--new-tab") { new_tab=true; new_browser=true; uri=true; html=true; keep_patch_dir=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())=="-o" || *(argument_list.begin())=="--output") { argument_list.pop_front(); if(argument_list.empty()) { throw arx_error("Need an argument for -o and --output"); } output_directory=path(*(argument_list.begin())); keep_patch_dir=true; argument_list.pop_front(); } else { parse_unknown_options(argument_list); break; } } if(recursive && (!output_directory.empty() || !input_revision.empty())) throw arx_error("You may not specify either --output or --revision with --recursive"); if(recursive) check_extra_args(argument_list,info); list tree_list; const path root(tree_root(tree_directory)); tree_list.push_back(root); if(recursive) { 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) tree_list.push_back(i->file_path); } Patch_Info pi(diffs,html,"", (system_complete(output_directory)).native_file_string(), control); for(list::iterator i=tree_list.begin(); i!=tree_list.end(); ++i) { /* Get the revision to look at. If the tree is an initial import, then we diff against an empty directory. */ bool import(false); Path_List path_list; fill_path_list(*i,paths_file,argument_list,path_list); Parsed_Name tree_revision(input_revision); if(tree_revision.empty()) { if(exists(*i/ "_arx/++branch-revision")) { tree_revision= Parsed_Name(get_option_from_file (*i/ "_arx/++branch-revision")); } else { tree_revision=tree_branch(*i); import=exists(*i/"_arx/++import"); } } valid_package_name(tree_revision,Branch); /* If only the branch is specified, then find the latest revision. */ if(tree_revision.revision().empty() && !import) { tree_revision=Parsed_Name(latest_archive_revision (tree_revision)); } if(output_directory.empty()) { output_directory=*i / (",,diff." + tree_revision.revision() + "--" + tree_revision.archive()); } if(new_browser && (get_config_option("browser")).empty()) throw arx_error("No web browser set. See 'arx my-browser --help'"); Temp_Directory temp_dir(tempdir(),",,pristine",false); bool changed(false); if(!import) { changed=make_patch(find_or_make_pristine(*i, temp_dir.path, tree_revision), *i,output_directory,path_list); } else { create_directory(temp_dir.path); create_directory(temp_dir.path/"_arx"); changed=make_patch(temp_dir.path,*i,output_directory,path_list); } if(changed) { if(*i==root.string()) { pi.append(output_directory); } else { pi.append(output_directory, relative_path(*i,root).string()+"/"); } recursive_changed=true; } if(!(changed && keep_patch_dir)) { remove_all(output_directory); } } /* Now print out the report. It is rather complicated because we can print out html or text, and we may spawn a browser. */ if(recursive_changed) { if(uri) { if(html) { fs::ofstream html_report(output_directory/"index.html"); html_report << pi; } else { fs::ofstream text_report(output_directory/"REPORT"); text_report << pi; } } else { cout << pi; } if(uri) { if(new_browser) { string action("new-window"); if(new_tab) action=string("new-tab"); Spawn s; s << get_config_option("browser") << "-remote" << ("openurl(file://" + (system_complete(output_directory / "index.html")) .native_file_string() + "," + action + ")"); s.execute(false); } else { if(html) cout << "file://" << (system_complete(output_directory / "index.html")) .native_file_string() << endl; else cout << "file://" << (system_complete(output_directory / "REPORT")) .native_file_string() << endl; } } } } catch (arx_error &arx_err) { throw arx_error(arx_err.what(),2); } catch (fs::filesystem_error &fs_error) { throw arx_error("Problems with a filesystem call\n" + fs_error.who() + "\n" + fs_error.path1().native_file_string() + "\n" + fs_error.path2().native_file_string() + "\n",2); } catch (std::exception &err) { throw arx_error(string("Unhandled exception\n") + err.what()); } return (recursive_changed ? 1 : 0); }