diff options
Diffstat (limited to 'cervisia/cvsservice')
-rw-r--r-- | cervisia/cvsservice/DESIGN | 108 | ||||
-rw-r--r-- | cervisia/cvsservice/Makefile.am | 54 | ||||
-rw-r--r-- | cervisia/cvsservice/TODO | 12 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsaskpass.cpp | 77 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsjob.cpp | 236 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsjob.h | 83 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsloginjob.cpp | 156 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsloginjob.h | 58 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsservice.cpp | 1008 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsservice.desktop | 73 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsservice.h | 375 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsserviceutils.cpp | 45 | ||||
-rw-r--r-- | cervisia/cvsservice/cvsserviceutils.h | 40 | ||||
-rw-r--r-- | cervisia/cvsservice/main.cpp | 46 | ||||
-rw-r--r-- | cervisia/cvsservice/repository.cpp | 266 | ||||
-rw-r--r-- | cervisia/cvsservice/repository.h | 109 | ||||
-rw-r--r-- | cervisia/cvsservice/sshagent.cpp | 237 | ||||
-rw-r--r-- | cervisia/cvsservice/sshagent.h | 64 |
18 files changed, 3047 insertions, 0 deletions
diff --git a/cervisia/cvsservice/DESIGN b/cervisia/cvsservice/DESIGN new file mode 100644 index 00000000..cbd414da --- /dev/null +++ b/cervisia/cvsservice/DESIGN @@ -0,0 +1,108 @@ +OVERVIEW +-------- + +The cvs DCOP service consists of the following three parts: + +1. CvsService - The main interface to the functionality of the cvs command line + client. There is one method for each cvs command, e.g. add, + checkout, commit, etc... The methods assemble the command line + arguments, create a CvsJob and return a DCOPRef object for it + to the caller. There is one instance of this service for each + application instance. + +2. Repository - This DCOPObject manages the configuration data of the current + cvs repository. The data is automatically updated when other + service instances change it. + +3. CvsJob - This class represents a cvs job. You can execute and cancel it, + and you can retrieve the output of the cvs client by either + connecting to the proper DCOP signals or by using the output() + method. There are two types of jobs. First the non-concurrent + job which has to run alone, like cvs update or import. Second + the jobs which can run concurrently like cvs log or annotate. + +USAGE +----- + +How-to use this service in C++ applications: + + // start DCOP service + QString error; + QCString appId; + + KApplication::startServiceByDesktopName("cvsservice", QStringList(), &error, + &appId); + + // create stub for repository + Repository_stub repository(appId, "CvsRepository"); + + // set directory of working copy + repository.setWorkingCopy("/home/user/kde/kdesdk/cervisia"); + + // create stub for service + CvsService_stub cvsService(appId, "CvsService"); + + // call "cvs log" for cervisiapart.h + DCOPRef job = cvsService.log("cervisiapart.h"); + + // connect to signals to get output + connectDCOPSignal(job.app(), job.obj(), "jobExited(bool, int)", [MY SLOT]); + connectDCOPSignal(job.app(), job.obj(), "receivedStdout(QString)", + [MY SLOT]); + + // execute the cvs command + job.execute(); + + +How-to use this service in a shell script: + + #!/bin/sh + + # start DCOP service + APP=`dcopstart cvsservice` + + # set directory of working copy + dcop $APP CvsRepository setWorkingCopy /home/user/kde/kdesdk/cervisia + + # call "cvs log" for cervisiapart.h + JOB=`dcop $APP CvsService log cervisiapart.h` + + # execute the cvs command + dcop $JOB execute + + # print the output on stdout + dcop $JOB output + + # stop DCOP service + dcop $APP CvsService quit + + +How-to use this service in a javascript: + + #!/usr/bin/env kjscmd + + var client = new DCOPClient(this); + if ( client.attach() ) + { + // start DCOP service + var appID = client.dcopStart("cvsservice"); + + // set directory of working copy + client.send(appID, "CvsRepository", "setWorkingCopy(QString)", "/home/user/kde/kdesdk/cervisia"); + + // call "cvs log" for cervisiapart.h + var job = client.call(appID, "CvsService", "log(QString)", "cervisiapart.h"); + + // execute the cvs command + job.call("execute()"); + + // wait for job to finish + while( job.call("isRunning()") ); + + // print the output on stdout + var output = job.call("output()"); + println(output); + + // stop DCOP service + client.send(appID, "CvsService", "quit()"); + } diff --git a/cervisia/cvsservice/Makefile.am b/cervisia/cvsservice/Makefile.am new file mode 100644 index 00000000..7b036036 --- /dev/null +++ b/cervisia/cvsservice/Makefile.am @@ -0,0 +1,54 @@ +# +# CvsService Makefile.am +# +# Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> +# +# + +INCLUDES = $(all_includes) + +# cvs DCOP service +bin_PROGRAMS = +kdeinit_LTLIBRARIES = cvsservice.la cvsaskpass.la + +cvsservice_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +cvsservice_la_LIBADD = $(LIB_KIO) +cvsservice_la_SOURCES = main.cpp cvsservice.cpp cvsjob.cpp \ + cvsservice.skel cvsservice.stub cvsjob.skel cvsjob.stub \ + repository.cpp repository.skel repository.stub sshagent.cpp \ + cvsserviceutils.cpp cvsloginjob.cpp cvsloginjob.skel cvsloginjob.stub + +cvsaskpass_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +cvsaskpass_la_LIBADD = $(LIB_KDEUI) +cvsaskpass_la_SOURCES = cvsaskpass.cpp + +include_HEADERS = cvsservice_stub.h cvsjob_stub.h repository_stub.h +noinst_HEADERS = cvsservice.h cvsjob.h repository.h cvsserviceutils.h \ + sshagent.h + +# cvs DCOP service stub library +lib_LTLIBRARIES = libcvsservice.la + +libcvsservice_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 0:1 +libcvsservice_la_LIBADD = $(LIB_KDECORE) +libcvsservice_la_SOURCES = cvsservice.stub cvsjob.stub repository.stub dummy.cpp + +dummy.cpp: + echo > dummy.cpp + +# let automoc handle all of the meta source files (moc) +METASOURCES = AUTO + +# install .desktop file +service_DATA = cvsservice.desktop +servicedir = $(kde_servicesdir) + +# i18n +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/cvsservice.pot + +# API documentation +# Not activated because KDE_INIT_DOXYGEN is missing in +# kdesdk's configure.in.in +#DOXYGEN_REFERENCES = dcop kdecore kdeui +#include ../../admin/Doxyfile.am diff --git a/cervisia/cvsservice/TODO b/cervisia/cvsservice/TODO new file mode 100644 index 00000000..d46d2f9a --- /dev/null +++ b/cervisia/cvsservice/TODO @@ -0,0 +1,12 @@ +TODO +---- + +* Move handling of recent commit messages from CervisiaPart to + DCOP service + +* Add special job classes derived from CvsJob that take over + the parsing of cvs' output from Cervisia + +* Missing cvs functionality needed for Cervisia: + - cvs watch (add/remove) + - cvs diff (make patch) diff --git a/cervisia/cvsservice/cvsaskpass.cpp b/cervisia/cvsservice/cvsaskpass.cpp new file mode 100644 index 00000000..c5410dee --- /dev/null +++ b/cervisia/cvsservice/cvsaskpass.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include <qregexp.h> +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kpassdlg.h> + +#include <iostream> + + +static KCmdLineOptions options[] = +{ + { "+[prompt]", I18N_NOOP("prompt"), 0 }, + KCmdLineLastOption +}; + + +extern "C" KDE_EXPORT int kdemain(int argc, char** argv) +{ + KAboutData about("cvsaskpass", I18N_NOOP("cvsaskpass"), "0.1", + I18N_NOOP("ssh-askpass for the CVS DCOP Service"), + KAboutData::License_LGPL, + I18N_NOOP("Copyright (c) 2003 Christian Loose")); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions(options); + + // no need to register with the dcop server + KApplication::disableAutoDcopRegistration(); + KApplication app; + + // no need for session management + app.disableSessionManagement(); + + if( !KCmdLineArgs::parsedArgs()->count() ) + return 1; + + // parse repository name from the passed argument + QString prompt = KCmdLineArgs::parsedArgs()->arg(0); + QRegExp rx("(.*@.*)'s password:"); + int pos = rx.search(prompt); + + KPasswordDialog dlg(KPasswordDialog::Password, false, 0); + dlg.setPrompt(i18n("Please type in your password below.")); + + if( pos > -1 ) + dlg.addLine(i18n("Repository:"), rx.cap(1)); + + int res = dlg.exec(); + if( res == KPasswordDialog::Accepted ) + { + std::cout << dlg.password() << std::endl; + return 0; + } + + return 1; +} diff --git a/cervisia/cvsservice/cvsjob.cpp b/cervisia/cvsservice/cvsjob.cpp new file mode 100644 index 00000000..1178de96 --- /dev/null +++ b/cervisia/cvsservice/cvsjob.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsjob.h" + +#include <qfile.h> +#include <kdebug.h> +#include <kprocess.h> + +#include "sshagent.h" + + +struct CvsJob::Private +{ + Private() : isRunning(false) + { + childproc = new KProcess; + childproc->setUseShell(true, "/bin/sh"); + } + ~Private() { delete childproc; } + + KProcess* childproc; + QString server; + QString rsh; + QString directory; + bool isRunning; + QStringList outputLines; +}; + + +CvsJob::CvsJob(unsigned jobNum) + : QObject() + , DCOPObject() + , d(new Private) +{ + QString objId("CvsJob" + QString::number(jobNum)); + setObjId(objId.local8Bit()); +} + + +CvsJob::CvsJob(const QString& objId) + : QObject() + , DCOPObject() + , d(new Private) +{ + setObjId(objId.local8Bit()); +} + + +CvsJob::~CvsJob() +{ + delete d; +} + + +void CvsJob::clearCvsCommand() +{ + d->childproc->clearArguments(); +} + + +void CvsJob::setRSH(const QString& rsh) +{ + d->rsh = rsh; +} + + +void CvsJob::setServer(const QString& server) +{ + d->server = server; +} + + +void CvsJob::setDirectory(const QString& directory) +{ + d->directory = directory; +} + + +bool CvsJob::isRunning() const +{ + return d->isRunning; +} + + +CvsJob& CvsJob::operator<<(const QString& arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const char* arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const QCString& arg) +{ + *d->childproc << arg; + return *this; +} + + +CvsJob& CvsJob::operator<<(const QStringList& args) +{ + *d->childproc << args; + return *this; +} + + +QString CvsJob::cvsCommand() const +{ + QString command; + + const QValueList<QCString>& args(d->childproc->args()); + for (QValueList<QCString>::const_iterator it = args.begin(), itEnd = args.end(); + it != itEnd; ++it) + { + if (!command.isEmpty()) + command += ' '; + + command += QFile::decodeName(*it); + } + + return command; +} + + +QStringList CvsJob::output() const +{ + return d->outputLines; +} + + +bool CvsJob::execute() +{ + // setup job environment to use the ssh-agent (if it is running) + SshAgent ssh; + if( !ssh.pid().isEmpty() ) + { + // kdDebug(8051) << "PID = " << ssh.pid() << endl; + // kdDebug(8051) << "SOCK = " << ssh.authSock() << endl; + + d->childproc->setEnvironment("SSH_AGENT_PID", ssh.pid()); + d->childproc->setEnvironment("SSH_AUTH_SOCK", ssh.authSock()); + } + + d->childproc->setEnvironment("SSH_ASKPASS", "cvsaskpass"); + + if( !d->rsh.isEmpty() ) + d->childproc->setEnvironment("CVS_RSH", d->rsh); + + if( !d->server.isEmpty() ) + d->childproc->setEnvironment("CVS_SERVER", d->server); + + if( !d->directory.isEmpty() ) + d->childproc->setWorkingDirectory(d->directory); + + connect(d->childproc, SIGNAL(processExited(KProcess*)), + SLOT(slotProcessExited())); + connect(d->childproc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(d->childproc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int)) ); + + kdDebug(8051) << "Execute cvs command: " << cvsCommand() << endl; + + d->isRunning = true; + return d->childproc->start(KProcess::NotifyOnExit, KProcess::AllOutput); +} + + +void CvsJob::cancel() +{ + d->childproc->kill(); +} + + +void CvsJob::slotProcessExited() +{ + // disconnect all connections to childproc's signals + d->childproc->disconnect(); + d->childproc->clearArguments(); + + d->isRunning = false; + + emit jobExited(d->childproc->normalExit(), d->childproc->exitStatus()); +} + + +void CvsJob::slotReceivedStdout(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + + // accumulate output + d->outputLines += QStringList::split("\n", output); + + emit receivedStdout(output); +} + + +void CvsJob::slotReceivedStderr(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + + // accumulate output + d->outputLines += QStringList::split("\n", output); + + emit receivedStderr(output); +} + +#include "cvsjob.moc" diff --git a/cervisia/cvsservice/cvsjob.h b/cervisia/cvsservice/cvsjob.h new file mode 100644 index 00000000..14194499 --- /dev/null +++ b/cervisia/cvsservice/cvsjob.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSJOB_H +#define CVSJOB_H + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> +#include <dcopobject.h> + +class KProcess; + + +class KDE_EXPORT CvsJob : public QObject, public DCOPObject +{ + Q_OBJECT + K_DCOP + +public: + explicit CvsJob(unsigned jobNum); + explicit CvsJob(const QString& objId); + virtual ~CvsJob(); + + void clearCvsCommand(); + void setRSH(const QString& rsh); + void setServer(const QString& server); + void setDirectory(const QString& directory); + + CvsJob& operator<<(const QString& arg); + CvsJob& operator<<(const char* arg); + CvsJob& operator<<(const QCString& arg); + CvsJob& operator<<(const QStringList& args); + +k_dcop: + bool execute(); + void cancel(); + + bool isRunning() const; + + /** + * Current cvs command. + * + * @return The current cvs command. Can be null if not set. + */ + QString cvsCommand() const; + + QStringList output() const; + +k_dcop_signals: + void jobExited(bool normalExit, int status); + void receivedStdout(const QString& buffer); + void receivedStderr(const QString& buffer); + +private slots: + void slotProcessExited(); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsloginjob.cpp b/cervisia/cvsservice/cvsloginjob.cpp new file mode 100644 index 00000000..79a9fa08 --- /dev/null +++ b/cervisia/cvsservice/cvsloginjob.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsloginjob.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kpassdlg.h> + +#include <sys/types.h> +#include <signal.h> + +static const char LOGIN_PHRASE[] = "Logging in to"; +static const char FAILURE_PHRASE[] = "authorization failed:"; +static const char PASS_PHRASE[] = "CVS password: "; + + +CvsLoginJob::CvsLoginJob(unsigned jobNum) + : DCOPObject() + , m_Proc(0) +{ + QString objId("CvsLoginJob" + QString::number(jobNum)); + setObjId(objId.local8Bit()); + + m_Proc = new PtyProcess; +} + + +CvsLoginJob::~CvsLoginJob() +{ + delete m_Proc; +} + + +void CvsLoginJob::setServer(const QString& server) +{ + m_Server = server; +} + + +void CvsLoginJob::setCvsClient(const QCString& cvsClient) +{ + m_CvsClient = cvsClient; + + m_Arguments.clear(); + m_Arguments += "-f"; +} + + +void CvsLoginJob::setRepository(const QCString& repository) +{ + m_Arguments += "-d"; + m_Arguments += repository; + m_Arguments += "login"; +} + + +bool CvsLoginJob::execute() +{ + static QCString repository; + + int res = m_Proc->exec(m_CvsClient, m_Arguments); + if( res < 0 ) + { + kdDebug(8051) << "Couldn't start 'cvs login' process!" << endl; + return false; + } + + bool result = false; + while( true ) + { + QCString line = m_Proc->readLine(); + if( line.isNull() ) + { + return result; + } + + // add line to output list + m_output << line; + kdDebug(8051) << "process output = " << line << endl; + + // retrieve repository from 'Logging in to'-line + if( line.contains(LOGIN_PHRASE) ) + { + repository = line.remove(0, line.find(":pserver:")); + continue; + } + + // process asks for the password + // search case insensitive as cvs and cvsnt use different capitalization + if( line.contains(PASS_PHRASE, false) ) + { + kdDebug(8051) << "process waits for the password." << endl; + + // show password dialog + // TODO: We really should display the repository name. Unfortunately + // the dialog doesn't show part of the repository name, because + // it's too long. :-( + QCString password; + int res = KPasswordDialog::getPassword(password, i18n("Please type " + "in your password for the repository below.")); + if( res == KPasswordDialog::Accepted ) + { + // send password to process + m_Proc->WaitSlave(); + m_Proc->writeLine(password); + + // wait for the result + while( !line.contains(FAILURE_PHRASE) ) + { + line = m_Proc->readLine(); + if( line.isNull() ) + return true; + + // add line to output list + m_output << line; + kdDebug(8051) << "process output = " << line << endl; + } + + result = false; + } + else + { + // user pressed cancel so kill the process + kill(m_Proc->pid(), SIGKILL); + m_Proc->waitForChild(); + result = false; + } + } + } + + return false; +} + + +QStringList CvsLoginJob::output() +{ + return m_output; +} diff --git a/cervisia/cvsservice/cvsloginjob.h b/cervisia/cvsservice/cvsloginjob.h new file mode 100644 index 00000000..0754074c --- /dev/null +++ b/cervisia/cvsservice/cvsloginjob.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSLOGINJOB_H +#define CVSLOGINJOB_H + +#include <qstring.h> +#include <qstringlist.h> +#include <dcopobject.h> + +#include <kdesu/process.h> + + +class CvsLoginJob : public DCOPObject +{ + K_DCOP + +public: + explicit CvsLoginJob(unsigned jobNum); + virtual ~CvsLoginJob(); + + void setServer(const QString& server); + + void setCvsClient(const QCString& cvsClient); + void setRepository(const QCString& repository); + +k_dcop: + bool execute(); + QStringList output(); + +private: + PtyProcess* m_Proc; + QString m_Server; + QString m_Rsh; + QCString m_CvsClient; + QCStringList m_Arguments; + QStringList m_output; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsservice.cpp b/cervisia/cvsservice/cvsservice.cpp new file mode 100644 index 00000000..a5f02e20 --- /dev/null +++ b/cervisia/cvsservice/cvsservice.cpp @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsservice.h" + +#include <qintdict.h> +#include <qstring.h> + +#include <dcopref.h> +#include <dcopclient.h> +#include <kapplication.h> +#include <kconfig.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> + +#include "cvsjob.h" +#include "cvsloginjob.h" +#include "cvsserviceutils.h" +#include "repository.h" +#include "sshagent.h" + + +static const char SINGLE_JOB_ID[] = "NonConcurrentJob"; +static const char REDIRECT_STDERR[] = "2>&1"; + +enum WatchEvents { None=0, All=1, Commits=2, Edits=4, Unedits=8 }; + +struct CvsService::Private +{ + Private() : singleCvsJob(0), lastJobId(0), repository(0) {} + ~Private() + { + delete repository; + delete singleCvsJob; + } + + CvsJob* singleCvsJob; // non-concurrent cvs job, like update or commit + DCOPRef singleJobRef; // DCOP reference to non-concurrent cvs job + QIntDict<CvsJob> cvsJobs; // concurrent cvs jobs, like diff or annotate + QIntDict<CvsLoginJob> loginJobs; + unsigned lastJobId; + + QCString appId; // cache the DCOP clients app id + + Repository* repository; + + CvsJob* createCvsJob(); + DCOPRef setupNonConcurrentJob(Repository* repo = 0); + + bool hasWorkingCopy(); + bool hasRunningJob(); +}; + + +CvsService::CvsService() + : DCOPObject("CvsService") + , d(new Private) +{ + d->appId = kapp->dcopClient()->appId(); + + // create non-concurrent cvs job + d->singleCvsJob = new CvsJob(SINGLE_JOB_ID); + d->singleJobRef.setRef(d->appId, d->singleCvsJob->objId()); + + // create repository manager + d->repository = new Repository(); + + d->cvsJobs.setAutoDelete(true); + d->loginJobs.setAutoDelete(true); + + KConfig* config = kapp->config(); + KConfigGroupSaver cs(config, "General"); + if( config->readBoolEntry("UseSshAgent", false) ) + { + // use the existing or start a new ssh-agent + SshAgent ssh; + // TODO CL do we need the return value? + //bool res = ssh.querySshAgent(); + ssh.querySshAgent(); + } +} + + +CvsService::~CvsService() +{ + // kill the ssh-agent (when we started it) + SshAgent ssh; + ssh.killSshAgent(); + + d->cvsJobs.clear(); + d->loginJobs.clear(); + delete d; +} + + +DCOPRef CvsService::add(const QStringList& files, bool isBinary) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs add [-kb] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "add"; + + if( isBinary ) + *d->singleCvsJob << "-kb"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::addWatch(const QStringList& files, int events) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watch add"; + + if( events != All ) + { + if( events & Commits ) + *d->singleCvsJob << "-a commit"; + if( events & Edits ) + *d->singleCvsJob << "-a edit"; + if( events & Unedits ) + *d->singleCvsJob << "-a unedit"; + } + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::annotate(const QString& fileName, const QString& revision) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // (cvs log [FILE] && cvs annotate [-r rev] [FILE]) + QString quotedName = KProcess::quote(fileName); + QString cvsClient = d->repository->cvsClient(); + + *job << "(" << cvsClient << "log" << quotedName << "&&" + << cvsClient << "annotate"; + + if( !revision.isEmpty() ) + *job << "-r" << revision; + + // *Hack* + // because the string "Annotations for blabla" is + // printed to stderr even with option -Q. + *job << quotedName << ")" << REDIRECT_STDERR; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] checkout [-r tag] [-P] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs, const QString& alias, bool exportOnly) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] co [-r tag] [-P] [-d alias] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository; + if( exportOnly) + *d->singleCvsJob << "export"; + else + *d->singleCvsJob << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs && !exportOnly ) + *d->singleCvsJob << "-P"; + + if( !alias.isEmpty() ) + *d->singleCvsJob << "-d" << alias; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + +DCOPRef CvsService::checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, + bool pruneDirs, const QString& alias, bool exportOnly, + bool recursive) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + // cd [DIRECTORY] && cvs -d [REPOSITORY] co [-r tag] [-P] [-d alias] [MODULE] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository; + if( exportOnly) + *d->singleCvsJob << "export"; + else + *d->singleCvsJob << "checkout"; + + if( !tag.isEmpty() ) + *d->singleCvsJob << "-r" << tag; + + if( pruneDirs && !exportOnly ) + *d->singleCvsJob << "-P"; + + if( !alias.isEmpty() ) + *d->singleCvsJob << "-d" << alias; + + if( ! recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << module; + + return d->setupNonConcurrentJob(&repo); +} + +DCOPRef CvsService::commit(const QStringList& files, const QString& commitMessage, + bool recursive) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs commit [-l] [-m MESSAGE] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "commit"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << "-m" << KProcess::quote(commitMessage) + << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::createRepository(const QString& repository) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs -d [REPOSITORY] init + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "mkdir -p" << KProcess::quote(repository) << "&&" + << d->repository->cvsClient() + << "-d" << KProcess::quote(repository) + << "init"; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::createTag(const QStringList& files, const QString& tag, + bool branch, bool force) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs tag [-b] [-F] [TAG] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "tag"; + + if( branch ) + *d->singleCvsJob << "-b"; + + if( force ) + *d->singleCvsJob << "-F"; + + *d->singleCvsJob << KProcess::quote(tag) + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::deleteTag(const QStringList& files, const QString& tag, + bool branch, bool force) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs tag -d [-b] [-F] [TAG] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "tag" << "-d"; + + if( branch ) + *d->singleCvsJob << "-b"; + + if( force ) + *d->singleCvsJob << "-F"; + + *d->singleCvsJob << KProcess::quote(tag) + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::downloadCvsIgnoreFile(const QString& repository, + const QString& outputFile) +{ + Repository repo(repository); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs -d [REPOSITORY] -q checkout -p CVSROOT/cvsignore > [OUTPUTFILE] + *job << repo.cvsClient() << "-d" << repository + << "-q checkout -p CVSROOT/cvsignore >" + << KProcess::quote(outputFile); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::downloadRevision(const QString& fileName, + const QString& revision, + const QString& outputFile) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs update -p -r [REV] [FILE] > [OUTPUTFILE] + *job << d->repository->cvsClient() << "update -p"; + + if( !revision.isEmpty() ) + *job << "-r" << KProcess::quote(revision); + + *job << KProcess::quote(fileName) << ">" << KProcess::quote(outputFile); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::downloadRevision(const QString& fileName, + const QString& revA, + const QString& outputFileA, + const QString& revB, + const QString& outputFileB) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs update -p -r [REVA] [FILE] > [OUTPUTFILEA] ; + // cvs update -p -r [REVB] [FILE] > [OUTPUTFILEB] + *job << d->repository->cvsClient() << "update -p" + << "-r" << KProcess::quote(revA) + << KProcess::quote(fileName) << ">" << KProcess::quote(outputFileA) + << ";" << d->repository->cvsClient() << "update -p" + << "-r" << KProcess::quote(revB) + << KProcess::quote(fileName) << ">" << KProcess::quote(outputFileB); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + unsigned contextLines) +{ + // cvs diff [DIFFOPTIONS] -U CONTEXTLINES [-r REVA] {-r REVB] [FILE] + QString format = "-U" + QString::number(contextLines); + return diff(fileName, revA, revB, diffOptions, format); +} + + +DCOPRef CvsService::diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + const QString& format) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs diff [DIFFOPTIONS] [FORMAT] [-r REVA] {-r REVB] [FILE] + *job << d->repository->cvsClient() << "diff" << diffOptions + << format; + + if( !revA.isEmpty() ) + *job << "-r" << KProcess::quote(revA); + + if( !revB.isEmpty() ) + *job << "-r" << KProcess::quote(revB); + + *job << KProcess::quote(fileName); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::edit(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs edit [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "edit" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::editors(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs editors [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "editors" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::history() +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs history -e -a + *job << d->repository->cvsClient() << "history -e -a"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "import"; + + if( importAsBinary ) + *d->singleCvsJob << "-kb"; + + const QString ignore = ignoreList.stripWhiteSpace(); + if( !ignore.isEmpty() ) + *d->singleCvsJob << "-I" << KProcess::quote(ignore); + + QString logMessage = comment.stripWhiteSpace(); + logMessage.prepend("\""); + logMessage.append("\""); + *d->singleCvsJob << "-m" << logMessage; + + *d->singleCvsJob << module << vendorTag << releaseTag; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary, + bool useModificationTime) +{ + if( d->hasRunningJob() ) + return DCOPRef(); + + Repository repo(repository); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "cd" << KProcess::quote(workingDir) << "&&" + << repo.cvsClient() + << "-d" << repository + << "import"; + + if( importAsBinary ) + *d->singleCvsJob << "-kb"; + + if( useModificationTime ) + *d->singleCvsJob << "-d"; + + const QString ignore = ignoreList.stripWhiteSpace(); + if( !ignore.isEmpty() ) + *d->singleCvsJob << "-I" << KProcess::quote(ignore); + + QString logMessage = comment.stripWhiteSpace(); + logMessage.prepend("\""); + logMessage.append("\""); + *d->singleCvsJob << "-m" << logMessage; + + *d->singleCvsJob << module << vendorTag << releaseTag; + + return d->setupNonConcurrentJob(&repo); +} + + +DCOPRef CvsService::lock(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs admin -l [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "admin -l" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::log(const QString& fileName) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs log [FILE] + *job << d->repository->cvsClient() << "log" << KProcess::quote(fileName); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::login(const QString& repository) +{ + if( repository.isEmpty() ) + return DCOPRef(); + + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsLoginJob* job = new CvsLoginJob(d->lastJobId); + d->loginJobs.insert(d->lastJobId, job); + + // TODO: CVS_SERVER doesn't work ATM +// job->setServer(repo.server()); + + // assemble the command line + // cvs -d [REPOSITORY] login + job->setCvsClient(repo.clientOnly().local8Bit()); + job->setRepository(repository.local8Bit()); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::logout(const QString& repository) +{ + if( repository.isEmpty() ) + return DCOPRef(); + + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + job->setDirectory(repo.workingCopy()); + + // assemble the command line + // cvs -d [REPOSITORY] logout + *job << repo.cvsClient() << "-d" << repository << "logout"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::makePatch() +{ + return makePatch("", "-u"); +} + + +DCOPRef CvsService::makePatch(const QString& diffOptions, const QString& format) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs diff [DIFFOPTIONS] [FORMAT] -R 2>/dev/null + *job << d->repository->cvsClient() << "diff" << diffOptions << format << "-R" + << "2>/dev/null"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::moduleList(const QString& repository) +{ + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + job->setDirectory(repo.workingCopy()); + + // assemble the command line + // cvs -d [REPOSITORY] checkout -c + *job << repo.cvsClient() << "-d" << repository << "checkout -c"; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::remove(const QStringList& files, bool recursive) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs remove -f [-l] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "remove -f"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::removeWatch(const QStringList& files, int events) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watch remove"; + + if( events != All ) + { + if( events & Commits ) + *d->singleCvsJob << "-a commit"; + if( events & Edits ) + *d->singleCvsJob << "-a edit"; + if( events & Unedits ) + *d->singleCvsJob << "-a unedit"; + } + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::rlog(const QString& repository, const QString& module, + bool recursive) +{ + Repository repo(repository); + + // create a cvs job + ++(d->lastJobId); + + CvsJob* job = new CvsJob(d->lastJobId); + d->cvsJobs.insert(d->lastJobId, job); + + job->setRSH(repo.rsh()); + job->setServer(repo.server()); + + // assemble the command line + // cvs -d [REPOSITORY] rlog [-l] [MODULE] + *job << repo.cvsClient() << "-d" << repository << "rlog"; + + if( !recursive ) + *job << "-l"; + + *job << module; + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::simulateUpdate(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs -n update [-l] [-d] [-P] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "-n -q update"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + if( createDirs ) + *d->singleCvsJob << "-d"; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << CvsServiceUtils::joinFileList(files) << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::status(const QStringList& files, bool recursive, bool tagInfo) +{ + if( !d->hasWorkingCopy() ) + return DCOPRef(); + + // create a cvs job + CvsJob* job = d->createCvsJob(); + + // assemble the command line + // cvs status [-l] [-v] [FILES] + *job << d->repository->cvsClient() << "status"; + + if( !recursive ) + *job << "-l"; + + if( tagInfo ) + *job << "-v"; + + *job << CvsServiceUtils::joinFileList(files); + + // return a DCOP reference to the cvs job + return DCOPRef(d->appId, job->objId()); +} + + +DCOPRef CvsService::unedit(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // echo y | cvs unedit [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << "echo y |" + << d->repository->cvsClient() << "unedit" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::unlock(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs admin -u [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "admin -u" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::update(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs, const QString& extraOpt) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs update [-l] [-d] [-P] [EXTRAOPTIONS] [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "-q update"; + + if( !recursive ) + *d->singleCvsJob << "-l"; + + if( createDirs ) + *d->singleCvsJob << "-d"; + + if( pruneDirs ) + *d->singleCvsJob << "-P"; + + *d->singleCvsJob << extraOpt << CvsServiceUtils::joinFileList(files) + << REDIRECT_STDERR; + + return d->setupNonConcurrentJob(); +} + + +DCOPRef CvsService::watchers(const QStringList& files) +{ + if( !d->hasWorkingCopy() || d->hasRunningJob() ) + return DCOPRef(); + + // assemble the command line + // cvs watchers [FILES] + d->singleCvsJob->clearCvsCommand(); + + *d->singleCvsJob << d->repository->cvsClient() << "watchers" + << CvsServiceUtils::joinFileList(files); + + return d->setupNonConcurrentJob(); +} + + +void CvsService::quit() +{ + kapp->quit(); +} + + +CvsJob* CvsService::Private::createCvsJob() +{ + ++lastJobId; + + // create a cvs job + CvsJob* job = new CvsJob(lastJobId); + cvsJobs.insert(lastJobId, job); + + job->setRSH(repository->rsh()); + job->setServer(repository->server()); + job->setDirectory(repository->workingCopy()); + + return job; +} + + +DCOPRef CvsService::Private::setupNonConcurrentJob(Repository* repo) +{ + // no explicit repository provided? + if( !repo ) + repo = repository; + + singleCvsJob->setRSH(repo->rsh()); + singleCvsJob->setServer(repo->server()); + singleCvsJob->setDirectory(repo->workingCopy()); + + return singleJobRef; +} + + +bool CvsService::Private::hasWorkingCopy() +{ + if( repository->workingCopy().isEmpty() ) + { + KMessageBox::sorry(0, i18n("You have to set a local working copy " + "directory before you can use this function!")); + return false; + } + + return true; +} + + +bool CvsService::Private::hasRunningJob() +{ + bool result = singleCvsJob->isRunning(); + + if( result ) + KMessageBox::sorry(0, i18n("There is already a job running")); + + return result; +} diff --git a/cervisia/cvsservice/cvsservice.desktop b/cervisia/cvsservice/cvsservice.desktop new file mode 100644 index 00000000..31cd6dad --- /dev/null +++ b/cervisia/cvsservice/cvsservice.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=Service +Name=CvsService +Name[br]=Servij Cvs +Name[bs]=CvsServis +Name[ca]=Servei de CVS +Name[cs]=CVS služba +Name[cy]=GwasanaethCVS +Name[de]=CVS-Dienst +Name[el]=Cvs υπηρεσία +Name[eo]=CvsServo +Name[es]=Servicio CVS +Name[et]=CVS teenus +Name[fi]=Cvs-palvelu +Name[gl]=Servizo CVS +Name[hi]=सीवीएस-सर्विस +Name[hu]=CVS szolgáltatás +Name[it]=Servizio CVS +Name[ja]=CVS サービス +Name[lt]=CvsTarnyba +Name[nds]=CVS-Deenst +Name[pt_BR]=ServiçoCVS +Name[sv]=CVS-tjänst +Name[ta]=Cvsசேவை +Name[tg]=ҲизматиCvs +Name[zh_TW]=CVS 服務 +Exec=cvsservice +X-DCOP-ServiceType=Multi +X-KDE-StartupNotify=false +Comment=A DCOP service that provides an interface to cvs +Comment[bg]=Услуга на DCOP, която предлага интерфейс към CVS +Comment[bs]=DCOP servis koji pruža interfejs na CVS +Comment[ca]=Un servei DCOP que proporciona una interfície per al cvs +Comment[cs]=DCOP služba poskytující rozhraní k CVS +Comment[cy]=Gwasanaeth DCOP sy'n darparu rhyngwyneb i cvs +Comment[da]=En DCOP-tjeneste der sørger for en CVS-grænseflade +Comment[de]=Ein DCOP-Dienst, der eine Schnittstelle zu CVS bereitstellt +Comment[el]=Μια υπηρεσία DCOP που προσφέρει ένα περιβάλλον χρήσης για το cvs +Comment[es]=Un servicio DCOP que proporciona una interfaz para cvs +Comment[et]=CVSi DCOP liidese teenus +Comment[eu]=cvs-rako interfazea eskeintzen duen DCOP zerbitzua +Comment[fa]=خدمت DCOP که واسطی را برای cvs فراهم میکند +Comment[fi]=DCOP-palvelu, joka tarjoaa rajapinnan cvs:lle +Comment[fr]=Un service DCOP qui fournit une interface à CVS +Comment[gl]=Un servizo de DCOP que fornece unha interface para CVS +Comment[hi]=एक डीकॉप सर्विस जो सीवीएस को इंटरफेस प्रदान करता है +Comment[hu]=DCOP-alapú szolgáltatás a CVS eléréséhez +Comment[is]=DCOP þjónusta sem veitir viðmót á cvs +Comment[it]=Un servizio DCOP che fornisce un'interfaccia a cvs +Comment[ja]=CVS インターフェースを提供する DCOP サービス +Comment[ka]=DCOP სერვისი, რომელიც cvs-ს ინტერფეისს შეიცავს +Comment[kk]=CVS интерфейсін қамтамасыз ететін DCOP қызметі +Comment[lt]=DCOP tarnyba, pateikianti cvs sąsają +Comment[nb]=En DCOP-tjeneste som tilbyr et grensesnitt mot cvs +Comment[nds]=En DCOP-Deenst, wat en Koppelsteed na CVS praatstellt +Comment[ne]=cvs मा इन्टरफेस उपलब्ध गर्ने एउटा डीसीओपी कार्य +Comment[nl]=Een DCOP-dienst die een interface naar cvs biedt +Comment[nn]=Ei DCOP-teneste som tilbyr eit grensesnitt mot CVS +Comment[pl]=Usługa DCOP pozwalająca na dostęp do CVS +Comment[pt]=Um serviço de DCOP que oferece uma interface para o CVS +Comment[pt_BR]=Um serviço DCOP que provê uma interface para o cvs +Comment[ru]=Сервис DCOP для интерфейса с cvs +Comment[sk]=Služba DCOP pre prístup k CVS +Comment[sl]=Storitev DCOP, ki omogoča vmesnik do CVS +Comment[sr]=DCOP сервис који пружа интерфејс за CVS +Comment[sr@Latn]=DCOP servis koji pruža interfejs za CVS +Comment[sv]=DCOP-tjänst som tillhandahåller ett gränssnitt till CVS +Comment[ta]=ஒரு cvsக்கு ஒரு இடைமுகத்தினை அளிக்கக்கூடிய DCOPசேவை +Comment[tg]=Хизмати DCOP барои интерфейс бо cvs +Comment[tr]= CVS'ye arayüz sağlayan bir DCOP Servisi +Comment[uk]=Служба DCOP, яка надає інтерфейс до cvs +Comment[zh_CN]=提供 CVS 接口的 DCOP 服务 +Comment[zh_TW]=提供 cvs 介面的 DCOP 服務 diff --git a/cervisia/cvsservice/cvsservice.h b/cervisia/cvsservice/cvsservice.h new file mode 100644 index 00000000..0cc43e66 --- /dev/null +++ b/cervisia/cvsservice/cvsservice.h @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSSERVICE_H +#define CVSSERVICE_H + +#include <qstringlist.h> +#include <dcopref.h> +#include <dcopobject.h> + +class QString; + + +class KDE_EXPORT CvsService : public DCOPObject +{ + K_DCOP + +public: + CvsService(); + ~CvsService(); + +k_dcop: + /** + * Adds new files to an existing project. The files don't actually + * appear in the repository until a subsequent commit is performed. + * + * @param files A list of files that should be added to the repository. + * @param isBinary Set to true to treat the files as binary files (-kb) + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef add(const QStringList& files, bool isBinary); + + /** + */ + DCOPRef addWatch(const QStringList& files, int events); + + /** + * Shows information on who last modified each line of a file and when. + * + * @param fileName the name of the file to show annotations for + * @param revision show annotations for this revision (number or tag) + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef annotate(const QString& fileName, const QString& revision); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * @param alias alternative directory to check out to + * @param exportOnly flag to show we want a cvs export rather than a checkout + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + //### KDE4: merge with above checkout() method + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs, + const QString& alias, bool exportOnly); + + /** + * Checks out a module from the repository into a working copy. + * + * @param workingDir path to a local working copy directory + * @param repository + * @param module the name of the module + * @param tag + * @param pruneDirs remove empty directories from the working copy. + * @param alias alternative directory to check out to + * @param exportOnly flag to show we want a cvs export rather than a checkout + * @param recursive check out dirs recursively + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef checkout(const QString& workingDir, const QString& repository, + const QString& module, const QString& tag, bool pruneDirs, + const QString& alias, bool exportOnly, bool recursive); + + /** + * + * @param files A list of files with changes that should be committed to + * the repository. + * @param commitMessage log message describing the changes + * @param recursive + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef commit(const QStringList& files, const QString& commitMessage, + bool recursive); + + /** + * Creates a new root repository. + * + * @param repository + */ + DCOPRef createRepository(const QString& repository); + + /** + */ + DCOPRef createTag(const QStringList& files, const QString& tag, + bool branch, bool force); + + /** + */ + DCOPRef deleteTag(const QStringList& files, const QString& tag, + bool branch, bool force); + + /** + */ + DCOPRef downloadCvsIgnoreFile(const QString& repository, + const QString& outputFile); + + /** + */ + DCOPRef downloadRevision(const QString& fileName, const QString& revision, + const QString& outputFile); + + /** + */ + DCOPRef downloadRevision(const QString& fileName, const QString& revA, + const QString& outputFileA, const QString& revB, + const QString& outputFileB); + + /** + * + * @param fileName + * @param revA + * @param revB + * @param diffOptions + * @param contextLines + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + unsigned contextLines); + + /** + * + * @param fileName + * @param revA + * @param revB + * @param diffOptions + * @param format + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef diff(const QString& fileName, const QString& revA, + const QString& revB, const QString& diffOptions, + const QString& format); + + /** + * @param files + */ + DCOPRef edit(const QStringList& files); + + /** + * @param files + */ + DCOPRef editors(const QStringList& files); + + /** + * Shows a history of activity (like checkouts, commits, etc) in the + * repository for all users and all record types. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef history(); + + /** + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary); + + /** + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + //### KDE4: merge with above import() method + DCOPRef import(const QString& workingDir, const QString& repository, + const QString& module, const QString& ignoreList, + const QString& comment, const QString& vendorTag, + const QString& releaseTag, bool importAsBinary, + bool useModificationTime); + + /** + * @param files + */ + DCOPRef lock(const QStringList& files); + + /** + * Shows log messages for a file. + * + * @param fileName the name of the file to show log messages for + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef log(const QString& fileName); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef login(const QString& repository); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef logout(const QString& repository); + + /** + */ + DCOPRef makePatch(); + + /** + */ + //### KDE4: merge with above makePatch() method + DCOPRef makePatch(const QString& diffOptions, const QString& format); + + /** + * @param repository + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef moduleList(const QString& repository); + + /** + * Deletes files from the local working copy and schedules them to be + * removed from the repository. The files don't actually disappear from + * the repository until a subsequent commit is performed. + * + * @param files A list of files that should be removed from the repository. + * @param recursive descend into subdirectories. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef remove(const QStringList& files, bool recursive); + + /** + */ + DCOPRef removeWatch(const QStringList& files, int events); + + /** + */ + DCOPRef rlog(const QString& repository, const QString& module, + bool recursive); + + /** + * Shows a summary of what's been done locally, without changing the + * working copy. (cvs -n update) + * + * @param files + * @param recursive descend into subdirectories. + * @param createDirs + * @param pruneDirs + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef simulateUpdate(const QStringList& files, bool recursive, + bool createDirs, bool pruneDirs); + + /** + * Shows the status of the files in the working copy. + * + * @param files + * @param recursive descend into subdirectories. + * @param tagInfo show tag information for the file. + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef status(const QStringList& files, bool recursive, bool tagInfo); + + /** + * @param files + */ + DCOPRef unedit(const QStringList& files); + + /** + * @param files + */ + DCOPRef unlock(const QStringList& files); + + /** + * Merges changes from the repository into the files of the + * working copy. + * + * @param files A list of files that should be updated. + * @param recursive descend into subdirectories. + * @param createDirs create directories that exist in the repository + * but not yet in the working copy. + * @param pruneDirs remove empty directories from the working copy. + * @param extraOpt + * + * @return A DCOP reference to the cvs job or in case of failure a + * null reference. + */ + DCOPRef update(const QStringList& files, bool recursive, bool createDirs, + bool pruneDirs, const QString& extraOpt); + + /** + * @param files + */ + DCOPRef watchers(const QStringList& files); + + /** + * Quits the DCOP service. + */ + void quit(); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/cvsserviceutils.cpp b/cervisia/cvsservice/cvsserviceutils.cpp new file mode 100644 index 00000000..73bfc531 --- /dev/null +++ b/cervisia/cvsservice/cvsserviceutils.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "cvsserviceutils.h" + +#include <qstring.h> +#include <qstringlist.h> +#include <kprocess.h> + + +QString CvsServiceUtils::joinFileList(const QStringList& files) +{ + QString result; + + QStringList::ConstIterator it = files.begin(); + QStringList::ConstIterator end = files.end(); + + for( ; it != end; ++it ) + { + result += KProcess::quote(*it); + result += " "; + } + + if( result.length() > 0 ) + result.truncate(result.length()-1); + + return result; +} diff --git a/cervisia/cvsservice/cvsserviceutils.h b/cervisia/cvsservice/cvsserviceutils.h new file mode 100644 index 00000000..8fb7290f --- /dev/null +++ b/cervisia/cvsservice/cvsserviceutils.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef CVSSERVICE_UTILS_H +#define CVSSERVICE_UTILS_H + +class QString; +class QStringList; + + +namespace CvsServiceUtils +{ + +/** + * Joins a list of file names to one QString and quotes + * each name properly for usage with KProcess. + */ +QString joinFileList(const QStringList& files); + +} + + +#endif diff --git a/cervisia/cvsservice/main.cpp b/cervisia/cvsservice/main.cpp new file mode 100644 index 00000000..4f6c748d --- /dev/null +++ b/cervisia/cvsservice/main.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include "cvsservice.h" + + +extern "C" KDE_EXPORT int kdemain(int argc, char** argv) +{ + KAboutData about("cvsservice", I18N_NOOP("CVS DCOP service"), "0.1", + I18N_NOOP("DCOP service for CVS"), KAboutData::License_LGPL, + "Copyright (c) 2002-2003 Christian Loose"); + about.addAuthor("Christian Loose", I18N_NOOP("Developer"), + "christian.loose@hamburg.de"); + + KCmdLineArgs::init(argc, argv, &about); + + KApplication app; + + // This app is started automatically, no need for session management + app.disableSessionManagement(); + + CvsService service; + + return app.exec(); +} diff --git a/cervisia/cvsservice/repository.cpp b/cervisia/cvsservice/repository.cpp new file mode 100644 index 00000000..d2cff113 --- /dev/null +++ b/cervisia/cvsservice/repository.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2002-2004 Christian Loose <christian.loose@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "repository.h" + +#include <qdir.h> +#include <qfile.h> +#include <qstring.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdirwatch.h> +#include <kstandarddirs.h> + +#include "sshagent.h" + + +struct Repository::Private +{ + Private() : compressionLevel(0) {} + + QString configFileName; + + QString workingCopy; + QString location; + + QString client; + QString rsh; + QString server; + int compressionLevel; + bool retrieveCvsignoreFile; + + void readConfig(); + void readGeneralConfig(); +}; + + + +Repository::Repository() + : QObject() + , DCOPObject("CvsRepository") + , d(new Private) +{ + d->readGeneralConfig(); + + // other cvsservice instances might change the configuration file + // so we watch it for changes + d->configFileName = locate("config", "cvsservicerc"); + KDirWatch* fileWatcher = new KDirWatch(this); + connect(fileWatcher, SIGNAL(dirty(const QString&)), + this, SLOT(slotConfigDirty(const QString&))); + fileWatcher->addFile(d->configFileName); +} + + +Repository::Repository(const QString& repository) + : QObject() + , DCOPObject() + , d(new Private) +{ + d->location = repository; + d->readGeneralConfig(); + d->readConfig(); + + // other cvsservice instances might change the configuration file + // so we watch it for changes + d->configFileName = locate("config", "cvsservicerc"); + KDirWatch* fileWatcher = new KDirWatch(this); + connect(fileWatcher, SIGNAL(dirty(const QString&)), + this, SLOT(slotConfigDirty(const QString&))); + fileWatcher->addFile(d->configFileName); +} + + +Repository::~Repository() +{ + delete d; +} + + +QString Repository::cvsClient() const +{ + QString client(d->client); + + // suppress reading of the '.cvsrc' file + client += " -f"; + + // we don't need the command line option if there is no compression level set + if( d->compressionLevel > 0 ) + { + client += " -z" + QString::number(d->compressionLevel) + " "; + } + + return client; +} + + +QString Repository::clientOnly() const +{ + return d->client; +} + + +QString Repository::rsh() const +{ + return d->rsh; +} + + +QString Repository::server() const +{ + return d->server; +} + + +bool Repository::setWorkingCopy(const QString& dirName) +{ + const QFileInfo fi(dirName); + const QString path = fi.absFilePath(); + + // is this really a cvs-controlled directory? + const QFileInfo cvsDirInfo(path + "/CVS"); + if( !cvsDirInfo.exists() || !cvsDirInfo.isDir() || + !QFile::exists( cvsDirInfo.filePath() + "/Entries" ) || + !QFile::exists( cvsDirInfo.filePath() + "/Repository" ) || + !QFile::exists( cvsDirInfo.filePath() + "/Root" ) ) + return false; + + d->workingCopy = path; + d->location = QString::null; + + // determine path to the repository + QFile rootFile(path + "/CVS/Root"); + if( rootFile.open(IO_ReadOnly) ) + { + QTextStream stream(&rootFile); + d->location = stream.readLine(); + } + rootFile.close(); + + // add identities (ssh-add) to ssh-agent + // TODO CL make sure this is called only once + if( d->location.contains(":ext:", false) > 0 ) + { + SshAgent ssh; + ssh.addSshIdentities(); + } + + QDir::setCurrent(path); + d->readConfig(); + + return true; +} + + +QString Repository::workingCopy() const +{ + return d->workingCopy; +} + + +QString Repository::location() const +{ + return d->location; +} + + +bool Repository::retrieveCvsignoreFile() const +{ + return d->retrieveCvsignoreFile; +} + + +void Repository::slotConfigDirty(const QString& fileName) +{ + if( fileName == d->configFileName ) + { + // reread the configuration data from disk + kapp->config()->reparseConfiguration(); + d->readConfig(); + } +} + + +void Repository::Private::readGeneralConfig() +{ + KConfig* config = kapp->config(); + + // get path to cvs client programm + config->setGroup("General"); + client = config->readPathEntry("CVSPath", "cvs"); +} + + +void Repository::Private::readConfig() +{ + KConfig* config = kapp->config(); + + // Sometimes the location can be unequal to the entry in the CVS/Root. + // + // This can happen when the checkout was done with a repository name + // like :pserver:user@cvs.kde.org:/home/kde. When cvs then saves the + // name into the .cvspass file, it adds the default cvs port to it like + // this :pserver:user@cvs.kde.org:2401/home/kde. This name is then also + // used for the configuration group. + // + // In order to be able to read this group, we then have to manually add + // the port number to it. + QString repositoryGroup = QString::fromLatin1("Repository-") + location; + if( !config->hasGroup(repositoryGroup) ) + { + // find the position of the first path separator + const int insertPos = repositoryGroup.find('/'); + if( insertPos > 0 ) + { + // add port to location + // (1) :pserver:user@hostname.com:/path + if( repositoryGroup.at(insertPos - 1) == ':' ) + repositoryGroup.insert(insertPos, "2401"); + // (2) :pserver:user@hostname.com/path + else + repositoryGroup.insert(insertPos, ":2401"); + } + } + + config->setGroup(repositoryGroup); + + // should we retrieve the CVSROOT/cvsignore file from the cvs server? + retrieveCvsignoreFile = config->readBoolEntry("RetrieveCvsignore", false); + + // see if there is a specific compression level set for this repository + compressionLevel = config->readNumEntry("Compression", -1); + + // use default global compression level instead? + if( compressionLevel < 0 ) + { + KConfigGroupSaver cs(config, "General"); + compressionLevel = config->readNumEntry("Compression", 0); + } + + // get remote shell client to access the remote repository + rsh = config->readPathEntry("rsh"); + + // get program to start on the server side + server = config->readEntry("cvs_server"); +} + + +#include "repository.moc" diff --git a/cervisia/cvsservice/repository.h b/cervisia/cvsservice/repository.h new file mode 100644 index 00000000..c90277bc --- /dev/null +++ b/cervisia/cvsservice/repository.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2002-2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef REPOSITORY_H +#define REPOSITORY_H + +#include <qobject.h> +#include <dcopobject.h> + +class QString; + + +/** + * Represents a local or remote cvs repository with + * its repository-specific configuration data. + */ +class KDE_EXPORT Repository : public QObject, public DCOPObject +{ + K_DCOP + Q_OBJECT + +public: + Repository(); + explicit Repository(const QString& repository); + ~Repository(); + + /** + * cvs command (including the user-specified path) with the options + * for this repository. + * + * @return A cvs command (including path). + */ + QString cvsClient() const; + + /** + */ + QString clientOnly() const; + + /** + * Remote shell command line client which should be used to + * access the remote cvs repository, when :ext: access method + * is specified. ($CVS_RSH) + * + * @return The remote shell client. Can be null if not set. + */ + QString rsh() const; + + /** + * Program to start on the server side when accessing a remote + * repository using :ext: access method. ($CVS_SERVER) + * + * @return The server program. Can be null if not set. + */ + QString server() const; + +k_dcop: + /** + * Changes the working copy and the corresponding cvs repository. + * + * @param dirName path to the local working copy directory. + */ + bool setWorkingCopy(const QString& dirName); + + /** + * Path to the current working copy. + * + * @return The working copy directory. Can be null if not set. + */ + QString workingCopy() const; + + /** + * Path and method to access the current cvs repository. + * i.e. :pserver:user@cvs.project.org:/home/project + * + * @return The path and method to access the cvs repository. + */ + QString location() const; + + /** + */ + bool retrieveCvsignoreFile() const; + +private slots: + void slotConfigDirty(const QString& fileName); + +private: + struct Private; + Private* d; +}; + + +#endif diff --git a/cervisia/cvsservice/sshagent.cpp b/cervisia/cvsservice/sshagent.cpp new file mode 100644 index 00000000..48bc5eef --- /dev/null +++ b/cervisia/cvsservice/sshagent.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include "sshagent.h" + +#include <qregexp.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kdeversion.h> +#include <kprocess.h> + +#include <stdlib.h> + + +// initialize static member variables +bool SshAgent::m_isRunning = false; +bool SshAgent::m_isOurAgent = false; +QString SshAgent::m_authSock = QString::null; +QString SshAgent::m_pid = QString::null; + + +SshAgent::SshAgent(QObject* parent, const char* name) + : QObject(parent, name) +{ +} + + +SshAgent::~SshAgent() +{ +} + + +bool SshAgent::querySshAgent() +{ + kdDebug(8051) << "SshAgent::querySshAgent(): ENTER" << endl; + + if( m_isRunning ) + return true; + + // Did the user already start a ssh-agent process? + char* pid; + if( (pid = ::getenv("SSH_AGENT_PID")) != 0 ) + { + kdDebug(8051) << "SshAgent::querySshAgent(): ssh-agent already exists" + << endl; + + m_pid = QString::fromLocal8Bit(pid); + + char* sock = ::getenv("SSH_AUTH_SOCK"); + if( sock ) + m_authSock = QString::fromLocal8Bit(sock); + + m_isOurAgent = false; + m_isRunning = true; + } + // We have to start a new ssh-agent process + else + { + kdDebug(8051) << "SshAgent::querySshAgent(): start ssh-agent" << endl; + + m_isOurAgent = true; + m_isRunning = startSshAgent(); + } + + return m_isRunning; +} + + +bool SshAgent::addSshIdentities() +{ + kdDebug(8051) << "SshAgent::addSshIdentities(): ENTER" << endl; + + if( !m_isRunning || !m_isOurAgent ) + return false; + + // add identities to ssh-agent + KProcess proc; + + proc.setEnvironment("SSH_AGENT_PID", m_pid); + proc.setEnvironment("SSH_AUTH_SOCK", m_authSock); + proc.setEnvironment("SSH_ASKPASS", "cvsaskpass"); + + proc << "ssh-add"; + + connect(&proc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(&proc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int))); + + proc.start(KProcess::DontCare, KProcess::AllOutput); + + // wait for process to finish + // TODO CL use timeout? + proc.wait(); + + kdDebug(8051) << "SshAgent::slotProcessExited(): added identities" << endl; + + return (proc.normalExit() && proc.exitStatus() == 0); +} + + +void SshAgent::killSshAgent() +{ + kdDebug(8051) << "SshAgent::killSshAgent(): ENTER" << endl; + + if( !m_isRunning || !m_isOurAgent ) + return; + + KProcess proc; + + proc << "kill" << m_pid; + + proc.start(KProcess::DontCare, KProcess::NoCommunication); + + kdDebug(8051) << "SshAgent::killSshAgent(): killed pid = " << m_pid << endl; +} + + +void SshAgent::slotProcessExited(KProcess*) +{ + kdDebug(8051) << "SshAgent::slotProcessExited(): ENTER" << endl; + + QRegExp cshPidRx("setenv SSH_AGENT_PID (\\d*);"); + QRegExp cshSockRx("setenv SSH_AUTH_SOCK (.*);"); + + QRegExp bashPidRx("SSH_AGENT_PID=(\\d*).*"); + QRegExp bashSockRx("SSH_AUTH_SOCK=(.*\\.\\d*);.*"); + + QStringList::Iterator it = m_outputLines.begin(); + QStringList::Iterator end = m_outputLines.end(); + for( ; it != end; ++it ) + { + if( m_pid.isEmpty() ) + { + int pos = cshPidRx.search(*it); + if( pos > -1 ) + { + m_pid = cshPidRx.cap(1); + continue; + } + + pos = bashPidRx.search(*it); + if( pos > -1 ) + { + m_pid = bashPidRx.cap(1); + continue; + } + } + + if( m_authSock.isEmpty() ) + { + int pos = cshSockRx.search(*it); + if( pos > -1 ) + { + m_authSock = cshSockRx.cap(1); + continue; + } + + pos = bashSockRx.search(*it); + if( pos > -1 ) + { + m_authSock = bashSockRx.cap(1); + continue; + } + } + } + + kdDebug(8051) << "SshAgent::slotProcessExited(): pid = " << m_pid + << ", socket = " << m_authSock << endl; +} + + +void SshAgent::slotReceivedStdout(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + m_outputLines += QStringList::split("\n", output); + + kdDebug(8051) << "SshAgent::slotReceivedStdout(): output = " << output << endl; +} + + +void SshAgent::slotReceivedStderr(KProcess* proc, char* buffer, int buflen) +{ + Q_UNUSED(proc); + + QString output = QString::fromLocal8Bit(buffer, buflen); + m_outputLines += QStringList::split("\n", output); + + kdDebug(8051) << "SshAgent::slotReceivedStderr(): output = " << output << endl; +} + + +bool SshAgent::startSshAgent() +{ + kdDebug(8051) << "SshAgent::startSshAgent(): ENTER" << endl; + + KProcess proc; + + proc << "ssh-agent"; + + connect(&proc, SIGNAL(processExited(KProcess*)), + SLOT(slotProcessExited(KProcess*))); + connect(&proc, SIGNAL(receivedStdout(KProcess*, char*, int)), + SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(&proc, SIGNAL(receivedStderr(KProcess*, char*, int)), + SLOT(slotReceivedStderr(KProcess*, char*, int)) ); + + proc.start(KProcess::NotifyOnExit, KProcess::All); + + // wait for process to finish + // TODO CL use timeout? + proc.wait(); + + return (proc.normalExit() && proc.exitStatus() == 0); +} + + +#include "sshagent.moc" diff --git a/cervisia/cvsservice/sshagent.h b/cervisia/cvsservice/sshagent.h new file mode 100644 index 00000000..894a9026 --- /dev/null +++ b/cervisia/cvsservice/sshagent.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2003 Christian Loose <christian.loose@hamburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef SSHAGENT_H +#define SSHAGENT_H + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> + +class KProcess; + + +class SshAgent : public QObject +{ + Q_OBJECT + +public: + SshAgent(QObject* parent = 0, const char* name = 0); + ~SshAgent(); + + bool querySshAgent(); + bool addSshIdentities(); + void killSshAgent(); + + bool isRunning() const { return m_isRunning; } + QString pid() const { return m_pid; } + QString authSock() const { return m_authSock; } + +private slots: + void slotProcessExited(KProcess*); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + +private: + bool startSshAgent(); + + QStringList m_outputLines; + + static bool m_isRunning; + static bool m_isOurAgent; + static QString m_authSock; + static QString m_pid; +}; + + +#endif |