Re: [PATCH v2 1/4] fetch: extract writing to FETCH_HEAD

2 messages, 2 authors, 2021-01-11 · open the first message on its own page

Re: [PATCH v2 1/4] fetch: extract writing to FETCH_HEAD

From: Junio C Hamano <hidden>
Date: 2021-01-08 23:41:09

Patrick Steinhardt [off-list ref] writes:
+static int open_fetch_head(struct fetch_head *fetch_head)
+{
+	const char *filename = git_path_fetch_head(the_repository);
+
+	if (!write_fetch_head)
+		return 0;
+
+	fetch_head->fp = fopen(filename, "a");
+	if (!fetch_head->fp)
+		return error_errno(_("cannot open %s"), filename);
+
+	return 0;
+}
So the difference from the original, which used to have a writable
filehandle to /dev/null in the dry-run mode, is that fetch_head->fp
is left as-is (not even NULLed out).
+static void append_fetch_head(struct fetch_head *fetch_head, const char *old_oid,
It is clear from the type these days but variable names like
"old_oid" hint the readers that they are not a hexadecimal object
name string but either an array of uchar[40] or a struct object_id;
perhaps "old_oid_hex" would be less misleading.

If the caller does have struct object_id, then it would be even
better to take it as-is as a parameter and use oid_to_hex_r() on it in
this function when it is given to fprintf().  [Nit #1]
+			      const char *merge_status_marker, const char *note,
+			      const char *url, size_t url_len)
+{
+	size_t i;
+
+	if (!write_fetch_head)
+		return;
Presumably, this check is what makes sure that fetch_head->fp that
is left uninitialized will never gets used.
+	fprintf(fetch_head->fp, "%s\t%s\t%s",
+		old_oid, merge_status_marker, note);
+	for (i = 0; i < url_len; ++i)
+		if ('\n' == url[i])
+			fputs("\\n", fetch_head->fp);
+		else
+			fputc(url[i], fetch_head->fp);
+	fputc('\n', fetch_head->fp);
+}
OK.  This is the "case FETCH_HEAD_NOT_FOR_MERGE" and "case
FETCH_HEAD_MERGE" parts in the original.

As an abstraction, it may be better to make the caller pass a
boolean "is this for merge?" and keep the knowledge of what exact
string is used for merge_status_marker to this function, instead of
letting the caller passing it as a parameter in the string form.
After all, we never allow anything other than an empty string or a
fixed "not-for-merge" string in that place in the file format.
[Nit #2]
+static void commit_fetch_head(struct fetch_head *fetch_head)
+{
+	/* Nothing to commit yet. */
+}
+
+static void close_fetch_head(struct fetch_head *fetch_head)
+{
+	if (!write_fetch_head)
+		return;
So is this check a protection against uninitialized fetch_head->fp.
Both changes make sense.
+	fclose(fetch_head->fp);
+}
quoted hunk
@@ -909,22 +959,19 @@ N_("It took %.2f seconds to check forced updates. You can use\n"
 static int store_updated_refs(const char *raw_url, const char *remote_name,
 			      int connectivity_checked, struct ref *ref_map)
 {
-	FILE *fp;
+	struct fetch_head fetch_head;
And that is why this variable is left uninitialised on stack and it
is OK.  An alternative design would be to initialize fetch_head.fp
to NULL, and return early with "if (!fetch_head->fp)" in the two
functions that take fetch_head struct.  That way, we have less
reliance on the global variable write_fetch_head.
 	struct commit *commit;
 	int url_len, i, rc = 0;
 	struct strbuf note = STRBUF_INIT;
 	const char *what, *kind;
 	struct ref *rm;
 	char *url;
-	const char *filename = (!write_fetch_head
-				? "/dev/null"
-				: git_path_fetch_head(the_repository));
 	int want_status;
 	int summary_width = transport_summary_width(ref_map);
 
-	fp = fopen(filename, "a");
-	if (!fp)
-		return error_errno(_("cannot open %s"), filename);
+	rc = open_fetch_head(&fetch_head);
+	if (rc)
+		return -1;
OK, we've already said "cannot open" in the open_fetch_head()
function, so we just return an error silently.
quoted hunk
@@ -1016,16 +1063,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 				merge_status_marker = "not-for-merge";
 				/* fall-through */
 			case FETCH_HEAD_MERGE:
-				fprintf(fp, "%s\t%s\t%s",
-					oid_to_hex(&rm->old_oid),
-					merge_status_marker,
-					note.buf);
-				for (i = 0; i < url_len; ++i)
-					if ('\n' == url[i])
-						fputs("\\n", fp);
-					else
-						fputc(url[i], fp);
-				fputc('\n', fp);
+				append_fetch_head(&fetch_head,
+						  oid_to_hex(&rm->old_oid),
+						  merge_status_marker,
+						  note.buf, url, url_len);
Here, we can lose merge_status_marker variable from this caller, and
then this caller becomes:

		switch (rm->fetch_head_status) {
		case FETCH_HEAD_NOT_FOR_MERGE:
		case FETCH_HEAD_MERGE:
			append_fetch_head(&fetch_head, &rm->old_oid,
				rm->fetch_head_status == FETCH_HEAD_MERGE,
                                note.buf, url, url_len);

Note that I am passing "is this a ref to be merged?" boolean to keep
the exact string of "not-for-merge" in the callee.
quoted hunk
@@ -1060,6 +1101,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 		}
 	}
 
+	if (!rc)
+		commit_fetch_head(&fetch_head);
+
 	if (rc & STORE_REF_ERROR_DF_CONFLICT)
 		error(_("some local refs could not be updated; try running\n"
 		      " 'git remote prune %s' to remove any old, conflicting "
@@ -1077,7 +1121,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
  abort:
 	strbuf_release(&note);
 	free(url);
-	fclose(fp);
+	close_fetch_head(&fetch_head);
 	return rc;
 }
Other than the above two nits, this step looks good to me.

Thanks.

Re: [PATCH v2 1/4] fetch: extract writing to FETCH_HEAD

From: Patrick Steinhardt <hidden>
Date: 2021-01-11 10:27:27

On Fri, Jan 08, 2021 at 03:40:17PM -0800, Junio C Hamano wrote:
Patrick Steinhardt [off-list ref] writes:
quoted
+static void append_fetch_head(struct fetch_head *fetch_head, const char *old_oid,
It is clear from the type these days but variable names like
"old_oid" hint the readers that they are not a hexadecimal object
name string but either an array of uchar[40] or a struct object_id;
perhaps "old_oid_hex" would be less misleading.

If the caller does have struct object_id, then it would be even
better to take it as-is as a parameter and use oid_to_hex_r() on it in
this function when it is given to fprintf().  [Nit #1]
Agreed.

[snip]
quoted
+	fprintf(fetch_head->fp, "%s\t%s\t%s",
+		old_oid, merge_status_marker, note);
+	for (i = 0; i < url_len; ++i)
+		if ('\n' == url[i])
+			fputs("\\n", fetch_head->fp);
+		else
+			fputc(url[i], fetch_head->fp);
+	fputc('\n', fetch_head->fp);
+}
OK.  This is the "case FETCH_HEAD_NOT_FOR_MERGE" and "case
FETCH_HEAD_MERGE" parts in the original.

As an abstraction, it may be better to make the caller pass a
boolean "is this for merge?" and keep the knowledge of what exact
string is used for merge_status_marker to this function, instead of
letting the caller passing it as a parameter in the string form.
After all, we never allow anything other than an empty string or a
fixed "not-for-merge" string in that place in the file format.
[Nit #2]
I think it's even nicer to just pass in `rm->fetch_head_status`
directly, which allows us to move below switch into `append_fetch_head`.

[snip]
quoted
+	fclose(fetch_head->fp);
+}
quoted
@@ -909,22 +959,19 @@ N_("It took %.2f seconds to check forced updates. You can use\n"
 static int store_updated_refs(const char *raw_url, const char *remote_name,
 			      int connectivity_checked, struct ref *ref_map)
 {
-	FILE *fp;
+	struct fetch_head fetch_head;
And that is why this variable is left uninitialised on stack and it
is OK.  An alternative design would be to initialize fetch_head.fp
to NULL, and return early with "if (!fetch_head->fp)" in the two
functions that take fetch_head struct.  That way, we have less
reliance on the global variable write_fetch_head.
I like your proposal more. I wasn't quite happy with leaving `fp`
uninitialized, and using it as a sentinel for whether to write something
or not feels natural to me.

[snip]
quoted
@@ -1016,16 +1063,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 				merge_status_marker = "not-for-merge";
 				/* fall-through */
 			case FETCH_HEAD_MERGE:
-				fprintf(fp, "%s\t%s\t%s",
-					oid_to_hex(&rm->old_oid),
-					merge_status_marker,
-					note.buf);
-				for (i = 0; i < url_len; ++i)
-					if ('\n' == url[i])
-						fputs("\\n", fp);
-					else
-						fputc(url[i], fp);
-				fputc('\n', fp);
+				append_fetch_head(&fetch_head,
+						  oid_to_hex(&rm->old_oid),
+						  merge_status_marker,
+						  note.buf, url, url_len);
Here, we can lose merge_status_marker variable from this caller, and
then this caller becomes:

		switch (rm->fetch_head_status) {
		case FETCH_HEAD_NOT_FOR_MERGE:
		case FETCH_HEAD_MERGE:
			append_fetch_head(&fetch_head, &rm->old_oid,
				rm->fetch_head_status == FETCH_HEAD_MERGE,
                                note.buf, url, url_len);

Note that I am passing "is this a ref to be merged?" boolean to keep
the exact string of "not-for-merge" in the callee.
As said above, I'm just moving this complete switch into
`append_fetch_head` and pass `rm->fetch_head_status`.

Patrick
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help