Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F122409453
DiffusionLowLevelResolveRefsQuery.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Jul 17, 17:44
Size
6 KB
Mime Type
text/x-php
Expires
Sat, Jul 19, 17:44 (2 d)
Engine
blob
Format
Raw Data
Handle
27468031
Attached To
rPH Phabricator
DiffusionLowLevelResolveRefsQuery.php
View Options
<?php
/**
* Resolves references (like short commit names, branch names, tag names, etc.)
* into canonical, stable commit identifiers. This query works for all
* repository types.
*/
final
class
DiffusionLowLevelResolveRefsQuery
extends
DiffusionLowLevelQuery
{
private
$refs
;
public
function
withRefs
(
array
$refs
)
{
$this
->
refs
=
$refs
;
return
$this
;
}
public
function
executeQuery
()
{
if
(!
$this
->
refs
)
{
return
array
();
}
switch
(
$this
->
getRepository
()->
getVersionControlSystem
())
{
case
PhabricatorRepositoryType
::
REPOSITORY_TYPE_GIT
:
$result
=
$this
->
resolveGitRefs
();
break
;
case
PhabricatorRepositoryType
::
REPOSITORY_TYPE_MERCURIAL
:
$result
=
$this
->
resolveMercurialRefs
();
break
;
case
PhabricatorRepositoryType
::
REPOSITORY_TYPE_SVN
:
$result
=
$this
->
resolveSubversionRefs
();
break
;
default
:
throw
new
Exception
(
'Unsupported repository type!'
);
}
return
$result
;
}
private
function
resolveGitRefs
()
{
$repository
=
$this
->
getRepository
();
$future
=
$repository
->
getLocalCommandFuture
(
'cat-file --batch-check'
);
$future
->
write
(
implode
(
"
\n
"
,
$this
->
refs
));
list
(
$stdout
)
=
$future
->
resolvex
();
$lines
=
explode
(
"
\n
"
,
rtrim
(
$stdout
,
"
\n
"
));
if
(
count
(
$lines
)
!==
count
(
$this
->
refs
))
{
throw
new
Exception
(
'Unexpected line count from `git cat-file`!'
);
}
$hits
=
array
();
$tags
=
array
();
$lines
=
array_combine
(
$this
->
refs
,
$lines
);
foreach
(
$lines
as
$ref
=>
$line
)
{
$parts
=
explode
(
' '
,
$line
);
if
(
count
(
$parts
)
<
2
)
{
throw
new
Exception
(
"Failed to parse `git cat-file` output: {$line}"
);
}
list
(
$identifier
,
$type
)
=
$parts
;
if
(
$type
==
'missing'
)
{
// This is either an ambiguous reference which resolves to several
// objects, or an invalid reference. For now, always treat it as
// invalid. It would be nice to resolve all possibilities for
// ambiguous references at some point, although the strategy for doing
// so isn't clear to me.
continue
;
}
switch
(
$type
)
{
case
'commit'
:
break
;
case
'tag'
:
$tags
[]
=
$identifier
;
break
;
default
:
throw
new
Exception
(
"Unexpected object type from `git cat-file`: {$line}"
);
}
$hits
[]
=
array
(
'ref'
=>
$ref
,
'type'
=>
$type
,
'identifier'
=>
$identifier
,
);
}
$tag_map
=
array
();
if
(
$tags
)
{
// If some of the refs were tags, just load every tag in order to figure
// out which commits they map to. This might be somewhat inefficient in
// repositories with a huge number of tags.
$tag_refs
=
id
(
new
DiffusionLowLevelGitRefQuery
())
->
setRepository
(
$repository
)
->
withIsTag
(
true
)
->
executeQuery
();
foreach
(
$tag_refs
as
$tag_ref
)
{
$tag_map
[
$tag_ref
->
getShortName
()]
=
$tag_ref
->
getCommitIdentifier
();
}
}
$results
=
array
();
foreach
(
$hits
as
$hit
)
{
$type
=
$hit
[
'type'
];
$ref
=
$hit
[
'ref'
];
$alternate
=
null
;
if
(
$type
==
'tag'
)
{
$alternate
=
$identifier
;
$identifier
=
idx
(
$tag_map
,
$ref
);
if
(!
$identifier
)
{
throw
new
Exception
(
"Failed to look up tag '{$ref}'!"
);
}
}
$result
=
array
(
'type'
=>
$type
,
'identifier'
=>
$identifier
,
);
if
(
$alternate
!==
null
)
{
$result
[
'alternate'
]
=
$alternate
;
}
$results
[
$ref
][]
=
$result
;
}
return
$results
;
}
private
function
resolveMercurialRefs
()
{
$repository
=
$this
->
getRepository
();
$futures
=
array
();
foreach
(
$this
->
refs
as
$ref
)
{
$futures
[
$ref
]
=
$repository
->
getLocalCommandFuture
(
'log --template=%s --rev %s'
,
'{node}'
,
hgsprintf
(
'%s'
,
$ref
));
}
$results
=
array
();
foreach
(
Futures
(
$futures
)
as
$ref
=>
$future
)
{
try
{
list
(
$stdout
)
=
$future
->
resolvex
();
}
catch
(
CommandException
$ex
)
{
if
(
preg_match
(
'/ambiguous identifier/'
,
$ex
->
getStdErr
()))
{
// This indicates that the ref ambiguously matched several things.
// Eventually, it would be nice to return all of them, but it is
// unclear how to best do that. For now, treat it as a miss instead.
continue
;
}
throw
$ex
;
}
// It doesn't look like we can figure out the type (commit/branch/rev)
// from this output very easily. For now, just call everything a commit.
$type
=
'commit'
;
$results
[
$ref
][]
=
array
(
'type'
=>
$type
,
'identifier'
=>
trim
(
$stdout
),
);
}
return
$results
;
}
private
function
resolveSubversionRefs
()
{
$repository
=
$this
->
getRepository
();
$max_commit
=
id
(
new
PhabricatorRepositoryCommit
())
->
loadOneWhere
(
'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1'
,
$repository
->
getID
());
if
(!
$max_commit
)
{
// This repository is empty or hasn't parsed yet, so none of the refs are
// going to resolve.
return
array
();
}
$max_commit_id
=
(
int
)
$max_commit
->
getCommitIdentifier
();
$results
=
array
();
foreach
(
$this
->
refs
as
$ref
)
{
if
(
$ref
==
'HEAD'
)
{
// Resolve "HEAD" to mean "the most recent commit".
$results
[
$ref
][]
=
array
(
'type'
=>
'commit'
,
'identifier'
=>
$max_commit_id
,
);
continue
;
}
if
(!
preg_match
(
'/^
\d
+$/'
,
$ref
))
{
// This ref is non-numeric, so it doesn't resolve to anything.
continue
;
}
// Resolve other commits if we can deduce their existence.
// TODO: When we import only part of a repository, we won't necessarily
// have all of the smaller commits. Should we fail to resolve them here
// for repositories with a subpath? It might let us simplify other things
// elsewhere.
if
((
int
)
$ref
<=
$max_commit_id
)
{
$results
[
$ref
][]
=
array
(
'type'
=>
'commit'
,
'identifier'
=>
(
int
)
$ref
,
);
}
}
return
$results
;
}
}
Event Timeline
Log In to Comment