threads / patch / 18048

patchremote: make copy_ref() perform a deep copy

Subject: [PATCH 07a/21] remote: make copy_ref() perform a deep copy

## tl;dr

5 messages between Feb 27, 2009 and Feb 28, 2009. Diffs are folded; open one to read it.

replies: 4people: 2as markdown or json

Jay Soffian· Feb 27, 2009, 19:10 UTC · lore

[PATCH 0/3] git remote: set-head and new show output (UPDATED)

Jeff, Junio,

I didn't want to resend the whole series (gmane 111394), so what I did was create a new patch which comes after patch 7 in that series. Call it patch 7a. The rest of that series is then identical (modulo line number offsets in patches touching remote.c) except for patches 8 and 19. Patches 7a, and the new 8 and 19 follow this email.

You should be able to do this:

$ git checkout js/remote-improvements $ git format-patch master $ git reset --hard $(git merge-base master HEAD)

Copy new "[PATCH 07a/21] remote: make copy_ref() perform a deep copy" to 0007a-remote-make-copy_ref-perform-a-deep-copy.patch

Copy new "[PATCH 08/21] remote: let guess_remote_head() optionally return all matches" to 0008-remote-let-guess_remote_head-optionally-return-al.patch

Copy new "[PATCH 19/21] remote: make guess_remote_head() use exact HEAD lookup if it is available" to 0019-remote-make-guess_remote_head-use-exact-HEAD-look.patch

$ git am -3 *.patch

For those playing along at home, js/remote-improvements is currently commits 661763a..5e4c90a.

Which should give you a new js/remote-improvements that looks like this:
  1  test scripts: refactor start_httpd helper
  2  add basic http clone/fetch tests
  3  refactor find_ref_by_name() to accept const list
  4  move duplicated get_local_heads() to remote.c
  5  move duplicated ref_newer() to remote.c
  6  move locate_head() to remote.c
  7  remote: simplify guess_remote_head()
  8  remote: make copy_ref() perform a deep copy
  9  remote: let guess_remote_head() optionally return all matches
 10  remote: make match_refs() copy src ref before assigning to peer_ref
 11  remote: make match_refs() not short-circuit
 12  string-list: new for_each_string_list() function
 13  builtin-remote: refactor duplicated cleanup code
 14  builtin-remote: remove unused code in get_ref_states
 15  builtin-remote: rename variables and eliminate redundant function call
 16  builtin-remote: make get_remote_ref_states() always populate states.tracked
 17  builtin-remote: fix two inconsistencies in the output of "show <remote>"
 18  builtin-remote: teach show to display remote HEAD
 19  builtin-remote: add set-head subcommand
 20  remote: make guess_remote_head() use exact HEAD lookup if it is available
 21  builtin-remote: new show output style
 22  builtin-remote: new show output style for push refspecs
Here's the new diff stat:
 Documentation/git-remote.txt           |   28 ++-
 Makefile                               |    1 +
 builtin-clone.c                        |   41 +---
 builtin-remote.c                       |  561 ++++++++++++++++++++++++++------
 builtin-send-pack.c                    |   79 +-----
 cache.h                                |    2 +-
 contrib/completion/git-completion.bash |    2 +-
 http-push.c                            |   72 +----
 refs.c                                 |    4 +-
 remote.c                               |  145 ++++++++-
 remote.h                               |   12 +
 string-list.c                          |   10 +
 string-list.h                          |    5 +
 t/lib-httpd.sh                         |    9 +-
 t/t5505-remote.sh                      |  114 +++++--
 t/t5540-http-push.sh                   |    9 +-
 t/t5550-http-fetch.sh                  |   57 ++++
 17 files changed, 821 insertions(+), 330 deletions(-)
 create mode 100755 t/t5550-http-fetch.sh
