source: trunk/lib/SVN.cc @ 1547

Last change on this file since 1547 was 1547, checked in by Peter Johansson, 9 years ago

refs #518. Implement SVNcat using 'svn_ra_get_file' which does not expand svn:keywords.

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