builtin: create url-parse command

Git commands can accept a rather wide variety of URLs syntaxes. The range of accepted inputs might expand even more in the future. This makes the parsing of URL components difficult since standard URL parsers cannot be used. Extracting the components of a git URL would require implementing all the schemes that git itself supports, not to mention tracking its development continuously in case new URL schemes are added. The url-parse builtin command is designed to solve this problem by exposing git's native URL parsing facilities as a plumbing command. Other programs can then call upon git itself to parse the git URLs and extract their components. This should be quite useful for scripts. Signed-off-by: Matheus Afonso Martins Moreira <matheus@matheusmoreira.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Matheus Afonso Martins Moreira committed May 2, 2026 at 05:28 UTC 533eb14798d0e4e288401b90d4684730a3ed9266
7 files changed +141
.gitignore
+1
@@ -182,6 +182,7 @@
182 /git-update-server-info
183 /git-upload-archive
184 /git-upload-pack
185 +/git-url-parse
186 /git-var
187 /git-verify-commit
188 /git-verify-pack
Makefile
+1
@@ -1497,6 +1497,7 @@ BUILTIN_OBJS += builtin/update-ref.o
1497 BUILTIN_OBJS += builtin/update-server-info.o
1498 BUILTIN_OBJS += builtin/upload-archive.o
1499 BUILTIN_OBJS += builtin/upload-pack.o
1500 +BUILTIN_OBJS += builtin/url-parse.o
1501 BUILTIN_OBJS += builtin/var.o
1502 BUILTIN_OBJS += builtin/verify-commit.o
1503 BUILTIN_OBJS += builtin/verify-pack.o
builtin.h
+1
@@ -271,6 +271,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix, stru
271 int cmd_upload_archive(int argc, const char **argv, const char *prefix, struct repository *repo);
272 int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix, struct repository *repo);
273 int cmd_upload_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
274 +int cmd_url_parse(int argc, const char **argv, const char *prefix, struct repository *repo);
275 int cmd_var(int argc, const char **argv, const char *prefix, struct repository *repo);
276 int cmd_verify_commit(int argc, const char **argv, const char *prefix, struct repository *repo);
277 int cmd_verify_tag(int argc, const char **argv, const char *prefix, struct repository *repo);
builtin/url-parse.c new
+135
@@ -0,0 +1,135 @@
1 +#include "builtin.h"
2 +#include "gettext.h"
3 +#include "parse-options.h"
4 +#include "url.h"
5 +#include "urlmatch.h"
6 +
7 +static const char * const builtin_url_parse_usage[] = {
8 + N_("git url-parse [-c <component>] [--] <url>..."),
9 + NULL
10 +};
11 +
12 +static char *component_arg;
13 +
14 +static struct option builtin_url_parse_options[] = {
15 + OPT_STRING('c', "component", &component_arg, N_("component"),
16 + N_("which URL component to extract")),
17 + OPT_END(),
18 +};
19 +
20 +enum url_component {
21 + URL_NONE = 0,
22 + URL_SCHEME,
23 + URL_USER,
24 + URL_PASSWORD,
25 + URL_HOST,
26 + URL_PORT,
27 + URL_PATH,
28 +};
29 +
30 +static void parse_or_die(const char *url, struct url_info *info)
31 +{
32 + if (url_is_local_not_ssh(url)) {
33 + if (*url == '/')
34 + die("'%s' is not a URL; if you meant a local "
35 + "repository, use 'file://%s'", url, url);
36 + if (has_dos_drive_prefix(url))
37 + die("'%s' is not a URL; if you meant a local "
38 + "repository, use 'file:///%s'", url, url);
39 + die("'%s' is not a URL; if you meant a local repository, "
40 + "use a 'file://' URL with an absolute path", url);
41 + }
42 + if (!url_parse(url, info))
43 + die("invalid git URL '%s': %s", url, info->err);
44 +}
45 +
46 +static enum url_component get_component_or_die(const char *arg)
47 +{
48 + if (!strcmp("path", arg))
49 + return URL_PATH;
50 + if (!strcmp("host", arg))
51 + return URL_HOST;
52 + if (!strcmp("scheme", arg))
53 + return URL_SCHEME;
54 + if (!strcmp("user", arg))
55 + return URL_USER;
56 + if (!strcmp("password", arg))
57 + return URL_PASSWORD;
58 + if (!strcmp("port", arg))
59 + return URL_PORT;
60 + die("invalid git URL component '%s'", arg);
61 +}
62 +
63 +static char *extract_component(enum url_component component,
64 + struct url_info *info)
65 +{
66 + size_t offset, length;
67 +
68 + switch (component) {
69 + case URL_SCHEME:
70 + offset = 0;
71 + length = info->scheme_len;
72 + break;
73 + case URL_USER:
74 + offset = info->user_off;
75 + length = info->user_len;
76 + break;
77 + case URL_PASSWORD:
78 + offset = info->passwd_off;
79 + length = info->passwd_len;
80 + break;
81 + case URL_HOST:
82 + offset = info->host_off;
83 + length = info->host_len;
84 + break;
85 + case URL_PORT:
86 + offset = info->port_off;
87 + length = info->port_len;
88 + break;
89 + case URL_PATH:
90 + offset = info->path_off;
91 + length = info->path_len;
92 + break;
93 + case URL_NONE:
94 + return NULL;
95 + }
96 +
97 + return xstrndup(info->url + offset, length);
98 +}
99 +
100 +int cmd_url_parse(int argc,
101 + const char **argv,
102 + const char *prefix,
103 + struct repository *repo UNUSED)
104 +{
105 + struct url_info info;
106 + enum url_component selected = URL_NONE;
107 + char *extracted;
108 + int i;
109 +
110 + argc = parse_options(argc, argv, prefix, builtin_url_parse_options,
111 + builtin_url_parse_usage, 0);
112 +
113 + if (argc == 0)
114 + usage_with_options(builtin_url_parse_usage,
115 + builtin_url_parse_options);
116 +
117 + if (component_arg)
118 + selected = get_component_or_die(component_arg);
119 +
120 + for (i = 0; i < argc; i++) {
121 + parse_or_die(argv[i], &info);
122 +
123 + if (selected != URL_NONE) {
124 + extracted = extract_component(selected, &info);
125 + if (extracted) {
126 + puts(extracted);
127 + free(extracted);
128 + }
129 + }
130 +
131 + free(info.url);
132 + }
133 +
134 + return 0;
135 +}
command-list.txt
+1
@@ -202,6 +202,7 @@ git-update-ref plumbingmanipulators
202 git-update-server-info synchingrepositories
203 git-upload-archive synchelpers
204 git-upload-pack synchelpers
205 +git-url-parse purehelpers
206 git-var plumbinginterrogators
207 git-verify-commit ancillaryinterrogators
208 git-verify-pack plumbinginterrogators
git.c
+1
@@ -670,6 +670,7 @@ static struct cmd_struct commands[] = {
670 { "upload-archive", cmd_upload_archive, NO_PARSEOPT },
671 { "upload-archive--writer", cmd_upload_archive_writer, NO_PARSEOPT },
672 { "upload-pack", cmd_upload_pack },
673 + { "url-parse", cmd_url_parse },
674 { "var", cmd_var, RUN_SETUP_GENTLY | NO_PARSEOPT },
675 { "verify-commit", cmd_verify_commit, RUN_SETUP },
676 { "verify-pack", cmd_verify_pack },
meson.build
+1
@@ -686,6 +686,7 @@ builtin_sources = [
686 'builtin/update-server-info.c',
687 'builtin/upload-archive.c',
688 'builtin/upload-pack.c',
689 + 'builtin/url-parse.c',
690 'builtin/var.c',
691 'builtin/verify-commit.c',
692 'builtin/verify-pack.c',