@@ -1918,26 +1918,120 @@ impl GithubClient {
1918
1918
}
1919
1919
}
1920
1920
1921
+ /// Issues an ad-hoc GraphQL query.
1922
+ pub async fn graphql_query < T : serde:: de:: DeserializeOwned > (
1923
+ & self ,
1924
+ query : & str ,
1925
+ vars : serde_json:: Value ,
1926
+ ) -> anyhow:: Result < T > {
1927
+ self . json (
1928
+ self . post ( Repository :: GITHUB_GRAPHQL_API_URL )
1929
+ . json ( & serde_json:: json!( {
1930
+ "query" : query,
1931
+ "variables" : vars,
1932
+ } ) ) ,
1933
+ )
1934
+ . await
1935
+ }
1936
+
1937
+ /// Returns the object ID of the given user.
1938
+ ///
1939
+ /// Returns `None` if the user doesn't exist.
1940
+ pub async fn user_object_id ( & self , user : & str ) -> anyhow:: Result < Option < String > > {
1941
+ let user_info: serde_json:: Value = self
1942
+ . graphql_query (
1943
+ "query($user:String!) {
1944
+ user(login:$user) {
1945
+ id
1946
+ }
1947
+ }" ,
1948
+ serde_json:: json!( {
1949
+ "user" : user,
1950
+ } ) ,
1951
+ )
1952
+ . await ?;
1953
+ if let Some ( id) = user_info[ "data" ] [ "user" ] [ "id" ] . as_str ( ) {
1954
+ return Ok ( Some ( id. to_string ( ) ) ) ;
1955
+ }
1956
+ if let Some ( errors) = user_info[ "errors" ] . as_array ( ) {
1957
+ if errors
1958
+ . iter ( )
1959
+ . any ( |err| err[ "type" ] . as_str ( ) . unwrap_or_default ( ) == "NOT_FOUND" )
1960
+ {
1961
+ return Ok ( None ) ;
1962
+ }
1963
+ let messages: Vec < _ > = errors
1964
+ . iter ( )
1965
+ . map ( |err| err[ "message" ] . as_str ( ) . unwrap_or_default ( ) )
1966
+ . collect ( ) ;
1967
+ anyhow:: bail!( "failed to query user: {}" , messages. join( "\n " ) ) ;
1968
+ }
1969
+ anyhow:: bail!( "query for user {user} failed, no error message? {user_info:?}" ) ;
1970
+ }
1971
+
1921
1972
/// Returns whether or not the given GitHub login has made any commits to
1922
1973
/// the given repo.
1923
1974
pub async fn is_new_contributor ( & self , repo : & Repository , author : & str ) -> bool {
1924
- let url = format ! (
1925
- "{}/repos/{}/commits?author={}" ,
1926
- Repository :: GITHUB_API_URL ,
1927
- repo. full_name,
1928
- author,
1929
- ) ;
1930
- let req = self . get ( & url) ;
1931
- match self . json :: < Vec < GithubCommit > > ( req) . await {
1932
- // Note: This only returns results for the default branch.
1933
- // That should be fine in most cases since I think it is rare for
1934
- // new users to make their first commit to a different branch.
1935
- Ok ( res) => res. is_empty ( ) ,
1975
+ let user_id = match self . user_object_id ( author) . await {
1976
+ Ok ( None ) => return true ,
1977
+ Ok ( Some ( id) ) => id,
1978
+ Err ( e) => {
1979
+ log:: warn!( "failed to query user: {e:?}" ) ;
1980
+ return true ;
1981
+ }
1982
+ } ;
1983
+ // Note: This only returns results for the default branch. That should
1984
+ // be fine in most cases since I think it is rare for new users to
1985
+ // make their first commit to a different branch.
1986
+ //
1987
+ // Note: This is using GraphQL because the
1988
+ // `/repos/ORG/REPO/commits?author=AUTHOR` API was having problems not
1989
+ // finding users (https://github.com/rust-lang/triagebot/issues/1689).
1990
+ // The other possibility is the `/search/commits?q=repo:{}+author:{}`
1991
+ // API, but that endpoint has a very limited rate limit, and doesn't
1992
+ // work on forks. This GraphQL query seems to work fairly reliably,
1993
+ // and seems to cost only 1 point.
1994
+ match self
1995
+ . graphql_query :: < serde_json:: Value > (
1996
+ "query($repository_owner:String!, $repository_name:String!, $user_id:ID!) {
1997
+ repository(owner: $repository_owner, name: $repository_name) {
1998
+ defaultBranchRef {
1999
+ target {
2000
+ ... on Commit {
2001
+ history(author: {id: $user_id}) {
2002
+ totalCount
2003
+ }
2004
+ }
2005
+ }
2006
+ }
2007
+ }
2008
+ }" ,
2009
+ serde_json:: json!( {
2010
+ "repository_owner" : repo. owner( ) ,
2011
+ "repository_name" : repo. name( ) ,
2012
+ "user_id" : user_id
2013
+ } ) ,
2014
+ )
2015
+ . await
2016
+ {
2017
+ Ok ( c) => {
2018
+ if let Some ( c) = c[ "data" ] [ "repository" ] [ "defaultBranchRef" ] [ "target" ] [ "history" ]
2019
+ [ "totalCount" ]
2020
+ . as_i64 ( )
2021
+ {
2022
+ return c == 0 ;
2023
+ }
2024
+ log:: warn!( "new user query failed: {c:?}" ) ;
2025
+ false
2026
+ }
1936
2027
Err ( e) => {
1937
2028
log:: warn!(
1938
2029
"failed to search for user commits in {} for author {author}: {e:?}" ,
1939
2030
repo. full_name
1940
2031
) ;
2032
+ // Using `false` since if there is some underlying problem, we
2033
+ // don't need to spam everyone with the "new user" welcome
2034
+ // message.
1941
2035
false
1942
2036
}
1943
2037
}
0 commit comments