Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F93460426
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, Nov 28, 22:54
Size
8 KB
Mime Type
text/x-php
Expires
Sat, Nov 30, 22:54 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
22644429
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.
*
* This query will always resolve refs which can be resolved, but may need to
* perform VCS operations. A faster (but less complete) counterpart query is
* available in @{class:DiffusionCachedResolveRefsQuery}; that query can
* resolve most refs without VCS operations.
*/
final
class
DiffusionLowLevelResolveRefsQuery
extends
DiffusionLowLevelQuery
{
private
$refs
;
private
$types
;
public
function
withRefs
(
array
$refs
)
{
$this
->
refs
=
$refs
;
return
$this
;
}
public
function
withTypes
(
array
$types
)
{
$this
->
types
=
$types
;
return
$this
;
}
protected
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
(
pht
(
'Unsupported repository type!'
));
}
if
(
$this
->
types
!==
null
)
{
$result
=
$this
->
filterRefsByType
(
$result
,
$this
->
types
);
}
return
$result
;
}
private
function
resolveGitRefs
()
{
$repository
=
$this
->
getRepository
();
$unresolved
=
array_fuse
(
$this
->
refs
);
$results
=
array
();
// First, resolve branches and tags.
$ref_map
=
id
(
new
DiffusionLowLevelGitRefQuery
())
->
setRepository
(
$repository
)
->
withIsTag
(
true
)
->
withIsOriginBranch
(
true
)
->
execute
();
$ref_map
=
mgroup
(
$ref_map
,
'getShortName'
);
$tag_prefix
=
'refs/tags/'
;
foreach
(
$unresolved
as
$ref
)
{
if
(
empty
(
$ref_map
[
$ref
]))
{
continue
;
}
foreach
(
$ref_map
[
$ref
]
as
$result
)
{
$fields
=
$result
->
getRawFields
();
$objectname
=
idx
(
$fields
,
'refname'
);
if
(!
strncmp
(
$objectname
,
$tag_prefix
,
strlen
(
$tag_prefix
)))
{
$type
=
'tag'
;
}
else
{
$type
=
'branch'
;
}
$info
=
array
(
'type'
=>
$type
,
'identifier'
=>
$result
->
getCommitIdentifier
(),
);
if
(
$type
==
'tag'
)
{
$alternate
=
idx
(
$fields
,
'objectname'
);
if
(
$alternate
)
{
$info
[
'alternate'
]
=
$alternate
;
}
}
$results
[
$ref
][]
=
$info
;
}
unset
(
$unresolved
[
$ref
]);
}
// If we resolved everything, we're done.
if
(!
$unresolved
)
{
return
$results
;
}
// Try to resolve anything else. This stuff either doesn't exist or is
// some ref like "HEAD^^^".
$future
=
$repository
->
getLocalCommandFuture
(
'cat-file --batch-check'
);
$future
->
write
(
implode
(
"
\n
"
,
$unresolved
));
list
(
$stdout
)
=
$future
->
resolvex
();
$lines
=
explode
(
"
\n
"
,
rtrim
(
$stdout
,
"
\n
"
));
if
(
count
(
$lines
)
!==
count
(
$unresolved
))
{
throw
new
Exception
(
pht
(
'Unexpected line count from `%s`!'
,
'git cat-file'
));
}
$hits
=
array
();
$tags
=
array
();
$lines
=
array_combine
(
$unresolved
,
$lines
);
foreach
(
$lines
as
$ref
=>
$line
)
{
$parts
=
explode
(
' '
,
$line
);
if
(
count
(
$parts
)
<
2
)
{
throw
new
Exception
(
pht
(
'Failed to parse `%s` output: %s'
,
'git cat-file'
,
$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
(
pht
(
'Unexpected object type from `%s`: %s'
,
'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
(
pht
(
"Failed to look up tag '%s'!"
,
$ref
));
}
}
$result
=
array
(
'type'
=>
$type
,
'identifier'
=>
$identifier
,
);
if
(
$alternate
!==
null
)
{
$result
[
'alternate'
]
=
$alternate
;
}
$results
[
$ref
][]
=
$result
;
}
return
$results
;
}
private
function
resolveMercurialRefs
()
{
$repository
=
$this
->
getRepository
();
// First, pull all of the branch heads in the repository. Doing this in
// bulk is much faster than querying each individual head if we're
// checking even a small number of refs.
$branches
=
id
(
new
DiffusionLowLevelMercurialBranchesQuery
())
->
setRepository
(
$repository
)
->
executeQuery
();
$branches
=
mgroup
(
$branches
,
'getShortName'
);
$results
=
array
();
$unresolved
=
$this
->
refs
;
foreach
(
$unresolved
as
$key
=>
$ref
)
{
if
(
empty
(
$branches
[
$ref
]))
{
continue
;
}
foreach
(
$branches
[
$ref
]
as
$branch
)
{
$fields
=
$branch
->
getRawFields
();
$results
[
$ref
][]
=
array
(
'type'
=>
'branch'
,
'identifier'
=>
$branch
->
getCommitIdentifier
(),
'closed'
=>
idx
(
$fields
,
'closed'
,
false
),
);
}
unset
(
$unresolved
[
$key
]);
}
if
(!
$unresolved
)
{
return
$results
;
}
// If we still have unresolved refs (which might be things like "tip"),
// try to resolve them individually.
$futures
=
array
();
foreach
(
$unresolved
as
$ref
)
{
$futures
[
$ref
]
=
$repository
->
getLocalCommandFuture
(
'log --template=%s --rev %s'
,
'{node}'
,
hgsprintf
(
'%s'
,
$ref
));
}
foreach
(
new
FutureIterator
(
$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
;
}
if
(
preg_match
(
'/unknown revision/'
,
$ex
->
getStdErr
()))
{
// No matches for this ref.
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
()
{
// We don't have any VCS logic for Subversion, so just use the cached
// query.
return
id
(
new
DiffusionCachedResolveRefsQuery
())
->
setRepository
(
$this
->
getRepository
())
->
withRefs
(
$this
->
refs
)
->
execute
();
}
}
Event Timeline
Log In to Comment