From f84f971f8140fca6fb8661ee4e830fb2c07bb159 Mon Sep 17 00:00:00 2001 From: Jason Tibbitts Date: Mar 10 2015 00:08:02 +0000 Subject: Update to 4.2.10. --- diff --git a/0001-Remove-configure-time-generated-files.patch b/0001-Remove-configure-time-generated-files.patch deleted file mode 100644 index 8bd2020..0000000 --- a/0001-Remove-configure-time-generated-files.patch +++ /dev/null @@ -1,16719 +0,0 @@ -From a5e8be9563b56168b93721ba3286d24d4960d1cb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Ralf=20Cors=C3=A9pius?= -Date: Tue, 18 Mar 2014 07:23:24 +0100 -Subject: [PATCH 1/8] Remove configure time generated files. - ---- - Makefile | 583 ----- - bin/rt | 2682 --------------------- - bin/rt-crontool | 456 ---- - bin/rt-mailgate | 512 ---- - etc/RT_Config.pm | 3024 ------------------------ - etc/upgrade/3.8-ical-extension | 96 - - etc/upgrade/4.0-customfield-checkbox-extension | 86 - - etc/upgrade/generate-rtaddressregexp | 109 - - etc/upgrade/split-out-cf-categories | 171 -- - etc/upgrade/switch-templates-to | 148 -- - etc/upgrade/upgrade-articles | 264 --- - etc/upgrade/vulnerable-passwords | 142 -- - lib/RT/Generated.pm | 84 - - sbin/rt-attributes-viewer | 115 - - sbin/rt-clean-sessions | 183 -- - sbin/rt-dump-metadata | 336 --- - sbin/rt-email-dashboards | 165 -- - sbin/rt-email-digest | 374 --- - sbin/rt-email-group-admin | 520 ---- - sbin/rt-fulltext-indexer | 467 ---- - sbin/rt-importer | 282 --- - sbin/rt-preferences-viewer | 144 -- - sbin/rt-serializer | 398 ---- - sbin/rt-server | 181 -- - sbin/rt-server.fcgi | 181 -- - sbin/rt-session-viewer | 114 - - sbin/rt-setup-database | 795 ------- - sbin/rt-setup-fulltext-index | 713 ------ - sbin/rt-shredder | 317 --- - sbin/rt-test-dependencies | 652 ----- - sbin/rt-validate-aliases | 373 --- - sbin/rt-validator | 1464 ------------ - sbin/standalone_httpd | 181 -- - t/data/configs/apache2.2+fastcgi.conf | 49 - - t/data/configs/apache2.2+mod_perl.conf | 67 - - 35 files changed, 16428 deletions(-) - delete mode 100644 Makefile - delete mode 100755 bin/rt - delete mode 100755 bin/rt-crontool - delete mode 100755 bin/rt-mailgate - delete mode 100644 etc/RT_Config.pm - delete mode 100755 etc/upgrade/3.8-ical-extension - delete mode 100755 etc/upgrade/4.0-customfield-checkbox-extension - delete mode 100755 etc/upgrade/generate-rtaddressregexp - delete mode 100755 etc/upgrade/split-out-cf-categories - delete mode 100755 etc/upgrade/switch-templates-to - delete mode 100755 etc/upgrade/upgrade-articles - delete mode 100755 etc/upgrade/vulnerable-passwords - delete mode 100644 lib/RT/Generated.pm - delete mode 100755 sbin/rt-attributes-viewer - delete mode 100755 sbin/rt-clean-sessions - delete mode 100755 sbin/rt-dump-metadata - delete mode 100755 sbin/rt-email-dashboards - delete mode 100755 sbin/rt-email-digest - delete mode 100755 sbin/rt-email-group-admin - delete mode 100755 sbin/rt-fulltext-indexer - delete mode 100755 sbin/rt-importer - delete mode 100755 sbin/rt-preferences-viewer - delete mode 100755 sbin/rt-serializer - delete mode 100755 sbin/rt-server - delete mode 100755 sbin/rt-server.fcgi - delete mode 100755 sbin/rt-session-viewer - delete mode 100755 sbin/rt-setup-database - delete mode 100755 sbin/rt-setup-fulltext-index - delete mode 100755 sbin/rt-shredder - delete mode 100755 sbin/rt-test-dependencies - delete mode 100755 sbin/rt-validate-aliases - delete mode 100755 sbin/rt-validator - delete mode 100755 sbin/standalone_httpd - delete mode 100644 t/data/configs/apache2.2+fastcgi.conf - delete mode 100644 t/data/configs/apache2.2+mod_perl.conf - -diff --git a/Makefile b/Makefile -deleted file mode 100644 -index 3037009..0000000 ---- a/Makefile -+++ /dev/null -@@ -1,583 +0,0 @@ --# BEGIN BPS TAGGED BLOCK {{{ --# --# COPYRIGHT: --# --# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC --# --# --# (Except where explicitly superseded by other copyright notices) --# --# --# LICENSE: --# --# This work is made available to you under the terms of Version 2 of --# the GNU General Public License. A copy of that license should have --# been provided with this software, but in any event can be snarfed --# from www.gnu.org. --# --# This work 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., 51 Franklin Street, Fifth Floor, Boston, MA --# 02110-1301 or visit their web page on the internet at --# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. --# --# --# CONTRIBUTION SUBMISSION POLICY: --# --# (The following paragraph is not intended to limit the rights granted --# to you to modify and distribute this software under the terms of --# the GNU General Public License and is only of importance to you if --# you choose to contribute your changes and enhancements to the --# community by submitting them to Best Practical Solutions, LLC.) --# --# By intentionally submitting any modifications, corrections or --# derivatives to this work, or any other work intended for use with --# Request Tracker, to Best Practical Solutions, LLC, you confirm that --# you are the copyright holder for those contributions and you grant --# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, --# royalty-free, perpetual, license to use, copy, create derivative --# works based on those contributions, and sublicense and distribute --# those contributions and any derivatives thereof. --# --# END BPS TAGGED BLOCK }}} --# --# DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated. --# Have a look at "configure" and "Makefile.in" instead --# -- -- --PERL = /usr/bin/perl --INSTALL = ./install-sh --CC = @CC@ -- --RT_LAYOUT = relative -- --CONFIG_FILE_PATH = /opt/rt4/etc --CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm --SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm -- -- --RT_VERSION_MAJOR = 4 --RT_VERSION_MINOR = 2 --RT_VERSION_PATCH = 9 -- --RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) --TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) -- -- --# This is the group that all of the installed files will be chgrp'ed to. --RTGROUP = www -- -- --# User which should own rt binaries. --BIN_OWNER = root -- --# User that should own all of RT's libraries, generally root. --LIBS_OWNER = root -- --# Group that should own all of RT's libraries, generally root. --LIBS_GROUP = bin -- --WEB_USER = www --WEB_GROUP = www -- --# DESTDIR allows you to specify that RT be installed somewhere other than --# where it will eventually reside. DESTDIR _must_ have a trailing slash --# if it's defined. -- --DESTDIR = -- -- -- --RT_PATH = /opt/rt4 --RT_ETC_PATH = /opt/rt4/etc --RT_BIN_PATH = /opt/rt4/bin --RT_SBIN_PATH = /opt/rt4/sbin --RT_LIB_PATH = /opt/rt4/lib --RT_MAN_PATH = /opt/rt4/man --RT_VAR_PATH = /opt/rt4/var --RT_DOC_PATH = /opt/rt4/docs --RT_FONT_PATH = /opt/rt4/share/fonts --RT_LEXICON_PATH = /opt/rt4/share/po --RT_STATIC_PATH = /opt/rt4/share/static --RT_LOCAL_PATH = /opt/rt4/local --LOCAL_PLUGIN_PATH = /opt/rt4/local/plugins --LOCAL_ETC_PATH = /opt/rt4/local/etc --LOCAL_LIB_PATH = /opt/rt4/local/lib --LOCAL_LEXICON_PATH = /opt/rt4/local/po --LOCAL_STATIC_PATH = /opt/rt4/local/static --MASON_HTML_PATH = /opt/rt4/share/html --MASON_LOCAL_HTML_PATH = /opt/rt4/local/html --MASON_DATA_PATH = /opt/rt4/var/mason_data --MASON_SESSION_PATH = /opt/rt4/var/session_data --RT_LOG_PATH = /opt/rt4/var/log -- --# RT_READABLE_DIR_MODE is the mode of directories that are generally meant --# to be accessable --RT_READABLE_DIR_MODE = 0755 -- -- -- -- -- --# RT's CLI --RT_CLI_BIN = rt --# RT's mail gateway --RT_MAILGATE_BIN = rt-mailgate --# RT's cron tool --RT_CRON_BIN = rt-crontool -- -- -- --BINARIES = $(RT_MAILGATE_BIN) \ -- $(RT_CLI_BIN) \ -- $(RT_CRON_BIN) -- --SYSTEM_BINARIES = rt-attributes-viewer \ -- rt-clean-sessions \ -- rt-dump-metadata \ -- rt-email-dashboards \ -- rt-email-digest \ -- rt-email-group-admin \ -- rt-fulltext-indexer \ -- rt-importer \ -- rt-preferences-viewer \ -- rt-serializer \ -- rt-server \ -- rt-server.fcgi \ -- rt-session-viewer \ -- rt-setup-database \ -- rt-setup-fulltext-index \ -- rt-shredder \ -- rt-test-dependencies \ -- rt-validator \ -- rt-validate-aliases \ -- standalone_httpd -- -- --ETC_FILES = acl.Pg \ -- acl.Oracle \ -- acl.mysql \ -- schema.Pg \ -- schema.Oracle \ -- schema.mysql \ -- schema.SQLite \ -- initialdata -- -- -- --WEB_HANDLER = standalone -- -- -- --# --# DB_TYPE defines what sort of database RT trys to talk to --# "mysql", "Oracle", "Pg", and "SQLite" are known to work. -- --DB_TYPE = SQLite -- --# Set DBA to the name of a unix account with the proper permissions and --# environment to run your commandline SQL sbin -- --# Set DB_DBA to the name of a DB user with permission to create new databases -- --# For mysql, you probably want 'root' --# For Pg, you probably want 'postgres' --# For Oracle, you want 'system' -- --DB_DBA = root -- --DB_HOST = localhost -- --# If you're not running your database server on its default port, --# specifiy the port the database server is running on below. --# It's generally safe to leave this blank -- --DB_PORT = -- -- -- -- --# --# Set this to the canonical name of the interface RT will be talking to the --# database on. If you said that the RT_DB_HOST above was "localhost," this --# should be too. This value will be used to grant rt access to the database. --# If you want to access the RT database from multiple hosts, you'll need --# to grant those database rights by hand. --# -- --DB_RT_HOST = localhost -- --# set this to the name you want to give to the RT database in --# your database server. For Oracle, this should be the name of your sid -- --DB_DATABASE = rt4 --DB_RT_USER = rt_user --DB_RT_PASS = rt_pass -- -- -- --TEST_FILES = t/*.t t/*/*.t t/*/*/*.t --TEST_VERBOSE = 0 -- --RT_TEST_PARALLEL_NUM ?= 5 -- -- --#################################################################### -- --all: default -- --default: -- @echo "Please read RT's README before beginning your installation." -- -- -- --instruct: -- @echo "Congratulations. RT is now installed." -- @echo "" -- @echo "" -- @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)." -- @echo "" -- @echo "(You will definitely need to set RT's database password in " -- @echo "$(SITE_CONFIG_FILE) before continuing. Not doing so could be " -- @echo "very dangerous. Note that you do not have to manually add a " -- @echo "database user or set up a database for RT. These actions will be " -- @echo "taken care of in the next step.)" -- @echo "" -- @echo "After that, you need to initialize RT's database by running" -- @echo " 'make initialize-database'" -- -- --upgrade-instruct: -- @echo "Congratulations. RT has been upgraded. You should now check over" -- @echo "$(CONFIG_FILE) for any necessary site customization. Additionally," -- @echo "you should update RT's system database objects by running " -- @echo " make upgrade-database" -- -- --upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct -- --my_with_web_handlers= $(shell $(PERL) -e 'print join " ", map "--with-$$_", grep defined && length, split /,/, "$(WEB_HANDLER)"') --testdeps: -- $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) $(my_with_web_handlers) -- --depends: fixdeps -- --fixdeps: -- $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) $(my_with_web_handlers) -- --#}}} -- --fixperms: -- # Make the libraries readable -- chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_PATH) -- chown -R $(LIBS_OWNER) $(DESTDIR)$(RT_LIB_PATH) -- chgrp -R $(LIBS_GROUP) $(DESTDIR)$(RT_LIB_PATH) -- chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(RT_LIB_PATH) -- -- -- chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH) -- -- chmod 0755 $(DESTDIR)$(RT_ETC_PATH) -- cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) -- -- #TODO: the config file should probably be able to have its -- # owner set separately from the binaries. -- chown -R $(BIN_OWNER) $(DESTDIR)$(RT_ETC_PATH) -- chgrp -R $(RTGROUP) $(DESTDIR)$(RT_ETC_PATH) -- -- chmod 0440 $(DESTDIR)$(CONFIG_FILE) -- chmod 0640 $(DESTDIR)$(SITE_CONFIG_FILE) -- -- # Make the system binaries -- cd $(DESTDIR)$(RT_BIN_PATH) && ( chmod 0755 $(BINARIES) ; chown $(BIN_OWNER) $(BINARIES); chgrp $(RTGROUP) $(BINARIES)) -- -- # Make the system binaries executable also -- cd $(DESTDIR)$(RT_SBIN_PATH) && ( chmod 0755 $(SYSTEM_BINARIES) ; chown $(BIN_OWNER) $(SYSTEM_BINARIES); chgrp $(RTGROUP) $(SYSTEM_BINARIES)) -- -- # Make upgrade scripts executable if they are in the source. -- # -- # Note that we use the deprecated (by GNU/POSIX find) -perm +0NNN syntax -- # instead of -perm /0NNN since BSD find doesn't support the latter. -- ( cd etc/upgrade && find . -type f -not -name '*.in' -perm +0111 -print ) | while read file ; do \ -- chmod a+x "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ -- done -- -- # Make the web ui readable by all. -- chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(MASON_HTML_PATH) \ -- $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ -- $(DESTDIR)$(RT_LEXICON_PATH) \ -- $(DESTDIR)$(LOCAL_LEXICON_PATH) \ -- $(DESTDIR)$(RT_STATIC_PATH) \ -- $(DESTDIR)$(LOCAL_STATIC_PATH) -- chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \ -- $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ -- $(DESTDIR)$(RT_LEXICON_PATH) \ -- $(DESTDIR)$(LOCAL_LEXICON_PATH) \ -- $(DESTDIR)$(RT_STATIC_PATH) \ -- $(DESTDIR)$(LOCAL_STATIC_PATH) -- chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \ -- $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ -- $(DESTDIR)$(RT_LEXICON_PATH) \ -- $(DESTDIR)$(LOCAL_LEXICON_PATH) \ -- $(DESTDIR)$(RT_STATIC_PATH) \ -- $(DESTDIR)$(LOCAL_STATIC_PATH) -- -- # Make the web ui's data dir writable -- chmod 0770 $(DESTDIR)$(MASON_DATA_PATH) \ -- $(DESTDIR)$(MASON_SESSION_PATH) -- chown -R $(WEB_USER) $(DESTDIR)$(MASON_DATA_PATH) \ -- $(DESTDIR)$(MASON_SESSION_PATH) -- chgrp -R $(WEB_GROUP) $(DESTDIR)$(MASON_DATA_PATH) \ -- $(DESTDIR)$(MASON_SESSION_PATH) -- --dirs: -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LOG_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) -- $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH) -- $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/cache -- $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/etc -- $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/obj -- $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_SESSION_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_LOCAL_HTML_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_ETC_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LIB_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH) -- $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_STATIC_PATH) -- --clean-mason-cache: -- rm -rf $(DESTDIR)$(MASON_DATA_PATH)/cache/* -- rm -rf $(DESTDIR)$(MASON_DATA_PATH)/etc/* -- rm -rf $(DESTDIR)$(MASON_DATA_PATH)/obj/* -- --install: testdeps config-install dirs files-install fixperms instruct -- --files-install: libs-install etc-install config-install bin-install sbin-install html-install doc-install font-install po-install static-install -- --config-install: -- $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH) -- -$(INSTALL) -m 0440 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_Config.pm $(DESTDIR)$(CONFIG_FILE) -- [ -f $(DESTDIR)$(SITE_CONFIG_FILE) ] || $(INSTALL) -m 0640 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_SiteConfig.pm $(DESTDIR)$(SITE_CONFIG_FILE) -- @echo "Installed configuration. About to install RT in $(RT_PATH)" -- --test: -- $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) -- --parallel-test: test-parallel -- --test-parallel: -- RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj$(RT_TEST_PARALLEL_NUM)","--state=slow,save", "t"); exit( $$p->run() ? 0 : 1 )' -- --regression-reset-db: force-dropdb -- $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba-password '' -- --initdb :: initialize-database -- --initialize-database: -- $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --prompt-for-dba-password -- --upgrade-database: -- $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action upgrade --prompt-for-dba-password -- --dropdb: -- $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --prompt-for-dba-password -- --force-dropdb: -- $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba-password '' --force -- --critic: -- perlcritic --quiet sbin bin lib -- --libs-install: -- [ -d $(DESTDIR)$(RT_LIB_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LIB_PATH) -- -( cd lib && find . -type d -print ) | while read dir ; do \ -- $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_LIB_PATH)/$$dir" ; \ -- done -- -( cd lib && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "lib/$$file" "$(DESTDIR)$(RT_LIB_PATH)/$$file" ; \ -- done -- --html-install: -- [ -d $(DESTDIR)$(MASON_HTML_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) -- -( cd share/html && find . -type d -print ) | while read dir ; do \ -- $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_HTML_PATH)/$$dir" ; \ -- done -- -( cd share/html && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "share/html/$$file" "$(DESTDIR)$(MASON_HTML_PATH)/$$file" ; \ -- done -- $(MAKE) clean-mason-cache -- --font-install: -- [ -d $(DESTDIR)$(RT_FONT_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_FONT_PATH) -- -( cd share/fonts && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "share/fonts/$$file" "$(DESTDIR)$(RT_FONT_PATH)/$$file" ; \ -- done -- -- --po-install: -- [ -d $(DESTDIR)$(RT_LEXICON_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LEXICON_PATH) -- -( cd share/po && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "share/po/$$file" "$(DESTDIR)$(RT_LEXICON_PATH)/$$file" ; \ -- done -- --static-install: -- [ -d $(DESTDIR)$(RT_STATIC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_STATIC_PATH) -- -( cd share/static && find . -type d -print ) | while read dir ; do \ -- $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_STATIC_PATH)/$$dir" ; \ -- done -- -( cd share/static && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "share/static/$$file" "$(DESTDIR)$(RT_STATIC_PATH)/$$file" ; \ -- done -- -- --doc-install: -- # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir -- -[ -f $(DESTDIR)$(RT_DOC_PATH) ] && rm $(DESTDIR)$(RT_DOC_PATH) -- [ -d $(DESTDIR)$(RT_DOC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_DOC_PATH) -- -( cd docs && find . -type d -print ) | while read dir ; do \ -- $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_DOC_PATH)/$$dir" ; \ -- done -- -( cd docs && find . -type f -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "docs/$$file" "$(DESTDIR)$(RT_DOC_PATH)/$$file" ; \ -- done -- -$(INSTALL) -m 0644 ./README $(DESTDIR)$(RT_DOC_PATH)/ -- -- --etc-install: -- [ -d $(DESTDIR)$(RT_ETC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH) -- for file in $(ETC_FILES) ; do \ -- $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(RT_ETC_PATH)/" ; \ -- done -- [ -d $(DESTDIR)$(RT_ETC_PATH)/upgrade ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH)/upgrade -- -( cd etc/upgrade && find . -type d -print ) | while read dir ; do \ -- $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$dir" ; \ -- done -- -( cd etc/upgrade && find . -type f -not -name '*.in' -print ) | while read file ; do \ -- $(INSTALL) -m 0644 "etc/upgrade/$$file" "$(DESTDIR)$(RT_ETC_PATH)/upgrade/$$file" ; \ -- done -- -- --sbin-install: -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_SBIN_PATH) -- for file in $(SYSTEM_BINARIES) ; do \ -- $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "sbin/$$file" "$(DESTDIR)$(RT_SBIN_PATH)/" ; \ -- done -- -- -- --bin-install: -- $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_BIN_PATH) -- for file in $(BINARIES) ; do \ -- $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "bin/$$file" "$(DESTDIR)$(RT_BIN_PATH)/" ; \ -- done -- -- -- --regenerate-catalogs: -- $(PERL) devel/tools/extract-message-catalog -- --license-tag: -- $(PERL) devel/tools/license_tag -- --start-httpd: -- $(PERL) sbin/standalone_httpd & -- --start-server: -- $(PERL) sbin/rt-server & -- -- --SNAPSHOT=$(shell git describe --tags) --THIRD_PARTY=devel/third-party/ --snapshot: build-snapshot build-third-party clearsign-snapshot clearsign-third-party snapshot-shasums -- --build-snapshot: -- git archive --prefix "$(SNAPSHOT)/" HEAD | tar -xf - -- ( cd $(SNAPSHOT) && \ -- echo "$(SNAPSHOT)" > .tag && \ -- autoconf && \ -- INSTALL=./install-sh PERL=/usr/bin/perl ./configure \ -- --with-db-type=SQLite \ -- --enable-layout=relative \ -- --with-web-handler=standalone && \ -- rm -rf autom4te.cache \ -- config.status config.log config.pld \ -- ) -- tar -czf "$(SNAPSHOT).tar.gz" "$(SNAPSHOT)/" -- rm -fr "$(SNAPSHOT)/" -- --clearsign-snapshot: -- gpg --armor --detach-sign "$(SNAPSHOT).tar.gz" -- --build-third-party: -- git archive --prefix "$(SNAPSHOT)/$(THIRD_PARTY)" HEAD:$(THIRD_PARTY) \ -- | gzip > "$(SNAPSHOT)-third-party-source.tar.gz" -- rm -rf "$(SNAPSHOT)/$(THIRD_PARTY)" -- --clearsign-third-party: -- gpg --armor --detach-sign "$(SNAPSHOT)-third-party-source.tar.gz" -- --snapshot-shasums: -- sha1sum $(SNAPSHOT)*.tar.gz* -- --vessel-import: build-snapshot -- [ -d $(VESSEL) ] || (echo "VESSEL isn't a path to your shipwright vessel" && exit -1) -- cp $(VESSEL)/scripts/RT/build.pl /tmp/build.pl -- ./sbin/rt-test-dependencies --with-standalone --with-fastcgi --with-sqlite --list > /tmp/rt.yml -- shipwright import file:$(SNAPSHOT).tar.gz \ -- --require-yml /tmp/rt.yml \ -- --build-script /tmp/build.pl \ -- --name RT \ -- --repository fs:$(VESSEL) \ -- --log-level=info \ -- --skip cpan-capitalization,cpan-mod_perl,cpan-Encode,cpan-PPI,cpan-Test-Exception-LessClever,cpan-Test-Manifest,cpan-Test-Object,cpan-Test-Pod,cpan-Test-Requires,cpan-Test-SubCalls,cpan-Test-cpan-Tester,cpan-Test-Warn --skip-all-recommends -- mv $(VESSEL)/scripts/RT/build $(VESSEL)/scripts/RT/build.pl -- --JSMIN_URL = http://download.bestpractical.com/mirror/jsmin-2013-03-29.c --JSMIN_SHA = 67dc8d73a8878f88cdaeb1a86775872eae5c3077 -- --jsmin: jsmin-checkcc jsmin-fetch jsmin-verify jsmin-confirm jsmin-build jsmin-install -- @echo "" -- @echo "To configure RT to use jsmin, add the following line to $(DESTDIR)$(RT_ETC_PATH)/RT_SiteConfig.pm:" -- @echo "" -- @echo " Set(\$$JSMinPath, '$(DESTDIR)$(RT_BIN_PATH)/jsmin');" -- @echo "" -- --jsmin-checkcc: -- @[ -n "$(CC)" ] || (echo "You don't appear to have a C compiler, please set CC and re-run configure" && exit 1) -- --jsmin-confirm: -- @echo "jsmin is distributed under a slightly unusual license and can't be shipped" -- @echo "with RT. Before configuring RT to use jsmin, please read jsmin's license" -- @echo "below:" -- @echo "" -- @$(PERL) -pe 'print && exit if /^\*\// or /^#include/' jsmin.c -- @echo "" -- @echo "Press Enter to accept the license, or Ctrl-C to stop now." -- @$(PERL) -e '' -- --jsmin-fetch: -- @echo "" -- @echo "Downloading jsmin.c from $(JSMIN_URL)" -- @echo "" -- @$(PERL) -MLWP::Simple -e 'exit not is_success(getstore("$(JSMIN_URL)", "jsmin.c"))' \ -- || (echo "Failed to download $(JSMIN_URL)" && exit 1) -- --jsmin-verify: -- @$(PERL) -MDigest::SHA -e \ -- 'exit not Digest::SHA->new(1)->addfile("jsmin.c")->hexdigest eq "$(JSMIN_SHA)"' \ -- || (echo "Verification of jsmin.c failed! Possible man in the middle?" && exit 1) -- --jsmin-build: -- $(CC) -o jsmin jsmin.c -- --jsmin-install: -- $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "jsmin" "$(DESTDIR)$(RT_BIN_PATH)/" -diff --git a/bin/rt b/bin/rt -deleted file mode 100755 -index a73bb42..0000000 ---- a/bin/rt -+++ /dev/null -@@ -1,2682 +0,0 @@ --#!/usr/bin/perl -w --# BEGIN BPS TAGGED BLOCK {{{ --# --# COPYRIGHT: --# --# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC --# --# --# (Except where explicitly superseded by other copyright notices) --# --# --# LICENSE: --# --# This work is made available to you under the terms of Version 2 of --# the GNU General Public License. A copy of that license should have --# been provided with this software, but in any event can be snarfed --# from www.gnu.org. --# --# This work 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., 51 Franklin Street, Fifth Floor, Boston, MA --# 02110-1301 or visit their web page on the internet at --# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. --# --# --# CONTRIBUTION SUBMISSION POLICY: --# --# (The following paragraph is not intended to limit the rights granted --# to you to modify and distribute this software under the terms of --# the GNU General Public License and is only of importance to you if --# you choose to contribute your changes and enhancements to the --# community by submitting them to Best Practical Solutions, LLC.) --# --# By intentionally submitting any modifications, corrections or --# derivatives to this work, or any other work intended for use with --# Request Tracker, to Best Practical Solutions, LLC, you confirm that --# you are the copyright holder for those contributions and you grant --# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, --# royalty-free, perpetual, license to use, copy, create derivative --# works based on those contributions, and sublicense and distribute --# those contributions and any derivatives thereof. --# --# END BPS TAGGED BLOCK }}} --# Designed and implemented for Best Practical Solutions, LLC by --# Abhijit Menon-Sen -- --use strict; --use warnings; -- --if ( $ARGV[0] && $ARGV[0] =~ /^(?:--help|-h)$/ ) { -- require Pod::Usage; -- print Pod::Usage::pod2usage( { verbose => 2 } ); -- exit; --} -- --# This program is intentionally written to have as few non-core module --# dependencies as possible. It should stay that way. -- --use Cwd; --use LWP; --use Text::ParseWords; --use HTTP::Request::Common; --use HTTP::Headers; --use Term::ReadLine; --use Time::Local; # used in prettyshow --use File::Temp; -- --# strong (GSSAPI based) authentication is supported if the server does provide --# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed --# it can be suppressed by setting externalauth=0 (default is undef) --eval { require GSSAPI }; --my $no_strong_auth = 'missing perl module GSSAPI'; --if ( ! $@ ) { -- eval {require LWP::Authen::Negotiate}; -- $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0; --} -- --# We derive configuration information from hardwired defaults, dotfiles, --# and the RT* environment variables (in increasing order of precedence). --# Session information is stored in ~/.rt_sessions. -- --my $VERSION = 0.02; --my $HOME = eval{(getpwuid($<))[7]} -- || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH} -- || "."; --my %config = ( -- ( -- debug => 0, -- user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, -- passwd => undef, -- server => 'http://localhost/', -- query => "Status!='resolved' and Status!='rejected'", -- orderby => 'id', -- queue => undef, --# to protect against unlimited searches a better choice would be --# queue => 'Unknown_Queue', --# setting externalauth => undef will try GSSAPI auth if the corresponding perl --# modules are installed, externalauth => 0 is the backward compatible choice -- externalauth => 0, -- ), -- config_from_file($ENV{RTCONFIG} || ".rtrc"), -- config_from_env() --); --my $session = Session->new("$HOME/.rt_sessions"); --my $REST = "$config{server}/REST/1.0"; --$no_strong_auth = 'switched off by externalauth=0' -- if defined $config{externalauth}; -- -- --my $prompt = 'rt> '; -- --sub whine; --sub DEBUG { warn @_ if $config{debug} >= shift } -- --# These regexes are used by command handlers to parse arguments. --# (XXX: Ask Autrijus how i18n changes these definitions.) -- --my $name = '[\w.-]+'; --my $CF_name = '[^,]+?'; --my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})'; --my $label = '[^,\\/]+'; --my $labels = "(?:$label,)*$label"; --my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; -- --# Our command line looks like this: --# --# rt [options] [arguments] --# --# We'll parse just enough of it to decide upon an action to perform, and --# leave the rest to per-action handlers to interpret appropriately. -- --my %handlers = ( --# handler => [ ...aliases... ], -- version => ["version", "ver"], -- shell => ["shell"], -- logout => ["logout"], -- help => ["help", "man"], -- show => ["show", "cat"], -- edit => ["create", "edit", "new", "ed"], -- list => ["search", "list", "ls"], -- comment => ["comment", "correspond"], -- link => ["link", "ln"], -- merge => ["merge"], -- grant => ["grant", "revoke"], -- take => ["take", "steal", "untake"], -- quit => ["quit", "exit"], -- setcommand => ["del", "delete", "give", "res", "resolve", -- "subject"], --); -- --my %actions; --foreach my $fn (keys %handlers) { -- foreach my $alias (@{ $handlers{$fn} }) { -- $actions{$alias} = \&{"$fn"}; -- } --} -- --# Once we find and call an appropriate handler, we're done. -- --sub handler { -- my $action; -- -- push @ARGV, 'shell' if (!@ARGV); # default to shell mode -- shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt' -- if (@ARGV && exists $actions{$ARGV[0]}) { -- $action = shift @ARGV; -- return $actions{$action}->($action); -- } -- else { -- print STDERR "rt: Unknown command '@ARGV'.\n"; -- print STDERR "rt: For help, run 'rt help'.\n"; -- return 1; -- } --} -- --exit handler(); -- --# Handler functions. --# ------------------ --# --# The following subs are handlers for each entry in %actions. -- --sub shell { -- $|=1; -- my $term = Term::ReadLine->new('RT CLI'); -- while ( defined ($_ = $term->readline($prompt)) ) { -- next if /^#/ || /^\s*$/; -- -- @ARGV = shellwords($_); -- handler(); -- } --} -- --sub version { -- print "rt $VERSION\n"; -- return 0; --} -- --sub logout { -- submit("$REST/logout") if defined $session->cookie; -- return 0; --} -- --sub quit { -- logout(); -- exit; --} -- --my %help; --sub help { -- my ($action, $type, $rv) = @_; -- $rv = defined $rv ? $rv : 0; -- my $key; -- -- # What help topics do we know about? -- if (!%help) { -- local $/ = undef; -- foreach my $item (@{ Form::parse() }) { -- my $title = $item->[2]{Title}; -- my @titles = ref $title eq 'ARRAY' ? @$title : $title; -- -- foreach $title (grep $_, @titles) { -- $help{$title} = $item->[2]{Text}; -- } -- } -- } -- -- # What does the user want help with? -- undef $action if ($action && $actions{$action} eq \&help); -- unless ($action || $type) { -- # If we don't know, we'll look for clues in @ARGV. -- foreach (@ARGV) { -- if (exists $help{$_}) { $key = $_; last; } -- } -- unless ($key) { -- # Tolerate possibly plural words. -- foreach (@ARGV) { -- if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; } -- } -- } -- } -- -- if ($type && $action) { -- $key = "$type.$action"; -- } -- $key ||= $type || $action || "introduction"; -- -- # Find a suitable topic to display. -- while (!exists $help{$key}) { -- if ($type && $action) { -- if ($key eq "$type.$action") { $key = $action; } -- elsif ($key eq $action) { $key = $type; } -- else { $key = "introduction"; } -- } -- else { -- $key = "introduction"; -- } -- } -- -- print STDERR $help{$key}, "\n\n"; -- return $rv; --} -- --# Displays a list of objects that match some specified condition. -- --sub list { -- my ($q, $type, %data); -- my $orderby = $config{orderby}; -- -- if ($config{orderby}) { -- $data{orderby} = $config{orderby}; -- } -- my $bad = 0; -- my $rawprint = 0; -- my $reverse_sort = 0; -- my $queue = $config{queue}; -- -- while (@ARGV) { -- $_ = shift @ARGV; -- -- if (/^-t$/) { -- $bad = 1, last unless defined($type = get_type_argument()); -- } -- elsif (/^-S$/) { -- $bad = 1, last unless get_var_argument(\%data); -- } -- elsif (/^-o$/) { -- $data{'orderby'} = shift @ARGV; -- } -- elsif (/^-([isl])$/) { -- $data{format} = $1; -- $rawprint = 1; -- } -- elsif (/^-q$/) { -- $queue = shift @ARGV; -- } -- elsif (/^-r$/) { -- $reverse_sort = 1; -- } -- elsif (/^-f$/) { -- if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { -- whine "No valid field list in '-f $ARGV[0]'."; -- $bad = 1; last; -- } -- $data{fields} = shift @ARGV; -- $data{format} = 's' if ! $data{format}; -- $rawprint = 1; -- } -- elsif (!defined $q && !/^-/) { -- $q = $_; -- } -- else { -- my $datum = /^-/ ? "option" : "argument"; -- whine "Unrecognised $datum '$_'."; -- $bad = 1; last; -- } -- } -- if ( ! $rawprint and ! exists $data{format} ) { -- $data{format} = 'l'; -- $data{fields} = 'subject,status,queue,created,told,owner,requestors'; -- } -- if ( $reverse_sort and $data{orderby} =~ /^-/ ) { -- $data{orderby} =~ s/^-/+/; -- } elsif ($reverse_sort) { -- $data{orderby} =~ s/^\+?(.*)/-$1/; -- } -- -- $type ||= "ticket"; -- -- if (!defined $q ) { -- if ( $type eq 'ticket' ) { -- $q = $config{query}; -- } -- else { -- $q = ''; -- } -- } -- -- if ( $type ne 'ticket' ) { -- $rawprint = 1; -- } -- -- unless (defined $q) { -- my $item = $type ? "query string" : "object type"; -- whine "No $item specified."; -- $bad = 1; -- } -- -- $q =~ s/^#//; # get rid of leading hash -- if ( $type eq 'ticket' ) { -- if ( $q =~ /^\d+$/ ) { -- -- # only digits, must be an id, formulate a correct query -- $q = "id=$q" if $q =~ /^\d+$/; -- } -- else { -- -- # a string only, take it as an owner or requestor (quoting done later) -- $q = "(Owner=$q or Requestor like $q) and $config{query}" -- if $q =~ /^[\w\-]+$/; -- -- # always add a query for a specific queue or (comma separated) queues -- $queue =~ s/,/ or Queue=/g if $queue; -- $q .= " and (Queue=$queue)" -- if $queue -- and $q -- and $q !~ /Queue\s*=/i -- and $q !~ /id\s*=/i; -- } -- -- # correctly quote strings in a query -- $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g; -- } -- -- #return help("list", $type) if $bad; -- return suggest_help("list", $type, $bad) if $bad; -- -- print "Query:$q\n" if ! $rawprint; -- my $r = submit("$REST/search/$type", { query => $q, %data }); -- if ( $rawprint ) { -- print $r->content; -- } else { -- my $forms = Form::parse($r->content); -- prettylist ($forms); -- } -- return 0; --} -- --# Displays selected information about a single object. -- --sub show { -- my ($type, @objects, %data); -- my $slurped = 0; -- my $bad = 0; -- my $rawprint = 0; -- my $histspec; -- -- while (@ARGV) { -- $_ = shift @ARGV; -- s/^#// if /^#\d+/; # get rid of leading hash -- if (/^-t$/) { -- $bad = 1, last unless defined($type = get_type_argument()); -- } -- elsif (/^-S$/) { -- $bad = 1, last unless get_var_argument(\%data); -- } -- elsif (/^-([isl])$/) { -- $data{format} = $1; -- $rawprint = 1; -- } -- elsif (/^-$/ && !$slurped) { -- chomp(my @lines = ); -- foreach (@lines) { -- unless (is_object_spec($_, $type)) { -- whine "Invalid object on STDIN: '$_'."; -- $bad = 1; last; -- } -- push @objects, $_; -- } -- $slurped = 1; -- } -- elsif (/^-f$/) { -- if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { -- whine "No valid field list in '-f $ARGV[0]'."; -- $bad = 1; last; -- } -- $data{fields} = shift @ARGV; -- # option f requires short raw listing format -- $data{format} = 's'; -- $rawprint = 1; -- } -- elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { -- push @objects, $spc2; -- $histspec = is_object_spec("ticket/$_/history", $type); -- } -- elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) { -- push @objects, $spc3; -- $rawprint = 1 if $_ =~ /\/content$/; -- } -- elsif (my $spec = is_object_spec($_, $type)) { -- push @objects, $spec; -- $rawprint = 1 if $_ =~ /\/content$/ or $_ =~ /\/links/ or $_ !~ /^ticket/; -- } -- else { -- my $datum = /^-/ ? "option" : "argument"; -- whine "Unrecognised $datum '$_'."; -- $bad = 1; last; -- } -- } -- if ( ! $rawprint ) { -- push @objects, $histspec if $histspec; -- $data{format} = 'l' if ! exists $data{format}; -- } -- -- unless (@objects) { -- whine "No objects specified."; -- $bad = 1; -- } -- #return help("show", $type) if $bad; -- return suggest_help("show", $type, $bad) if $bad; -- -- my $r = submit("$REST/show", { id => \@objects, %data }); -- my $c = $r->content; -- # if this isn't a text reply, remove the trailing newline so we -- # don't corrupt things like tarballs when people do -- # show ticket/id/attachments/id/content > foo.tar.gz -- if ($r->content_type !~ /^text\//) { -- chomp($c); -- $rawprint = 1; -- } -- if ( $rawprint ) { -- print $c; -- } else { -- # I do not know how to get more than one form correctly returned -- $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg; -- my $forms = Form::parse($c); -- prettyshow ($forms); -- } -- return 0; --} -- --# To create a new object, we ask the server for a form with the defaults --# filled in, allow the user to edit it, and send the form back. --# --# To edit an object, we must ask the server for a form representing that --# object, make changes requested by the user (either on the command line --# or interactively via $EDITOR), and send the form back. -- --sub edit { -- my ($action) = @_; -- my (%data, $type, @objects); -- my ($cl, $text, $edit, $input, $output, $content_type); -- -- use vars qw(%set %add %del); -- %set = %add = %del = (); -- my $slurped = 0; -- my $bad = 0; -- -- while (@ARGV) { -- $_ = shift @ARGV; -- s/^#// if /^#\d+/; # get rid of leading hash -- -- if (/^-e$/) { $edit = 1 } -- elsif (/^-i$/) { $input = 1 } -- elsif (/^-o$/) { $output = 1 } -- elsif (/^-ct$/) { $content_type = shift @ARGV } -- elsif (/^-t$/) { -- $bad = 1, last unless defined($type = get_type_argument()); -- } -- elsif (/^-S$/) { -- $bad = 1, last unless get_var_argument(\%data); -- } -- elsif (/^-$/ && !($slurped || $input)) { -- chomp(my @lines = ); -- foreach (@lines) { -- unless (is_object_spec($_, $type)) { -- whine "Invalid object on STDIN: '$_'."; -- $bad = 1; last; -- } -- push @objects, $_; -- } -- $slurped = 1; -- } -- elsif (/^set$/i) { -- my $vars = 0; -- -- while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/s) { -- my ($key, $op, $val) = ($1, $2, $3); -- my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del; -- -- vpush($hash, lc $key, $val); -- shift @ARGV; -- $vars++; -- } -- unless ($vars) { -- whine "No variables to set."; -- $bad = 1; last; -- } -- $cl = $vars; -- } -- elsif (/^(?:add|del)$/i) { -- my $vars = 0; -- my $hash = ($_ eq "add") ? \%add : \%del; -- -- while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/s) { -- my ($key, $val) = ($1, $2); -- -- vpush($hash, lc $key, $val); -- shift @ARGV; -- $vars++; -- } -- unless ($vars) { -- whine "No variables to set."; -- $bad = 1; last; -- } -- $cl = $vars; -- } -- elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { -- push @objects, $spc2; -- } -- elsif (my $spec = is_object_spec($_, $type)) { -- push @objects, $spec; -- } -- else { -- my $datum = /^-/ ? "option" : "argument"; -- whine "Unrecognised $datum '$_'."; -- $bad = 1; last; -- } -- } -- -- if ($action =~ /^ed(?:it)?$/) { -- unless (@objects) { -- whine "No objects specified."; -- $bad = 1; -- } -- } -- else { -- if (@objects) { -- whine "You shouldn't specify objects as arguments to $action."; -- $bad = 1; -- } -- unless ($type) { -- whine "What type of object do you want to create?"; -- $bad = 1; -- } -- @objects = ("$type/new") if defined($type); -- } -- #return help($action, $type) if $bad; -- return suggest_help($action, $type, $bad) if $bad; -- -- # We need a form to make changes to. We usually ask the server for -- # one, but we can avoid that if we are fed one on STDIN, or if the -- # user doesn't want to edit the form by hand, and the command line -- # specifies only simple variable assignments. We *should* get a -- # form if we're creating a new ticket, so that the default values -- # get filled in properly. -- -- my @new_objects = grep /\/new$/, @objects; -- -- if ($input) { -- local $/ = undef; -- $text = ; -- } -- elsif ($edit || %add || %del || !$cl || @new_objects) { -- my $r = submit("$REST/show", { id => \@objects, format => 'l' }); -- $text = $r->content; -- } -- -- # If any changes were specified on the command line, apply them. -- if ($cl) { -- if ($text) { -- # We're updating forms from the server. -- my $forms = Form::parse($text); -- -- foreach my $form (@$forms) { -- my ($c, $o, $k, $e) = @$form; -- my ($key, $val); -- -- next if ($e || !@$o); -- -- local %add = %add; -- local %del = %del; -- local %set = %set; -- -- # Make changes to existing fields. -- foreach $key (@$o) { -- if (exists $add{lc $key}) { -- $val = delete $add{lc $key}; -- vpush($k, $key, $val); -- $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/; -- } -- if (exists $del{lc $key}) { -- $val = delete $del{lc $key}; -- my %val = map {$_=>1} @{ vsplit($val) }; -- $k->{$key} = vsplit($k->{$key}); -- @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}}; -- } -- if (exists $set{lc $key}) { -- $k->{$key} = delete $set{lc $key}; -- } -- } -- -- # Then update the others. -- foreach $key (keys %set) { vpush($k, $key, $set{$key}) } -- foreach $key (keys %add) { -- vpush($k, $key, $add{$key}); -- $k->{$key} = vsplit($k->{$key}); -- } -- push @$o, (keys %add, keys %set); -- } -- -- $text = Form::compose($forms); -- } -- else { -- # We're rolling our own set of forms. -- my @forms; -- foreach (@objects) { -- my ($type, $ids, $args) = -- m{^($name)/($idlist|$labels)(?:(/.*))?$}o; -- -- $args ||= ""; -- foreach my $obj (expand_list($ids)) { -- my %set = (%set, id => "$type/$obj$args"); -- push @forms, ["", [keys %set], \%set]; -- } -- } -- $text = Form::compose(\@forms); -- } -- } -- -- if ($output) { -- print $text; -- return 0; -- } -- -- my @files; -- @files = @{ vsplit($set{'attachment'}) } if exists $set{'attachment'}; -- -- my $synerr = 0; -- --EDIT: -- # We'll let the user edit the form before sending it to the server, -- # unless we have enough information to submit it non-interactively. -- if ( $type && $type eq 'ticket' && $text !~ /^Content-Type:/m ) { -- $text .= "Content-Type: $content_type\n" -- if $content_type and $content_type ne "text/plain"; -- } -- -- if ($edit || (!$input && !$cl)) { -- my ($newtext) = vi_form_while( -- $text, -- sub { -- my ($text, $form) = @_; -- return 1 unless exists $form->[2]{'Attachment'}; -- -- foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) { -- return (0, "File '$f' doesn't exist") unless -f $f; -- } -- @files = @{ vsplit($form->[2]{'Attachment'}) }; -- return 1; -- }, -- ); -- return $newtext unless $newtext; -- # We won't resubmit a bad form unless it was changed. -- $text = ($synerr && $newtext eq $text) ? undef : $newtext; -- } -- -- delete @data{ grep /^attachment_\d+$/, keys %data }; -- my $i = 1; -- foreach my $file (@files) { -- $data{"attachment_$i"} = bless([ $file ], "Attachment"); -- $i++; -- } -- -- if ($text) { -- my $r = submit("$REST/edit", {content => $text, %data}); -- if ($r->code == 409) { -- # If we submitted a bad form, we'll give the user a chance -- # to correct it and resubmit. -- if ($edit || (!$input && !$cl)) { -- my $content = $r->content . "\n"; -- $content =~ s/^(?!#)/# /mg; -- $text = $content . $text; -- $synerr = 1; -- goto EDIT; -- } -- else { -- print $r->content; -- return 0; -- } -- } -- print $r->content; -- } -- return 0; --} -- --# handler for special edit commands. A valid edit command is constructed and --# further work is delegated to the edit handler -- --sub setcommand { -- my ($action) = @_; -- my ($id, $bad, $what); -- if ( @ARGV ) { -- $_ = shift @ARGV; -- $id = $1 if (m|^(?:ticket/)?($idlist)$|); -- } -- if ( ! $id ) { -- $bad = 1; -- whine "No ticket number specified."; -- } -- if ( @ARGV ) { -- if ($action eq 'subject') { -- my $subject = '"'.join (" ", @ARGV).'"'; -- @ARGV = (); -- $what = "subject=$subject"; -- } elsif ($action eq 'give') { -- my $owner = shift @ARGV; -- $what = "owner=$owner"; -- } -- } else { -- if ( $action eq 'delete' or $action eq 'del' ) { -- $what = "status=deleted"; -- } elsif ($action eq 'resolve' or $action eq 'res' ) { -- $what = "status=resolved"; -- } elsif ($action eq 'take' ) { -- $what = "owner=$config{user}"; -- } elsif ($action eq 'untake') { -- $what = "owner=Nobody"; -- } -- } -- if (@ARGV) { -- $bad = 1; -- whine "Extraneous arguments for action $action: @ARGV."; -- } -- if ( ! $what ) { -- $bad = 1; -- whine "unrecognized action $action."; -- } -- return help("edit", undef, $bad) if $bad; -- @ARGV = ( $id, "set", $what ); -- print "Executing: rt edit @ARGV\n"; -- return edit("edit"); --} -- --# We roll "comment" and "correspond" into the same handler. -- --sub comment { -- my ($action) = @_; -- my (%data, $id, @files, @bcc, @cc, $msg, $content_type, $wtime, $edit); -- my $bad = 0; -- my $status = ''; -- -- while (@ARGV) { -- $_ = shift @ARGV; -- -- if (/^-e$/) { -- $edit = 1; -- } -- elsif (/^-(?:[abcmws]|ct)$/) { -- unless (@ARGV) { -- whine "No argument specified with $_."; -- $bad = 1; last; -- } -- -- if (/-a/) { -- unless (-f $ARGV[0] && -r $ARGV[0]) { -- whine "Cannot read attachment: '$ARGV[0]'."; -- return 0; -- } -- push @files, shift @ARGV; -- } -- elsif (/-ct/) { -- $content_type = shift @ARGV; -- } -- elsif (/-s/) { -- $status = shift @ARGV; -- } -- elsif (/-([bc])/) { -- my $a = $_ eq "-b" ? \@bcc : \@cc; -- @$a = split /\s*,\s*/, shift @ARGV; -- } -- elsif (/-m/) { -- $msg = shift @ARGV; -- if ( $msg =~ /^-$/ ) { -- undef $msg; -- while () { $msg .= $_ } -- } -- } -- elsif (/-w/) { $wtime = shift @ARGV } -- } -- elsif (!$id && m|^(?:ticket/)?($idlist)$|) { -- $id = $1; -- } -- else { -- my $datum = /^-/ ? "option" : "argument"; -- whine "Unrecognised $datum '$_'."; -- $bad = 1; last; -- } -- } -- -- unless ($id) { -- whine "No object specified."; -- $bad = 1; -- } -- #return help($action, "ticket") if $bad; -- return suggest_help($action, "ticket") if $bad; -- -- my $form = [ -- "", -- [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Content-Type", "Text" ], -- { -- Ticket => $id, -- Action => $action, -- Cc => [ @cc ], -- Bcc => [ @bcc ], -- Attachment => [ @files ], -- TimeWorked => $wtime || '', -- 'Content-Type' => $content_type || 'text/plain', -- Text => $msg || '', -- Status => $status -- } -- ]; -- if ($status ne '') { -- push(@{$form->[1]}, "Status"); -- } -- -- my $text = Form::compose([ $form ]); -- -- if ($edit || !$msg) { -- my ($tmp) = vi_form_while( -- $text, -- sub { -- my ($text, $form) = @_; -- foreach my $f ( @{ vsplit($form->[2]{'Attachment'}) } ) { -- return (0, "File '$f' doesn't exist") unless -f $f; -- } -- @files = @{ vsplit($form->[2]{'Attachment'}) }; -- return 1; -- }, -- ); -- return $tmp unless $tmp; -- $text = $tmp; -- } -- -- my $i = 1; -- foreach my $file (@files) { -- $data{"attachment_$i"} = bless([ $file ], "Attachment"); -- $i++; -- } -- $data{content} = $text; -- -- my $r = submit("$REST/ticket/$id/comment", \%data); -- print $r->content; -- return 0; --} -- --# Merge one ticket into another. -- --sub merge { -- my @id; -- my $bad = 0; -- -- while (@ARGV) { -- $_ = shift @ARGV; -- s/^#// if /^#\d+/; # get rid of leading hash -- -- if (/^\d+$/) { -- push @id, $_; -- } -- else { -- whine "Unrecognised argument: '$_'."; -- $bad = 1; last; -- } -- } -- -- unless (@id == 2) { -- my $evil = @id > 2 ? "many" : "few"; -- whine "Too $evil arguments specified."; -- $bad = 1; -- } -- #return help("merge", "ticket") if $bad; -- return suggest_help("merge", "ticket", $bad) if $bad; -- -- my $r = submit("$REST/ticket/$id[0]/merge/$id[1]"); -- print $r->content; -- return 0; --} -- --# Link one ticket to another. -- --sub link { -- my ($bad, $del, %data) = (0, 0, ()); -- my $type; -- -- my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo -- ReferredToBy HasMember MemberOf); -- -- while (@ARGV && $ARGV[0] =~ /^-/) { -- $_ = shift @ARGV; -- -- if (/^-d$/) { -- $del = 1; -- } -- elsif (/^-t$/) { -- $bad = 1, last unless defined($type = get_type_argument()); -- } -- else { -- whine "Unrecognised option: '$_'."; -- $bad = 1; last; -- } -- } -- -- $type = "ticket" unless $type; # default type to tickets -- -- if (@ARGV == 3) { -- my ($from, $rel, $to) = @ARGV; -- if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) { -- whine "Invalid link '$rel' for type $type specified."; -- $bad = 1; -- } -- %data = (id => $from, rel => $rel, to => $to, del => $del); -- } -- else { -- my $bad = @ARGV < 3 ? "few" : "many"; -- whine "Too $bad arguments specified."; -- $bad = 1; -- } -- return suggest_help("link", $type, $bad) if $bad; -- -- my $r = submit("$REST/$type/link", \%data); -- print $r->content; -- return 0; --} -- --# Take/steal a ticket --sub take { -- my ($cmd) = @_; -- my ($bad, %data) = (0, ()); -- -- my $id; -- -- # get the ticket id -- if (@ARGV == 1) { -- ($id) = @ARGV; -- unless ($id =~ /^\d+$/) { -- whine "Invalid ticket ID $id specified."; -- $bad = 1; -- } -- my $form = [ -- "", -- [ "Ticket", "Action" ], -- { -- Ticket => $id, -- Action => $cmd, -- Status => '', -- } -- ]; -- -- my $text = Form::compose([ $form ]); -- $data{content} = $text; -- } -- else { -- $bad = @ARGV < 1 ? "few" : "many"; -- whine "Too $bad arguments specified."; -- $bad = 1; -- } -- return suggest_help("take", "ticket", $bad) if $bad; -- -- my $r = submit("$REST/ticket/$id/take", \%data); -- print $r->content; -- return 0; --} -- --# Grant/revoke a user's rights. -- --sub grant { -- my ($cmd) = @_; -- -- whine "$cmd is unimplemented."; -- return 1; --} -- --# Client <-> Server communication. --# -------------------------------- --# --# This function composes and sends an HTTP request to the RT server, and --# interprets the response. It takes a request URI, and optional request --# data (a string, or a reference to a set of key-value pairs). -- --sub submit { -- my ($uri, $content) = @_; -- my ($req, $data); -- my $ua = LWP::UserAgent->new(agent => "RT/3.0b", env_proxy => 1); -- my $h = HTTP::Headers->new; -- -- # Did the caller specify any data to send with the request? -- $data = []; -- if (defined $content) { -- unless (ref $content) { -- # If it's just a string, make sure LWP handles it properly. -- # (By pretending that it's a file!) -- $content = [ content => [undef, "", Content => $content] ]; -- } -- elsif (ref $content eq 'HASH') { -- my @data; -- foreach my $k (keys %$content) { -- if (ref $content->{$k} eq 'ARRAY') { -- foreach my $v (@{ $content->{$k} }) { -- push @data, $k, $v; -- } -- } -- else { push @data, $k, $content->{$k} } -- } -- $content = \@data; -- } -- $data = $content; -- } -- -- # Should we send authentication information to start a new session? -- my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted'; -- (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/; -- if ($config{externalauth}) { -- $h->authorization_basic($config{user}, $config{passwd} || read_passwd() ); -- print " Password will be sent to $server $how\n", -- " Press CTRL-C now if you do not want to continue\n" -- if ! $config{passwd}; -- } elsif ( $no_strong_auth ) { -- if (!defined $session->cookie) { -- print " Strong encryption not available, $no_strong_auth\n", -- " Password will be sent to $server $how\n", -- " Press CTRL-C now if you do not want to continue\n" -- if ! $config{passwd}; -- push @$data, ( user => $config{user} ); -- push @$data, ( pass => $config{passwd} || read_passwd() ); -- } -- } -- -- # Now, we construct the request. -- if (@$data) { -- $req = POST($uri, $data, Content_Type => 'form-data'); -- } -- else { -- $req = GET($uri); -- } -- $session->add_cookie_header($req); -- if ($config{externalauth}) { -- $req->header(%$h); -- } -- -- # Then we send the request and parse the response. -- DEBUG(3, $req->as_string); -- my $res = $ua->request($req); -- DEBUG(3, $res->as_string); -- -- if ($res->is_success) { -- # The content of the response we get from the RT server consists -- # of an HTTP-like status line followed by optional header lines, -- # a blank line, and arbitrary text. -- -- my ($head, $text) = split /\n\n/, $res->content, 2; -- my ($status, @headers) = split /\n/, $head; -- $text =~ s/\n*$/\n/ if ($text); -- -- # "RT/3.0.1 401 Credentials required" -- if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) { -- warn "rt: Malformed RT response from $config{server}.\n"; -- warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3; -- exit -1; -- } -- -- # Our caller can pretend that the server returned a custom HTTP -- # response code and message. (Doing that directly is apparently -- # not sufficiently portable and uncomplicated.) -- $res->code($1); -- $res->message($2); -- $res->content($text); -- $session->update($res) if ($res->is_success || $res->code != 401); -- -- if (!$res->is_success) { -- # We can deal with authentication failures ourselves. Either -- # we sent invalid credentials, or our session has expired. -- if ($res->code == 401) { -- my %d = @$data; -- if (exists $d{user}) { -- warn "rt: Incorrect username or password.\n"; -- exit -1; -- } -- elsif ($req->header("Cookie")) { -- # We'll retry the request with credentials, unless -- # we only wanted to logout in the first place. -- $session->delete; -- return submit(@_) unless $uri eq "$REST/logout"; -- } -- } -- # Conflicts should be dealt with by the handler and user. -- # For anything else, we just die. -- elsif ($res->code != 409) { -- warn "rt: ", $res->content; -- #exit; -- } -- } -- } -- else { -- warn "rt: Server error: ", $res->message, " (", $res->code, ")\n"; -- exit -1; -- } -- -- return $res; --} -- --# Session management. --# ------------------- --# --# Maintains a list of active sessions in the ~/.rt_sessions file. --{ -- package Session; -- my ($s, $u); -- -- # Initialises the session cache. -- sub new { -- my ($class, $file) = @_; -- my $self = { -- file => $file || "$HOME/.rt_sessions", -- sids => { } -- }; -- -- # The current session is identified by the currently configured -- # server and user. -- ($s, $u) = @config{"server", "user"}; -- -- bless $self, $class; -- $self->load(); -- -- return $self; -- } -- -- # Returns the current session cookie. -- sub cookie { -- my ($self) = @_; -- my $cookie = $self->{sids}{$s}{$u}; -- return defined $cookie ? "RT_SID_$cookie" : undef; -- } -- -- # Deletes the current session cookie. -- sub delete { -- my ($self) = @_; -- delete $self->{sids}{$s}{$u}; -- } -- -- # Adds a Cookie header to an outgoing HTTP request. -- sub add_cookie_header { -- my ($self, $request) = @_; -- my $cookie = $self->cookie(); -- -- $request->header(Cookie => $cookie) if defined $cookie; -- } -- -- # Extracts the Set-Cookie header from an HTTP response, and updates -- # session information accordingly. -- sub update { -- my ($self, $response) = @_; -- my $cookie = $response->header("Set-Cookie"); -- -- if (defined $cookie && $cookie =~ /^RT_SID_(.[^;,\s]+=[0-9A-Fa-f]+);/) { -- $self->{sids}{$s}{$u} = $1; -- } -- } -- -- # Loads the session cache from the specified file. -- sub load { -- my ($self, $file) = @_; -- $file ||= $self->{file}; -- -- open( my $handle, '<', $file ) or return 0; -- -- $self->{file} = $file; -- my $sids = $self->{sids} = {}; -- while (<$handle>) { -- chomp; -- next if /^$/ || /^#/; -- next unless m#^https?://[^ ]+ \w+ [^;,\s]+=[0-9A-Fa-f]+$#; -- my ($server, $user, $cookie) = split / /, $_; -- $sids->{$server}{$user} = $cookie; -- } -- return 1; -- } -- -- # Writes the current session cache to the specified file. -- sub save { -- my ($self, $file) = shift; -- $file ||= $self->{file}; -- -- open( my $handle, '>', "$file" ) or return 0; -- -- my $sids = $self->{sids}; -- foreach my $server (keys %$sids) { -- foreach my $user (keys %{ $sids->{$server} }) { -- my $sid = $sids->{$server}{$user}; -- if (defined $sid) { -- print $handle "$server $user $sid\n"; -- } -- } -- } -- close($handle); -- chmod 0600, $file; -- return 1; -- } -- -- sub DESTROY { -- my $self = shift; -- $self->save; -- } --} -- --# Form handling. --# -------------- --# --# Forms are RFC822-style sets of (field, value) specifications with some --# initial comments and interspersed blank lines allowed for convenience. --# Sets of forms are separated by --\n (in a cheap parody of MIME). --# --# Each form is parsed into an array with four elements: commented text --# at the start of the form, an array with the order of keys, a hash with --# key/value pairs, and optional error text if the form syntax was wrong. -- --# Returns a reference to an array of parsed forms. --sub Form::parse { -- my $state = 0; -- my @forms = (); -- my @lines = split /\n/, $_[0] if $_[0]; -- my ($c, $o, $k, $e) = ("", [], {}, ""); -- -- LINE: -- while (@lines) { -- my $line = shift @lines; -- -- next LINE if $line eq ''; -- -- if ($line eq '--') { -- # We reached the end of one form. We'll ignore it if it was -- # empty, and store it otherwise, errors and all. -- if ($e || $c || @$o) { -- push @forms, [ $c, $o, $k, $e ]; -- $c = ""; $o = []; $k = {}; $e = ""; -- } -- $state = 0; -- } -- elsif ($state != -1) { -- if ($state == 0 && $line =~ /^#/) { -- # Read an optional block of comments (only) at the start -- # of the form. -- $state = 1; -- $c = $line; -- while (@lines && $lines[0] =~ /^#/) { -- $c .= "\n".shift @lines; -- } -- $c .= "\n"; -- } -- elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) { -- # Read a field: value specification. -- my $f = $1; -- my @v = ($2 || ()); -- -- # Read continuation lines, if any. -- while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) { -- push @v, shift @lines; -- } -- pop @v while (@v && $v[-1] eq ''); -- -- # Strip longest common leading indent from text. -- my $ws = ""; -- foreach my $ls (map {/^(\s+)/} @v[1..$#v]) { -- $ws = $ls if (!$ws || length($ls) < length($ws)); -- } -- s/^$ws// foreach @v; -- -- push(@$o, $f) unless exists $k->{$f}; -- vpush($k, $f, join("\n", @v)); -- -- $state = 1; -- } -- elsif ($line !~ /^#/) { -- # We've found a syntax error, so we'll reconstruct the -- # form parsed thus far, and add an error marker. (>>) -- $state = -1; -- $e = Form::compose([[ "", $o, $k, "" ]]); -- $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n"; -- } -- } -- else { -- # We saw a syntax error earlier, so we'll accumulate the -- # contents of this form until the end. -- $e .= "$line\n"; -- } -- } -- push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o); -- -- foreach my $l (keys %$k) { -- $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY'); -- } -- -- return \@forms; --} -- --# Returns text representing a set of forms. --sub Form::compose { -- my ($forms) = @_; -- my @text; -- -- foreach my $form (@$forms) { -- my ($c, $o, $k, $e) = @$form; -- my $text = ""; -- -- if ($c) { -- $c =~ s/\n*$/\n/; -- $text = "$c\n"; -- } -- if ($e) { -- $text .= $e; -- } -- elsif ($o) { -- my @lines; -- -- foreach my $key (@$o) { -- my ($line, $sp); -- my $v = $k->{$key}; -- my @values = ref $v eq 'ARRAY' ? @$v : $v; -- -- $sp = " "x(length("$key: ")); -- $sp = " "x4 if length($sp) > 16; -- -- foreach $v (@values) { -- if ($v =~ /\n/) { -- $v =~ s/^/$sp/gm; -- $v =~ s/^$sp//; -- -- if ($line) { -- push @lines, "$line\n\n"; -- $line = ""; -- } -- elsif (@lines && $lines[-1] !~ /\n\n$/) { -- $lines[-1] .= "\n"; -- } -- push @lines, "$key: $v\n\n"; -- } -- elsif ($line && -- length($line)+length($v)-rindex($line, "\n") >= 70) -- { -- $line .= ",\n$sp$v"; -- } -- else { -- $line = $line ? "$line,$v" : "$key: $v"; -- } -- } -- -- $line = "$key:" unless @values; -- if ($line) { -- if ($line =~ /\n/) { -- if (@lines && $lines[-1] !~ /\n\n$/) { -- $lines[-1] .= "\n"; -- } -- $line .= "\n"; -- } -- push @lines, "$line\n"; -- } -- } -- -- $text .= join "", @lines; -- } -- else { -- chomp $text; -- } -- push @text, $text; -- } -- -- return join "\n--\n\n", @text; --} -- --# Configuration. --# -------------- -- --# Returns configuration information from the environment. --sub config_from_env { -- my %env; -- -- foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) { -- -- if (exists $ENV{"RT$k"}) { -- $env{lc $k} = $ENV{"RT$k"}; -- } -- } -- -- return %env; --} -- --# Finds a suitable configuration file and returns information from it. --sub config_from_file { -- my ($rc) = @_; -- -- if ($rc =~ m#^/#) { -- # We'll use an absolute path if we were given one. -- return parse_config_file($rc); -- } -- else { -- # Otherwise we'll use the first file we can find in the current -- # directory, or in one of its (increasingly distant) ancestors. -- -- my @dirs = split /\//, cwd; -- while (@dirs) { -- my $file = join('/', @dirs, $rc); -- if (-r $file) { -- return parse_config_file($file); -- } -- -- # Remove the last directory component each time. -- pop @dirs; -- } -- -- # Still nothing? We'll fall back to some likely defaults. -- for ("$HOME/$rc", "local/etc/rt.conf", "/etc/rt.conf") { -- return parse_config_file($_) if (-r $_); -- } -- } -- -- return (); --} -- --# Makes a hash of the specified configuration file. --sub parse_config_file { -- my %cfg; -- my ($file) = @_; -- local $_; # $_ may be aliased to a constant, from line 1163 -- -- open( my $handle, '<', $file ) or return; -- -- while (<$handle>) { -- chomp; -- next if (/^#/ || /^\s*$/); -- -- if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) { -- $cfg{$1} = $2; -- } -- else { -- die "rt: $file:$.: unknown configuration directive.\n"; -- } -- } -- -- return %cfg; --} -- --# Helper functions. --# ----------------- -- --sub whine { -- my $sub = (caller(1))[3]; -- $sub =~ s/^main:://; -- warn "rt: $sub: @_\n"; -- return 0; --} -- --sub read_passwd { -- eval 'require Term::ReadKey'; -- if ($@) { -- die "No password specified (and Term::ReadKey not installed).\n"; -- } -- -- print "Password: "; -- Term::ReadKey::ReadMode('noecho'); -- chomp(my $passwd = Term::ReadKey::ReadLine(0)); -- Term::ReadKey::ReadMode('restore'); -- print "\n"; -- -- return $passwd; --} -- --sub vi_form_while { -- my $text = shift; -- my $cb = shift; -- -- my $error = 0; -- my ($c, $o, $k, $e); -- do { -- my $ntext = vi($text); -- return undef if ($error && $ntext eq $text); -- -- $text = $ntext; -- -- my $form = Form::parse($text); -- $error = 0; -- ($c, $o, $k, $e) = @{ $form->[0] }; -- if ( $e ) { -- $error = 1; -- $c = "# Syntax error."; -- goto NEXT; -- } -- elsif (!@$o) { -- return 0; -- } -- -- my ($status, $msg) = $cb->( $text, [$c, $o, $k, $e] ); -- unless ( $status ) { -- $error = 1; -- $c = "# $msg"; -- } -- -- NEXT: -- $text = Form::compose([[$c, $o, $k, $e]]); -- } while ($error); -- -- return $text; --} -- --sub vi { -- my ($text) = @_; -- my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi"; -- -- local $/ = undef; -- -- my $handle = File::Temp->new; -- print $handle $text; -- close($handle); -- -- system($editor, $handle->filename) && die "Couldn't run $editor.\n"; -- -- open( $handle, '<', $handle->filename ) or die "$handle: $!\n"; -- $text = <$handle>; -- close($handle); -- -- return $text; --} -- --# Add a value to a (possibly multi-valued) hash key. --sub vpush { -- my ($hash, $key, $val) = @_; -- my @val = ref $val eq 'ARRAY' ? @$val : $val; -- -- if (exists $hash->{$key}) { -- unless (ref $hash->{$key} eq 'ARRAY') { -- my @v = $hash->{$key} ne '' ? $hash->{$key} : (); -- $hash->{$key} = \@v; -- } -- push @{ $hash->{$key} }, @val; -- } -- else { -- $hash->{$key} = $val; -- } --} -- --# "Normalise" a hash key that's known to be multi-valued. --sub vsplit { -- my ($val) = @_; -- my ($word, @words); -- my @values = ref $val eq 'ARRAY' ? @$val : $val; -- -- foreach my $line (map {split /\n/} @values) { -- # XXX: This should become a real parser, à la Text::ParseWords. -- $line =~ s/^\s+//; -- $line =~ s/\s+$//; -- my ( $a, $b ) = split /\s*,\s*/, $line, 2; -- -- while ($a) { -- no warnings 'uninitialized'; -- if ( $a =~ /^'/ ) { -- my $s = $a; -- while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/ -- && $a =~ /(\\)+'$/ )) { -- ( $a, $b ) = split /\s*,\s*/, $b, 2; -- $s .= ',' . $a; -- } -- push @words, $s; -- } -- elsif ( $a =~ /^q\{/ ) { -- my $s = $a; -- while ( $a !~ /\}$/ ) { -- ( $a, $b ) = -- split /\s*,\s*/, $b, 2; -- $s .= ',' . $a; -- } -- $s =~ s/^q\{/'/; -- $s =~ s/\}/'/; -- push @words, $s; -- } -- else { -- push @words, $a; -- } -- ( $a, $b ) = split /\s*,\s*/, $b, 2; -- } -- -- -- } -- -- return \@words; --} -- --# WARN: this code is duplicated in lib/RT/Interface/REST.pm --# change both functions at once --sub expand_list { -- my ($list) = @_; -- -- my @elts; -- foreach (split /\s*,\s*/, $list) { -- push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_; -- } -- -- return map $_->[0], # schwartzian transform -- sort { -- defined $a->[1] && defined $b->[1]? -- # both numbers -- $a->[1] <=> $b->[1] -- :!defined $a->[1] && !defined $b->[1]? -- # both letters -- $a->[2] cmp $b->[2] -- # mix, number must be first -- :defined $a->[1]? -1: 1 -- } -- map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ], -- @elts; --} -- --sub get_type_argument { -- my $type; -- -- if (@ARGV) { -- $type = shift @ARGV; -- unless ($type =~ /^[A-Za-z0-9_.-]+$/) { -- # We want whine to mention our caller, not us. -- @_ = ("Invalid type '$type' specified."); -- goto &whine; -- } -- } -- else { -- @_ = ("No type argument specified with -t."); -- goto &whine; -- } -- -- $type =~ s/s$//; # "Plural". Ugh. -- return $type; --} -- --sub get_var_argument { -- my ($data) = @_; -- -- if (@ARGV) { -- my $kv = shift @ARGV; -- if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) { -- push @{ $data->{$k} }, $v; -- } -- else { -- @_ = ("Invalid variable specification: '$kv'."); -- goto &whine; -- } -- } -- else { -- @_ = ("No variable argument specified with -S."); -- goto &whine; -- } --} -- --sub is_object_spec { -- my ($spec, $type) = @_; -- -- $spec =~ s|^(?:$type/)?|$type/| if defined $type; -- return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o); -- return 0; --} -- --sub suggest_help { -- my ($action, $type, $rv) = @_; -- -- print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action; -- print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type; -- return $rv; --} -- --sub str2time { -- # simplified procedure for parsing date, avoid loading Date::Parse -- my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, -- Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11); -- $_ = shift; -- my ($mon, $day, $hr, $min, $sec, $yr, $monstr); -- if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) { -- ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6); -- $mon = $month{$monstr} if exists $month{$monstr}; -- } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) { -- ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6); -- } -- if ( $yr and defined $mon and $day and defined $hr and defined $sec ) { -- return timelocal($sec,$min,$hr,$day,$mon,$yr); -- } else { -- print "Unknown date format in parsedate: $_\n"; -- return undef; -- } --} -- --sub date_diff { -- my ($old, $new) = @_; -- $new = time() if ! $new; -- $old = str2time($old) if $old !~ /^\d+$/; -- $new = str2time($new) if $new !~ /^\d+$/; -- return "???" if ! $old or ! $new; -- -- my %seconds = (min => 60, -- hr => 60*60, -- day => 60*60*24, -- wk => 60*60*24*7, -- mth => 60*60*24*30, -- yr => 60*60*24*365); -- -- my $diff = $new - $old; -- my $what = 'sec'; -- my $howmuch = $diff; -- for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) { -- last if $diff < $seconds{$_}; -- $what = $_; -- $howmuch = int($diff/$seconds{$_}); -- } -- return "$howmuch $what"; --} -- --sub prettyshow { -- my $forms = shift; -- my ($form) = grep { exists $_->[2]->{Queue} } @$forms; -- my $k = $form->[2]; -- # dates are in local time zone -- if ( $k ) { -- print "Date: $k->{Created}\n"; -- print "From: $k->{Requestors}\n"; -- print "Cc: $k->{Cc}\n" if $k->{Cc}; -- print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc}; -- print "X-Queue: $k->{Queue}\n"; -- print "Subject: [rt #$k->{id}] $k->{Subject}\n\n"; -- } -- # dates in these attributes are in GMT and will be converted -- foreach my $form (@$forms) { -- my ($c, $o, $k, $e) = @$form; -- next if ! $k->{id} or exists $k->{Queue}; -- if ( exists $k->{Created} ) { -- my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/); -- $m--; -- my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y)); -- if ( exists $k->{Description} ) { -- print "===> $k->{Description} on $created\n"; -- } -- } -- print "$k->{Content}\n" if exists $k->{Content} and -- $k->{Content} !~ /to have no content$/ and -- ($k->{Type}||'') ne 'EmailRecord'; -- print "$k->{Attachments}\n" if exists $k->{Attachments} and -- $k->{Attachments}; -- } --} -- --sub prettylist { -- my $forms = shift; -- my $heading = "Ticket Owner Queue Age Told Status Requestor Subject\n"; -- $heading .= '-' x 80 . "\n"; -- my (@open, @me); -- foreach my $form (@$forms) { -- my ($c, $o, $k, $e) = @$form; -- next if ! $k->{id}; -- print $heading if $heading; -- $heading = ''; -- my $id = $k->{id}; -- $id =~ s!^ticket/!!; -- my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner}; -- $owner = substr($owner, 0, 5); -- my $queue = substr($k->{Queue}, 0, 5); -- my $subject = substr($k->{Subject}, 0, 30); -- my $age = date_diff($k->{Created}); -- my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told}); -- my $status = substr($k->{Status}, 0, 6); -- my $requestor = substr($k->{Requestors}, 0, 9); -- my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n", -- $id, $owner, $queue, $age, $told, $status, $requestor, $subject; -- if ( $k->{Owner} eq 'Nobody' ) { -- push @open, $line; -- } elsif ($k->{Owner} eq $config{user} ) { -- push @me, $line; -- } else { -- print $line; -- } -- } -- print "No matches found\n" if $heading; -- printf "========== my %2d open tickets ==========\n", scalar @me if @me; -- print @me if @me; -- printf "========== %2d unowned tickets ==========\n", scalar @open if @open; -- print @open if @open; --} -- --__DATA__ -- --Title: intro --Title: introduction --Text: -- -- This is a command-line interface to RT 3.0 or newer. -- -- It allows you to interact with an RT server over HTTP, and offers an -- interface to RT's functionality that is better-suited to automation -- and integration with other tools. -- -- In general, each invocation of this program should specify an action -- to perform on one or more objects, and any other arguments required -- to complete the desired action. -- -- For more information: -- -- - rt help usage (syntax information) -- - rt help objects (how to specify objects) -- - rt help actions (a list of possible actions) -- - rt help types (a list of object types) -- -- - rt help config (configuration details) -- - rt help examples (a few useful examples) -- - rt help topics (a list of help topics) -- ---- -- --Title: usage --Title: syntax --Text: -- -- Syntax: -- -- rt [options] [arguments] -- or -- rt shell -- -- Each invocation of this program must specify an action (e.g. "edit", -- "create"), options to modify behaviour, and other arguments required -- by the specified action. (For example, most actions expect a list of -- numeric object IDs to act upon.) -- -- The details of the syntax and arguments for each action are given by -- "rt help ". Some actions may be referred to by more than one -- name ("create" is the same as "new", for example). -- -- You may also call "rt shell", which will give you an 'rt>' prompt at -- which you can issue commands of the form " [options] -- [arguments]". See "rt help shell" for details. -- -- Objects are identified by a type and an ID (which can be a name or a -- number, depending on the type). For some actions, the object type is -- implied (you can only comment on tickets); for others, the user must -- specify it explicitly. See "rt help objects" for details. -- -- In syntax descriptions, mandatory arguments that must be replaced by -- appropriate value are enclosed in <>, and optional arguments are -- indicated by [] (for example, and [options] above). -- -- For more information: -- -- - rt help objects (how to specify objects) -- - rt help actions (a list of actions) -- - rt help types (a list of object types) -- - rt help shell (how to use the shell) -- ---- -- --Title: conf --Title: config --Title: configuration --Text: -- -- This program has two major sources of configuration information: its -- configuration files, and the environment. -- -- The program looks for configuration directives in a file named .rtrc -- (or $RTCONFIG; see below) in the current directory, and then in more -- distant ancestors, until it reaches /. If no suitable configuration -- files are found, it will also check for ~/.rtrc, local/etc/rt.conf -- and /etc/rt.conf. -- -- Configuration directives: -- -- The following directives may occur, one per line: -- -- - server URL to RT server. -- - user RT username. -- - passwd RT user's password. -- - query Default RT Query for list action -- - orderby Default RT order for list action -- - queue Default RT Queue for list action -- - externalauth <0|1> Use HTTP Basic authentication -- explicitely setting externalauth to 0 inhibits also GSSAPI based -- authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed -- -- Blank and #-commented lines are ignored. -- -- Sample configuration file contents: -- -- server https://rt.somewhere.com/ -- # more than one queue can be given (by adding a query expression) -- queue helpdesk or queue=support -- query Status != resolved and Owner=myaccount -- -- -- Environment variables: -- -- The following environment variables override any corresponding -- values defined in configuration files: -- -- - RTUSER -- - RTPASSWD -- - RTEXTERNALAUTH -- - RTSERVER -- - RTDEBUG Numeric debug level. (Set to 3 for full logs.) -- - RTCONFIG Specifies a name other than ".rtrc" for the -- configuration file. -- - RTQUERY Default RT Query for rt list -- - RTORDERBY Default order for rt list -- ---- -- --Title: objects --Text: -- -- Syntax: -- -- /[/] -- -- Every object in RT has a type (e.g. "ticket", "queue") and a numeric -- ID. Some types of objects can also be identified by name (like users -- and queues). Furthermore, objects may have named attributes (such as -- "ticket/1/history"). -- -- An object specification is like a path in a virtual filesystem, with -- object types as top-level directories, object IDs as subdirectories, -- and named attributes as further subdirectories. -- -- A comma-separated list of names, numeric IDs, or numeric ranges can -- be used to specify more than one object of the same type. Note that -- the list must be a single argument (i.e., no spaces). For example, -- "user/root,1-3,5,7-10,ams" is a list of ten users; the same list -- can also be written as "user/ams,root,1,2,3,5,7,8-10". -- -- If just a number is given as object specification it will be -- interpreted as ticket/ -- -- Examples: -- -- 1 # the same as ticket/1 -- ticket/1 -- ticket/1/attachments -- ticket/1/attachments/3 -- ticket/1/attachments/3/content -- ticket/1-3/links -- ticket/1-3,5-7/history -- -- user/ams -- -- For more information: -- -- - rt help (action-specific details) -- - rt help (type-specific details) -- ---- -- --Title: actions --Title: commands --Text: -- -- You can currently perform the following actions on all objects: -- -- - list (list objects matching some condition) -- - show (display object details) -- - edit (edit object details) -- - create (create a new object) -- -- Each type may define actions specific to itself; these are listed in -- the help item about that type. -- -- For more information: -- -- - rt help (action-specific details) -- - rt help types (a list of possible types) -- -- The following actions on tickets are also possible: -- -- - comment Add comments to a ticket -- - correspond Add comments to a ticket -- - merge Merge one ticket into another -- - link Link one ticket to another -- - take Take a ticket (steal and untake are possible as well) -- -- For several edit set subcommands that are frequently used abbreviations -- have been introduced. These abbreviations are: -- -- - delete or del delete a ticket (edit set status=deleted) -- - resolve or res resolve a ticket (edit set status=resolved) -- - subject change subject of ticket (edit set subject=string) -- - give give a ticket to somebody (edit set owner=user) -- ---- -- --Title: types --Text: -- -- You can currently operate on the following types of objects: -- -- - tickets -- - users -- - groups -- - queues -- -- For more information: -- -- - rt help (type-specific details) -- - rt help objects (how to specify objects) -- - rt help actions (a list of possible actions) -- ---- -- --Title: ticket --Text: -- -- Tickets are identified by a numeric ID. -- -- The following generic operations may be performed upon tickets: -- -- - list -- - show -- - edit -- - create -- -- In addition, the following ticket-specific actions exist: -- -- - link -- - merge -- - comment -- - correspond -- - take -- - steal -- - untake -- - give -- - resolve -- - delete -- - subject -- -- Attributes: -- -- The following attributes can be used with "rt show" or "rt edit" -- to retrieve or edit other information associated with tickets: -- -- links A ticket's relationships with others. -- history All of a ticket's transactions. -- history/type/ Only a particular type of transaction. -- history/id/ Only the transaction of the specified id. -- attachments A list of attachments. -- attachments/ The metadata for an individual attachment. -- attachments//content The content of an individual attachment. -- ---- -- --Title: user --Title: group --Text: -- -- Users and groups are identified by name or numeric ID. -- -- The following generic operations may be performed upon them: -- -- - list -- - show -- - edit -- - create -- ---- -- --Title: queue --Text: -- -- Queues are identified by name or numeric ID. -- -- Currently, they can be subjected to the following actions: -- -- - show -- - edit -- - create -- ---- -- --Title: subject --Text: -- -- Syntax: -- -- rt subject -- -- Change the subject of a ticket whose ticket id is given. -- ---- -- --Title: give --Text: -- -- Syntax: -- -- rt give -- -- Give a ticket whose ticket id is given to another user. -- ---- -- --Title: steal --Text: -- -- rt steal -- -- Steal a ticket whose ticket id is given, i.e. set the owner to myself. -- ---- -- --Title: take --Text: -- -- Syntax: -- -- rt take -- -- Take a ticket whose ticket id is given, i.e. set the owner to myself. -- ---- -- --Title: untake --Text: -- -- Syntax: -- -- rt untake -- -- Untake a ticket whose ticket id is given, i.e. set the owner to Nobody. -- ---- -- --Title: resolve --Title: res --Text: -- -- Syntax: -- -- rt resolve -- -- Resolves a ticket whose ticket id is given. -- ---- -- --Title: delete --Title: del --Text: -- -- Syntax: -- -- rt delete -- -- Deletes a ticket whose ticket id is given. -- ---- -- --Title: logout --Text: -- -- Syntax: -- -- rt logout -- -- Terminates the currently established login session. You will need to -- provide authentication credentials before you can continue using the -- server. (See "rt help config" for details about authentication.) -- ---- -- --Title: ls --Title: list --Title: search --Text: -- -- Syntax: -- -- rt [options] "query string" -- -- Displays a list of objects matching the specified conditions. -- ("ls", "list", and "search" are synonyms.) -- -- The query string must be supplied as one argument. -- -- if on tickets, query is in the SQL-like syntax used internally by -- RT. (For more information, see "rt help query".), otherwise, query -- is plain string with format "FIELD OP VALUE", e.g. "Name = General". -- -- if query string is absent, we limit to privileged ones on users and -- user defined ones on groups automatically. -- -- Options: -- -- The following options control how much information is displayed -- about each matching object: -- -- -i Numeric IDs only. (Useful for |rt edit -; see examples.) -- -s Short description. -- -l Longer description. -- -f Orders the returned list by the specified field. -- -r reversed order (useful if a default was given) -- -q queue[s] restricts the query to the queue[s] given -- multiple queues are separated by comma -- -S var=val Submits the specified variable with the request. -- -t type Specifies the type of object to look for. (The -- default is "ticket".) -- -- Examples: -- -- rt ls "Priority > 5 and Status=new" -- rt ls -o +Subject "Priority > 5 and Status=new" -- rt ls -o -Created "Priority > 5 and Status=new" -- rt ls -i "Priority > 5"|rt edit - set status=resolved -- rt ls -t ticket "Subject like '[PATCH]%'" -- rt ls -q systems -- rt ls -f owner,subject -- rt ls -t queue 'Name = General' -- rt ls -t user 'EmailAddress like foo@bar.com' -- rt ls -t group 'Name like foo' -- ---- -- --Title: show --Text: -- -- Syntax: -- -- rt show [options] -- -- Displays details of the specified objects. -- -- For some types, object information is further classified into named -- attributes (for example, "1-3/links" is a valid ticket specification -- that refers to the links for tickets 1-3). Consult "rt help " -- and "rt help objects" for further details. -- -- If only a number is given it will be interpreted as the objects -- ticket/number and ticket/number/history -- -- This command writes a set of forms representing the requested object -- data to STDOUT. -- -- Options: -- -- The following options control how much information is displayed -- about each matching object: -- -- Without any formatting options prettyprinted output is generated. -- Giving any of the two options below reverts to raw output. -- -s Short description (history and attachments only). -- -l Longer description (history and attachments only). -- -- In addition, -- - Read IDs from STDIN instead of the command-line. -- -t type Specifies object type. -- -f a,b,c Restrict the display to the specified fields. -- -S var=val Submits the specified variable with the request. -- -- Examples: -- -- rt show -t ticket -f id,subject,status 1-3 -- rt show ticket/3/attachments/29 -- rt show ticket/3/attachments/29/content -- rt show ticket/1-3/links -- rt show ticket/3/history -- rt show -l ticket/3/history -- rt show -t user 2 -- rt show 2 -- ---- -- --Title: new --Title: edit --Title: create --Text: -- -- Syntax: -- -- rt edit [options] set field=value [field=value] ... -- add field=value [field=value] ... -- del field=value [field=value] ... -- -- Edits information corresponding to the specified objects. -- -- A purely numeric object id nnn is translated into ticket/nnn -- -- If, instead of "edit", an action of "new" or "create" is specified, -- then a new object is created. In this case, no numeric object IDs -- may be specified, but the syntax and behaviour remain otherwise -- unchanged. -- -- This command typically starts an editor to allow you to edit object -- data in a form for submission. If you specified enough information -- on the command-line, however, it will make the submission directly. -- -- The command line may specify field-values in three different ways. -- "set" sets the named field to the given value, "add" adds a value -- to a multi-valued field, and "del" deletes the corresponding value. -- Each "field=value" specification must be given as a single argument. -- -- For some types, object information is further classified into named -- attributes (for example, "1-3/links" is a valid ticket specification -- that refers to the links for tickets 1-3). These attributes may also -- be edited. Consult "rt help " and "rt help object" for further -- details. -- -- Options: -- -- - Read numeric IDs from STDIN instead of the command-line. -- (Useful with rt ls ... | rt edit -; see examples below.) -- -i Read a completed form from STDIN before submitting. -- -o Dump the completed form to STDOUT instead of submitting. -- -e Allows you to edit the form even if the command-line has -- enough information to make a submission directly. -- -S var=val -- Submits the specified variable with the request. -- -t type Specifies object type. -- -ct content-type Specifies content type of message(tickets only). -- -- Examples: -- -- # Interactive (starts $EDITOR with a form). -- rt edit ticket/3 -- rt create -t ticket -- rt create -t ticket -ct text/html -- -- # Non-interactive. -- rt edit ticket/1-3 add cc=foo@example.com set priority=3 due=tomorrow -- rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved -- rt edit ticket/4 set priority=3 owner=bar@example.com \ -- add cc=foo@example.com bcc=quux@example.net -- rt create -t ticket set subject='new ticket' priority=10 \ -- add cc=foo@example.com -- ---- -- --Title: comment --Title: correspond --Text: -- -- Syntax: -- -- rt [options] -- -- Adds a comment (or correspondence) to the specified ticket (the only -- difference being that comments aren't sent to the requestors.) -- -- This command will typically start an editor and allow you to type a -- comment into a form. If, however, you specified all the necessary -- information on the command line, it submits the comment directly. -- -- (See "rt help forms" for more information about forms.) -- -- Options: -- -- -m Specify comment text. -- -ct Specify content-type of comment text. -- -a Attach a file to the comment. (May be used more -- than once to attach multiple files.) -- -c A comma-separated list of Cc addresses. -- -b A comma-separated list of Bcc addresses. -- -s Set a new status for the ticket (default will -- leave the status unchanged) -- -w