diff options
-rw-r--r-- | man/github-mirror.conf.5 | 8 | ||||
-rw-r--r-- | queries/github/gh_list_repos.graphql | 1 | ||||
-rw-r--r-- | src/config.c | 13 | ||||
-rw-r--r-- | src/config.h | 7 | ||||
-rw-r--r-- | src/git.c | 61 | ||||
-rw-r--r-- | src/github/types.c | 24 | ||||
-rw-r--r-- | src/github/types.h | 1 | ||||
-rw-r--r-- | src/main.c | 8 | ||||
-rw-r--r-- | tests/fixtures/normal.ini | 1 | ||||
-rw-r--r-- | tests/test_config.c | 1 |
10 files changed, 123 insertions, 2 deletions
diff --git a/man/github-mirror.conf.5 b/man/github-mirror.conf.5 index f610857..5a811ea 100644 --- a/man/github-mirror.conf.5 +++ b/man/github-mirror.conf.5 @@ -62,6 +62,14 @@ The default is false. If set to true, skip mirroring private repositories owned by the user. The default is false. +.It Cm transport +The transport to use for the repository. The default is +.Dq Cm https . +This can be one of +.Dq Cm https +or +.Dq Cm ssh . + .El .Pp diff --git a/queries/github/gh_list_repos.graphql b/queries/github/gh_list_repos.graphql index d7d36b3..0e728b6 100644 --- a/queries/github/gh_list_repos.graphql +++ b/queries/github/gh_list_repos.graphql @@ -4,6 +4,7 @@ query GetUserRepos($username: String!, $after: String) { nodes { name url + sshUrl isFork isPrivate } diff --git a/src/config.c b/src/config.c index f27e72f..f267d96 100644 --- a/src/config.c +++ b/src/config.c @@ -210,6 +210,18 @@ static int parse_line_inner(struct config *cfg, enum config_section section, value); return -1; } + } else if (!strcmp(key, "transport")) { + if (!strcmp(value, "ssh")) + cfg->head->gh.transport = git_transport_ssh; + else if (!strcmp(value, "https")) + cfg->head->gh.transport = git_transport_https; + else { + fprintf(stderr, + "Error parsing config file: " + "invalid value for transport: %s\n", + value); + return -1; + } } else { fprintf(stderr, "Error parsing config file: unknown key: %s\n", @@ -281,6 +293,7 @@ static int parse_line(struct config *cfg, char *line, remote->type = remote_type_github; remote->gh.endpoint = GH_DEFAULT_ENDPOINT; remote->gh.user_agent = DEFAULT_USER_AGENT; + remote->gh.transport = git_transport_https; remote->next = cfg->head; cfg->head = remote; } else if (!strcmp(section_name, "srht")) { diff --git a/src/config.h b/src/config.h index 4d08b7a..c0beb0a 100644 --- a/src/config.h +++ b/src/config.h @@ -13,11 +13,18 @@ extern const char *config_locations[]; +enum git_transport { + git_transport_https, + git_transport_ssh, +}; + struct github_cfg { /// Whether to skip mirroring fork repositories int skip_forks; /// Whether to skip mirroring private repositories int skip_private; + /// Transport protocol to use for mirroring + enum git_transport transport; // Borrowed /// Github graphql API endpoint @@ -251,6 +251,62 @@ static int create_git_path(const struct repo_ctx *ctx) } /** + * Updates the mirror URL of the git repository at the specified path. + * @param path Full path to the git repository + * @param ctx Context containing the repository information + * @return 0 on success, -1 on error + */ +static int update_mirror_url(const char *path, const struct repo_ctx *ctx) +{ + // Prepare the URL + char *url = prepare_git_url(ctx->url, ctx->username, ctx->token); + if (!url) { + perror("prepare_git_url"); + return -1; + } + + const pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid == 0) { + // Child process + char *args[8]; + int i = 0; + args[i++] = "git"; + args[i++] = "--git-dir"; + args[i++] = (char *) path; + args[i++] = "remote"; + args[i++] = "set-url"; + args[i++] = "origin"; + args[i++] = url; + args[i] = NULL; + + execvp("git", args); + perror("execvp"); + _exit(127); // execvp only returns on error + } + free(url); + + int status; + pid_t result; + while ((result = waitpid(pid, &status, 0)) == -1 && errno == EINTR) { + } + if (result == -1) { + perror("waitpid"); + return -1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return 0; // Success + fprintf(stderr, "Error: git remote set-url failed with status %d\n", + WEXITSTATUS(status)); + return -1; // Error occurred +} + +/** * Updates the git repository at the specified path from the remote. * @param path Full path to the git repository * @param quiet Suppress output if non-zero @@ -314,6 +370,11 @@ int git_mirror_repo(const struct repo_ctx *ctx, int quiet) // Repo exists, so we can just update it if (!quiet) printf("Repo already exists, updating...\n"); + if (update_mirror_url(path, ctx) == -1) { + perror("update_mirror_url"); + ret = -1; + goto end; + } if (update_mirror(path, quiet) == -1) { perror("update_mirror"); ret = -1; diff --git a/src/github/types.c b/src/github/types.c index ce9441e..a80e8fc 100644 --- a/src/github/types.c +++ b/src/github/types.c @@ -119,6 +119,29 @@ int gh_list_repos_from_json(cJSON *root, struct gh_list_repos_res *res) status = -1; goto end; } + cJSON *ssh_url_v = cJSON_GetObjectItemCaseSensitive(repo, + "sshUrl"); + if (!ssh_url_v || !cJSON_IsString(ssh_url_v)) { + fprintf(stderr, "Error: sshUrl not found\n"); + status = -1; + goto end; + } + const char *prefix = "ssh://"; + char *ssh_url = malloc(strlen(prefix) + + strlen(ssh_url_v->valuestring) + 1); + if (!ssh_url) { + fprintf(stderr, "Error: malloc failed\n"); + status = -1; + goto end; + } + strcpy(ssh_url, prefix); + strcat(ssh_url, ssh_url_v->valuestring); + // Replace 2nd colon with slash + char *colon = strchr(ssh_url + strlen(prefix), ':'); + if (colon) + *colon = '/'; + + cJSON *is_fork = cJSON_GetObjectItemCaseSensitive(repo, "isFork"); if (!is_fork || !cJSON_IsBool(is_fork)) { @@ -136,6 +159,7 @@ int gh_list_repos_from_json(cJSON *root, struct gh_list_repos_res *res) res->repos[res->repos_len].name = strdup(name->valuestring); res->repos[res->repos_len].url = strdup(url->valuestring); + res->repos[res->repos_len].ssh_url = ssh_url; res->repos[res->repos_len].is_fork = cJSON_IsTrue(is_fork); res->repos[res->repos_len].is_private = cJSON_IsTrue(is_private); diff --git a/src/github/types.h b/src/github/types.h index dec3551..fa88e27 100644 --- a/src/github/types.h +++ b/src/github/types.h @@ -16,6 +16,7 @@ struct gh_list_repos_res { struct { char *name; char *url; + char *ssh_url; int is_fork; int is_private; } *repos; @@ -126,16 +126,20 @@ static int mirror_github(const char *git_base, const struct github_cfg *cfg, continue; } + const char *url = cfg->transport == git_transport_ssh + ? res.repos[i].ssh_url + : res.repos[i].url; + if (!quiet) printf("Repo: %s\t%s\n", res.repos[i].name, - res.repos[i].url); + url); const struct repo_ctx repo = { .git_base = git_base, .owner = cfg->owner, .token = cfg->token, .name = res.repos[i].name, - .url = res.repos[i].url, + .url = url, .username = login, }; if (git_mirror_repo(&repo, quiet) != 0) { diff --git a/tests/fixtures/normal.ini b/tests/fixtures/normal.ini index 2f2bf4d..97a1d2f 100644 --- a/tests/fixtures/normal.ini +++ b/tests/fixtures/normal.ini @@ -3,6 +3,7 @@ endpoint = https://api.github.com/graphql token = ghp_1234567890abcdef user_agent = user-agent owner = my-org +transport = ssh [git] base = /srv/git diff --git a/tests/test_config.c b/tests/test_config.c index 5e4aab1..fe7df4d 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -29,6 +29,7 @@ static void config_read_normal(void **state) assert_non_null(cfg->head); assert_int_equal(cfg->head->type, remote_type_github); + assert_int_equal(cfg->head->gh.transport, git_transport_ssh); assert_string_equal(cfg->head->gh.endpoint, "https://api.github.com/graphql"); assert_string_equal(cfg->head->gh.token, "ghp_1234567890abcdef"); |