2 * Copyright (C) 2007 Thiago Macieira <thiago@kde.org>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * Based on svn-fast-export by Chris Lee <clee@kde.org>
20 * License: MIT <http://www.opensource.org/licenses/mit-license.php>
21 * URL: git://repo.or.cz/fast-import.git http://repo.or.cz/w/fast-export.git
25 #define _LARGEFILE_SUPPORT
26 #define _LARGEFILE64_SUPPORT
37 #include <apr_getopt.h>
38 #include <apr_general.h>
41 #include <svn_pools.h>
42 #include <svn_repos.h>
43 #include <svn_types.h>
48 #include "repository.h"
51 #define SVN_ERR(expr) SVN_INT_ERR(expr)
53 typedef QList
<Rules::Match
> MatchRuleList
;
54 typedef QHash
<QString
, Repository
*> RepositoryHash
;
55 typedef QHash
<QByteArray
, QByteArray
> IdentityHash
;
60 AprAutoPool(const AprAutoPool
&);
61 AprAutoPool
&operator=(const AprAutoPool
&);
63 inline AprAutoPool(apr_pool_t
*parent
= NULL
)
64 { pool
= svn_pool_create(parent
); }
66 { svn_pool_destroy(pool
); }
68 inline void clear() { svn_pool_clear(pool
); }
69 inline apr_pool_t
*data() const { return pool
; }
70 inline operator apr_pool_t
*() const { return pool
; }
76 MatchRuleList matchRules
;
77 RepositoryHash repositories
;
78 IdentityHash identities
;
80 SvnPrivate(const QString
&pathToRepository
);
82 int youngestRevision();
83 int exportRevision(int revnum
);
85 int openRepository(const QString
&pathToRepository
);
88 AprAutoPool global_pool
;
90 svn_revnum_t youngest_rev
;
93 void Svn::initialize()
95 // initialize APR or exit
96 if (apr_initialize() != APR_SUCCESS
) {
97 fprintf(stderr
, "You lose at apr_initialize().\n");
102 static struct Destructor
{ ~Destructor() { apr_terminate(); } } destructor
;
105 Svn::Svn(const QString
&pathToRepository
)
106 : d(new SvnPrivate(pathToRepository
))
115 void Svn::setMatchRules(const MatchRuleList
&matchRules
)
117 d
->matchRules
= matchRules
;
120 void Svn::setRepositories(const RepositoryHash
&repositories
)
122 d
->repositories
= repositories
;
125 int Svn::youngestRevision()
127 return d
->youngestRevision();
130 bool Svn::exportRevision(int revnum
)
132 return d
->exportRevision(revnum
) == EXIT_SUCCESS
;
135 SvnPrivate::SvnPrivate(const QString
&pathToRepository
)
138 openRepository(pathToRepository
);
140 // get the youngest revision
141 svn_fs_youngest_rev(&youngest_rev
, fs
, global_pool
);
144 SvnPrivate::~SvnPrivate()
146 svn_pool_destroy(global_pool
);
149 int SvnPrivate::youngestRevision()
154 int SvnPrivate::openRepository(const QString
&pathToRepository
)
157 SVN_ERR(svn_repos_open(&repos
, QFile::encodeName(pathToRepository
), global_pool
));
158 fs
= svn_repos_fs(repos
);
163 static int pathMode(svn_fs_root_t
*fs_root
, const char *pathname
, apr_pool_t
*pool
)
165 svn_string_t
*propvalue
;
166 SVN_ERR(svn_fs_node_prop(&propvalue
, fs_root
, pathname
, "svn:executable", pool
));
171 // maybe it's a symlink?
172 SVN_ERR(svn_fs_node_prop(&propvalue
, fs_root
, pathname
, "svn:special", pool
));
173 if (propvalue
&& strcmp(propvalue
->data
, "symlink") == 0)
179 svn_error_t
*QIODevice_write(void *baton
, const char *data
, apr_size_t
*len
)
181 QIODevice
*device
= reinterpret_cast<QIODevice
*>(baton
);
182 device
->write(data
, *len
);
184 if (device
->bytesToWrite() > 16384)
185 device
->waitForBytesWritten(0);
189 static svn_stream_t
*streamForDevice(QIODevice
*device
, apr_pool_t
*pool
)
191 svn_stream_t
*stream
= svn_stream_create(device
, pool
);
192 svn_stream_set_write(stream
, QIODevice_write
);
197 static int dumpBlob(Repository::Transaction
*txn
, svn_fs_root_t
*fs_root
,
198 const char *pathname
, const QString
&finalPathName
, apr_pool_t
*pool
)
201 int mode
= pathMode(fs_root
, pathname
, pool
);
203 svn_filesize_t stream_length
;
205 SVN_ERR(svn_fs_file_length(&stream_length
, fs_root
, pathname
, pool
));
206 QIODevice
*io
= txn
->addFile(finalPathName
, mode
, stream_length
);
210 svn_stream_t
*in_stream
, *out_stream
;
211 SVN_ERR(svn_fs_file_contents(&in_stream
, fs_root
, pathname
, pool
));
213 // open a generic svn_stream_t for the QIODevice
214 out_stream
= streamForDevice(io
, pool
);
215 SVN_ERR(svn_stream_copy(in_stream
, out_stream
, pool
));
217 // print an ending newline
224 static int recursiveDumpDir(Repository::Transaction
*txn
, svn_fs_root_t
*fs_root
,
225 const QByteArray
&pathname
, const QString
&finalPathName
,
228 // get the dir listing
230 SVN_ERR(svn_fs_dir_entries(&entries
, fs_root
, pathname
, pool
));
231 AprAutoPool
dirpool(pool
);
233 for (apr_hash_index_t
*i
= apr_hash_first(pool
, entries
); i
; i
= apr_hash_next(i
)) {
237 apr_hash_this(i
, &vkey
, NULL
, &value
);
239 svn_fs_dirent_t
*dirent
= reinterpret_cast<svn_fs_dirent_t
*>(value
);
240 QByteArray entryName
= pathname
+ '/' + dirent
->name
;
241 QString entryFinalName
= finalPathName
+ '/' + dirent
->name
;
243 if (dirent
->kind
== svn_node_dir
) {
244 if (recursiveDumpDir(txn
, fs_root
, entryName
, entryFinalName
, dirpool
) == EXIT_FAILURE
)
246 } else if (dirent
->kind
== svn_node_file
) {
247 if (dumpBlob(txn
, fs_root
, entryName
, entryFinalName
, dirpool
) == EXIT_FAILURE
)
253 time_t get_epoch(char *svn_date
)
256 memset(&tm
, 0, sizeof tm
);
257 QByteArray
date(svn_date
, strlen(svn_date
) - 8);
258 strptime(date
, "%Y-%m-%dT%H:%M:%S", &tm
);
262 int SvnPrivate::exportRevision(int revnum
)
264 AprAutoPool
pool(global_pool
.data());
266 // open this revision:
267 qDebug() << "Exporting revision" << revnum
;
268 svn_fs_root_t
*fs_root
;
269 SVN_ERR(svn_fs_revision_root(&fs_root
, fs
, revnum
, pool
));
271 // find out what was changed in this revision:
272 QHash
<QString
, Repository::Transaction
*> transactions
;
274 SVN_ERR(svn_fs_paths_changed(&changes
, fs_root
, pool
));
275 AprAutoPool
revpool(pool
.data());
276 for (apr_hash_index_t
*i
= apr_hash_first(pool
, changes
); i
; i
= apr_hash_next(i
)) {
281 apr_hash_this(i
, &vkey
, NULL
, &value
);
282 const char *key
= reinterpret_cast<const char *>(vkey
);
284 // is this a directory?
285 svn_boolean_t is_dir
;
286 SVN_ERR(svn_fs_is_dir(&is_dir
, fs_root
, key
, revpool
));
288 // was this directory copied from somewhere?
289 svn_revnum_t rev_from
;
290 const char *path_from
;
291 SVN_ERR(svn_fs_copied_from(&rev_from
, &path_from
, fs_root
, key
, revpool
));
293 if (path_from
== NULL
)
294 // no, it's a new directory being added
295 // Git doesn't handle directories, so we don't either
298 qDebug() << "..." << key
<< "was copied from" << path_from
;
301 QString current
= QString::fromUtf8(key
);
303 // find the first rule that matches this pathname
304 bool foundMatch
= false;
305 foreach (Rules::Match rule
, matchRules
) {
306 if (rule
.rx
.exactMatch(current
)) {
308 QString repository
= current
;
309 QString branch
= current
;
310 QString path
= current
;
312 // do the replacement
313 repository
.replace(rule
.rx
, rule
.repository
);
314 branch
.replace(rule
.rx
, rule
.branch
);
315 path
.replace(rule
.rx
, rule
.path
);
317 qDebug() << "..." << qPrintable(current
) << "rev" << revnum
<< "->"
318 << qPrintable(repository
) << qPrintable(branch
) << qPrintable(path
);
320 Repository::Transaction
*txn
= transactions
.value(repository
, 0);
322 Repository
*repo
= repositories
.value(repository
, 0);
324 qCritical() << "Rule" << rule
.rx
.pattern()
325 << "references unknown repository" << repository
;
329 QString svnprefix
= current
;
330 if (svnprefix
.endsWith(path
))
331 svnprefix
.chop(path
.length());
333 txn
= repo
->newTransaction(branch
, svnprefix
, revnum
);
337 transactions
.insert(repository
, txn
);
340 svn_fs_path_change_t
*change
= reinterpret_cast<svn_fs_path_change_t
*>(value
);
341 if (change
->change_kind
== svn_fs_path_change_delete
)
342 txn
->deleteFile(path
);
344 dumpBlob(txn
, fs_root
, key
, path
, revpool
);
346 recursiveDumpDir(txn
, fs_root
, key
, path
, revpool
);
353 qCritical() << current
<< "did not match any rules; cannot continue";
359 if (transactions
.isEmpty())
360 return EXIT_SUCCESS
; // no changes?
362 // now create the commit
363 apr_hash_t
*revprops
;
364 SVN_ERR(svn_fs_revision_proplist(&revprops
, fs
, revnum
, pool
));
365 svn_string_t
*svnauthor
= (svn_string_t
*)apr_hash_get(revprops
, "svn:author", APR_HASH_KEY_STRING
);
366 svn_string_t
*svndate
= (svn_string_t
*)apr_hash_get(revprops
, "svn:date", APR_HASH_KEY_STRING
);
367 svn_string_t
*svnlog
= (svn_string_t
*)apr_hash_get(revprops
, "svn:log", APR_HASH_KEY_STRING
);
369 QByteArray log
= (char *)svnlog
->data
;
370 QByteArray authorident
= identities
.value((char *)svnauthor
->data
);
371 time_t epoch
= get_epoch((char*)svndate
->data
);
372 if (authorident
.isEmpty()) {
373 if (!svnauthor
|| svn_string_isempty(svnauthor
))
374 authorident
= "nobody <nobody@localhost>";
376 authorident
= svnauthor
->data
+ QByteArray(" <") +
377 svnauthor
->data
+ QByteArray("@localhost>");
380 foreach (Repository::Transaction
*txn
, transactions
) {
381 txn
->setAuthor(authorident
);
382 txn
->setDateTime(epoch
);