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 time_t get_epoch(char *svn_date
)
227 memset(&tm
, 0, sizeof tm
);
228 QByteArray
date(svn_date
, strlen(svn_date
) - 8);
229 strptime(date
, "%Y-%m-%dT%H:%M:%S", &tm
);
233 int SvnPrivate::exportRevision(int revnum
)
235 AprAutoPool
pool(global_pool
.data());
237 // open this revision:
238 qDebug() << "Exporting revision" << revnum
;
239 svn_fs_root_t
*fs_root
;
240 SVN_ERR(svn_fs_revision_root(&fs_root
, fs
, revnum
, pool
));
242 // find out what was changed in this revision:
243 QHash
<QString
, Repository::Transaction
*> transactions
;
245 SVN_ERR(svn_fs_paths_changed(&changes
, fs_root
, pool
));
246 AprAutoPool
revpool(pool
.data());
247 for (apr_hash_index_t
*i
= apr_hash_first(pool
, changes
); i
; i
= apr_hash_next(i
)) {
251 apr_hash_this(i
, &vkey
, NULL
, &value
);
252 const char *key
= reinterpret_cast<const char *>(vkey
);
254 // is this a directory?
255 svn_boolean_t is_dir
;
256 SVN_ERR(svn_fs_is_dir(&is_dir
, fs_root
, key
, revpool
));
258 continue; // Git doesn't handle directories, so we don't either
260 QString current
= QString::fromUtf8(key
);
262 // find the first rule that matches this pathname
263 bool foundMatch
= false;
264 foreach (Rules::Match rule
, matchRules
) {
265 if (rule
.rx
.exactMatch(current
)) {
267 QString repository
= current
;
268 QString branch
= current
;
269 QString path
= current
;
271 // do the replacement
272 repository
.replace(rule
.rx
, rule
.repository
);
273 branch
.replace(rule
.rx
, rule
.branch
);
274 path
.replace(rule
.rx
, rule
.path
);
276 qDebug() << "..." << current
<< "rev" << revnum
<< "->"
277 << repository
<< branch
<< path
;
279 Repository::Transaction
*txn
= transactions
.value(repository
, 0);
281 Repository
*repo
= repositories
.value(repository
, 0);
283 qCritical() << "Rule" << rule
.rx
.pattern()
284 << "references unknown repository" << repository
;
288 QString svnprefix
= current
;
289 if (svnprefix
.endsWith(path
))
290 svnprefix
.chop(path
.length());
292 txn
= repo
->newTransaction(branch
, svnprefix
, revnum
);
296 transactions
.insert(repository
, txn
);
299 svn_fs_path_change_t
*change
= reinterpret_cast<svn_fs_path_change_t
*>(value
);
300 if (change
->change_kind
== svn_fs_path_change_delete
)
301 txn
->deleteFile(path
);
303 dumpBlob(txn
, fs_root
, key
, path
, revpool
);
310 qCritical() << current
<< "did not match any rules; cannot continue";
316 if (transactions
.isEmpty())
317 return EXIT_SUCCESS
; // no changes?
319 // now create the commit
320 apr_hash_t
*revprops
;
321 SVN_ERR(svn_fs_revision_proplist(&revprops
, fs
, revnum
, pool
));
322 svn_string_t
*svnauthor
= (svn_string_t
*)apr_hash_get(revprops
, "svn:author", APR_HASH_KEY_STRING
);
323 svn_string_t
*svndate
= (svn_string_t
*)apr_hash_get(revprops
, "svn:date", APR_HASH_KEY_STRING
);
324 svn_string_t
*svnlog
= (svn_string_t
*)apr_hash_get(revprops
, "svn:log", APR_HASH_KEY_STRING
);
326 QByteArray log
= (char *)svnlog
->data
;
327 QByteArray authorident
= identities
.value((char *)svnauthor
->data
);
328 time_t epoch
= get_epoch((char*)svndate
->data
);
329 if (authorident
.isEmpty()) {
330 if (!svnauthor
|| svn_string_isempty(svnauthor
))
331 authorident
= "nobody <nobody@localhost>";
333 authorident
= svnauthor
->data
+ QByteArray(" <") +
334 svnauthor
->data
+ QByteArray("@localhost>");
337 foreach (Repository::Transaction
*txn
, transactions
) {
338 txn
->setAuthor(authorident
);
339 txn
->setDateTime(epoch
);