source: trunk/lib/SVN.cc @ 1219

Last change on this file since 1219 was 1219, checked in by Peter Johansson, 12 years ago

closes #371. SVNException copy with ref counter.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.3 KB
Line 
1// $Id: SVN.cc 1219 2010-10-12 02:37:53Z peter $
2
3/*
4  Copyright (C) 2006 Jari Häkkinen
5  Copyright (C) 2007, 2008 Jari Häkkinen, Peter Johansson
6
7  This file is part of svndigest, http://dev.thep.lu.se/svndigest
8
9  svndigest is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 3 of the License, or
12  (at your option) any later version.
13
14  svndigest is distributed in the hope that it will be useful, but
15  WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with svndigest. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include "SVN.h"
24
25#include <map>
26#include <string>
27#include <vector>
28
29#include <cassert>
30
31#include <apr_allocator.h>
32#include <apr_hash.h>
33#include <subversion-1/svn_client.h>
34#include <subversion-1/svn_cmdline.h>
35#include <subversion-1/svn_path.h>
36#include <subversion-1/svn_pools.h>
37#include <subversion-1/svn_wc.h>
38#include <subversion-1/svn_subst.h>
39
40#include <sstream>
41#include <string>
42
43namespace theplu {
44namespace svndigest {
45
46
47  SVNException::SVNException(const std::string& msg, svn_error_t* error)
48    : std::runtime_error(""), error_(error), msg_(msg) 
49  {
50    ref_count_ = new int(1);
51  }
52
53  SVNException::SVNException(const SVNException& other)
54    : std::runtime_error(other), error_(other.error_), msg_(other.msg_),
55      ref_count_(other.ref_count_)
56  {
57    ++(*ref_count_);
58  }
59
60
61  SVNException::~SVNException(void) throw() 
62  {
63    --ref_count_;
64    if (!ref_count_) {
65      delete ref_count_;
66      ref_count_=NULL;
67      svn_error_clear(error_);
68      error_=SVN_NO_ERROR;
69    }
70  }
71
72
73  const char* SVNException::what(void) const throw()
74  {
75    std::stringstream ss;
76    if (error_) {
77      apr_size_t bufsize=255;
78      char buf[255];
79      ss << svn_err_best_message(error_, buf, bufsize);
80    }
81    if (msg_.size()) {
82      ss << msg_;
83    }
84   
85    return ss.str().c_str();
86  }
87
88
89  const svn_error_t* SVNException::error(void) const
90  {
91    return error_;
92  }
93
94
95  SVN* SVN::instance_=NULL;
96
97
98  SVN::SVN(const std::string& path)
99    : adm_access_(NULL), allocator_(NULL), context_(NULL), pool_(NULL)
100  {
101    svn_error_t* err=NULL;
102
103    // initialize something (APR subsystem and more). The APR
104    // subsystem is automatically destroyed at program exit. In case
105    // of several calls to svn_cmdline_init (ie. several exceptions
106    // thrown and caught with subsequent reinitializatios is safe
107    // memorywise but what about APR internal counters?)
108    if (svn_cmdline_init("svndigest",stderr) != EXIT_SUCCESS)
109      throw SVNException("SVN: svn_cmdline_init failed");
110
111    /// create top-level pool
112    if (apr_allocator_create(&allocator_))
113      throw SVNException("SVN(_allocator_create failed");
114    apr_allocator_max_free_set(allocator_,SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
115    pool_ = svn_pool_create_ex(NULL, allocator_);
116    apr_allocator_owner_set(allocator_, pool_);
117
118    // initialize the repository access library
119    if ((err=svn_ra_initialize(pool_)))
120      cleanup_failed_init(err, "SVN: svn_ra_initialize failed");
121
122    // Check that users .subversion exist. Should this really be done?
123    // If this check is to be done, we might be forced to support a
124    // command line option to change the location of the .subversion
125    // stuff (compare with the svn binary).
126    if ((err=svn_config_ensure(NULL, pool_)))
127      cleanup_failed_init(err, "SVN: svn_config_ensure failed");
128
129    // create a client context object
130    if ((err=svn_client_create_context(&context_, pool_)))
131      cleanup_failed_init(err, "SVN: svn_client_create_context failed");
132
133    if ((err=svn_config_get_config(&(context_->config), NULL, pool_)))
134      cleanup_failed_init(err, "SVN: svn_config_get_config failed");
135
136    // set up authentication stuff
137    if ((err=svn_cmdline_setup_auth_baton(&(context_->auth_baton), false, NULL,
138                                          NULL, NULL, false,
139                        static_cast<svn_config_t*>(apr_hash_get(context_->config,
140                                                    SVN_CONFIG_CATEGORY_CONFIG,
141                                                    APR_HASH_KEY_STRING)),
142                                          context_->cancel_func,
143                                          context_->cancel_baton, pool_)))
144      cleanup_failed_init(err, "SVN: svn_cmdline_setup_auth_baton failed");
145
146    // Set up svn administration area access. The whole structure is
147    // setup, maybe this is unnecessary?
148    const char* canonical_path=svn_path_internal_style(path.c_str(), pool_);
149    if ((err=svn_wc_adm_open3(&adm_access_, NULL, canonical_path,
150                              false, -1, context_->cancel_func,
151                              context_->cancel_baton, pool_))){
152      if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
153        cleanup_failed_init(err, std::string(err->message));
154      cleanup_failed_init(err, "SVN: svn_wc_adm_open3 failed");
155    }
156
157    // get a session to the repository
158    struct root_url_receiver_baton rurb;
159    client_info(path, root_url_receiver, static_cast<void*>(&rurb));
160    if ((err=svn_client_open_ra_session(&ra_session_,
161                                        rurb.path.c_str(),
162                                        context_, pool_)))
163      cleanup_failed_init(err, "SVN: svn_client_open_ra_session failed");
164  }
165
166
167  SVN::~SVN(void)
168  {
169    if (adm_access_)
170      svn_error_clear(svn_wc_adm_close(adm_access_));
171    svn_pool_destroy(pool_);
172    pool_=NULL;
173    // other apr resources acquired in svn_cmdline_init are destroyed
174    // at program exit, ok since SVN is a singleton
175  }
176
177
178  void SVN::cleanup(svn_error_t *err,apr_pool_t *pool,
179                    std::string message, bool mute)
180  {
181    svn_error_clear(err);
182    if (pool){
183      svn_pool_destroy(pool);
184      pool=NULL;
185    }
186    assert(message.size()); // compatible with r1213
187    if (!mute)
188      throw SVNException(message, err);
189    // mute implies we don't wanna hear the message from svn_error_t
190    throw SVNException(message);
191  }
192
193
194  void SVN::cleanup_failed_init(svn_error_t *err, const std::string& message)
195  {
196    assert(message.size()); // compatible with r1213
197    cleanup(err,pool_, message, true);
198    assert(false && "cleanup should throw");
199  }
200
201
202  void SVN::client_blame(const std::string& path,
203                         svn_client_blame_receiver_t receiver,
204                         void *baton, svn_revnum_t rev)
205  {
206    svn_opt_revision_t head;
207    head.kind=svn_opt_revision_number;
208    head.value.number=rev;
209    client_blame_call(path, receiver, baton, head);
210  }
211
212  void SVN::client_blame(const std::string& path,
213                         svn_client_blame_receiver_t receiver,
214                         void *baton)
215  {
216    svn_opt_revision_t head;
217    head.kind = ( svn_path_is_url(path.c_str()) ?
218                  svn_opt_revision_head : svn_opt_revision_base );
219    client_blame_call(path, receiver, baton, head);
220  }
221
222
223  void SVN::client_blame_call(const std::string& path,
224                              svn_client_blame_receiver_t receiver,
225                              void *baton, svn_opt_revision_t& head)
226  {
227    // Setup to use all revisions
228    svn_opt_revision_t peg, start;
229    peg.kind=svn_opt_revision_unspecified;
230    start.kind=svn_opt_revision_number;
231    start.value.number=0;
232    apr_pool_t *subpool = svn_pool_create(pool_);
233    svn_error_t* err=svn_client_blame3(path.c_str(), &peg, &start, &head,
234                                       svn_diff_file_options_create(subpool),
235                                       false, receiver, baton, context_,
236                                       subpool);
237    if (err)
238      // cleanup will throw an exception
239      cleanup(err, subpool, "SVN::client_blame: svn_client_blame3 failed");
240    svn_pool_destroy(subpool);
241  }
242
243
244  void SVN::client_info(const std::string& path, svn_info_receiver_t receiver,
245                        void *baton)
246  {
247    apr_pool_t *subpool = svn_pool_create(pool_);
248    if (svn_error_t *err=svn_client_info(path.c_str(), NULL, NULL, receiver,
249                                         baton, false, context_, subpool))
250      // cleanup will throw an exception
251      cleanup(err, subpool, "repository: svn_client_info failed");
252    svn_pool_destroy(subpool);
253  }
254
255
256  void SVN::client_log(const std::string& path,
257                       svn_log_message_receiver_t receiver, void *baton)
258  {
259    // Allocate space in subpool to pool_ for apr_path (here a string).
260    apr_pool_t *subpool = svn_pool_create(pool_);
261    apr_array_header_t* apr_path=apr_array_make(subpool,1,4);
262    // Copy path to apr_path.
263    (*((const char **) apr_array_push(apr_path))) =
264      apr_pstrdup(subpool, svn_path_internal_style(path.c_str(),subpool));
265
266    // Setup to retrieve all commit logs.
267    svn_opt_revision_t peg, start, head;
268    peg.kind=svn_opt_revision_unspecified;
269    start.kind=svn_opt_revision_number;
270    start.value.number=0;
271    head.kind = ( svn_path_is_url(path.c_str()) ?
272                  svn_opt_revision_head : svn_opt_revision_base );
273    svn_error_t* err=NULL;
274    if ((err=svn_client_log3(apr_path, &peg, &start, &head, 0, false, false,
275                             receiver, baton, context_, subpool)))
276      // cleanupp will throw an exception
277      cleanup(err, subpool, "commit_dates: svn_client_log3 failed");
278    svn_pool_destroy(subpool);
279  }
280
281
282  void SVN::client_proplist(const std::string& path,
283                            std::map<std::string, std::string>& property)
284  {
285    svn_opt_revision_t peg, revision;
286    peg.kind=svn_opt_revision_unspecified;
287    revision.kind = ( svn_path_is_url(path.c_str()) ?
288                      svn_opt_revision_head : svn_opt_revision_base );
289    apr_pool_t *subpool = svn_pool_create(pool_);
290    apr_array_header_t * properties;
291    svn_error_t *err=svn_client_proplist2(&properties, path.c_str(), &peg,
292                                          &revision, false, context_, subpool);
293    if (err)
294      cleanup(err, subpool, "repository: svn_client_proplist2 failed");
295
296    for (int j = 0; j < properties->nelts; ++j) {
297      svn_client_proplist_item_t *item = 
298        ((svn_client_proplist_item_t **)properties->elts)[j];
299      for (apr_hash_index_t *hi = apr_hash_first(subpool, item->prop_hash); hi; 
300           hi = apr_hash_next (hi)) {
301        const void *key;
302        void *val;
303        apr_hash_this (hi, &key, NULL, &val);
304        svn_string_t *value;
305        err=svn_subst_detranslate_string(&value,
306                                         static_cast<const svn_string_t*>(val),
307                                         false, subpool);
308        if (err)
309          cleanup(err, subpool,
310                  path +
311                  " property: svn_subst_detranslate_string failed on key " +
312                  static_cast<const char*>(key));
313        property[static_cast<const char*>(key)]=value->data;
314      }
315    }
316    svn_pool_destroy(subpool);
317  }
318
319
320  SVN* SVN::instance(void)
321  {
322    if (!instance_)
323      throw SVNException("SVN::instance(void): SVN singleton not initialized");
324    return instance_;
325  }
326
327
328  SVN* SVN::instance(const std::string& url)
329  {
330    if (!instance_) {
331      instance_=new SVN(url);
332    }
333    return instance_;
334  }
335
336
337  svn_error_t* SVN::root_url_receiver(void *baton, const char *,
338                                      const svn_info_t *info, apr_pool_t*)
339  {
340    if (!info)
341      throw SVNException(std::string("SVN::url_receriver: ") +
342                         "Failed to acquire an svn info object");
343
344    root_url_receiver_baton* rurb=
345      static_cast<struct root_url_receiver_baton*>(baton);
346    if (info->repos_root_URL)
347      rurb->path=info->repos_root_URL;
348    return SVN_NO_ERROR;
349  }
350
351
352  SVN::vc_status SVN::version_controlled(const std::string& path)
353  {
354    svn_wc_status2_t* status=NULL;
355    apr_pool_t *subpool = svn_pool_create(pool_);
356    if (svn_error_t *err=
357        svn_wc_status2(&status,svn_path_internal_style(path.c_str(), subpool),
358                       adm_access_, subpool))
359      // cleanup will throw an exception
360      cleanup(err, subpool,"version_controlled(): svn_config_get_config failed");
361    svn_pool_destroy(subpool);
362
363    if ((status->text_status==svn_wc_status_none) ||
364        (status->text_status==svn_wc_status_unversioned))
365      return unversioned;
366    else if (status->text_status==svn_wc_status_normal)
367      return uptodate;
368
369    return unresolved;
370  }
371
372}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.