<?php
class
IDF_Scm_Svn
extends
IDF_Scm
{
public
$username
=
''
;
public
$password
=
''
;
private
$assoc
=
array
(
'dir'
=>
'tree'
,
'file'
=>
'blob'
);
public
function
__construct(
$repo
,
$project
=null)
{
$this
->repo =
$repo
;
$this
->project =
$project
;
$this
->cache[
'commitmess'
] =
array
();
}
public
function
isAvailable()
{
$cmd
=
$this
->svnCmd(
array
(
'info'
,
'--xml'
),
$this
->repo);
$xmlInfo
= self::shell_exec(
'IDF_Scm_Svn::isAvailable'
,
$cmd
);
try
{
$xml
= simplexml_load_string(
$xmlInfo
);
}
catch
(Exception
$e
) {
return
false;
}
if
(!isset(
$xml
->entry->commit[
'revision'
])) {
return
false;
}
if
(0 == (int)
$xml
->entry->commit[
'revision'
]) {
return
false;
}
return
true;
}
public
function
getRepositorySize()
{
if
(
strpos
(
$this
->repo,
'file://'
) !== 0) {
return
-1;
}
$cmd
= Pluf::f(
'idf_exec_cmd_prefix'
,
''
).
'du -sk '
.
escapeshellarg
(
substr
(
$this
->repo, 7));
$out
=
explode
(
' '
, self::shell_exec(
'IDF_Scm_Svn::getRepositorySize'
,
$cmd
), 2);
return
(int)
$out
[0]*1024;
}
public
function
findAuthor(
$author
)
{
$sql
=
new
Pluf_SQL(
'login=%s'
,
array
(trim(
$author
)));
$users
= Pluf::factory(
'Pluf_User'
)->getList(
array
(
'filter'
=>
$sql
->gen()));
return
(
$users
->
count
() > 0) ?
$users
[0] : null;
}
public
static
function
getAnonymousAccessUrl(
$project
,
$commit
=null)
{
$conf
=
$project
->getConf();
if
(false !== (
$url
=
$conf
->getVal(
'svn_remote_url'
, false))
&& !
empty
(
$url
)) {
return
$url
;
}
return
sprintf(Pluf::f(
'svn_remote_url'
),
$project
->shortname);
}
public
static
function
getAuthAccessUrl(
$project
,
$user
,
$commit
=null)
{
$conf
=
$project
->getConf();
if
(false !== (
$url
=
$conf
->getVal(
'svn_remote_url'
, false))
&& !
empty
(
$url
)) {
return
$url
;
}
return
sprintf(Pluf::f(
'svn_remote_url'
),
$project
->shortname);
}
public
static
function
factory(
$project
)
{
$conf
=
$project
->getConf();
if
(false !== (
$rep
=
$conf
->getVal(
'svn_remote_url'
, false))
&& !
empty
(
$rep
)) {
$scm
=
new
IDF_Scm_Svn(
$rep
,
$project
);
$scm
->username =
$conf
->getVal(
'svn_username'
);
$scm
->password =
$conf
->getVal(
'svn_password'
);
return
$scm
;
}
else
{
$rep
= sprintf(Pluf::f(
'svn_repositories'
),
$project
->shortname);
return
new
IDF_Scm_Svn(
$rep
,
$project
);
}
}
public
function
validateRevision(
$rev
)
{
if
(
$rev
==
'HEAD'
) {
return
IDF_Scm::REVISION_VALID;
}
$cmd
=
$this
->svnCmd(
array
(
'info'
),
$this
->repo,
$rev
);
self::
exec
(
'IDF_Scm_Svn::validateRevision'
,
$cmd
,
$out
,
$ret
);
if
(
$ret
== 0)
return
IDF_Scm::REVISION_VALID;
return
IDF_Scm::REVISION_INVALID;
}
public
function
testHash(
$rev
,
$path
=
''
)
{
if
(
$rev
===
'HEAD'
&&
$path
===
''
) {
return
'commit'
;
}
$cmd
=
$this
->svnCmd(
array
(
'info'
,
'--xml'
),
$this
->repo.
'/'
.self::smartEncode(
$path
),
$rev
);
$xmlInfo
= self::shell_exec(
'IDF_Scm_Svn::testHash'
,
$cmd
);
try
{
$xml
= simplexml_load_string(
$xmlInfo
);
}
catch
(Exception
$e
) {
return
false;
}
if
(!isset(
$xml
->entry)) {
return
false;
}
return
'commit'
;
}
public
function
getTree(
$commit
,
$folder
=
'/'
,
$branch
=null)
{
$cmd
=
$this
->svnCmd(
array
(
'ls'
,
'--xml'
),
$this
->repo.
'/'
.self::smartEncode(
$folder
),
$commit
);
$xml
= simplexml_load_string(self::shell_exec(
'IDF_Scm_Svn::getTree'
,
$cmd
));
$res
=
array
();
$folder
= (
strlen
(
$folder
)
and
(
$folder
!=
'/'
)) ?
$folder
.
'/'
:
''
;
foreach
(
$xml
->list->entry
as
$entry
) {
$file
=
array
();
$file
[
'type'
] =
$this
->assoc[(string)
$entry
[
'kind'
]];
$file
[
'file'
] = (string)
$entry
->name;
$file
[
'fullpath'
] =
$folder
.((string)
$entry
->name);
$file
[
'efullpath'
] = self::smartEncode(
$file
[
'fullpath'
]);
$file
[
'date'
] =
gmdate
(
'Y-m-d H:i:s'
,
strtotime
((string)
$entry
->commit->
date
));
$file
[
'rev'
] = (string)
$entry
->commit[
'revision'
];
$file
[
'log'
] =
$this
->getCommitMessage(
$file
[
'rev'
]);
if
(
$file
[
'type'
] ==
'blob'
) {
$file
[
'size'
] = (string)
$entry
->size;
}
$file
[
'author'
] = (string)
$entry
->commit->author;
$file
[
'perm'
] =
''
;
$res
[] = (object)
$file
;
}
return
$res
;
}
private
function
getCommitMessage(
$rev
=
'HEAD'
)
{
if
(isset(
$this
->cache[
'commitmess'
][
$rev
])) {
return
$this
->cache[
'commitmess'
][
$rev
];
}
$cmd
=
$this
->svnCmd(
array
(
'log'
,
'--xml'
,
'--limit'
,
'1'
),
$this
->repo,
$rev
);
try
{
$xml
= simplexml_load_string(self::shell_exec(
'IDF_Scm_Svn::getCommitMessage'
,
$cmd
));
$this
->cache[
'commitmess'
][
$rev
] = (string)
$xml
->logentry->msg;
}
catch
(Exception
$e
) {
$this
->cache[
'commitmess'
][
$rev
] =
''
;
}
return
$this
->cache[
'commitmess'
][
$rev
];
}
public
function
getPathInfo(
$filename
,
$rev
=null)
{
if
(
$rev
== null) {
$rev
=
'HEAD'
;
}
$cmd
=
$this
->svnCmd(
array
(
'info'
,
'--xml'
),
$this
->repo.
'/'
.self::smartEncode(
$filename
),
$rev
);
$xml
= simplexml_load_string(self::shell_exec(
'IDF_Scm_Svn::getPathInfo'
,
$cmd
));
if
(!isset(
$xml
->entry)) {
return
false;
}
$entry
=
$xml
->entry;
$file
=
array
();
$file
[
'fullpath'
] =
$filename
;
$file
[
'hash'
] = (string)
$entry
->repository->uuid;
$file
[
'type'
] =
$this
->assoc[(string)
$entry
[
'kind'
]];
$pathinfo
=
pathinfo
(
$filename
);
$file
[
'file'
] =
$pathinfo
[
'basename'
];
$file
[
'rev'
] =
$rev
;
$file
[
'author'
] = (string)
$entry
->author;
$file
[
'date'
] =
gmdate
(
'Y-m-d H:i:s'
,
strtotime
((string)
$entry
->commit->
date
));
$file
[
'size'
] = (string)
$entry
->size;
$file
[
'log'
] =
''
;
return
(object)
$file
;
}
public
function
getFile(
$def
,
$cmd_only
=false)
{
$cmd
=
$this
->svnCmd(
array
(
'cat'
),
$this
->repo.
'/'
.self::smartEncode(
$def
->fullpath),
$def
->rev);
return
(
$cmd_only
) ?
$cmd
: self::shell_exec(
'IDF_Scm_Svn::getFile'
,
$cmd
);
}
public
function
getBranches()
{
if
(isset(
$this
->cache[
'branches'
])) {
return
$this
->cache[
'branches'
];
}
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'ls'
),
$this
->repo.
'/branches'
,
'HEAD'
);
self::
exec
(
'IDF_Scm_Svn::getBranches'
,
$cmd
,
$out
,
$ret
);
if
(
$ret
== 0) {
foreach
(
$out
as
$entry
) {
if
(
substr
(trim(
$entry
), -1) ==
'/'
) {
$branch
=
substr
(trim(
$entry
), 0, -1);
$res
[
$branch
] =
'branches/'
.
$branch
;
}
}
}
ksort(
$res
);
$cmd
=
$this
->svnCmd(
array
(
'info'
),
$this
->repo.
'/trunk'
,
'HEAD'
);
self::
exec
(
'IDF_Scm_Svn::getBranches'
,
$cmd
,
$out
,
$ret
);
if
(
$ret
== 0) {
$res
=
array
(
'trunk'
=>
'trunk'
) +
$res
;
}
$this
->cache[
'branches'
] =
$res
;
return
$res
;
}
public
function
getTags()
{
if
(isset(
$this
->cache[
'tags'
])) {
return
$this
->cache[
'tags'
];
}
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'ls'
),
$this
->repo.
'/tags'
,
'HEAD'
);
self::
exec
(
'IDF_Scm_Svn::getTags'
,
$cmd
,
$out
,
$ret
);
if
(
$ret
== 0) {
foreach
(
$out
as
$entry
) {
if
(
substr
(trim(
$entry
), -1) ==
'/'
) {
$tag
=
substr
(trim(
$entry
), 0, -1);
$res
[
$tag
] =
'tags/'
.
$tag
;
}
}
}
ksort(
$res
);
$this
->cache[
'tags'
] =
$res
;
return
$res
;
}
public
function
getMainBranch()
{
return
'HEAD'
;
}
public
function
inBranches(
$commit
,
$path
)
{
foreach
(
$this
->getBranches()
as
$branch
=>
$bpath
) {
if
(
$bpath
and
0 ===
strpos
(
$path
,
$bpath
)) {
return
array
(
$branch
);
}
}
return
array
();
}
public
function
inTags(
$commit
,
$path
)
{
foreach
(
$this
->getTags()
as
$tag
=>
$tpath
) {
if
(
$tpath
and
0 ===
strpos
(
$path
,
$tpath
)) {
return
array
(
$tag
);
}
}
return
array
();
}
public
function
getCommit(
$commit
,
$getdiff
=false)
{
if
(
$this
->validateRevision(
$commit
) != IDF_Scm::REVISION_VALID) {
return
false;
}
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'log'
,
'--xml'
,
'--limit'
,
'1'
,
'-v'
),
$this
->repo,
$commit
);
$xmlRes
= self::shell_exec(
'IDF_Scm_Svn::getCommit'
,
$cmd
);
$xml
= simplexml_load_string(
$xmlRes
);
$res
[
'author'
] = (string)
$xml
->logentry->author;
$res
[
'date'
] =
gmdate
(
'Y-m-d H:i:s'
,
strtotime
((string)
$xml
->logentry->
date
));
$res
[
'title'
] = (string)
$xml
->logentry->msg;
$res
[
'commit'
] = (string)
$xml
->logentry[
'revision'
];
$res
[
'parents'
] =
$xml
->logentry[
'revision'
] > 1
?
array
((string)
$xml
->logentry[
'revision'
] - 1)
:
array
();
$res
[
'diff'
] = (
$getdiff
) ?
$this
->getDiff(
$commit
) :
''
;
$res
[
'tree'
] =
''
;
$res
[
'branch'
] =
''
;
return
(object)
$res
;
}
public
function
isCommitLarge(
$commit
=
'HEAD'
)
{
if
(
substr
(
$this
->repo, 0, 7) !=
'file://'
) {
return
false;
}
$repo
=
substr
(
$this
->repo, 7);
$cmd
= sprintf(Pluf::f(
'svnlook_path'
,
'svnlook'
).
' changed -r %s %s'
,
escapeshellarg
(
$commit
),
escapeshellarg
(
$repo
));
$cmd
= Pluf::f(
'idf_exec_cmd_prefix'
,
''
).
$cmd
;
$out
= self::shell_exec(
'IDF_Scm_Svn::isCommitLarge'
,
$cmd
);
$lines
= preg_split(
"/\015\012|\015|\012/"
,
$out
);
return
(
count
(
$lines
) > 100);
}
private
function
getDiff(
$rev
=
'HEAD'
)
{
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'diff'
,
'-c'
,
$rev
),
$this
->repo);
return
self::shell_exec(
'IDF_Scm_Svn::getDiff'
,
$cmd
);
}
public
function
getChanges(
$commit
)
{
if
(
$this
->validateRevision(
$commit
) != IDF_Scm::REVISION_VALID) {
return
null;
}
$cmd
=
$this
->svnCmd(
array
(
'log'
,
'--xml'
,
'-v'
),
$this
->repo,
$commit
);
$out
=
array
();
$out
= self::shell_exec(
'IDF_Scm_Svn::getChanges'
,
$cmd
);
$xml
= simplexml_load_string(
$out
);
if
(
count
(
$xml
) == 0) {
return
null;
}
$entry
= current(
$xml
);
$return
= (object)
array
(
'additions'
=>
array
(),
'deletions'
=>
array
(),
'patches'
=>
array
(),
'properties'
=>
array
(),
'copies'
=>
array
(),
'renames'
=>
array
(),
);
foreach
(
$entry
->paths->path
as
$p
) {
$path
= (string)
$p
;
foreach
(
$p
->attributes()
as
$k
=>
$v
) {
$key
= (string)
$k
;
$val
= (string)
$v
;
if
(
$key
!=
'action'
)
continue
;
if
(
$val
==
'M'
)
$return
->patches[] =
$path
;
else
if
(
$val
==
'A'
)
$return
->additions[] =
$path
;
else
if
(
$val
==
'D'
)
$return
->deletions[] =
$path
;
}
}
foreach
(
$entry
->paths->path
as
$p
) {
$trg
= (string)
$p
;
$src
= null;
foreach
(
$p
->attributes()
as
$k
=>
$v
) {
if
((string)
$k
==
'copyfrom-path'
) {
$src
= (string)
$v
;
break
;
}
}
if
(
$src
== null)
continue
;
$srcidx
=
array_search
(
$src
,
$return
->deletions);
$trgidx
=
array_search
(
$trg
,
$return
->additions);
if
(
$srcidx
!== false &&
$trgidx
!== false) {
$return
->renames[
$src
] =
$trg
;
unset(
$return
->deletions[
$srcidx
]);
unset(
$return
->additions[
$trgidx
]);
continue
;
}
if
(
$srcidx
=== false &&
$trgidx
!== false) {
$return
->copies[
$src
] =
$trg
;
unset(
$return
->additions[
$trgidx
]);
continue
;
}
}
return
$return
;
}
public
function
getChangeLog(
$rev
=null,
$n
=10)
{
if
(
$rev
!=
'HEAD'
and
!preg_match(
'/^\d+$/'
,
$rev
)) {
$rev
=
'HEAD'
;
}
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'log'
,
'--xml'
,
'-v'
,
'--limit'
,
$n
),
$this
->repo.
'@'
.
$rev
);
$xmlRes
= self::shell_exec(
'IDF_Scm_Svn::getChangeLog'
,
$cmd
);
$xml
= simplexml_load_string(
$xmlRes
);
foreach
(
$xml
->logentry
as
$entry
) {
$log
=
array
();
$log
[
'author'
] = (string)
$entry
->author;
$log
[
'date'
] =
gmdate
(
'Y-m-d H:i:s'
,
strtotime
((string)
$entry
->
date
));
$split
= preg_split(
"[\n\r]"
, (string)
$entry
->msg, 2);
$log
[
'title'
] =
$split
[0];
$log
[
'commit'
] = (string)
$entry
[
'revision'
];
$log
[
'full_message'
] = (isset(
$split
[1])) ? trim(
$split
[1]) :
''
;
$res
[] = (object)
$log
;
}
return
$res
;
}
public
function
getProperties(
$rev
,
$path
=
''
)
{
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'proplist'
,
'--xml'
),
$this
->repo.
'/'
.self::smartEncode(
$path
),
$rev
);
$xmlProps
= self::shell_exec(
'IDF_Scm_Svn::getProperties'
,
$cmd
);
$props
= simplexml_load_string(
$xmlProps
);
if
(!isset(
$props
->target)) {
return
$res
;
}
foreach
(
$props
->target->property
as
$prop
) {
$key
= (string)
$prop
[
'name'
];
$res
[
$key
] =
$this
->getProperty(
$key
,
$rev
,
$path
);
}
return
$res
;
}
private
function
getProperty(
$property
,
$rev
,
$path
=
''
)
{
$res
=
array
();
$cmd
=
$this
->svnCmd(
array
(
'propget'
,
$property
,
'--xml'
),
$this
->repo.
'/'
.self::smartEncode(
$path
),
$rev
);
$xmlProp
= self::shell_exec(
'IDF_Scm_Svn::getProperty'
,
$cmd
);
$prop
= simplexml_load_string(
$xmlProp
);
return
(string)
$prop
->target->property;
}
public
function
getLastCommit(
$rev
=
'HEAD'
)
{
$xmlInfo
=
''
;
$cmd
=
$this
->svnCmd(
array
(
'info'
,
'--xml'
),
$this
->repo,
$rev
);
$xmlInfo
= self::shell_exec(
'IDF_Scm_Svn::getLastCommit'
,
$cmd
);
$xml
= simplexml_load_string(
$xmlInfo
);
return
(string)
$xml
->entry->commit[
'revision'
];
}
private
function
svnCmd(
$args
=
array
(),
$repoarg
= null,
$revarg
= null)
{
$cmdline
=
array
();
$cmdline
[] = Pluf::f(
'idf_exec_cmd_prefix'
,
''
);
$cmdline
[] = Pluf::f(
'svn_path'
,
'svn'
);
$cmdline
[] =
'--no-auth-cache'
;
$cmdline
[] =
'--username='
.
escapeshellarg
(
$this
->username);
$cmdline
[] =
'--password='
.
escapeshellarg
(
$this
->password);
foreach
(
$args
as
$arg
) {
$cmdline
[] =
escapeshellarg
(
$arg
);
}
if
(
$repoarg
!= null) {
if
(
$revarg
!= null) {
$repoarg
.=
'@'
.
$revarg
;
}
$cmdline
[] =
escapeshellarg
(
$repoarg
);
}
if
(
$revarg
!= null) {
$cmdline
[] =
'--revision='
.
escapeshellarg
(
$revarg
);
}
return
implode(
' '
,
$cmdline
);
}
}