File indexing completed on 2024-05-05 04:41:00

0001 /*
0002     SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de>
0003 
0004     Parts of the file are copied from the RapidSvn C++ library
0005     SPDX-FileCopyrightText: 2002-2006 The RapidSvn Group
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "svnclient.h"
0011 
0012 #include <QDateTime>
0013 #include <QStandardPaths>
0014 
0015 extern "C" {
0016 #include <svn_client.h>
0017 #include <svn_io.h>
0018 }
0019 #include "kdevsvncpp/targets.hpp"
0020 #include "kdevsvncpp/pool.hpp"
0021 
0022 #include <vcs/vcsrevision.h>
0023 #include <vcs/vcsannotation.h>
0024 
0025 void fail (apr_pool_t *pool, apr_status_t status, const char *fmt, ...)
0026 {
0027     va_list ap;
0028     char *msg;
0029     svn_error_t * error;
0030 
0031     va_start (ap, fmt);
0032     msg = apr_pvsprintf (pool, fmt, ap);
0033     va_end (ap);
0034 
0035     error = svn_error_create (status, nullptr, msg);
0036     throw svn::ClientException (error);
0037 }
0038 
0039 void cleanup( apr_file_t* outfile, const char* outfileName, apr_file_t* errfile, const char* errfileName, const svn::Pool& pool )
0040 {
0041     if( outfile != nullptr )
0042     {
0043         apr_file_close( outfile );
0044     }
0045 
0046     if( errfile != nullptr )
0047     {
0048         apr_file_close( outfile );
0049     }
0050 
0051     if( outfileName != nullptr )
0052     {
0053         svn_error_clear( svn_io_remove_file ( outfileName, pool ) );
0054     }
0055 
0056 
0057     if( errfileName != nullptr )
0058     {
0059         svn_error_clear( svn_io_remove_file ( errfileName, pool ) );
0060     }
0061 
0062 }
0063 
0064 SvnClient::SvnClient( svn::Context* ctx )
0065     : QObject(nullptr), svn::Client( ctx ), m_ctxt( ctx )
0066 {
0067 }
0068 
0069 QString SvnClient::diff( const svn::Path& src, const svn::Revision& srcRev,
0070                 const svn::Path& dst, const svn::Revision& dstRev,
0071                 const bool recurse, const bool ignoreAncestry,
0072                 const bool noDiffDeleted, const bool ignoreContentType )
0073 {
0074     svn::Pool pool;
0075     // null options
0076     apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool );
0077 
0078     svn_error_t* error;
0079 
0080     const char* outfileName = nullptr;
0081     apr_file_t* outfile = nullptr;
0082     const char* errfileName = nullptr;
0083     apr_file_t* errfile = nullptr;
0084 
0085     QByteArray ba = QString(QStandardPaths::writableLocation(QStandardPaths::TempLocation)+QLatin1String("/kdevelop_svn_diff")).toUtf8();
0086     
0087     error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool );
0088 
0089     if( error != nullptr )
0090     {
0091         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0092         throw svn::ClientException( error );
0093     }
0094 
0095     error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool );
0096 
0097     if( error != nullptr )
0098     {
0099         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0100         throw svn::ClientException( error );
0101     }
0102         
0103     error = svn_client_diff3( options,
0104                             src.c_str(), srcRev.revision(),
0105                             dst.c_str(), dstRev.revision(),
0106                             recurse, ignoreAncestry, noDiffDeleted,
0107                             ignoreContentType, "UTF-8",
0108                             outfile, errfile, m_ctxt->ctx(), pool );
0109     if ( error )
0110     {
0111         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0112         throw svn::ClientException(error);
0113     }
0114 
0115     // then we reopen outfile for reading
0116     apr_status_t aprstatus = apr_file_close (outfile);
0117     if (aprstatus)
0118     {
0119       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0120       ::fail (pool, aprstatus, "failed to close '%s'", outfileName);
0121     }
0122 
0123     aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool);
0124     if (aprstatus)
0125     {
0126       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0127       ::fail (pool, aprstatus, "failed to open '%s'", outfileName);
0128     }
0129 
0130 
0131     svn_stringbuf_t* stringbuf;
0132     // now we can read the diff output from outfile and return that
0133     error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool);
0134 
0135     if (error != nullptr)
0136     {
0137       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0138       throw svn::ClientException (error);
0139     }
0140 
0141     ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0142     return QString::fromUtf8( stringbuf->data );
0143 }
0144 
0145 QString SvnClient::diff( const svn::Path& src, const svn::Revision& pegRev,
0146                 const svn::Revision& srcRev, const svn::Revision& dstRev,
0147                 const bool recurse, const bool ignoreAncestry,
0148                 const bool noDiffDeleted, const bool ignoreContentType )
0149 {
0150     svn::Pool pool;
0151     // null options
0152     apr_array_header_t *options = svn_cstring_split( "", "\t\r\n", false, pool );
0153 
0154 
0155     svn_error_t* error;
0156 
0157     const char* outfileName = nullptr;
0158     apr_file_t* outfile = nullptr;
0159     const char* errfileName = nullptr;
0160     apr_file_t* errfile = nullptr;
0161 
0162     QByteArray ba = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toUtf8();
0163 
0164     error = svn_io_open_unique_file( &outfile, &outfileName, ba.data(), ".tmp", false, pool );
0165 
0166     if( error != nullptr )
0167     {
0168         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0169         throw svn::ClientException( error );
0170     }
0171 
0172     error = svn_io_open_unique_file( &errfile, &errfileName, ba.data(), ".tmp", false, pool );
0173 
0174     if( error != nullptr )
0175     {
0176         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0177         throw svn::ClientException( error );
0178     }
0179 
0180     error = svn_client_diff_peg3( options,
0181                             src.c_str(), pegRev.revision(),
0182                             srcRev.revision(), dstRev.revision(),
0183                             recurse, ignoreAncestry, noDiffDeleted,
0184                             ignoreContentType, "UTF-8",
0185                             outfile, errfile, m_ctxt->ctx(), pool );
0186     if ( error )
0187     {
0188         ::cleanup( outfile, outfileName, errfile, errfileName, pool );
0189         throw svn::ClientException(error);
0190     }
0191 
0192     // then we reopen outfile for reading
0193     apr_status_t aprstatus = apr_file_close (outfile);
0194     if (aprstatus)
0195     {
0196       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0197       ::fail (pool, aprstatus, "failed to close '%s'", outfileName);
0198     }
0199 
0200     aprstatus = apr_file_open (&outfile, outfileName, APR_READ, APR_OS_DEFAULT, pool);
0201     if (aprstatus)
0202     {
0203       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0204       ::fail (pool, aprstatus, "failed to open '%s'", outfileName);
0205     }
0206 
0207 
0208     svn_stringbuf_t* stringbuf;
0209     // now we can read the diff output from outfile and return that
0210     error = svn_stringbuf_from_aprfile (&stringbuf, outfile, pool);
0211 
0212     if (error != nullptr)
0213     {
0214       ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0215       throw svn::ClientException(error);
0216     }
0217 
0218     ::cleanup (outfile, outfileName, errfile, errfileName, pool);
0219     return QString::fromUtf8( stringbuf->data );
0220 }
0221 
0222 static svn_error_t *
0223 kdev_logReceiver (void *baton,
0224                 apr_hash_t * changedPaths,
0225                 svn_revnum_t rev,
0226                 const char *author,
0227                 const char *date,
0228                 const char *msg,
0229                 apr_pool_t * pool)
0230 {
0231     auto* client = (SvnClient *) baton;
0232 
0233     KDevelop::VcsEvent ev;
0234     ev.setAuthor( QString::fromUtf8( author ) );
0235     ev.setDate( QDateTime::fromString( QString::fromUtf8( date ), Qt::ISODate ) );
0236     ev.setMessage( QString::fromUtf8( msg ) );
0237     KDevelop::VcsRevision vcsrev;
0238     vcsrev.setRevisionValue( QVariant( qlonglong( rev ) ), KDevelop::VcsRevision::GlobalNumber );
0239     ev.setRevision( vcsrev );
0240 
0241     if (changedPaths != nullptr)
0242     {
0243         for (apr_hash_index_t *hi = apr_hash_first (pool, changedPaths);
0244             hi != nullptr;
0245             hi = apr_hash_next (hi))
0246         {
0247             char *path;
0248             void *val;
0249             apr_hash_this (hi, (const void **)&path, nullptr, &val);
0250 
0251             auto *log_item = reinterpret_cast<svn_log_changed_path_t *> (val);
0252             KDevelop::VcsItemEvent iev;
0253             iev.setRepositoryLocation( QString::fromUtf8( path ) );
0254             iev.setRepositoryCopySourceLocation( QString::fromUtf8( log_item->copyfrom_path ) );
0255             KDevelop::VcsRevision irev;
0256             irev.setRevisionValue( QVariant( qlonglong( log_item->copyfrom_rev ) ),
0257                                    KDevelop::VcsRevision::GlobalNumber );
0258             iev.setRepositoryCopySourceRevision( irev );
0259             switch( log_item->action )
0260             {
0261                 case 'A':
0262                     iev.setActions( KDevelop::VcsItemEvent::Added );
0263                     break;
0264                 case 'M':
0265                     iev.setActions( KDevelop::VcsItemEvent::Modified  );
0266                     break;
0267                 case 'D':
0268                     iev.setActions( KDevelop::VcsItemEvent::Deleted );
0269                     break;
0270                 case 'R':
0271                     iev.setActions( KDevelop::VcsItemEvent::Replaced );
0272                     break;
0273             }
0274             
0275             auto items = ev.items();
0276             items.append( iev );
0277             ev.setItems( items );
0278         }
0279     }
0280     client->emitLogEventReceived( ev );
0281 
0282     return nullptr;
0283 }
0284 
0285 void SvnClient::log( const char* path,
0286                      const svn::Revision& start,
0287                      const svn::Revision& end,
0288                      int limit,
0289                      bool discoverChangedPaths,
0290                      bool strictNodeHistory )
0291 {
0292     svn::Pool pool;
0293     svn::Targets target(path);
0294     svn_error_t *error;
0295 
0296     error = svn_client_log2 (
0297     target.array(pool),
0298     start.revision(),
0299     end.revision(),
0300     limit,
0301     discoverChangedPaths ? 1 : 0,
0302     strictNodeHistory ? 1 : 0,
0303     kdev_logReceiver,
0304     this,
0305     m_ctxt->ctx(), // client ctx
0306     pool);
0307 
0308     if (error != nullptr)
0309     {
0310         throw svn::ClientException (error);
0311     }
0312 }
0313 
0314 void SvnClient::emitLogEventReceived( const KDevelop::VcsEvent& ev )
0315 {
0316     emit logEventReceived( ev );
0317 }
0318 
0319 #include "moc_svnclient.cpp"