* [PATCH] status: add --json output format to git status
@ 2025-07-30 6:27 Tach via GitGitGadget
2025-07-30 9:01 ` Phillip Wood
2025-07-30 14:58 ` Junio C Hamano
0 siblings, 2 replies; 6+ messages in thread
From: Tach via GitGitGadget @ 2025-07-30 6:27 UTC (permalink / raw)
To: git; +Cc: Tach, tacherasasi
From: tacherasasi <tacherasasi@gmail.com>
Add a new --json flag to 'git status' that outputs repository state
in a structured JSON format. This enables reliable machine parsing
of status information for tools and automation.
The JSON output includes:
- Branch information (name, detached state, ahead/behind counts)
- Staged files array
- Unstaged files array
- Untracked files array
- Ignored files array (with --ignored flag)
Implementation details:
- Add STATUS_FORMAT_JSON to wt_status_format enum
- Add JSON output option to git status and git commit
- Implement JSON formatting helpers for arrays and branch info
- Structure output for easy parsing and future extensibility
Example:
$ git status --json
{
"branch": {
"current": "main",
"detached": false
},
"staged": ["file1.txt"],
"unstaged": ["file2.txt"],
"untracked": ["file3.txt"]
}
This provides a robust alternative to parsing traditional output
formats, making it easier to build reliable tools and automation
around Git status information.
Signed-off-by: Tachera Sasi <tachera@ekilie.com>
Signed-off-by: tacherasasi <tacherasasi@gmail.com>
---
status: add --json output format to git status
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1937/tacheraSasi/feature/json-status-output-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1937
builtin/commit.c | 9 ++++-
wt-status.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++
wt-status.h | 1 +
3 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/builtin/commit.c b/builtin/commit.c
index fba0dded64a..f1db4fdfd9a 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
+ OPT_SET_INT(0, "json", &status_format,
+ N_("show status in JSON format"),
+ STATUS_FORMAT_JSON),
OPT_BOOL('z', "null", &s.null_termination,
N_("terminate entries with NUL")),
{
@@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
prefix, argv);
if (status_format != STATUS_FORMAT_PORCELAIN &&
- status_format != STATUS_FORMAT_PORCELAIN_V2)
+ status_format != STATUS_FORMAT_PORCELAIN_V2 &&
+ status_format != STATUS_FORMAT_JSON)
progress_flag = REFRESH_PROGRESS;
repo_read_index(the_repository);
refresh_index(the_repository->index,
@@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
+ OPT_SET_INT(0, "json", &status_format,
+ N_("show status in JSON format"),
+ STATUS_FORMAT_JSON),
OPT_BOOL('z', "null", &s.null_termination,
N_("terminate entries with NUL")),
OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
diff --git a/wt-status.c b/wt-status.c
index 454601afa15..7192fb4d057 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct wt_status *s)
}
}
+
+static void wt_json_print_string_array(struct wt_status *s, const char *name, struct string_list *list)
+{
+ int i;
+ fprintf(s->fp, " \"%s\": [", name);
+ for (i = 0; i < list->nr; i++) {
+ if (i > 0)
+ fprintf(s->fp, ", ");
+ fprintf(s->fp, "\"%s\"", list->items[i].string);
+ }
+ fprintf(s->fp, "]");
+}
+
+static void wt_json_print_change_array(struct wt_status *s, const char *name, int change_type)
+{
+ int i;
+ struct string_list files = STRING_LIST_INIT_DUP;
+
+ for (i = 0; i < s->change.nr; i++) {
+ struct wt_status_change_data *d;
+ struct string_list_item *it;
+ it = &(s->change.items[i]);
+ d = it->util;
+
+ if ((change_type == WT_STATUS_UPDATED && d->index_status &&
+ d->index_status != DIFF_STATUS_UNMERGED) ||
+ (change_type == WT_STATUS_CHANGED && d->worktree_status &&
+ d->worktree_status != DIFF_STATUS_UNMERGED)) {
+ string_list_append(&files, it->string);
+ }
+ }
+
+ wt_json_print_string_array(s, name, &files);
+ string_list_clear(&files, 0);
+}
+
+static void wt_json_print_branch_info(struct wt_status *s)
+{
+ struct branch *branch;
+ const char *branch_name;
+ int ahead = 0, behind = 0;
+
+ fprintf(s->fp, " \"branch\": {\n");
+
+ if (s->branch && !s->is_initial) {
+ if (!strcmp(s->branch, "HEAD")) {
+ fprintf(s->fp, " \"current\": \"HEAD\",\n");
+ fprintf(s->fp, " \"detached\": true");
+ } else {
+ if (skip_prefix(s->branch, "refs/heads/", &branch_name)) {
+ fprintf(s->fp, " \"current\": \"%s\",\n", branch_name);
+ fprintf(s->fp, " \"detached\": false");
+
+ branch = branch_get(branch_name);
+ if (branch && branch->merge && branch->merge[0] && branch->merge[0]->dst) {
+ if (!stat_tracking_info(branch, &ahead, &behind, NULL, 0, 0)) {
+ fprintf(s->fp, ",\n \"ahead\": %d,\n \"behind\": %d", ahead, behind);
+ }
+ }
+ } else {
+ fprintf(s->fp, " \"current\": \"%s\",\n", s->branch);
+ fprintf(s->fp, " \"detached\": false");
+ }
+ }
+ } else {
+ fprintf(s->fp, " \"current\": null,\n");
+ fprintf(s->fp, " \"detached\": false");
+ }
+
+ fprintf(s->fp, "\n }");
+}
+
+static void wt_json_status_print(struct wt_status *s)
+{
+ fprintf(s->fp, "{\n");
+
+ wt_json_print_branch_info(s);
+ fprintf(s->fp, ",\n");
+
+ wt_json_print_change_array(s, "staged", WT_STATUS_UPDATED);
+ fprintf(s->fp, ",\n");
+
+ wt_json_print_change_array(s, "unstaged", WT_STATUS_CHANGED);
+ fprintf(s->fp, ",\n");
+
+ wt_json_print_string_array(s, "untracked", &s->untracked);
+
+ if (s->ignored.nr > 0) {
+ fprintf(s->fp, ",\n");
+ wt_json_print_string_array(s, "ignored", &s->ignored);
+ }
+
+ fprintf(s->fp, "\n}\n");
+}
+
void wt_status_print(struct wt_status *s)
{
trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
@@ -2583,6 +2678,9 @@ void wt_status_print(struct wt_status *s)
case STATUS_FORMAT_PORCELAIN_V2:
wt_porcelain_v2_print(s);
break;
+ case STATUS_FORMAT_JSON:
+ wt_json_status_print(s);
+ break;
case STATUS_FORMAT_UNSPECIFIED:
BUG("finalize_deferred_config() should have been called");
break;
diff --git a/wt-status.h b/wt-status.h
index 4e377ce62b8..e929af832b2 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -74,6 +74,7 @@ enum wt_status_format {
STATUS_FORMAT_SHORT,
STATUS_FORMAT_PORCELAIN,
STATUS_FORMAT_PORCELAIN_V2,
+ STATUS_FORMAT_JSON,
STATUS_FORMAT_UNSPECIFIED
};
base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6
--
gitgitgadget
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH] status: add --json output format to git status
2025-07-30 6:27 [PATCH] status: add --json output format to git status Tach via GitGitGadget
@ 2025-07-30 9:01 ` Phillip Wood
[not found] ` <CA+m0rScismG8VVf8mYS=+_AjwgrgX8GNZrFD+8-0EaeCDYPgbg@mail.gmail.com>
2025-07-30 14:58 ` Junio C Hamano
1 sibling, 1 reply; 6+ messages in thread
From: Phillip Wood @ 2025-07-30 9:01 UTC (permalink / raw)
To: Tach via GitGitGadget, git; +Cc: Tach
On 30/07/2025 07:27, Tach via GitGitGadget wrote:
> From: tacherasasi <tacherasasi@gmail.com>
>
> Add a new --json flag to 'git status' that outputs repository state
> in a structured JSON format. This enables reliable machine parsing
> of status information for tools and automation.
>
> The JSON output includes:
> - Branch information (name, detached state, ahead/behind counts)
> - Staged files array
> - Unstaged files array
> - Untracked files array
> - Ignored files array (with --ignored flag)
>
> Implementation details:
> - Add STATUS_FORMAT_JSON to wt_status_format enum
> - Add JSON output option to git status and git commit
> - Implement JSON formatting helpers for arrays and branch info
> - Structure output for easy parsing and future extensibility
>
> Example:
> $ git status --json
> {
> "branch": {
> "current": "main",
> "detached": false
> },
> "staged": ["file1.txt"],
> "unstaged": ["file2.txt"],
> "untracked": ["file3.txt"]
> }
>
> This provides a robust alternative to parsing traditional output
> formats, making it easier to build reliable tools and automation
> around Git status information.
What problem are you having paring the current output? You say this
patch provides a robust alternative but I'm afraid it does not seem to
handle filenames containing newlines or double quotes and it assumes
they are utf-8 encoded. It also seems to print a trailing comma if an
array has more than one item which is not allowed by the json spec. We
already have support for generating json output as documented
json-writer.h which will help with most of that but not the filename
encoding problem. New features should also be accompanied by tests to
prevent future regressions. I'm not opposed to adding json output if it
is robust and handles non utf-8 filenames but it would be helpful to
understand what problems you are having parsing the output of "git
status --porcelain[=v2]".
Thanks
Phillip
> Signed-off-by: Tachera Sasi <tachera@ekilie.com>
> Signed-off-by: tacherasasi <tacherasasi@gmail.com>
> ---
> status: add --json output format to git status
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1937/tacheraSasi/feature/json-status-output-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/1937
>
> builtin/commit.c | 9 ++++-
> wt-status.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++
> wt-status.h | 1 +
> 3 files changed, 107 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/commit.c b/builtin/commit.c
> index fba0dded64a..f1db4fdfd9a 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
> OPT_SET_INT(0, "long", &status_format,
> N_("show status in long format (default)"),
> STATUS_FORMAT_LONG),
> + OPT_SET_INT(0, "json", &status_format,
> + N_("show status in JSON format"),
> + STATUS_FORMAT_JSON),
> OPT_BOOL('z', "null", &s.null_termination,
> N_("terminate entries with NUL")),
> {
> @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
> prefix, argv);
>
> if (status_format != STATUS_FORMAT_PORCELAIN &&
> - status_format != STATUS_FORMAT_PORCELAIN_V2)
> + status_format != STATUS_FORMAT_PORCELAIN_V2 &&
> + status_format != STATUS_FORMAT_JSON)
> progress_flag = REFRESH_PROGRESS;
> repo_read_index(the_repository);
> refresh_index(the_repository->index,
> @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
> OPT_SET_INT(0, "long", &status_format,
> N_("show status in long format (default)"),
> STATUS_FORMAT_LONG),
> + OPT_SET_INT(0, "json", &status_format,
> + N_("show status in JSON format"),
> + STATUS_FORMAT_JSON),
> OPT_BOOL('z', "null", &s.null_termination,
> N_("terminate entries with NUL")),
> OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
> diff --git a/wt-status.c b/wt-status.c
> index 454601afa15..7192fb4d057 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct wt_status *s)
> }
> }
>
> +
> +static void wt_json_print_string_array(struct wt_status *s, const char *name, struct string_list *list)
> +{
> + int i;
> + fprintf(s->fp, " \"%s\": [", name);
> + for (i = 0; i < list->nr; i++) {
> + if (i > 0)
> + fprintf(s->fp, ", ");
> + fprintf(s->fp, "\"%s\"", list->items[i].string);
> + }
> + fprintf(s->fp, "]");
> +}
> +
> +static void wt_json_print_change_array(struct wt_status *s, const char *name, int change_type)
> +{
> + int i;
> + struct string_list files = STRING_LIST_INIT_DUP;
> +
> + for (i = 0; i < s->change.nr; i++) {
> + struct wt_status_change_data *d;
> + struct string_list_item *it;
> + it = &(s->change.items[i]);
> + d = it->util;
> +
> + if ((change_type == WT_STATUS_UPDATED && d->index_status &&
> + d->index_status != DIFF_STATUS_UNMERGED) ||
> + (change_type == WT_STATUS_CHANGED && d->worktree_status &&
> + d->worktree_status != DIFF_STATUS_UNMERGED)) {
> + string_list_append(&files, it->string);
> + }
> + }
> +
> + wt_json_print_string_array(s, name, &files);
> + string_list_clear(&files, 0);
> +}
> +
> +static void wt_json_print_branch_info(struct wt_status *s)
> +{
> + struct branch *branch;
> + const char *branch_name;
> + int ahead = 0, behind = 0;
> +
> + fprintf(s->fp, " \"branch\": {\n");
> +
> + if (s->branch && !s->is_initial) {
> + if (!strcmp(s->branch, "HEAD")) {
> + fprintf(s->fp, " \"current\": \"HEAD\",\n");
> + fprintf(s->fp, " \"detached\": true");
> + } else {
> + if (skip_prefix(s->branch, "refs/heads/", &branch_name)) {
> + fprintf(s->fp, " \"current\": \"%s\",\n", branch_name);
> + fprintf(s->fp, " \"detached\": false");
> +
> + branch = branch_get(branch_name);
> + if (branch && branch->merge && branch->merge[0] && branch->merge[0]->dst) {
> + if (!stat_tracking_info(branch, &ahead, &behind, NULL, 0, 0)) {
> + fprintf(s->fp, ",\n \"ahead\": %d,\n \"behind\": %d", ahead, behind);
> + }
> + }
> + } else {
> + fprintf(s->fp, " \"current\": \"%s\",\n", s->branch);
> + fprintf(s->fp, " \"detached\": false");
> + }
> + }
> + } else {
> + fprintf(s->fp, " \"current\": null,\n");
> + fprintf(s->fp, " \"detached\": false");
> + }
> +
> + fprintf(s->fp, "\n }");
> +}
> +
> +static void wt_json_status_print(struct wt_status *s)
> +{
> + fprintf(s->fp, "{\n");
> +
> + wt_json_print_branch_info(s);
> + fprintf(s->fp, ",\n");
> +
> + wt_json_print_change_array(s, "staged", WT_STATUS_UPDATED);
> + fprintf(s->fp, ",\n");
> +
> + wt_json_print_change_array(s, "unstaged", WT_STATUS_CHANGED);
> + fprintf(s->fp, ",\n");
> +
> + wt_json_print_string_array(s, "untracked", &s->untracked);
> +
> + if (s->ignored.nr > 0) {
> + fprintf(s->fp, ",\n");
> + wt_json_print_string_array(s, "ignored", &s->ignored);
> + }
> +
> + fprintf(s->fp, "\n}\n");
> +}
> +
> void wt_status_print(struct wt_status *s)
> {
> trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
> @@ -2583,6 +2678,9 @@ void wt_status_print(struct wt_status *s)
> case STATUS_FORMAT_PORCELAIN_V2:
> wt_porcelain_v2_print(s);
> break;
> + case STATUS_FORMAT_JSON:
> + wt_json_status_print(s);
> + break;
> case STATUS_FORMAT_UNSPECIFIED:
> BUG("finalize_deferred_config() should have been called");
> break;
> diff --git a/wt-status.h b/wt-status.h
> index 4e377ce62b8..e929af832b2 100644
> --- a/wt-status.h
> +++ b/wt-status.h
> @@ -74,6 +74,7 @@ enum wt_status_format {
> STATUS_FORMAT_SHORT,
> STATUS_FORMAT_PORCELAIN,
> STATUS_FORMAT_PORCELAIN_V2,
> + STATUS_FORMAT_JSON,
>
> STATUS_FORMAT_UNSPECIFIED
> };
>
> base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] status: add --json output format to git status
[not found] ` <CA+m0rScismG8VVf8mYS=+_AjwgrgX8GNZrFD+8-0EaeCDYPgbg@mail.gmail.com>
@ 2025-07-30 10:08 ` phillip.wood123
0 siblings, 0 replies; 6+ messages in thread
From: phillip.wood123 @ 2025-07-30 10:08 UTC (permalink / raw)
To: Tachera W sasi, phillip.wood; +Cc: Tach via GitGitGadget, git
Hi Tachera
On 30/07/2025 10:15, Tachera W sasi wrote:
>
> For v2, I will:
> * Rework the patch to use `json-writer.h` so escaping and JSON
> compliance are handled
> properly.
That will handle the escaping problems but we still have the problem
that filenames and refnames are not guaranteed to be utf-8 encoded. I
think the only way to handle that is to base64 encode them so that the
caller can retrieve the raw bytes. See
https://lore.kernel.org/git/ZvXMSKaUWWA-MG9J@tapette.crustytoothpaste.net/
for a previous discussion about this.
> * Add tests covering filenames with quotes, newlines, and non-UTF-8
> cases.
> * Include tests under `t/` to prevent future regressions.
That's great, you should update Documentation/git-status.adoc as well to
document the new option and output format
Thanks
Phillip
> Thanks again for your review and for pointing me towards the proper helpers.
>
> Best regards,
> Tachera Sasi
>
> On Wed, Jul 30, 2025 at 12:01 PM Phillip Wood <phillip.wood123@gmail.com
> <mailto:phillip.wood123@gmail.com>> wrote:
>
> On 30/07/2025 07:27, Tach via GitGitGadget wrote:
> > From: tacherasasi <tacherasasi@gmail.com
> <mailto:tacherasasi@gmail.com>>
> >
> > Add a new --json flag to 'git status' that outputs repository state
> > in a structured JSON format. This enables reliable machine parsing
> > of status information for tools and automation.
> >
> > The JSON output includes:
> > - Branch information (name, detached state, ahead/behind counts)
> > - Staged files array
> > - Unstaged files array
> > - Untracked files array
> > - Ignored files array (with --ignored flag)
> >
> > Implementation details:
> > - Add STATUS_FORMAT_JSON to wt_status_format enum
> > - Add JSON output option to git status and git commit
> > - Implement JSON formatting helpers for arrays and branch info
> > - Structure output for easy parsing and future extensibility
> >
> > Example:
> > $ git status --json
> > {
> > "branch": {
> > "current": "main",
> > "detached": false
> > },
> > "staged": ["file1.txt"],
> > "unstaged": ["file2.txt"],
> > "untracked": ["file3.txt"]
> > }
> >
> > This provides a robust alternative to parsing traditional output
> > formats, making it easier to build reliable tools and automation
> > around Git status information.
>
> What problem are you having paring the current output? You say this
> patch provides a robust alternative but I'm afraid it does not seem to
> handle filenames containing newlines or double quotes and it assumes
> they are utf-8 encoded. It also seems to print a trailing comma if an
> array has more than one item which is not allowed by the json spec. We
> already have support for generating json output as documented
> json-writer.h which will help with most of that but not the filename
> encoding problem. New features should also be accompanied by tests to
> prevent future regressions. I'm not opposed to adding json output if it
> is robust and handles non utf-8 filenames but it would be helpful to
> understand what problems you are having parsing the output of "git
> status --porcelain[=v2]".
>
> Thanks
>
> Phillip
>
> > Signed-off-by: Tachera Sasi <tachera@ekilie.com
> <mailto:tachera@ekilie.com>>
> > Signed-off-by: tacherasasi <tacherasasi@gmail.com
> <mailto:tacherasasi@gmail.com>>
> > ---
> > status: add --json output format to git status
> >
> > Published-As: https://github.com/gitgitgadget/git/releases/tag/
> pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1 <https://
> github.com/gitgitgadget/git/releases/tag/
> pr-1937%2FtacheraSasi%2Ffeature%2Fjson-status-output-v1>
> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git
> <https://github.com/gitgitgadget/git> pr-1937/tacheraSasi/feature/
> json-status-output-v1
> > Pull-Request: https://github.com/gitgitgadget/git/pull/1937
> <https://github.com/gitgitgadget/git/pull/1937>
> >
> > builtin/commit.c | 9 ++++-
> > wt-status.c | 98 +++++++++++++++++++++++++++++++++++++++++
> +++++++
> > wt-status.h | 1 +
> > 3 files changed, 107 insertions(+), 1 deletion(-)
> >
> > diff --git a/builtin/commit.c b/builtin/commit.c
> > index fba0dded64a..f1db4fdfd9a 100644
> > --- a/builtin/commit.c
> > +++ b/builtin/commit.c
> > @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
> > OPT_SET_INT(0, "long", &status_format,
> > N_("show status in long format (default)"),
> > STATUS_FORMAT_LONG),
> > + OPT_SET_INT(0, "json", &status_format,
> > + N_("show status in JSON format"),
> > + STATUS_FORMAT_JSON),
> > OPT_BOOL('z', "null", &s.null_termination,
> > N_("terminate entries with NUL")),
> > {
> > @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
> > prefix, argv);
> >
> > if (status_format != STATUS_FORMAT_PORCELAIN &&
> > - status_format != STATUS_FORMAT_PORCELAIN_V2)
> > + status_format != STATUS_FORMAT_PORCELAIN_V2 &&
> > + status_format != STATUS_FORMAT_JSON)
> > progress_flag = REFRESH_PROGRESS;
> > repo_read_index(the_repository);
> > refresh_index(the_repository->index,
> > @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
> > OPT_SET_INT(0, "long", &status_format,
> > N_("show status in long format (default)"),
> > STATUS_FORMAT_LONG),
> > + OPT_SET_INT(0, "json", &status_format,
> > + N_("show status in JSON format"),
> > + STATUS_FORMAT_JSON),
> > OPT_BOOL('z', "null", &s.null_termination,
> > N_("terminate entries with NUL")),
> > OPT_BOOL(0, "amend", &amend, N_("amend previous
> commit")),
> > diff --git a/wt-status.c b/wt-status.c
> > index 454601afa15..7192fb4d057 100644
> > --- a/wt-status.c
> > +++ b/wt-status.c
> > @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct
> wt_status *s)
> > }
> > }
> >
> > +
> > +static void wt_json_print_string_array(struct wt_status *s,
> const char *name, struct string_list *list)
> > +{
> > + int i;
> > + fprintf(s->fp, " \"%s\": [", name);
> > + for (i = 0; i < list->nr; i++) {
> > + if (i > 0)
> > + fprintf(s->fp, ", ");
> > + fprintf(s->fp, "\"%s\"", list->items[i].string);
> > + }
> > + fprintf(s->fp, "]");
> > +}
> > +
> > +static void wt_json_print_change_array(struct wt_status *s,
> const char *name, int change_type)
> > +{
> > + int i;
> > + struct string_list files = STRING_LIST_INIT_DUP;
> > +
> > + for (i = 0; i < s->change.nr <http://change.nr>; i++) {
> > + struct wt_status_change_data *d;
> > + struct string_list_item *it;
> > + it = &(s->change.items[i]);
> > + d = it->util;
> > +
> > + if ((change_type == WT_STATUS_UPDATED && d-
> >index_status &&
> > + d->index_status != DIFF_STATUS_UNMERGED) ||
> > + (change_type == WT_STATUS_CHANGED && d-
> >worktree_status &&
> > + d->worktree_status != DIFF_STATUS_UNMERGED)) {
> > + string_list_append(&files, it->string);
> > + }
> > + }
> > +
> > + wt_json_print_string_array(s, name, &files);
> > + string_list_clear(&files, 0);
> > +}
> > +
> > +static void wt_json_print_branch_info(struct wt_status *s)
> > +{
> > + struct branch *branch;
> > + const char *branch_name;
> > + int ahead = 0, behind = 0;
> > +
> > + fprintf(s->fp, " \"branch\": {\n");
> > +
> > + if (s->branch && !s->is_initial) {
> > + if (!strcmp(s->branch, "HEAD")) {
> > + fprintf(s->fp, " \"current\": \"HEAD\",\n");
> > + fprintf(s->fp, " \"detached\": true");
> > + } else {
> > + if (skip_prefix(s->branch, "refs/heads/",
> &branch_name)) {
> > + fprintf(s->fp, " \"current\":
> \"%s\",\n", branch_name);
> > + fprintf(s->fp, " \"detached\":
> false");
> > +
> > + branch = branch_get(branch_name);
> > + if (branch && branch->merge &&
> branch->merge[0] && branch->merge[0]->dst) {
> > + if (!
> stat_tracking_info(branch, &ahead, &behind, NULL, 0, 0)) {
> > + fprintf(s->fp, ",
> \n \"ahead\": %d,\n \"behind\": %d", ahead, behind);
> > + }
> > + }
> > + } else {
> > + fprintf(s->fp, " \"current\":
> \"%s\",\n", s->branch);
> > + fprintf(s->fp, " \"detached\":
> false");
> > + }
> > + }
> > + } else {
> > + fprintf(s->fp, " \"current\": null,\n");
> > + fprintf(s->fp, " \"detached\": false");
> > + }
> > +
> > + fprintf(s->fp, "\n }");
> > +}
> > +
> > +static void wt_json_status_print(struct wt_status *s)
> > +{
> > + fprintf(s->fp, "{\n");
> > +
> > + wt_json_print_branch_info(s);
> > + fprintf(s->fp, ",\n");
> > +
> > + wt_json_print_change_array(s, "staged", WT_STATUS_UPDATED);
> > + fprintf(s->fp, ",\n");
> > +
> > + wt_json_print_change_array(s, "unstaged", WT_STATUS_CHANGED);
> > + fprintf(s->fp, ",\n");
> > +
> > + wt_json_print_string_array(s, "untracked", &s->untracked);
> > +
> > + if (s->ignored.nr <http://ignored.nr> > 0) {
> > + fprintf(s->fp, ",\n");
> > + wt_json_print_string_array(s, "ignored", &s->ignored);
> > + }
> > +
> > + fprintf(s->fp, "\n}\n");
> > +}
> > +
> > void wt_status_print(struct wt_status *s)
> > {
> > trace2_data_intmax("status", s->repo, "count/changed", s-
> >change.nr <http://change.nr>);
> > @@ -2583,6 +2678,9 @@ void wt_status_print(struct wt_status *s)
> > case STATUS_FORMAT_PORCELAIN_V2:
> > wt_porcelain_v2_print(s);
> > break;
> > + case STATUS_FORMAT_JSON:
> > + wt_json_status_print(s);
> > + break;
> > case STATUS_FORMAT_UNSPECIFIED:
> > BUG("finalize_deferred_config() should have been
> called");
> > break;
> > diff --git a/wt-status.h b/wt-status.h
> > index 4e377ce62b8..e929af832b2 100644
> > --- a/wt-status.h
> > +++ b/wt-status.h
> > @@ -74,6 +74,7 @@ enum wt_status_format {
> > STATUS_FORMAT_SHORT,
> > STATUS_FORMAT_PORCELAIN,
> > STATUS_FORMAT_PORCELAIN_V2,
> > + STATUS_FORMAT_JSON,
> >
> > STATUS_FORMAT_UNSPECIFIED
> > };
> >
> > base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] status: add --json output format to git status
2025-07-30 6:27 [PATCH] status: add --json output format to git status Tach via GitGitGadget
2025-07-30 9:01 ` Phillip Wood
@ 2025-07-30 14:58 ` Junio C Hamano
2025-08-04 13:49 ` Phillip Wood
1 sibling, 1 reply; 6+ messages in thread
From: Junio C Hamano @ 2025-07-30 14:58 UTC (permalink / raw)
To: Tach via GitGitGadget; +Cc: git, Tach
"Tach via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: tacherasasi <tacherasasi@gmail.com>
> Add a new --json flag to 'git status' that outputs repository state
> in a structured JSON format. This enables reliable machine parsing
> of status information for tools and automation.
The --porcelain format is meant to serve this "parseable for tools
and automation to read" use case. Was there anything your parser
couldn't fathom out of the format?
> This provides a robust alternative to parsing traditional output
> formats, making it easier to build reliable tools and automation
> around Git status information.
The writer is obviously biased ;-) but I find this a bit hand-wavy
and unconvincing.
> Signed-off-by: Tachera Sasi <tachera@ekilie.com>
> Signed-off-by: tacherasasi <tacherasasi@gmail.com>
It is not like two separate people worked on this patch, or somebody
at ekilie worked on the patch but asked a different person with a
very similar name to send it on their behalf, right?
Perhaps update the user.{name,email} on the in-body From: to match
the one used on the first Sign-off, and drop the second Sign-off?
> diff --git a/builtin/commit.c b/builtin/commit.c
> index fba0dded64a..f1db4fdfd9a 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -1540,6 +1540,9 @@ struct repository *repo UNUSED)
> OPT_SET_INT(0, "long", &status_format,
> N_("show status in long format (default)"),
> STATUS_FORMAT_LONG),
> + OPT_SET_INT(0, "json", &status_format,
> + N_("show status in JSON format"),
> + STATUS_FORMAT_JSON),
There are now several ways to set to status_format now. It could be
a good idea to make a preliminary UI clean-up to introduce a new
option "--format={short,long,porcelain,porcelain-v1,porcelain-v2}"
as a separate step, declare that any new format will use this new
option without adding a separate option on its own, before adding
any new features. And then on top that, a new format can be
introduced without cluttering the set of options.
Not that I am enthused to see json here, given that we have
introduced porcelain format for the same purpose eons ago.
> @@ -1603,7 +1606,8 @@ struct repository *repo UNUSED)
> prefix, argv);
>
> if (status_format != STATUS_FORMAT_PORCELAIN &&
> - status_format != STATUS_FORMAT_PORCELAIN_V2)
> + status_format != STATUS_FORMAT_PORCELAIN_V2 &&
> + status_format != STATUS_FORMAT_JSON)
> progress_flag = REFRESH_PROGRESS;
> repo_read_index(the_repository);
> refresh_index(the_repository->index,
Also, we might want to tweak the bit-assignment for the
status_format constants to make this part easier to maintain (e.g.,
ones below 100 are for human consumption and gives progress bar and
ones above 100 are for machine consumption and suppresses progress,
or something like that), again as a separate preliminary clean-up
step before adding any new features.
> @@ -1735,6 +1739,9 @@ int cmd_commit(int argc,
> OPT_SET_INT(0, "long", &status_format,
> N_("show status in long format (default)"),
> STATUS_FORMAT_LONG),
> + OPT_SET_INT(0, "json", &status_format,
> + N_("show status in JSON format"),
> + STATUS_FORMAT_JSON),
And if we are to clean-up with --format=<name> and build on top,
this part would become somewhat different. The clean-up would
probably be somewhat like what opt_parse_porcelain() is doing and
"json" or "yaml" or any new format name would join if/else if/
cascade there.
> diff --git a/wt-status.c b/wt-status.c
> index 454601afa15..7192fb4d057 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -2564,6 +2564,101 @@ static void wt_porcelain_v2_print(struct wt_status *s)
> }
> }
>
> +
> +static void wt_json_print_string_array(struct wt_status *s, const char *name, struct string_list *list)
> +{
We seem to have a json write-out library-ish helpers already; is it
too tied to its current use (i.e. trace output) and too inflexible
to be used here, or does the status output have so specialized and
unusual need as a normal JSON producing application that we need a
special purpose code here with bunch of fprintf() and manual
formatting?
Thanks.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] status: add --json output format to git status
2025-07-30 14:58 ` Junio C Hamano
@ 2025-08-04 13:49 ` Phillip Wood
2025-08-04 13:54 ` Junio C Hamano
0 siblings, 1 reply; 6+ messages in thread
From: Phillip Wood @ 2025-08-04 13:49 UTC (permalink / raw)
To: Junio C Hamano, Tach via GitGitGadget; +Cc: git, Tach
On 30/07/2025 15:58, Junio C Hamano wrote:
> "Tach via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> This provides a robust alternative to parsing traditional output
>> formats, making it easier to build reliable tools and automation
>> around Git status information.
>
> The writer is obviously biased ;-) but I find this a bit hand-wavy
> and unconvincing.
I agree this isn't a very convincing reason. To me a more convincing
reason is that virtually every scripting language comes with a way to
parse json and base64 so if we emit json with path and ref names encoded
as base64 there is no need for script authors to write a parser to parse
our porcelain output. To me the fact that we have to encode names using
base64 is a sign that json is not particularly well suited to the task
but it is widely supported format.
Thanks
Phillip
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] status: add --json output format to git status
2025-08-04 13:49 ` Phillip Wood
@ 2025-08-04 13:54 ` Junio C Hamano
0 siblings, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2025-08-04 13:54 UTC (permalink / raw)
To: Phillip Wood; +Cc: Tach via GitGitGadget, git, Tach
Phillip Wood <phillip.wood123@gmail.com> writes:
> On 30/07/2025 15:58, Junio C Hamano wrote:
>> "Tach via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>
>>> This provides a robust alternative to parsing traditional output
>>> formats, making it easier to build reliable tools and automation
>>> around Git status information.
>> The writer is obviously biased ;-) but I find this a bit hand-wavy
>> and unconvincing.
>
> I agree this isn't a very convincing reason. To me a more convincing
> reason is that virtually every scripting language comes with a way to
> parse json and base64 so if we emit json with path and ref names
> encoded as base64 there is no need for script authors to write a
> parser to parse our porcelain output. To me the fact that we have to
> encode names using base64 is a sign that json is not particularly well
> suited to the task but it is widely supported format.
Very well put. Thanks.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-08-04 13:54 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-30 6:27 [PATCH] status: add --json output format to git status Tach via GitGitGadget
2025-07-30 9:01 ` Phillip Wood
[not found] ` <CA+m0rScismG8VVf8mYS=+_AjwgrgX8GNZrFD+8-0EaeCDYPgbg@mail.gmail.com>
2025-07-30 10:08 ` phillip.wood123
2025-07-30 14:58 ` Junio C Hamano
2025-08-04 13:49 ` Phillip Wood
2025-08-04 13:54 ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).