Re: [PATCHv3] clone: fix refspec on "--single-branch" option

9 messages, 3 authors, 2016-06-15 · open the first message on its own page

Re: [PATCHv3] clone: fix refspec on "--single-branch" option

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:54:45

Ralf Thielow [off-list ref] writes:
+	if (option_mirror || !option_bare) {
+		strbuf_reset(&value);
+		if (option_single_branch) {
+			if (option_branch)
+				strbuf_addf(&value, "+%s%s:%s%s",
+						src_ref_prefix, option_branch,
+						branch_top.buf, option_branch);
+			else if (remote_head_points_at)
+				strbuf_addf(&value, "+%s:%s%s",
+						remote_head_points_at->name, branch_top.buf,
+						skip_prefix(remote_head_points_at->name, "refs/heads/"));
We have already set "remote.origin.url" to this repository, so the
next "git fetch" would simply fetch from "HEAD" per default.
Perhaps worth commenting that here?

Other than that, looks good.  Perhaps we would want a test or two,
too?

Thanks.

[PATCHv4] clone --single: limit the fetch refspec to fetched branch

From: Ralf Thielow <hidden>
Date: 2016-06-15 22:54:46

After running "git clone --single", the resulting repository has the
usual default "+refs/heads/*:refs/remotes/origin/*" wildcard fetch
refspec installed, which means that a subsequent "git fetch" will
end up grabbing all the other branches.

Update the fetch refspec to cover only the singly cloned ref instead
to correct this.

Signed-off-by: Ralf Thielow <redacted>
---

Changes to v3:
- use commit message from Junio's topic branch
- add comment for the 'detached HEAD' case (also from Junio's topic branch)
(thanks for that)
- add tests for the refspec installed by the clone command

 builtin/clone.c          | 49 ++++++++++++++++++++++++++++------------
 t/t5709-clone-refspec.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 Dateien geändert, 94 Zeilen hinzugefügt(+), 14 Zeilen entfernt(-)
 create mode 100755 t/t5709-clone-refspec.sh
diff --git a/builtin/clone.c b/builtin/clone.c
index 5e8f3ba..be4c62b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -755,20 +755,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
-	if (option_mirror || !option_bare) {
-		/* Configure the remote */
-		strbuf_addf(&key, "remote.%s.fetch", option_origin);
-		git_config_set_multivar(key.buf, value.buf, "^$", 0);
-		strbuf_reset(&key);
-
-		if (option_mirror) {
-			strbuf_addf(&key, "remote.%s.mirror", option_origin);
-			git_config_set(key.buf, "true");
-			strbuf_reset(&key);
-		}
-	}
-
 	strbuf_addf(&key, "remote.%s.url", option_origin);
 	git_config_set(key.buf, repo);
 	strbuf_reset(&key);
@@ -853,6 +839,41 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	if (option_mirror || !option_bare) {
+		strbuf_reset(&value);
+		if (option_single_branch) {
+			if (option_branch)
+				strbuf_addf(&value, "+%s%s:%s%s",
+						src_ref_prefix, option_branch,
+						branch_top.buf, option_branch);
+			else if (remote_head_points_at)
+				strbuf_addf(&value, "+%s:%s%s",
+						remote_head_points_at->name, branch_top.buf,
+						skip_prefix(remote_head_points_at->name, "refs/heads/"));
+			/*
+			 * otherwise, the next "git fetch" will
+			 * simply fetch from HEAD without updating
+			 * any remote tracking branch, which is what
+			 * we want.
+			 */
+		} else {
+			strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+		}
+		/* Configure the remote */
+		if (value.len) {
+			strbuf_reset(&key);
+			strbuf_addf(&key, "remote.%s.fetch", option_origin);
+			git_config_set_multivar(key.buf, value.buf, "^$", 0);
+			strbuf_reset(&key);
+
+			if (option_mirror) {
+				strbuf_addf(&key, "remote.%s.mirror", option_origin);
+				git_config_set(key.buf, "true");
+				strbuf_reset(&key);
+			}
+		}
+	}
+
 	if (is_local)
 		clone_local(path, git_dir);
 	else if (refs && complete_refs_before_fetch)
diff --git a/t/t5709-clone-refspec.sh b/t/t5709-clone-refspec.sh
new file mode 100755
index 0000000..f4c8e31
--- /dev/null
+++ b/t/t5709-clone-refspec.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+test_description='test refspec written by clone-command'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo one >file &&
+	git add file &&
+	git commit -m one &&
+	echo two >file &&
+	git commit -a -m two &&
+	git tag two &&
+	echo three >file &&
+	git commit -a -m three &&
+	git checkout -b foo &&
+	echo four >file &&
+	git commit -a -m four &&
+	git checkout master
+'
+
+test_expect_success 'refspec contains all branches by default' '
+	git clone "file://$PWD" dir_all &&
+	echo "+refs/heads/*:refs/remotes/origin/*" > expected &&
+	git --git-dir=dir_all/.git config --get remote.origin.fetch > actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'refspec contains only master with option --single-branch and remotes HEAD point to master' '
+	git clone --single-branch "file://$PWD" dir_master &&
+	echo "+refs/heads/master:refs/remotes/origin/master" > expected &&
+	git --git-dir=dir_master/.git config --get remote.origin.fetch > actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'refspec contains only foo with option --single-branch and remotes HEAD point to foo' '
+	git checkout foo &&
+	git clone --single-branch "file://$PWD" dir_foo &&
+	echo "+refs/heads/foo:refs/remotes/origin/foo" > expected &&
+	git --git-dir=dir_foo/.git config --get remote.origin.fetch > actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'refspec contains one branch after using option --single-branch with --branch' '
+	git checkout master &&
+	git clone --single-branch --branch foo "file://$PWD" dir_foo2 &&
+	echo "+refs/heads/foo:refs/remotes/origin/foo" > expected &&
+	git --git-dir=dir_foo2/.git config --get remote.origin.fetch > actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'no refspec is written if remotes HEAD is detached' '
+	git checkout two^ &&
+	git clone --single-branch "file://$PWD" dir_detached &&
+	rm expected && touch expected &&
+	git --git-dir=dir_detached/.git config --get remote.origin.fetch > actual
+	test_cmp expected actual
+'
+
+test_done
-- 
1.7.12.396.g6bea32d.dirty

Re: [PATCHv4] clone --single: limit the fetch refspec to fetched branch

From: Nguyen Thai Ngoc Duy <hidden>
Date: 2016-06-15 22:54:46

On Sun, Sep 16, 2012 at 3:13 PM, Ralf Thielow [off-list ref] wrote:
+       if (option_mirror || !option_bare) {
+               strbuf_reset(&value);
I think we should use a new strbuf local variable here to avoid
resetting this. At least reviewers don't have to check if this
statememt causes any effect later on because "value"'s value is gone.
+               if (option_single_branch) {
+                       if (option_branch)
+                               strbuf_addf(&value, "+%s%s:%s%s",
+                                               src_ref_prefix, option_branch,
+                                               branch_top.buf, option_branch);
+                       else if (remote_head_points_at)
+                               strbuf_addf(&value, "+%s:%s%s",
+                                               remote_head_points_at->name, branch_top.buf,
+                                               skip_prefix(remote_head_points_at->name, "refs/heads/"));
+                       /*
+                        * otherwise, the next "git fetch" will
+                        * simply fetch from HEAD without updating
+                        * any remote tracking branch, which is what
+                        * we want.
+                        */
Maybe document updates too? Though if it's obvious that
--single-branch should prepare refspec so that only one branch is
fetched later on, then maybe not.
+               } else {
+                       strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+               }
--mirror --single-branch combination does not look right. The "heads/"
part is missing..

$ git branch
  master * wildmatch
$ LANG=C ./git clone --mirror --single-branch .git abc
Cloning into bare repository 'abc'...
done.
$ grep fetch abc/config
        fetch = +refs/heads/wildmatch:refs/wildmatch
$ rm -rf abc
$ LANG=C ./git clone --mirror --single-branch --branch=master .git abc
Cloning into bare repository 'abc'...
done.
$ grep fetch abc/config
        fetch = +refs/master:refs/master
-- 
Duy

Re: [PATCHv4] clone --single: limit the fetch refspec to fetched branch

From: Nguyen Thai Ngoc Duy <hidden>
Date: 2016-06-15 22:54:46

On Mon, Sep 17, 2012 at 7:06 PM, Nguyen Thai Ngoc Duy [off-list ref] wrote:
--mirror --single-branch combination does not look right. The "heads/"
part is missing..
It also does not look right for cloning a tag:

$ LANG=C ./git clone --single-branch --branch=v1.7.0 .git abc
Cloning into 'abc'...
done.
Note: checking out 'e923eaeb901ff056421b9007adcbbce271caa7b6'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

$ grep fetch abc/.git/config
        fetch = +refs/heads/v1.7.0:refs/remotes/origin/v1.7.0
-- 
Duy

Re: [PATCHv4] clone --single: limit the fetch refspec to fetched branch

From: Ralf Thielow <hidden>
Date: 2016-06-15 22:54:46

On Mon, Sep 17, 2012 at 2:06 PM, Nguyen Thai Ngoc Duy [off-list ref] wrote:
On Sun, Sep 16, 2012 at 3:13 PM, Ralf Thielow [off-list ref] wrote:
quoted
+       if (option_mirror || !option_bare) {
+               strbuf_reset(&value);
I think we should use a new strbuf local variable here to avoid
resetting this. At least reviewers don't have to check if this
statememt causes any effect later on because "value"'s value is gone.
It seems that we don't need this reset here because it's already
done earlier in this function. The variable "key" is also used multiple
times so I wouldn't use a new variable.
quoted
+               if (option_single_branch) {
+                       if (option_branch)
+                               strbuf_addf(&value, "+%s%s:%s%s",
+                                               src_ref_prefix, option_branch,
+                                               branch_top.buf, option_branch);
+                       else if (remote_head_points_at)
+                               strbuf_addf(&value, "+%s:%s%s",
+                                               remote_head_points_at->name, branch_top.buf,
+                                               skip_prefix(remote_head_points_at->name, "refs/heads/"));
+                       /*
+                        * otherwise, the next "git fetch" will
+                        * simply fetch from HEAD without updating
+                        * any remote tracking branch, which is what
+                        * we want.
+                        */
Maybe document updates too? Though if it's obvious that
--single-branch should prepare refspec so that only one branch is
fetched later on, then maybe not.
I think it's obvious.
quoted
+               } else {
+                       strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+               }
--mirror --single-branch combination does not look right. The "heads/"
part is missing..

$ git branch
  master * wildmatch
$ LANG=C ./git clone --mirror --single-branch .git abc
Cloning into bare repository 'abc'...
done.
$ grep fetch abc/config
        fetch = +refs/heads/wildmatch:refs/wildmatch
$ rm -rf abc
$ LANG=C ./git clone --mirror --single-branch --branch=master .git abc
Cloning into bare repository 'abc'...
done.
$ grep fetch abc/config
        fetch = +refs/master:refs/master
--
Duy
Thanks, I'll check this later and send a new version.

[PATCHv5] clone --single: limit the fetch refspec to fetched branch

From: Ralf Thielow <hidden>
Date: 2016-06-15 22:54:47

After running "git clone --single", the resulting repository has the
usual default "+refs/heads/*:refs/remotes/origin/*" wildcard fetch
refspec installed, which means that a subsequent "git fetch" will
end up grabbing all the other branches.

Update the fetch refspec to cover only the singly cloned ref instead
to correct this.

Signed-off-by: Ralf Thielow <redacted>
---

changes in v5:
- extract a function to write refspec config
- handle --mirror option (test added)
- install correct refspec if the value of --branch is a tag (test added)
Thanks to Junio for:
- refactor tests
- add tests for created refs 

I'm not happy about using "our_head_points_to" for the remote
part of the refspec. Junio already complaint about that. As far
as I can see it's the only way of getting the information that
it's a tag. Also the condition "is this a tag" might not be the
best way.

 builtin/clone.c          |  66 +++++++++++++++-----
 t/t5709-clone-refspec.sh | 156 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 208 insertions(+), 14 deletions(-)
 create mode 100755 t/t5709-clone-refspec.sh
diff --git a/builtin/clone.c b/builtin/clone.c
index 5e8f3ba..431635c 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -610,6 +610,55 @@ static void write_config(struct string_list *config)
 	}
 }
 
+static void write_refspec_config(const char* src_ref_prefix,
+		const struct ref* our_head_points_at,
+		const struct ref* remote_head_points_at, struct strbuf* branch_top)
+{
+	struct strbuf key = STRBUF_INIT;
+	struct strbuf value = STRBUF_INIT;
+
+	if (option_mirror || !option_bare) {
+		if (option_single_branch && !option_mirror) {
+			if (option_branch) {
+				if (strstr(our_head_points_at->name, "refs/tags/"))
+					strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
+						our_head_points_at->name);
+				else
+					strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
+						branch_top->buf, option_branch);
+			} else if (remote_head_points_at) {
+				strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
+						branch_top->buf,
+						skip_prefix(remote_head_points_at->name, "refs/heads/"));
+			}
+			/*
+			 * otherwise, the next "git fetch" will
+			 * simply fetch from HEAD without updating
+			 * any remote tracking branch, which is what
+			 * we want.
+			 */
+		} else {
+			strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf);
+		}
+		/* Configure the remote */
+		if (value.len) {
+			strbuf_reset(&key);
+			strbuf_addf(&key, "remote.%s.fetch", option_origin);
+			git_config_set_multivar(key.buf, value.buf, "^$", 0);
+			strbuf_reset(&key);
+
+			if (option_mirror) {
+				strbuf_addf(&key, "remote.%s.mirror", option_origin);
+				git_config_set(key.buf, "true");
+				strbuf_reset(&key);
+			}
+		}
+	}
+
+	strbuf_release(&key);
+	strbuf_release(&value);
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
 	int is_bundle = 0, is_local;
