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
;
61 inline AprAutoPool(apr_pool_t
*parent
= NULL
)
62 { pool
= svn_pool_create(parent
); }
64 { svn_pool_destroy(pool
); }
66 inline apr_pool_t
*data() const { return pool
; }
67 inline operator apr_pool_t
*() const { return pool
; }
73 MatchRuleList matchRules
;
74 RepositoryHash repositories
;
75 IdentityHash identities
;
77 SvnPrivate(const QString
&pathToRepository
);
79 int youngestRevision();
80 int exportRevision(int revnum
);
82 int openRepository(const QString
&pathToRepository
);
85 AprAutoPool global_pool
;
87 svn_revnum_t youngest_rev
;
90 void Svn::initialize()
92 // initialize APR or exit
93 if (apr_initialize() != APR_SUCCESS
) {
94 fprintf(stderr
, "You lose at apr_initialize().\n");
99 static struct Destructor
{ ~Destructor() { apr_terminate(); } } destructor
;
102 Svn::Svn(const QString
&pathToRepository
)
103 : d(new SvnPrivate(pathToRepository
))
112 void Svn::setMatchRules(const MatchRuleList
&matchRules
)
114 d
->matchRules
= matchRules
;
117 void Svn::setRepositories(const RepositoryHash
&repositories
)
119 d
->repositories
= repositories
;
122 int Svn::youngestRevision()
124 return d
->youngestRevision();
127 bool Svn::exportRevision(int revnum
)
129 return d
->exportRevision(revnum
) == EXIT_SUCCESS
;
132 SvnPrivate::SvnPrivate(const QString
&pathToRepository
)
135 openRepository(pathToRepository
);
137 // get the youngest revision
138 svn_fs_youngest_rev(&youngest_rev
, fs
, global_pool
);
141 SvnPrivate::~SvnPrivate()
143 svn_pool_destroy(global_pool
);
146 int SvnPrivate::youngestRevision()
151 int SvnPrivate::openRepository(const QString
&pathToRepository
)
154 SVN_ERR(svn_repos_open(&repos
, QFile::encodeName(pathToRepository
), global_pool
));
155 fs
= svn_repos_fs(repos
);
160 static int pathMode(svn_fs_root_t
*fs_root
, const char *pathname
, apr_pool_t
*pool
)
162 svn_string_t
*propvalue
;
163 SVN_ERR(svn_fs_node_prop(&propvalue
, fs_root
, pathname
, "svn:executable", pool
));
168 // maybe it's a symlink?
169 SVN_ERR(svn_fs_node_prop(&propvalue
, fs_root
, pathname
, "svn:special", pool
));
170 if (strcmp(propvalue
->data
, "symlink") == 0)
176 svn_error_t
*QIODevice_write(void *baton
, const char *data
, apr_size_t
*len
)
178 QIODevice
*device
= reinterpret_cast<QIODevice
*>(baton
);
179 device
->write(data
, *len
);
181 if (device
->bytesToWrite() > 16384)
182 device
->waitForBytesWritten(0);
186 static svn_stream_t
*streamForDevice(QIODevice
*device
, apr_pool_t
*pool
)
188 svn_stream_t
*stream
= svn_stream_create(device
, pool
);
189 svn_stream_set_write(stream
, QIODevice_write
);
194 static int dumpBlob(Repository::Transaction
*txn
, svn_fs_root_t
*fs_root
,
195 const char *pathname
, apr_pool_t
*pool
)
198 int mode
= pathMode(fs_root
, pathname
, pool
);
200 svn_filesize_t stream_length
;
202 SVN_ERR(svn_fs_file_length(&stream_length
, fs_root
, pathname
, pool
));
203 QIODevice
*io
= txn
->addFile(pathname
, mode
, stream_length
);
207 svn_stream_t
*in_stream
, *out_stream
;
208 SVN_ERR(svn_fs_file_contents(&in_stream
, fs_root
, pathname
, pool
));
210 // open a generic svn_stream_t for the QIODevice
211 out_stream
= streamForDevice(io
, pool
);
212 SVN_ERR(svn_stream_copy(in_stream
, out_stream
, pool
));
214 // print an ending newline
221 time_t get_epoch(char *svn_date
)
224 memset(&tm
, 0, sizeof tm
);
225 QByteArray
date(svn_date
, strlen(svn_date
) - 8);
226 strptime(date
, "%Y-%m-%dT%H:%M:%S", &tm
);
230 int SvnPrivate::exportRevision(int revnum
)
232 AprAutoPool
pool(global_pool
);
234 // open this revision:
235 svn_fs_root_t
*fs_root
;
236 SVN_ERR(svn_fs_revision_root(&fs_root
, fs
, revnum
, pool
));
237 qDebug() << "Exporting revision" << revnum
;
239 // find out what was changed in this revision:
240 QHash
<QString
, Repository::Transaction
*> transactions
;
242 SVN_ERR(svn_fs_paths_changed(&changes
, fs_root
, pool
));
243 AprAutoPool
revpool(pool
);
244 for (apr_hash_index_t
*i
= apr_hash_first(pool
, changes
); i
; i
= apr_hash_next(i
)) {
245 svn_pool_clear(revpool
);
249 apr_hash_this(i
, &vkey
, NULL
, &value
);
250 const char *key
= reinterpret_cast<const char *>(vkey
);
252 // is this a directory?
253 svn_boolean_t is_dir
;
254 SVN_ERR(svn_fs_is_dir(&is_dir
, fs_root
, key
, revpool
));
256 continue; // Git doesn't handle directories, so we don't either
258 QString current
= QString::fromUtf8(key
);
260 // find the first rule that matches this pathname
261 bool foundMatch
= false;
262 foreach (Rules::Match rule
, matchRules
)
263 if (rule
.rx
.exactMatch(current
)) {
265 QString repository
= current
;
266 QString branch
= current
;
267 QString path
= current
;
269 // do the replacement
270 repository
.replace(rule
.rx
, rule
.repository
);
271 branch
.replace(rule
.rx
, rule
.branch
);
272 path
.replace(rule
.rx
, rule
.path
);
274 qDebug() << "..." << current
<< "->"
275 << repository
<< branch
<< path
;
277 Repository::Transaction
*txn
= transactions
.value(repository
, 0);
279 Repository
*repo
= repositories
.value(repository
, 0);
281 qCritical() << "Rule" << rule
.rx
.pattern()
282 << "references unknown repository" << repository
;
286 QString svnprefix
= current
;
287 if (current
.endsWith(path
))
288 current
.chop(path
.length());
290 txn
= repo
->newTransaction(branch
, svnprefix
, revnum
);
294 transactions
.insert(repository
, txn
);
297 svn_fs_path_change_t
*change
= reinterpret_cast<svn_fs_path_change_t
*>(value
);
298 if (change
->change_kind
== svn_fs_path_change_delete
)
299 txn
->deleteFile(path
);
301 dumpBlob(txn
, fs_root
, key
, revpool
);
307 qCritical() << current
<< "did not match any rules; cannot continue";
311 svn_pool_clear(revpool
);
313 if (transactions
.isEmpty())
314 return EXIT_SUCCESS
; // no changes?
316 // now create the commit
317 apr_hash_t
*revprops
;
318 SVN_ERR(svn_fs_revision_proplist(&revprops
, fs
, revnum
, pool
));
319 svn_string_t
*svnauthor
= (svn_string_t
*)apr_hash_get(revprops
, "svn:author", APR_HASH_KEY_STRING
);
320 svn_string_t
*svndate
= (svn_string_t
*)apr_hash_get(revprops
, "svn:date", APR_HASH_KEY_STRING
);
321 svn_string_t
*svnlog
= (svn_string_t
*)apr_hash_get(revprops
, "svn:log", APR_HASH_KEY_STRING
);
323 QByteArray log
= (char *)svnlog
->data
;
324 QByteArray authorident
= identities
.value((char *)svnauthor
->data
);
325 time_t epoch
= get_epoch((char*)svndate
->data
);
326 if (authorident
.isEmpty()) {
327 if (!svnauthor
|| svn_string_isempty(svnauthor
))
328 authorident
= "nobody <nobody@localhost>";
330 authorident
= svnauthor
->data
+ QByteArray(" <") +
331 svnauthor
->data
+ QByteArray("@localhost>");
334 foreach (Repository::Transaction
*txn
, transactions
) {
335 txn
->setAuthor(authorident
);
336 txn
->setDateTime(epoch
);