Subject: [PATCH v2 1/3] bugreport: include +i in outfile suffix as needed

From: Jacob Stopak <[email-masked]>
Date: Sat, 14 Oct 2023 20:42:35 -0700

When the -s flag is absent, git bugreport includes the current hour and
minute values in the default bugreport filename (and diagnostics zip
filename if --diagnose is supplied).

If a user runs the bugreport command more than once within a calendar
minute, a filename conflict with an existing file occurs and the program
errors, since the new output filename was already used for the previous
file. If the user waits anywhere from 1 to 60 seconds (depending on
_when during the calendar minute_ the first command was run) the command
works again with no error since the default filename is now unique, and
multiple bug reports are able to be created with default settings.

This is a minor thing but can cause confusion especially for first time
users of the bugreport command, who are likely to run it multiple times
in quick succession to learn how it works, (like I did).

Add a '+i' into the bugreport filename suffix to make the filename
unique, where 'i' is an integer starting at 1 and able to grow up to 9
in the unlikely event a user runs the command 9 times in a single
minute. This leads to default output filenames like:

git-bugreport-%Y-%m-%d-%H%M+1.txt
git-bugreport-%Y-%m-%d-%H%M+2.txt
...
git-bugreport-%Y-%m-%d-%H%M+9.txt

This means the user will end up with multiple bugreport files being
created if they run the command multiple times quickly, but that feels
more intuitive and consistent than an error arbitrarily occuring within
a calendar minute, especially given that the time window in which the
error currently occurs is variable as described above.

Signed-off-by: Jacob Stopak <[email-masked]>
---
 builtin/bugreport.c | 26 +++++++++++++++++++++-----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index d2ae5c305d..71ee7d7f4b 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -3,7 +3,6 @@
 #include "editor.h"
 #include "gettext.h"
 #include "parse-options.h"
-#include "strbuf.h"
 #include "help.h"
 #include "compat/compiler.h"
 #include "hook.h"
@@ -11,6 +10,7 @@
 #include "diagnose.h"
 #include "object-file.h"
 #include "setup.h"
+#include "dir.h"
 
 static void get_system_info(struct strbuf *sys_info)
 {
@@ -101,12 +101,13 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf buffer = STRBUF_INIT;
 	struct strbuf report_path = STRBUF_INIT;
+	struct strbuf option_suffix = STRBUF_INIT;
+	struct strbuf default_option_suffix = STRBUF_INIT;
 	int report = -1;
 	time_t now = time(NULL);
 	struct tm tm;
 	enum diagnose_mode diagnose = DIAGNOSE_NONE;
 	char *option_output = NULL;
-	char *option_suffix = "%Y-%m-%d-%H%M";
 	const char *user_relative_path = NULL;
 	char *prefixed_filename;
 	size_t output_path_len;
@@ -118,11 +119,14 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 			       PARSE_OPT_OPTARG, option_parse_diagnose),
 		OPT_STRING('o', "output-directory", &option_output, N_("path"),
 			   N_("specify a destination for the bugreport file(s)")),
-		OPT_STRING('s', "suffix", &option_suffix, N_("format"),
+		OPT_STRING('s', "suffix", &option_suffix.buf, N_("format"),
 			   N_("specify a strftime format suffix for the filename(s)")),
 		OPT_END()
 	};
 
+	strbuf_addstr(&default_option_suffix, "%Y-%m-%d-%H%M");
+	strbuf_addstr(&option_suffix, default_option_suffix.buf);
+
 	argc = parse_options(argc, argv, prefix, bugreport_options,
 			     bugreport_usage, 0);
 
@@ -134,9 +138,20 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 	output_path_len = report_path.len;
 
 	strbuf_addstr(&report_path, "git-bugreport-");
-	strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0);
+	strbuf_addftime(&report_path, option_suffix.buf, localtime_r(&now, &tm), 0, 0);
 	strbuf_addstr(&report_path, ".txt");
 
+	if (strbuf_cmp(&option_suffix, &default_option_suffix) == 0) {
+		int i = 1;
+		int pos = report_path.len - 4;
+		while (file_exists(report_path.buf) && i < 10) {
+			if (i > 1)
+				strbuf_remove(&report_path, pos, 2);
+			strbuf_insertf(&report_path, pos, "+%d", i);
+			i++;
+		}
+	}
+
 	switch (safe_create_leading_directories(report_path.buf)) {
 	case SCLD_OK:
 	case SCLD_EXISTS:
@@ -151,7 +166,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 		struct strbuf zip_path = STRBUF_INIT;
 		strbuf_add(&zip_path, report_path.buf, output_path_len);
 		strbuf_addstr(&zip_path, "git-diagnostics-");
-		strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0);
+		strbuf_addftime(&zip_path, option_suffix.buf, localtime_r(&now, &tm), 0, 0);
 		strbuf_addstr(&zip_path, ".zip");
 
 		if (create_diagnostics_archive(&zip_path, diagnose))
@@ -188,6 +203,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
 
 	free(prefixed_filename);
 	strbuf_release(&buffer);
+	strbuf_release(&default_option_suffix);
 
 	ret = !!launch_editor(report_path.buf, NULL, NULL);
 	strbuf_release(&report_path);
-- 
2.42.0.298.gd89efca819.dirty