Skip to content

Instantly share code, notes, and snippets.

@jumarko
Last active April 17, 2019 09:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jumarko/2bd8f89c39a7fc2f719ec185d8b0c3f5 to your computer and use it in GitHub Desktop.
Save jumarko/2bd8f89c39a7fc2f719ec185d8b0c3f5 to your computer and use it in GitHub Desktop.
fetch github repos branches via GraphQL API
;; `get-repos-branches-page` ommited but it basically calls GitHub GraphQL API using the following query
(def repo-branches-query "
repo_name%s: repository(owner: \"%s\", name: \"%s\") {
owner {
login
}
name
defaultBranchRef {
name
}
refs(first: %d, after: \"%s\" refPrefix: \"refs/heads/\", orderBy: {field: ALPHABETICAL, direction: ASC}) {
pageInfo {
hasNextPage,
endCursor
},
edges {
node {
name
}
}
}
}")
;; ... and then returns vector of repositories with the shape like this:
(def sample-repo-data
{:owner-login (get owner "login")
:name name
:default-branch (get defaultBranchRef "name")
:next-page? (get-in refs ["pageInfo" "hasNextPage"])
:end-cursor (get-in refs ["pageInfo" "endCursor"])
:refs (mapv (fn parse-branch [refs-edge]
(get-in refs-edge ["node" "name"]))
(get refs "edges"))})
;;; specs
(s/def ::owner-login ::specs/non-empty-string)
(s/def ::name ::specs/non-empty-string)
;; nil end-cursor means "the first page"
(s/def ::end-cursor (s/nilable string?))
(s/def ::repo (s/keys :req-un [::owner-login ::name]
:opt-un [::end-cursor]))
(s/def ::default-branch (s/nilable string?))
(s/def ::branch-name ::specs/non-empty-string)
(s/def ::refs (s/coll-of ::branch-name))
(s/def ::repo-with-branches (s/keys :req-un [::owner-login ::name ::default-branch ::refs]))
;;; get-repos-branches - paging implementation and calling `get-repos-branches-page` for fetching actual data
(s/fdef get-repos-branches
:args (s/cat
:repos (s/coll-of ::repo)
:access-token ::specs/non-empty-string
:max-branches pos-int?)
:ret (s/coll-of ::repo-with-branches))
(defn get-repos-branches
"Lists branches for all given repos up to `max-branches`.
Default branch is always returned separately, even if not included in `refs`."
[repos access-token max-branches]
(log/infof "Listing branches (up to %d branches per repo) for %d repos: %s"
max-branches (count repos) (mapv :name repos))
(loop [fetched-branches-so-far 0
repos repos
repos-with-branches {}]
(if (or
(empty? repos)
(>= fetched-branches-so-far max-branches))
(vals repos-with-branches)
(let [current-page-repos-data (get-repos-branches-page repos access-token)
;; this contains all repos with all their branches that we've fetched so far
repos-with-updated-branches (reduce (fn [updated-repos
{:keys [owner-login name refs] :as repo}]
(update updated-repos [owner-login name]
#(if %
(update % :refs into refs)
;; we're processing the first page
repo)))
repos-with-branches
current-page-repos-data)]
(recur (+ fetched-branches-so-far max-github-api-page-size)
;; continue only with repos which have more branches
(filterv :next-page? current-page-repos-data)
repos-with-updated-branches)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment