private bool ValidateUrls(HosterRepository repo) { bool result = true; var validator = new UrlHelper(); if (!validator.UrlIsValid(repo.CloneUrl)) { return(false); } if (repo.HasWiki) { if (!validator.UrlIsValid(repo.WikiUrl)) { return(false); } } if (repo.HasIssues) { if (!validator.UrlIsValid(repo.IssueUrl)) { return(false); } } return(result); }
public void IsPrivateWorks() { var sut = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); sut.SetPrivate(true); Assert.True(sut.IsPrivate); }
public void SetIssuesWorks() { var sut = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); sut.SetIssues(true, "url"); Assert.True(sut.HasIssues); Assert.Equal("url", sut.IssueUrl); }
public void SetWikiWorks() { var sut = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); sut.SetWiki(true, "url"); Assert.True(sut.HasWiki); Assert.Equal("url", sut.WikiUrl); }
public void InvalidCharsInRepoNameAreReplaced(string inputName, string savedName) { var sut = new HosterRepository(inputName, "name", "http://clone", ScmType.Git); Assert.Equal(savedName, sut.FullName); sut = new HosterRepository(inputName, "name", "http://clone", ScmType.Git, false, "", false, ""); Assert.Equal(savedName, sut.FullName); }
public void ThrowsWhenConfigSourceIsNull() { var factory = new FakeHosterFactory(new FakeHoster()); var repo = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); var sut = new HosterBackupMaker(factory); Assert.Throws <ArgumentNullException>(() => sut.MakeBackup(null, repo, "foo")); }
public void MakeBackup(ConfigSource source, HosterRepository repo, string repoFolder) { if (source == null) { throw new ArgumentNullException(Resource.ConfigSourceIsNull); } var hoster = factory.Create(source.Hoster); hoster.Backup.MakeBackup(source, repo, repoFolder); }
public void BackupBaseExecutesAllSubMethods() { var repo = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); repo.SetWiki(true, "http://wiki"); repo.SetIssues(true, "http://issues"); var sut = new FakeHosterBackup(); sut.MakeBackup(new ConfigSource(), repo, @"c:\foo"); Assert.True(sut.WasExecuted); }
public void MakeBackupCallsUnderlyingMethod() { var hoster = new FakeHoster(); var factory = new FakeHosterFactory(hoster); var repo = new HosterRepository("foo", "foo", "http://clone", ScmType.Git); repo.SetWiki(true, "http://wiki"); repo.SetIssues(true, "http://issues"); var reader = new FakeConfigReader(); reader.SetDefaultFakeConfig(); var config = reader.ReadConfig(); var source = config.Sources.First(); var sut = new HosterBackupMaker(factory); sut.MakeBackup(source, repo, "foo"); Assert.True(hoster.FakeBackup.WasExecuted); }
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 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; var product = new ProductHeaderValue(this.context.UserAgent, this.context.VersionNumberString); var client = new GitHubClient(product); if (source.IsAuthenticated) { var basicAuth = new Credentials(source.AuthName, source.Password); client.Credentials = basicAuth; } IReadOnlyList <Repository> repos = null; try { switch (source.Type.ToLower()) { case "user": if (source.IsAuthenticated) { // If authenticated, lists ALL repos for the user, include private ones (https://developer.github.com/v3/repos/#list-your-repositories) repos = client.Repository.GetAllForCurrent().Result; } else { // GetAllForCurrent REQUIRES authentication, so we must use GetAllForUser when not authenticated to list public repos (https://developer.github.com/v3/repos/#list-user-repositories) repos = client.Repository.GetAllForUser(source.Name).Result; } break; case "org": repos = client.Repository.GetAllForOrg(source.Name).Result; break; } } catch (Exception e) { string message = e.Message; if (e.InnerException is AuthorizationException) { message = string.Format(Resource.ApiAuthenticationFailed, source.AuthName); } else if (e.InnerException is ForbiddenException) { message = Resource.ApiMissingPermissions; } else if (e.InnerException is NotFoundException) { message = string.Format(Resource.ApiInvalidUsername, source.Name); } throw new ApiException(message, e); } // Check the right scope: // #29: when authenticated, the personal access token must at least have the "repo" scope, otherwise the API doesn't return private repos // There are no tests for this, because this would require everybody to create a second token with insufficient permissions in order to run the integration tests. // -> so we just throw an exception here, which means that most of the integration tests will fail if (source.IsAuthenticated) { var info = client.GetLastApiInfo(); if (!info.OauthScopes.Contains("repo")) { throw new SecurityException(string.Format(Resource.ApiGithubNotEnoughScope, source.Title)); } } var scm = this.factory.Create(ScmType.Git); if (repos != null) { // #29: GetAllForCurrent (see above) returns ALL repos the user has access to (for example, the repos of orgs where the user is a member). // We only want the repos under the user: var userRepos = repos.Where(r => r.Owner.Login == source.Name); foreach (var apiRepo in userRepos) { var repo = new HosterRepository(apiRepo.FullName, apiRepo.Name, apiRepo.CloneUrl, ScmType.Git); repo.SetPrivate(apiRepo.Private); if (apiRepo.HasWiki && apiRepo.CloneUrl.EndsWith(".git")) { // build wiki clone URL, because API doesn't return it string wikiUrl = apiRepo.CloneUrl.Substring(0, apiRepo.CloneUrl.Length - ".git".Length) + ".wiki.git"; // issue #13: the GitHub API only returns whether it's *possible* to create a wiki, but not if the repo actually *has* a wiki. // So we need to skip the wiki when the URL (which we just built manually) is not a valid repository. if (scm.RemoteRepositoryExists(wikiUrl)) { repo.SetWiki(true, wikiUrl); } } if (apiRepo.HasIssues) { // The API has only a URL for one issue (with a placeholder at the end), but this URL isn't in Octokit. // So we have to build it manually: repo.SetIssues(true, apiRepo.Url + "/issues/"); } list.Add(repo); } } return(list); }
public List <HosterRepository> GetRepositoryList(ConfigSource source) { var list = new List <HosterRepository>(); string className = this.GetType().Name; var product = new ProductHeaderValue(this.context.UserAgent, this.context.VersionNumberString); var client = new GitHubClient(product); if (source.IsAuthenticated) { var basicAuth = new Credentials(source.AuthName, source.Password); client.Credentials = basicAuth; } IReadOnlyList <Repository> repos = null; try { switch (source.Type.ToLower()) { case "user": repos = client.Repository.GetAllForUser(source.Name).Result; break; case "org": repos = client.Repository.GetAllForOrg(source.Name).Result; break; } } catch (Exception e) { string message = e.Message; if (e.InnerException is AuthorizationException) { message = string.Format(Resource.ApiAuthenticationFailed, source.AuthName); } else if (e.InnerException is ForbiddenException) { message = Resource.ApiMissingPermissions; } else if (e.InnerException is NotFoundException) { message = string.Format(Resource.ApiInvalidUsername, source.Name); } throw new ApiException(message, e); } var scm = this.factory.Create(ScmType.Git); if (repos != null) { foreach (var apiRepo in repos) { var repo = new HosterRepository(apiRepo.FullName, apiRepo.Name, apiRepo.CloneUrl, ScmType.Git); repo.SetPrivate(apiRepo.Private); if (apiRepo.HasWiki && apiRepo.CloneUrl.EndsWith(".git")) { // build wiki clone URL, because API doesn't return it string wikiUrl = apiRepo.CloneUrl.Substring(0, apiRepo.CloneUrl.Length - ".git".Length) + ".wiki.git"; // issue #13: the GitHub API only returns whether it's *possible* to create a wiki, but not if the repo actually *has* a wiki. // So we need to skip the wiki when the URL (which we just built manually) is not a valid repository. if (scm.RemoteRepositoryExists(wikiUrl)) { repo.SetWiki(true, wikiUrl); } } if (apiRepo.HasIssues) { // The API has only a URL for one issue (with a placeholder at the end), but this URL isn't in Octokit. // So we have to build it manually: repo.SetIssues(true, apiRepo.Url + "/issues/"); } list.Add(repo); } } 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); }