summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Anshul Gupta <ansg191@anshulg.com> 2025-06-13 15:55:55 -0700
committerGravatar Anshul Gupta <ansg191@anshulg.com> 2025-06-13 15:59:55 -0700
commit6bf51383587f7d6ff31f253dba5fa6d2209fee6d (patch)
treec33fe7e87b312e3618aaebe72da6c8bd6a6e6be4
parentd814e2111a539bacf8be4958a6cea14040490015 (diff)
downloadgithub-mirror-6bf51383587f7d6ff31f253dba5fa6d2209fee6d.tar.gz
github-mirror-6bf51383587f7d6ff31f253dba5fa6d2209fee6d.tar.zst
github-mirror-6bf51383587f7d6ff31f253dba5fa6d2209fee6d.zip
Add ssh transport option for github
-rw-r--r--man/github-mirror.conf.58
-rw-r--r--queries/github/gh_list_repos.graphql1
-rw-r--r--src/config.c13
-rw-r--r--src/config.h7
-rw-r--r--src/git.c61
-rw-r--r--src/github/types.c24
-rw-r--r--src/github/types.h1
-rw-r--r--src/main.c8
-rw-r--r--tests/fixtures/normal.ini1
-rw-r--r--tests/test_config.c1
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
diff --git a/src/git.c b/src/git.c
index 75f9888..4dc9c97 100644
--- a/src/git.c
+++ b/src/git.c
@@ -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;
diff --git a/src/main.c b/src/main.c
index 44dd901..fdb8211 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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");