And here's the inter-diff:
Show changes to remote.c +17 −18
diff --git a/remote.c b/remote.c
index 1c09cf0..9b8522d 100644
--- a/remote.c
+++ b/remote.c
@@ -779,10 +779,18 @@ struct ref *alloc_ref(const char *name)
 
 static struct ref *copy_ref(const struct ref *ref)
 {
-	struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
-	memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
-	ret->next = NULL;
-	return ret;
+	struct ref *cpy;
+	size_t len;
+	if (!ref)
+		return NULL;
+	len = strlen(ref->name);
+	cpy = xmalloc(sizeof(struct ref) + len + 1);
+	memcpy(cpy, ref, sizeof(struct ref) + len + 1);
+	cpy->next = NULL;
+	cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL;
+	cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL;
+	cpy->peer_ref = copy_ref(ref->peer_ref);
+	return cpy;
 }
 
 struct ref *copy_ref_list(const struct ref *ref)
@@ -801,6 +809,7 @@ static void free_ref(struct ref *ref)
 {
 	if (!ref)
 		return;
+	free_ref(ref->peer_ref);
 	free(ref->remote_status);
 	free(ref->symref);
 	free(ref);
@@ -811,7 +820,6 @@ void free_refs(struct ref *ref)
 	struct ref *next;
 	while (ref) {
 		next = ref->next;
-		free(ref->peer_ref);
 		free_ref(ref);
 		ref = next;
 	}
@@ -1457,13 +1465,6 @@ struct ref *get_local_heads(void)
 	return local_refs;
 }
 
-struct ref *copy_ref_with_peer(const struct ref *src)
-{
-	struct ref *dst = copy_ref(src);
-	dst->peer_ref = copy_ref(src->peer_ref);
-	return dst;
-}
-
 struct ref *guess_remote_head(const struct ref *head,
 			      const struct ref *refs,
 			      int all)
