public List <HosterRepository> GetRepositoryList(ConfigSource config) { var repos = new List <HosterRepository>(); this.req.SetBaseUrl("https://gitlab.com"); this.req.AddHeader("Accept", "application/json"); if (config.IsAuthenticated) { this.req.AddHeader("Private-Token", config.Password); } string type = "users"; string args = string.Empty; if (config.Type.ToLower() != "user") { type = "groups"; args = "?include_subgroups=true"; } var scm = this.factory.Create(ScmType.Git); string url = string.Format("/api/v4/{0}/{1}/projects{2}", type, config.Name, args); while (url != null) { var result = req.Execute(url).Result; url = null; if (result.IsSuccessStatusCode) { var response = JsonConvert.DeserializeObject <List <GitlabApiRepo> >(result.Content); foreach (var apiRepo in response) { string cloneUrl = apiRepo.http_url_to_repo; var repo = new HosterRepository(apiRepo.path_with_namespace, apiRepo.name, cloneUrl, ScmType.Git); repo.SetPrivate(apiRepo.visibility == "private"); // wiki: the API only returns if it's enabled, but not if it actually contains pages // The Git repo always exists, even when the wiki has no pages. // So we can't check for the existence of the Git repo -> we need to make another API call to check if it has at least one page if (apiRepo.wiki_enabled && cloneUrl.EndsWith(".git")) { // Rate limit of 10 requests per second per IP address (https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits) // --> block long enough so that 10 requests will take longer than a second Task.Delay(110).Wait(); string wikiUrl = string.Format("/api/v4/projects/{0}/wikis", apiRepo.id); var wikiResult = req.Execute(wikiUrl).Result; if (wikiResult.IsSuccessStatusCode) { var wikis = JsonConvert.DeserializeObject <List <GitlabApiWiki> >(wikiResult.Content); if (wikis.Any()) { repo.SetWiki(true, cloneUrl.Substring(0, cloneUrl.Length - ".git".Length) + ".wiki.git"); } } } // TODO: Issues // _links -> issues ?? // issues_access_level ?? repos.Add(repo); } if (result.Headers.Contains("Link")) { // There are multiple links, but all in one header value // https://docs.gitlab.com/ee/api/README.html#pagination-link-header string links = result.Headers.GetValues("Link").First(); // The API returns something like this and we need the link named "next": // <https://gitlab.com/api/foo>; rel="next", <https://gitlab.com/api/bar>; rel="first", <https://gitlab.com/api/baz>; rel="last" foreach (var link in links.Split(',')) { var items = link.Split(';'); if (items[1].Contains("next")) { url = items[0].Trim('<', '>', ' '); break; } } } } else { switch (result.Status) { case HttpStatusCode.Unauthorized: throw new AuthenticationException(string.Format(Resource.ApiAuthenticationFailed, config.AuthName)); case HttpStatusCode.Forbidden: throw new SecurityException(Resource.ApiMissingPermissions); case HttpStatusCode.NotFound: throw new InvalidOperationException(string.Format(Resource.ApiInvalidUsername, config.Name)); } } } return(repos); }
public List <HosterRepository> GetRepositoryList(ConfigSource source) { var list = new List <HosterRepository>(); string className = this.GetType().Name; request.SetBaseUrl("https://api.bitbucket.org"); if (source.IsAuthenticated) { request.AddBasicAuthHeader(source.AuthName, source.Password); } string url = "/2.0/repositories/" + source.Name; while (url != null) { var result = request.Execute(url).Result; if (result.IsSuccessStatusCode) { var apiResponse = JsonConvert.DeserializeObject <BitbucketApiResponse>(result.Content); // #60: 2 months after Bitbucket's HG deprecation, their API still returns HG repos but cloning/pulling them fails -> ignore them foreach (var apiRepo in apiResponse.values.Where(x => x.scm.ToLower() != "hg")) { ScmType type; switch (apiRepo.scm.ToLower()) { case "git": type = ScmType.Git; break; default: throw new InvalidOperationException(string.Format(Resource.ApiInvalidScmType, apiRepo.full_name)); } var clone = apiRepo.links.clone.Where(r => r.name == "https").First(); string cloneurl = clone.href; var repo = new HosterRepository(apiRepo.full_name, apiRepo.slug, cloneurl, type); repo.SetPrivate(apiRepo.is_private); if (apiRepo.has_wiki) { string wikiUrl = cloneurl + "/wiki"; repo.SetWiki(true, wikiUrl.ToString()); } // TODO: Issues list.Add(repo); } url = apiResponse.next; } else { switch (result.Status) { case HttpStatusCode.Unauthorized: throw new AuthenticationException(string.Format(Resource.ApiAuthenticationFailed, source.AuthName)); case HttpStatusCode.Forbidden: throw new SecurityException(Resource.ApiMissingPermissions); case HttpStatusCode.NotFound: throw new InvalidOperationException(string.Format(Resource.ApiInvalidUsername, source.Name)); } } } return(list); }
public List <HosterRepository> GetRepositoryList(ConfigSource source) { var list = new List <HosterRepository>(); string className = this.GetType().Name; request.SetBaseUrl("https://api.bitbucket.org"); if (source.IsAuthenticated) { request.AddBasicAuthHeader(source.AuthName, source.Password); } string url = string.Empty; string apiUsername = null; // Issue #32: from Apr 29 2019, usernames (not team names) must be replaced by UUIDs if (source.Type.ToLower() == "user") { url = "/2.0/users/" + source.Name; var result = request.Execute(url).Result; if (result.IsSuccessStatusCode) { var apiResponse = JsonConvert.DeserializeObject <BitbucketApiUserResponse>(result.Content); if (apiResponse != null) { apiUsername = Uri.EscapeUriString(apiResponse.uuid); } } if (string.IsNullOrWhiteSpace(apiUsername)) { throw new InvalidOperationException(string.Format(Resource.ApiBitbucketCantGetUuid, source.Name)); } } else { apiUsername = source.Name; } url = "/2.0/repositories/" + apiUsername; while (url != null) { var result = request.Execute(url).Result; if (result.IsSuccessStatusCode) { var apiResponse = JsonConvert.DeserializeObject <BitbucketApiResponse>(result.Content); foreach (var apiRepo in apiResponse.values) { ScmType type; switch (apiRepo.scm.ToLower()) { case "hg": type = ScmType.Mercurial; break; case "git": type = ScmType.Git; break; default: throw new InvalidOperationException(string.Format(Resource.ApiInvalidScmType, apiRepo.full_name)); } var clone = apiRepo.links.clone.Where(r => r.name == "https").First(); string cloneurl = clone.href; var repo = new HosterRepository(apiRepo.full_name, apiRepo.slug, cloneurl, type); repo.SetPrivate(apiRepo.is_private); if (apiRepo.has_wiki) { string wikiUrl = cloneurl + "/wiki"; repo.SetWiki(true, wikiUrl.ToString()); } // TODO: Issues list.Add(repo); } url = apiResponse.next; } else { switch (result.Status) { case HttpStatusCode.Unauthorized: throw new AuthenticationException(string.Format(Resource.ApiAuthenticationFailed, source.AuthName)); case HttpStatusCode.Forbidden: throw new SecurityException(Resource.ApiMissingPermissions); case HttpStatusCode.NotFound: throw new InvalidOperationException(string.Format(Resource.ApiInvalidUsername, source.Name)); } } } return(list); }