@@ -755,20 +804,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
-	if (option_mirror || !option_bare) {
-		/* Configure the remote */
-		strbuf_addf(&key, "remote.%s.fetch", option_origin);
-		git_config_set_multivar(key.buf, value.buf, "^$", 0);
-		strbuf_reset(&key);
-
-		if (option_mirror) {
-			strbuf_addf(&key, "remote.%s.mirror", option_origin);
-			git_config_set(key.buf, "true");
-			strbuf_reset(&key);
-		}
-	}
-
 	strbuf_addf(&key, "remote.%s.url", option_origin);
 	git_config_set(key.buf, repo);
 	strbuf_reset(&key);
@@ -853,6 +888,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	write_refspec_config(src_ref_prefix, our_head_points_at,
+			remote_head_points_at, &branch_top);
+
 	if (is_local)
 		clone_local(path, git_dir);
 	else if (refs && complete_refs_before_fetch)
diff --git a/t/t5709-clone-refspec.sh b/t/t5709-clone-refspec.sh
new file mode 100755
index 0000000..af45182
--- /dev/null
+++ b/t/t5709-clone-refspec.sh
@@ -0,0 +1,156 @@
+#!/bin/sh
+
+test_description='test refspec written by clone-command'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	# Make two branches, "master" and "side"
+	echo one >file &&
+	git add file &&
+	git commit -m one &&
+	echo two >file &&
+	git commit -a -m two &&
+	git tag two &&
+	echo three >file &&
+	git commit -a -m three &&
+	git checkout -b side &&
+	echo four >file &&
+	git commit -a -m four &&
+	git checkout master &&
+
+	# default clone
+	git clone . dir_all &&
+
+	# default --single that follows HEAD=master
+	git clone --single-branch . dir_master &&
+
+	# default --single that follows HEAD=side
+	git checkout side &&
+	git clone --single-branch . dir_side &&
+
+	# explicit --single that follows side
+	git checkout master &&
+	git clone --single-branch --branch side . dir_side2 &&
+
+	# default --single with --mirror
+	git clone --single-branch --mirror . dir_mirror &&
+
+	# --single that does not know what branch to follow
+	git checkout two^ &&
+	git clone --single-branch . dir_detached &&
+
+	# explicit --single with tag
+	git clone --single-branch --branch two . dir_tag &&
+
+	# advance both "master" and "side" branches
+	git checkout side &&
+	echo five >file &&
+	git commit -a -m five &&
+	git checkout master &&
+	echo six >file &&
+	git commit -a -m six
+'
+
+test_expect_success 'refspec contains all branches by default' '
+	echo "+refs/heads/*:refs/remotes/origin/*" > expect &&
+	git --git-dir=dir_all/.git config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'refspec contains all refs with option --mirror' '
+	echo "+refs/*:refs/*" > expect &&
+	git --git-dir=dir_mirror config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'refspec contains tag ref' '
+	echo "+refs/tags/two:refs/tags/two" > expect &&
+	git --git-dir=dir_tag/.git config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'refspec contains only master with option --single-branch and remotes HEAD point to master' '
+	echo "+refs/heads/master:refs/remotes/origin/master" > expect &&
+	git --git-dir=dir_master/.git config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'refspec contains only foo with option --single-branch and remotes HEAD point to side' '
+	echo "+refs/heads/side:refs/remotes/origin/side" > expect &&
+	git --git-dir=dir_side/.git config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'refspec contains one branch after using option --single-branch with --branch' '
+	echo "+refs/heads/side:refs/remotes/origin/side" > expect &&
+	git --git-dir=dir_side2/.git config --get remote.origin.fetch > actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no refspec is written if remotes HEAD is detached' '
+	> expect &&
+	git --git-dir=dir_detached/.git config --get remote.origin.fetch > actual
+	test_cmp expect actual
+'
+
+test_expect_success 'by default all branches will be kept updated' '
+	(
+		cd dir_all && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# follow both master and side
+	git for-each-ref refs/heads >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at master' '
+	(
+		cd dir_master && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow master
+	git for-each-ref refs/heads/master >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at side' '
+	(
+		cd dir_side && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow side
+	git for-each-ref refs/heads/side >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch side' '
+	(
+		cd dir_side2 && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow side
+	git for-each-ref refs/heads/side >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with detached' '
+	(
+		cd dir_detached && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	)
+	# nothing
+	>expect &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.7.12.397.ge29f79e.dirty

[PATCHv6] clone --single: limit the fetch refspec to fetched branch

From: Ralf Thielow <hidden>
Date: 2016-06-15 22:54:48

After running "git clone --single", the resulting repository has the
usual default "+refs/heads/*:refs/remotes/origin/*" wildcard fetch
refspec installed, which means that a subsequent "git fetch" will
end up grabbing all the other branches.

Update the fetch refspec to cover only the singly cloned ref instead
to correct this.

That means:
If "--single" is used without "--branch" or "--mirror", the
fetch refspec covers the branch on which remote's HEAD points to.
If "--single" is used with "--branch", it'll cover only the branch
specified in the "--branch" option.
If "--single" is combined with "--mirror", then it'll cover all
refs of the cloned repository.
If "--single" is used with "--branch" that specifies a tag, then
it'll cover only the ref for this tag.

Signed-off-by: Ralf Thielow <redacted>
---

changes in v6
- remove initial created tests (they tested in a too deep level)
- add tests for "--mirror" option
- add tests for the case of cloning a tag
- update commit message

I've tried to update "Documentation/git-clone.txt", but I don't
know in which way this patch changes already described behaviour.
The resulting refspec seems only be covered in the last part of
the "--single-branch" section by describing "--no-single-branch",
but this hasn't changed. Or did I miss something?

 builtin/clone.c          |  66 ++++++++++++++++-----
 t/t5709-clone-refspec.sh | 145 +++++++++++++++++++++++++++++++++++++++++++++++
 2 Dateien geändert, 197 Zeilen hinzugefügt(+), 14 Zeilen entfernt(-)
 create mode 100755 t/t5709-clone-refspec.sh
diff --git a/builtin/clone.c b/builtin/clone.c
index 5e8f3ba..431635c 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -610,6 +610,55 @@ static void write_config(struct string_list *config)
 	}
 }
 
+static void write_refspec_config(const char* src_ref_prefix,
+		const struct ref* our_head_points_at,
+		const struct ref* remote_head_points_at, struct strbuf* branch_top)
+{
+	struct strbuf key = STRBUF_INIT;
+	struct strbuf value = STRBUF_INIT;
+
+	if (option_mirror || !option_bare) {
+		if (option_single_branch && !option_mirror) {
+			if (option_branch) {
+				if (strstr(our_head_points_at->name, "refs/tags/"))
+					strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
+						our_head_points_at->name);
+				else
+					strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
+						branch_top->buf, option_branch);
+			} else if (remote_head_points_at) {
+				strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
+						branch_top->buf,
+						skip_prefix(remote_head_points_at->name, "refs/heads/"));
+			}
+			/*
+			 * otherwise, the next "git fetch" will
+			 * simply fetch from HEAD without updating
+			 * any remote tracking branch, which is what
+			 * we want.
+			 */
+		} else {
+			strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf);
+		}
+		/* Configure the remote */
+		if (value.len) {
+			strbuf_reset(&key);
+			strbuf_addf(&key, "remote.%s.fetch", option_origin);
+			git_config_set_multivar(key.buf, value.buf, "^$", 0);
+			strbuf_reset(&key);
+
+			if (option_mirror) {
+				strbuf_addf(&key, "remote.%s.mirror", option_origin);
+				git_config_set(key.buf, "true");
+				strbuf_reset(&key);
+			}
+		}
+	}
+
+	strbuf_release(&key);
+	strbuf_release(&value);
+}
+
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
 	int is_bundle = 0, is_local;