@@ -1480,22 +1481,20 @@ struct ref *guess_remote_head(const struct ref *head,
 	 * where HEAD points; if that is the case, then
 	 * we don't have to guess.
 	 */
-	if (head->symref) {
-		r = find_ref_by_name(refs, head->symref);
-		return r ? copy_ref_with_peer(r) : NULL;
-	}
+	if (head->symref)
+		return copy_ref(find_ref_by_name(refs, head->symref));
 
 	/* If refs/heads/master could be right, it is. */
 	if (!all) {
 		r = find_ref_by_name(refs, "refs/heads/master");
 		if (r && !hashcmp(r->old_sha1, head->old_sha1))
-			return copy_ref_with_peer(r);
+			return copy_ref(r);
 	}
 
 	/* Look for another ref that points there */
 	for (r = refs; r; r = r->next) {
 		if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
-			*tail = copy_ref_with_peer(r);
+			*tail = copy_ref(r);
 			tail = &((*tail)->next);
 			if (!all)
 				break;
Jay Soffian· Feb 27, 2009, 19:10 UTC · re: Jay Soffian · lore

To ensure that copied refs can always be freed w/o causing a double-free, make copy_ref() perform a deep copy.

Also have copy_ref() return NULL if asked to copy NULL to simplify things for the caller.

Background: currently copy_ref() performs a shallow copy. This is fine
for current callers who never free the result and/or only copy refs
which contain NULL pointers. But copy_ref() is about to gain a new
caller (guess_remote_head()) which copies refs where peer_ref is not
NULL and the caller of guess_remote_head() will want to free the result.
Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
This is a new patch, which should come after 07/21 in the original
series - http://article.gmane.org/gmane.comp.version-control.git/111394
 remote.c |   18 +++++++++++++-----
 1 files changed, 13 insertions(+), 5 deletions(-)
Show changes to remote.c +13 −5
diff --git a/remote.c b/remote.c
index aed760e..22203ea 100644
--- a/remote.c
+++ b/remote.c
@@ -779,10 +779,18 @@ struct ref *alloc_ref(const char *name)
 
 static struct ref *copy_ref(const struct ref *ref)
 {
-	struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
-	memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
-	ret->next = NULL;
-	return ret;
+	struct ref *cpy;
+	size_t len;
+	if (!ref)
+		return NULL;
+	len = strlen(ref->name);
+	cpy = xmalloc(sizeof(struct ref) + len + 1);
+	memcpy(cpy, ref, sizeof(struct ref) + len + 1);
+	cpy->next = NULL;
+	cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL;
+	cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL;
+	cpy->peer_ref = copy_ref(ref->peer_ref);
+	return cpy;
 }
 
 struct ref *copy_ref_list(const struct ref *ref)
@@ -801,6 +809,7 @@ static void free_ref(struct ref *ref)
 {
 	if (!ref)
 		return;
+	free_ref(ref->peer_ref);
 	free(ref->remote_status);
 	free(ref->symref);
 	free(ref);
@@ -811,7 +820,6 @@ void free_refs(struct ref *ref)
 	struct ref *next;
 	while (ref) {
 		next = ref->next;
-		free(ref->peer_ref);
 		free_ref(ref);
 		ref = next;
 	}
-- 
1.6.2.rc1.309.g5f417
Jay Soffian· Feb 27, 2009, 19:10 UTC · re: Jay Soffian · lore

[PATCH 08/21] remote: let guess_remote_head() optionally return all matches

Determining HEAD is ambiguous since it is done by comparing SHA1s.

In the case of multiple matches we return refs/heads/master if it matches, else we return the first match we encounter. builtin-remote needs all matches returned to it, so add a flag for it to request such.

To be simple and consistent, the return value is now a copy (including peer_ref) of the matching refs.

Originally contributed by Jeff King along with the prior commit as a single patch.

Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
This patch replaces 08/21 in the original series
series - http://article.gmane.org/gmane.comp.version-control.git/111394
 builtin-clone.c |    2 +-
 remote.c        |   29 +++++++++++++++++++----------
 remote.h        |   14 ++++++++------
 3 files changed, 28 insertions(+), 17 deletions(-)
Show changes to 3 files +28 −17

builtin-clone.c, remote.c, remote.h

diff --git a/builtin-clone.c b/builtin-clone.c
index f9ce4fb..3146ca8 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -510,7 +510,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
 
 		remote_head = find_ref_by_name(refs, "HEAD");
-		head_points_at = guess_remote_head(remote_head, mapped_refs);
+		head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
 	}
 	else {
 		warning("You appear to have cloned an empty repository.");
diff --git a/remote.c b/remote.c
index 22203ea..304e967 100644
--- a/remote.c
+++ b/remote.c
@@ -1460,24 +1460,33 @@ struct ref *get_local_heads(void)
 	return local_refs;
 }
 
-const struct ref *guess_remote_head(const struct ref *head,
-				    const struct ref *refs)
+struct ref *guess_remote_head(const struct ref *head,
+			      const struct ref *refs,
+			      int all)
 {
 	const struct ref *r;
+	struct ref *list = NULL;
+	struct ref **tail = &list;
 
 	if (!head)
 		return NULL;
 
 	/* If refs/heads/master could be right, it is. */
-	r = find_ref_by_name(refs, "refs/heads/master");
-	if (r && !hashcmp(r->old_sha1, head->old_sha1))
-		return r;
+	if (!all) {
+		r = find_ref_by_name(refs, "refs/heads/master");
+		if (r && !hashcmp(r->old_sha1, head->old_sha1))
+			return copy_ref(r);
+	}
 
 	/* Look for another ref that points there */
-	for (r = refs; r; r = r->next)
-		if (r != head && !hashcmp(r->old_sha1, head->old_sha1))
-			return r;
+	for (r = refs; r; r = r->next) {
+		if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+			*tail = copy_ref(r);
+			tail = &((*tail)->next);
+			if (!all)
+				break;
+		}
+	}
 
-	/* Nothing is the same */
-	return NULL;
+	return list;
 }
diff --git a/remote.h b/remote.h
index db49ce0..de3d21b 100644
--- a/remote.h
+++ b/remote.h
@@ -139,12 +139,14 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
 int format_tracking_info(struct branch *branch, struct strbuf *sb);
 
 struct ref *get_local_heads(void);
-
 /*
- * Look for a ref in refs whose SHA1 matches head, first checking if
- * refs/heads/master matches. Return NULL if nothing matches or if head
- * is NULL.
+ * Find refs from a list which are likely to be pointed to by the given HEAD
+ * ref. If 'all' is false, returns the most likely ref; otherwise, returns a
+ * list of all candidate refs. If no match is found (or 'head' is NULL),
+ * returns NULL. All returns are newly allocated and should be freed.
  */
-const struct ref *guess_remote_head(const struct ref *head,
-				    const struct ref *refs);
+struct ref *guess_remote_head(const struct ref *head,
+			      const struct ref *refs,
+			      int all);
+
 #endif
-- 
1.6.2.rc1.309.g5f417
Jay Soffian· Feb 27, 2009, 19:10 UTC · re: Jay Soffian · lore

[PATCH 19/21] remote: make guess_remote_head() use exact HEAD lookup if it is available

From: Jeff King <peff@peff.net>

Our usual method for determining the ref pointed to by HEAD is to compare HEAD's sha1 to the sha1 of all refs, trying to find a unique match.

However, some transports actually get to look at HEAD directly; we should make use of that information when it is available. Currently, only http remotes support this feature.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
This patch replaces 19/21 in the original series
series - http://article.gmane.org/gmane.comp.version-control.git/111394
 remote.c              |    8 ++++++++
 t/t5550-http-fetch.sh |   11 +++++++++++
 2 files changed, 19 insertions(+), 0 deletions(-)
Show changes to 2 files +19 −0

remote.c, t/t5550-http-fetch.sh

diff --git a/remote.c b/remote.c
index 2123005..9b8522d 100644
--- a/remote.c
+++ b/remote.c
@@ -1476,6 +1476,14 @@ struct ref *guess_remote_head(const struct ref *head,
 	if (!head)
 		return NULL;
 
+	/*
+	 * Some transports support directly peeking at
+	 * where HEAD points; if that is the case, then
+	 * we don't have to guess.
+	 */
+	if (head->symref)
+		return copy_ref(find_ref_by_name(refs, head->symref));
+
 	/* If refs/heads/master could be right, it is. */
 	if (!all) {
 		r = find_ref_by_name(refs, "refs/heads/master");
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index b6e6ec9..05b1b62 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -42,5 +42,16 @@ test_expect_success 'fetch changes via http' '
 	test_cmp file clone/file
 '
 
+test_expect_success 'http remote detects correct HEAD' '
+	git push public master:other &&
+	(cd clone &&
+	 git remote set-head origin -d &&
+	 git remote set-head origin -a &&
+	 git symbolic-ref refs/remotes/origin/HEAD > output &&
+	 echo refs/remotes/origin/master > expect &&
+	 test_cmp expect output
+	)
+'
+
 stop_httpd
 test_done
-- 
1.6.2.rc1.309.g5f417
Jeff King· Feb 28, 2009, 06:33 UTC · re: Jay Soffian · lore

Re: [PATCH 0/3] git remote: set-head and new show output (UPDATED)

On Fri, Feb 27, 2009 at 02:10:03PM -0500, Jay Soffian wrote:
Show 5 quoted lines
> I didn't want to resend the whole series (gmane 111394), so what I did was
> create a new patch which comes after patch 7 in that series. Call it patch 7a.
> The rest of that series is then identical (modulo line number offsets in
> patches touching remote.c) except for patches 8 and 19. Patches 7a, and the
> new 8 and 19 follow this email.
These look good to me.
-Peff

← back to recent threads