source: trunk/lib/SVN.cc

Last change on this file was 1590, checked in by Peter Johansson, 6 years ago

avoid compiler warning with gcc 4.8.3

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.4 KB
Line 
1// $Id: SVN.cc 1590 2015-08-20 07:28:13Z peter $
2
3/*
4  Copyright (C) 2006 Jari Häkkinen
5  Copyright (C) 2007, 2008 Jari Häkkinen, Peter Johansson
6  Copyright (C) 2010, 2011, 2012 Peter Johansson
7  Copyright (C) 2015 Jari Häkkinen
8
9  This file is part of svndigest, http://dev.thep.lu.se/svndigest
10
11  svndigest is free software; you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation; either version 3 of the License, or
14  (at your option) any later version.
15
16  svndigest is distributed in the hope that it will be useful, but
17  WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  General Public License for more details.
20
21  You should have received a copy of the GNU General Public License
22  along with svndigest. If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#include "SVN.h"
26
27#include "utility.h"
28
29#include <map>
30#include <string>
31#include <vector>
32
33#include <cassert>
34
35#include <apr_allocator.h>
36#include <apr_hash.h>
37#include <subversion-1/svn_client.h>
38#include <subversion-1/svn_cmdline.h>
39#include <subversion-1/svn_path.h>
40#include <subversion-1/svn_pools.h>
41#include <subversion-1/svn_wc.h>
42#include <subversion-1/svn_subst.h>
43
44#include <string>
45
46#include <iostream>
47namespace theplu {
48namespace svndigest {
49
50
51  SVNException::SVNException(const std::string& msg, svn_error_t* error)
52    : std::runtime_error(""), error_(error)
53  {
54    ref_count_ = new int(1);
55    if (error_) {
56      apr_size_t bufsize=255;
57      char buf[255];
58      msg_ = svn_err_best_message(error_, buf, bufsize);
59    }
60    if (msg.size()) {
61      msg_ += msg;
62    }
63  }
64
65  SVNException::SVNException(const SVNException& other)
66    : std::runtime_error(other), error_(other.error_), msg_(other.msg_),
67      ref_count_(other.ref_count_)
68  {
69    ++(*ref_count_);
70  }
71
72
73  SVNException::~SVNException(void) throw()
74  {
75    --(*ref_count_);
76    if (!(*ref_count_)) {
77      delete ref_count_;
78      ref_count_=NULL;
79      svn_error_clear(error_);
80      error_=SVN_NO_ERROR;
81    }
82  }
83
84
85  const char* SVNException::what(void) const throw()
86  {
87    return msg_.c_str();
88  }
89
90
91  const svn_error_t* SVNException::error(void) const
92  {
93    return error_;
94  }
95
96
97  SVN* SVN::instance_=NULL;
98
99
100  SVN::SVN(const std::string& path)
101    : adm_access_(NULL), allocator_(NULL), context_(NULL), pool_(NULL)
102  {
103    svn_error_t* err=NULL;
104
105    // From http://subversion.apache.org: Applications using the
106    // Subversion libraries must call apr_initialize() before calling
107    // any Subversion functions.
108    apr_initialize();
109
110    // The subversion API documentation says that svn_cmdline_init
111    // will iSet up the locale for character conversion, and
112    // initialize APR". The documentation goes on to say that this
113    // function should only be called once before any other APR and
114    // subversion functions. This of course is in conflict with the
115    // apr_initialize() recommendation above.
116    if (svn_cmdline_init("svndigest",stderr) != EXIT_SUCCESS)
117      throw SVNException("SVN: svn_cmdline_init failed");
118
119    /// create top-level pool
120    if (apr_allocator_create(&allocator_))
121      throw SVNException("SVN(_allocator_create failed");
122    apr_allocator_max_free_set(allocator_,SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
123    pool_ = svn_pool_create_ex(NULL, allocator_);
124    apr_allocator_owner_set(allocator_, pool_);
125
126    // initialize the repository access library
127    if ((err=svn_ra_initialize(pool_)))
128      cleanup_failed_init(err, "SVN: svn_ra_initialize failed");
129
130    // Check that users .subversion exist. Should this really be done?
131    // If this check is to be done, we might be forced to support a
132    // command line option to change the location of the .subversion
133    // stuff (compare with the svn binary).
134    if ((err=svn_config_ensure(NULL, pool_)))
135      cleanup_failed_init(err, "SVN: svn_config_ensure failed");
136
137    // create a client context object
138    if ((err=svn_client_create_context(&context_, pool_)))
139      cleanup_failed_init(err, "SVN: svn_client_create_context failed");
140
141    if ((err=svn_config_get_config(&(context_->config), NULL, pool_)))
142      cleanup_failed_init(err, "SVN: svn_config_get_config failed");
143
144    // set up authentication stuff
145    if ((err=svn_cmdline_setup_auth_baton(&(context_->auth_baton), false, NULL,
146                                          NULL, NULL, false,
147                        static_cast<svn_config_t*>(apr_hash_get(context_->config,
148                                                    SVN_CONFIG_CATEGORY_CONFIG,
149                                                    APR_HASH_KEY_STRING)),
150                                          context_->cancel_func,
151                                          context_->cancel_baton, pool_)))
152      cleanup_failed_init(err, "SVN: svn_cmdline_setup_auth_baton failed");
153
154    // Set up svn administration area access. The whole structure is
155    // setup, maybe this is unnecessary?
156    const char* canonical_path=svn_path_internal_style(path.c_str(), pool_);
157    abs_wc_root_path_ = absolute_path(canonical_path);
158    if ((err=svn_wc_adm_open3(&adm_access_, NULL, canonical_path,
159                              false, -1, context_->cancel_func,
160                              context_->cancel_baton, pool_))){
161      if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
162        cleanup_failed_init(err, std::string(err->message));
163      cleanup_failed_init(err, "SVN: svn_wc_adm_open3 failed");
164    }
165
166    // get a session to the repository
167    struct url_receiver_baton urb;
168    client_info(path, url_receiver, static_cast<void*>(&urb));
169    if ((err=svn_client_open_ra_session(&ra_session_,
170                                        urb.root_url.c_str(),
171                                        context_, pool_)))
172      cleanup_failed_init(err, "SVN: svn_client_open_ra_session failed");
173
174    // extract and store URL relative to root URL
175    relative_url_ = relative_path(urb.url, urb.root_url);
176  }
177
178
179  SVN::~SVN(void)
180  {
181    if (adm_access_)
182      svn_error_clear(svn_wc_adm_close(adm_access_));
183    svn_pool_destroy(pool_);
184    pool_=NULL;
185    // other apr resources acquired in svn_cmdline_init are destroyed
186    // at program exit, ok since SVN is a singleton
187  }
188
189
190  void SVN::cleanup_failed_init(svn_error_t *err, const std::string& message)
191  {
192    assert(message.size()); // compatible with r1213
193    svn_error_clear(err);
194    if (pool_) {
195      svn_pool_destroy(pool_);
196      pool_=NULL;
197    }
198    throw SVNException(message);
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    AprPool subpool(pool_);
233    svn_error_t* err =
234      svn_client_blame3(path.c_str(), &peg, &start, &head,
235                        svn_diff_file_options_create(subpool.get()),
236                        false, receiver, baton, context_,
237                        subpool.get());
238    if (err)
239      throw SVNException("SVN::client_blame: svn_client_blame3 failed", err);
240  }
241
242
243  void SVN::client_cat(const std::string& path, svn_revnum_t rev,
244                       std::string& result)
245  {
246    AprPool subpool(pool_);
247    svn_stringbuf_t* str = svn_stringbuf_create("", subpool.get());
248    assert(str);
249    svn_stream_t* out = svn_stream_from_stringbuf(str, subpool.get());
250    assert(out);
251
252    svn_error_t* err = NULL;
253
254    // We need the path relative to the root of the ra_session_, which
255    // was created from root URL. The path is typically relative to
256    // 'URL' (or absolute) so we need to prepend relative_url_
257    // (typically 'trunk/') and potentially strip off abs_wc_root_path_.
258    std::string abs_path = absolute_path(path);
259    std::string repo_path =
260      concatenate_path(relative_url_,
261                       relative_path(abs_path, abs_wc_root_path_));
262
263    err = svn_ra_get_file(ra_session_, repo_path.c_str(), rev, out,
264                          NULL, NULL, subpool.get());
265
266    if (err)
267      SVNException("client_cat failed", err);
268
269    // copy to result
270    result = std::string(str->data, str->len);
271  }
272
273
274  void SVN::client_info(const std::string& path, svn_info_receiver_t receiver,
275                        void *baton)
276  {
277    AprPool subpool(pool_);
278    if (svn_error_t *err=svn_client_info(path.c_str(), NULL, NULL, receiver,
279                                         baton, false, context_,subpool.get()))
280      throw SVNException("repository: svn_client_info failed", err);
281  }
282
283
284  void SVN::client_log(const std::string& path,
285                       svn_log_message_receiver_t receiver, void *baton)
286  {
287    // Allocate space in subpool to pool_ for apr_path (here a string).
288    AprPool subpool(pool_);
289    apr_array_header_t* apr_path=apr_array_make(subpool.get(),1,4);
290    // Copy path to apr_path.
291    (*((const char **) apr_array_push(apr_path))) =
292      apr_pstrdup(subpool.get(),
293                  svn_path_internal_style(path.c_str(), subpool.get()));
294
295    // Setup to retrieve all commit logs.
296    svn_opt_revision_t peg, start, head;
297    peg.kind=svn_opt_revision_unspecified;
298    start.kind=svn_opt_revision_number;
299    start.value.number=0;
300    head.kind = ( svn_path_is_url(path.c_str()) ?
301                  svn_opt_revision_head : svn_opt_revision_base );
302    svn_error_t* err=NULL;
303    if ((err=svn_client_log3(apr_path, &peg, &start, &head, 0, false, false,
304                             receiver, baton, context_, subpool.get())))
305      throw SVNException("commit_dates: svn_client_log3 failed", err);
306  }
307
308
309  void SVN::client_proplist(const std::string& path,
310                            std::map<std::string, std::string>& property)
311  {
312    svn_opt_revision_t peg, revision;
313    peg.kind=svn_opt_revision_unspecified;
314    revision.kind = ( svn_path_is_url(path.c_str()) ?
315                      svn_opt_revision_head : svn_opt_revision_base );
316    AprPool subpool(pool_);
317    apr_array_header_t * properties;
318    svn_error_t *err=svn_client_proplist2(&properties, path.c_str(), &peg,
319                                          &revision, false, context_,
320                                          subpool.get());
321    if (err)
322      throw SVNException("repository: svn_client_proplist2 failed", err);
323
324    for (int j = 0; j < properties->nelts; ++j) {
325      svn_client_proplist_item_t *item =
326        ((svn_client_proplist_item_t **)properties->elts)[j];
327      for (apr_hash_index_t *hi =apr_hash_first(subpool.get(),item->prop_hash);
328           hi; hi = apr_hash_next (hi)) {
329        const void *key;
330        void *val;
331        apr_hash_this (hi, &key, NULL, &val);
332        svn_string_t *value;
333        err=svn_subst_detranslate_string(&value,
334                                         static_cast<const svn_string_t*>(val),
335                                         false, subpool.get());
336        if (err)
337          throw SVNException(path +
338                  " property: svn_subst_detranslate_string failed on key " +
339                             static_cast<const char*>(key), err);
340        property[static_cast<const char*>(key)]=value->data;
341      }
342    }
343  }
344
345
346  SVN* SVN::instance(void)
347  {
348    if (!instance_)
349      throw SVNException("SVN::instance(void): SVN singleton not initialized");
350    return instance_;
351  }
352
353
354  SVN* SVN::instance(const std::string& url)
355  {
356    if (!instance_) {
357      instance_=new SVN(url);
358    }
359    return instance_;
360  }
361
362
363  svn_error_t* SVN::url_receiver(void *baton, const char *,
364                                 const svn_info_t *info, apr_pool_t*)
365  {
366    if (!info)
367      throw SVNException(std::string("SVN::url_receriver: ") +
368                         "Failed to acquire an svn info object");
369
370    url_receiver_baton* rurb=
371      static_cast<struct url_receiver_baton*>(baton);
372    if (info->repos_root_URL)
373      rurb->root_url = info->repos_root_URL;
374    if (info->URL)
375      rurb->url = info->URL;
376    return SVN_NO_ERROR;
377  }
378
379
380  SVN::vc_status SVN::version_controlled(const std::string& path)
381  {
382    svn_wc_status2_t* status=NULL;
383    AprPool subpool(pool_);
384    if (svn_error_t *err=
385        svn_wc_status2(&status,
386                       svn_path_internal_style(path.c_str(), subpool.get()),
387                       adm_access_, subpool.get()))
388      throw SVNException("version_controlled(): svn_config_get_config failed",
389                         err);
390
391    if ((status->text_status==svn_wc_status_none) ||
392        (status->text_status==svn_wc_status_unversioned))
393      return unversioned;
394    else if (status->text_status==svn_wc_status_normal)
395      return uptodate;
396
397    return unresolved;
398  }
399
400
401  SVN::AprPool::AprPool(apr_pool_t* pool)
402    : pool_(NULL)
403  {
404    pool_ = svn_pool_create(pool);
405    assert(pool_);
406  }
407
408
409  SVN::AprPool::~AprPool(void)
410  {
411    if (pool_)
412      svn_pool_destroy(pool_);
413  }
414
415
416  apr_pool_t* SVN::AprPool::get(void)
417  {
418    return pool_;
419  }
420
421}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.