@@ -755,20 +804,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	}
 
 	strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
-	if (option_mirror || !option_bare) {
-		/* Configure the remote */
-		strbuf_addf(&key, "remote.%s.fetch", option_origin);
-		git_config_set_multivar(key.buf, value.buf, "^$", 0);
-		strbuf_reset(&key);
-
-		if (option_mirror) {
-			strbuf_addf(&key, "remote.%s.mirror", option_origin);
-			git_config_set(key.buf, "true");
-			strbuf_reset(&key);
-		}
-	}
-
 	strbuf_addf(&key, "remote.%s.url", option_origin);
 	git_config_set(key.buf, repo);
 	strbuf_reset(&key);
@@ -853,6 +888,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					      "refs/heads/master");
 	}
 
+	write_refspec_config(src_ref_prefix, our_head_points_at,
+			remote_head_points_at, &branch_top);
+
 	if (is_local)
 		clone_local(path, git_dir);
 	else if (refs && complete_refs_before_fetch)
diff --git a/t/t5709-clone-refspec.sh b/t/t5709-clone-refspec.sh
new file mode 100755
index 0000000..8d32a97
--- /dev/null
+++ b/t/t5709-clone-refspec.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='test refspec written by clone-command'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	# Make two branches, "master" and "side"
+	echo one >file &&
+	git add file &&
+	git commit -m one &&
+	echo two >file &&
+	git commit -a -m two &&
+	git tag two &&
+	echo three >file &&
+	git commit -a -m three &&
+	git checkout -b side &&
+	echo four >file &&
+	git commit -a -m four &&
+	git checkout master &&
+
+	# default clone
+	git clone . dir_all &&
+
+	# default --single that follows HEAD=master
+	git clone --single-branch . dir_master &&
+
+	# default --single that follows HEAD=side
+	git checkout side &&
+	git clone --single-branch . dir_side &&
+
+	# explicit --single that follows side
+	git checkout master &&
+	git clone --single-branch --branch side . dir_side2 &&
+
+	# default --single with --mirror
+	git clone --single-branch --mirror . dir_mirror &&
+
+	# default --single with --branch and --mirror
+	git clone --single-branch --mirror --branch side . dir_mirror_side &&
+
+	# --single that does not know what branch to follow
+	git checkout two^ &&
+	git clone --single-branch . dir_detached &&
+
+	# explicit --single with tag
+	git clone --single-branch --branch two . dir_tag &&
+
+	# advance both "master" and "side" branches
+	git checkout side &&
+	echo five >file &&
+	git commit -a -m five &&
+	git checkout master &&
+	echo six >file &&
+	git commit -a -m six
+'
+
+test_expect_success 'by default all branches will be kept updated' '
+	(
+		cd dir_all && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# follow both master and side
+	git for-each-ref refs/heads >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at master' '
+	(
+		cd dir_master && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow master
+	git for-each-ref refs/heads/master >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at side' '
+	(
+		cd dir_side && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow side
+	git for-each-ref refs/heads/side >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch side' '
+	(
+		cd dir_side2 && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	) &&
+	# only follow side
+	git for-each-ref refs/heads/side >expect &&
+	test_cmp expect actual
+'
+
+
+test_expect_success '--single-branch with explicit --branch tag' '
+	(
+		cd dir_tag && git fetch &&
+		git for-each-ref refs/tags >../actual
+	) &&
+	git for-each-ref refs/tags >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with --mirror' '
+	(
+		cd dir_mirror && git fetch &&
+		git for-each-ref refs > ../actual
+	) &&
+	git for-each-ref refs >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch and --mirror' '
+	(
+		cd dir_mirror_side && git fetch &&
+		git for-each-ref refs > ../actual
+	) &&
+	git for-each-ref refs >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success '--single-branch with detached' '
+	(
+		cd dir_detached && git fetch &&
+		git for-each-ref refs/remotes/origin |
+		sed -e "/HEAD$/d" \
+		    -e "s|/remotes/origin/|/heads/|" >../actual
+	)
+	# nothing
+	>expect &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.7.12.396.g7954078

Re: [PATCHv6] clone --single: limit the fetch refspec to fetched branch

From: Nguyen Thai Ngoc Duy <hidden>
Date: 2016-06-15 22:54:48

On Wed, Sep 19, 2012 at 2:14 AM, Ralf Thielow [off-list ref] wrote:
+test_expect_success '--single-branch with explicit --branch tag' '
+       (
+               cd dir_tag && git fetch &&
+               git for-each-ref refs/tags >../actual
+       ) &&
+       git for-each-ref refs/tags >expect &&
+       test_cmp expect actual
+'
We should have added the tag right after cloning, not until the first
git-fetch. Not that I object how you do it in this patch. Just a note
to myself that if I'm going to do that, I'll need to update this test
to update the change tag before fetching and verify the tag is updated
after git-fetch.
-- 
Duy

Re: [PATCHv6] clone --single: limit the fetch refspec to fetched branch

From: Ralf Thielow <hidden>
Date: 2016-06-15 22:54:48

On Wed, Sep 19, 2012 at 9:36 AM, Nguyen Thai Ngoc Duy [off-list ref] wrote:
On Wed, Sep 19, 2012 at 2:14 AM, Ralf Thielow [off-list ref] wrote:
quoted
+test_expect_success '--single-branch with explicit --branch tag' '
+       (
+               cd dir_tag && git fetch &&
+               git for-each-ref refs/tags >../actual
+       ) &&
+       git for-each-ref refs/tags >expect &&
+       test_cmp expect actual
+'
We should have added the tag right after cloning, not until the first
git-fetch. Not that I object how you do it in this patch. Just a note
to myself that if I'm going to do that, I'll need to update this test
to update the change tag before fetching and verify the tag is updated
after git-fetch.
--
Duy
Right. I'll create two tests to replace this one. The first test
verifys that the tag
has been added after cloning. And in the second test, the tag gets updated in
the cloned repository and we verify that the next fetch updates the tag, which
catches my "delivery" tag example.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help