Git development
 help / color / mirror / Atom feed
* [PATCH 09/12] tests for sparse clone
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:57 UTC (permalink / raw)
  To: git


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t5703-clone-sparse.sh |   40 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 40 insertions(+), 0 deletions(-)
 create mode 100755 t/t5703-clone-sparse.sh

diff --git a/t/t5703-clone-sparse.sh b/t/t5703-clone-sparse.sh
new file mode 100755
index 0000000..012ead0
--- /dev/null
+++ b/t/t5703-clone-sparse.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='sparse clone'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	rm -fr .git &&
+	test_create_repo src &&
+	(
+		cd src
+		mkdir -p work/sub/dir
+		touch untracked tracked modified added
+		touch work/untracked work/tracked work/modified work/added
+		git add tracked work/tracked
+		git add modified work/modified
+		git commit -m initial
+	)
+
+'
+
+test_expect_success 'sparse clone incompatible with --bare' '
+	rm -fr dst &&
+	test_must_fail git clone --path=work --bare src dst
+'
+
+test_expect_success 'sparse clone incompatible with --no-checkout' '
+	rm -fr dst &&
+	test_must_fail git clone --path=work -n src dst
+'
+
+test_expect_success 'clone with --path' '
+	rm -fr dst &&
+	git clone --path=work src dst &&
+	cd dst &&
+	test work = "$(git rev-parse --show-sparse-prefix)" &&
+	test -z "$(git ls-files | grep -v ^work/)"
+'
+
+test_done
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 08/12] git-clone: support --path to do sparse clone
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:57 UTC (permalink / raw)
  To: git


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-clone.c |   13 +++++++++++++
 1 files changed, 13 insertions(+), 0 deletions(-)

diff --git a/builtin-clone.c b/builtin-clone.c
index 3522245..229f2e2 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -36,6 +36,7 @@ static const char * const builtin_clone_usage[] = {
 static int option_quiet, option_no_checkout, option_bare;
 static int option_local, option_no_hardlinks, option_shared;
 static char *option_template, *option_reference, *option_depth;
+static char *option_sparse_prefix;
 static char *option_origin = NULL;
 static char *option_upload_pack = "git-upload-pack";
 
@@ -43,6 +44,8 @@ static struct option builtin_clone_options[] = {
 	OPT__QUIET(&option_quiet),
 	OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
 		    "don't create a checkout"),
+	OPT_STRING(0, "path", &option_sparse_prefix, "prefixes",
+		    "limit checkout to specified paths (sparse checkout)"),
 	OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
 	OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
 	OPT_BOOLEAN('l', "local", &option_local,
@@ -364,9 +367,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			die("--bare and --origin %s options are incompatible.",
 			    option_origin);
 		option_no_checkout = 1;
+		if (option_sparse_prefix)
+			die("--bare and --path options are incompatible.");
 		use_separate_remote = 0;
 	}
 
+	if (option_no_checkout && option_sparse_prefix)
+		die("--no-checkout and --path options are incompatible.");
+
 	if (!option_origin)
 		option_origin = "origin";
 
@@ -549,6 +557,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		/* We need to be in the new work tree for the checkout */
 		setup_work_tree();
 
+		if (option_sparse_prefix) {
+			git_config_set("core.sparsecheckout", option_sparse_prefix);
+			set_sparse_prefix(option_sparse_prefix);
+		}
+
 		fd = hold_locked_index(lock_file, 1);
 
 		memset(&opts, 0, sizeof opts);
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 07/12] tests for sparse checkout, worktree protection
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:57 UTC (permalink / raw)
  To: git


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2302-sparse-worktree.sh                 |  113 ++++++++++++++
 t/t2302/add-u.expected                     |    1 +
 t/t2302/add.expected                       |    2 +
 t/t2302/commit.expected                    |   14 ++
 t/t2302/diff-1.expected                    |    7 +
 t/t2302/diff-cc.expected                   |    9 +
 t/t2302/grep-work.expected                 |    2 +
 t/t2302/grep.expected                      |    2 +
 t/t2302/ls-files.expected                  |    2 +
 t/t2303-sparse-worktree-apply.sh           |   62 ++++++++
 t/t2303/apply-initial.patch                |   14 ++
 t/t2303/apply-inside.patch                 |    7 +
 t/t2303/apply-leading-dirs.patch           |    3 +
 t/t2303/apply-outside.patch                |    7 +
 t/t2303/apply-remove.patch                 |    7 +
 t/t2303/apply-rename.expected              |   13 ++
 t/t2303/apply-rename.patch                 |    4 +
 t/t2304-sparse-worktree-merge-recursive.sh |  233 ++++++++++++++++++++++++++++
 18 files changed, 502 insertions(+), 0 deletions(-)
 create mode 100755 t/t2302-sparse-worktree.sh
 create mode 100644 t/t2302/add-u.expected
 create mode 100644 t/t2302/add.expected
 create mode 100644 t/t2302/commit.expected
 create mode 100644 t/t2302/diff-1.expected
 create mode 100644 t/t2302/diff-cc.expected
 create mode 100644 t/t2302/grep-work.expected
 create mode 100644 t/t2302/grep.expected
 create mode 100644 t/t2302/ls-files.expected
 create mode 100755 t/t2303-sparse-worktree-apply.sh
 create mode 100644 t/t2303/apply-initial.patch
 create mode 100644 t/t2303/apply-inside.patch
 create mode 100644 t/t2303/apply-leading-dirs.patch
 create mode 100644 t/t2303/apply-outside.patch
 create mode 100644 t/t2303/apply-remove.patch
 create mode 100644 t/t2303/apply-rename.expected
 create mode 100644 t/t2303/apply-rename.patch
 create mode 100755 t/t2304-sparse-worktree-merge-recursive.sh

diff --git a/t/t2302-sparse-worktree.sh b/t/t2302-sparse-worktree.sh
new file mode 100755
index 0000000..0aaee3e
--- /dev/null
+++ b/t/t2302-sparse-worktree.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+test_description='sparse checkout -- worktree update
+
+This test makes sure all commands that will not write
+worktree outside sparse prefix, once set.
+'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_tick &&
+	mkdir work &&
+	echo one > tracked &&
+	cp tracked work/tracked &&
+	cp tracked untracked &&
+	cp tracked work/untracked &&
+	cp tracked modified &&
+	cp tracked work/modified &&
+	git add tracked modified work/tracked work/modified &&
+	echo two >> modified &&
+	echo two >> work/modified &&
+	git commit -m initial &&
+	git config core.sparsecheckout work'
+
+test_expect_success 'rev-parse --show-sparse-prefix' '
+	test "$(git rev-parse --show-sparse-prefix)" = "work"
+'
+
+test_expect_success 'ls-files' '
+	git ls-files | cmp ../t2302/ls-files.expected &&
+	test "$(git ls-files -o)" = work/untracked &&
+	test "$(git ls-files -o .)" = work/untracked &&
+	test "$(git ls-files -m)" = work/modified
+
+'
+
+test_expect_success 'grep' 'git grep -l --no-external-grep one | cmp ../t2302/grep.expected'
+
+test_expect_success 'grep' '
+	(
+	cd work &&
+	git grep -l --no-external-grep one | cmp ../../t2302/grep-work.expected
+	)
+'
+
+test_expect_success 'checkout-index' '! git checkout-index tracked'
+
+test_expect_success 'checkout-index -a' '
+	mv work/modified work/modified.old &&
+	git checkout-index -a &&
+	mv work/modified.old work/modified
+'
+
+test_expect_success 'checkout tracked' '! git checkout tracked'
+
+test_expect_success 'checkout work/tracked' 'rm work/tracked && git checkout work/tracked'
+
+test_expect_success 'clean' 'test "$(git clean -n)" = "Would remove work/untracked"'
+
+test_expect_success 'rm' '! git rm tracked && test -f tracked'
+
+test_expect_success 'add' '
+	git add -n . | cmp ../t2302/add.expected &&
+	git add -n -u | cmp ../t2302/add-u.expected &&
+	! git add -n modified
+'
+
+test_expect_success 'commit' '
+	test_tick &&
+	echo one > work/one &&
+	git add work/one
+	echo two >> work/one &&
+	git commit -m one work/one &&
+	git show HEAD > commit.result &&
+	cmp commit.result ../t2302/commit.expected &&
+	rm commit.result
+'
+
+null_sha1=0000000000000000000000000000000000000000
+one_sha1=$(echo one|git hash-object --stdin)
+onethree_sha1=$(echo -e "one\nthree"|git hash-object -w --stdin)
+onefour_sha1=$(echo -e "one\nfour"|git hash-object -w --stdin)
+
+diff_expected=":100644 100644 $one_sha1 $null_sha1 M	work/modified"
+external_diff_pattern="^work/modified [^ ]* $one_sha1 100644 work/modified $null_sha1 100644\$"
+
+cat >index.info <<EOF
+0 $null_sha1	work/modified
+100644 $onethree_sha1 2	work/modified
+100644 $onefour_sha1 3	work/modified
+EOF
+
+test_expect_success 'diff-files' '
+	test "$(git diff-files)" = "$diff_expected" &&
+	test "$(git diff-files -- work/modified)" = "$diff_expected"
+	cp .git/index .git/index.save &&
+	git update-index --index-info < index.info &&
+	git diff-files --cc | diff - ../t2302/diff-cc.expected &&
+	mv .git/index.save .git/index
+'
+
+test_expect_success 'diff-index' '
+	test "$(git diff-index HEAD)" = "$diff_expected"
+'
+
+test_expect_success 'diff' '
+	git diff HEAD | cmp ../t2302/diff-1.expected &&
+	git diff | cmp ../t2302/diff-1.expected &&
+	GIT_EXTERNAL_DIFF=echo git diff --ext-diff HEAD | grep -q "$external_diff_pattern" &&
+	GIT_EXTERNAL_DIFF=echo git diff --ext-diff | grep -q "$external_diff_pattern" 
+'
+
+test_done
diff --git a/t/t2302/add-u.expected b/t/t2302/add-u.expected
new file mode 100644
index 0000000..e0d6f54
--- /dev/null
+++ b/t/t2302/add-u.expected
@@ -0,0 +1 @@
+add 'work/modified'
diff --git a/t/t2302/add.expected b/t/t2302/add.expected
new file mode 100644
index 0000000..4ee7b0d
--- /dev/null
+++ b/t/t2302/add.expected
@@ -0,0 +1,2 @@
+add 'work/modified'
+add 'work/untracked'
diff --git a/t/t2302/commit.expected b/t/t2302/commit.expected
new file mode 100644
index 0000000..7e629aa
--- /dev/null
+++ b/t/t2302/commit.expected
@@ -0,0 +1,14 @@
+commit 33ce2cea204feebac3994cd4520cca60657e65de
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:14:13 2005 -0700
+
+    one
+
+diff --git a/work/one b/work/one
+new file mode 100644
+index 0000000..814f4a4
+--- /dev/null
++++ b/work/one
+@@ -0,0 +1,2 @@
++one
++two
diff --git a/t/t2302/diff-1.expected b/t/t2302/diff-1.expected
new file mode 100644
index 0000000..67be4fb
--- /dev/null
+++ b/t/t2302/diff-1.expected
@@ -0,0 +1,7 @@
+diff --git a/work/modified b/work/modified
+index 5626abf..814f4a4 100644
+--- a/work/modified
++++ b/work/modified
+@@ -1 +1,2 @@
+ one
++two
diff --git a/t/t2302/diff-cc.expected b/t/t2302/diff-cc.expected
new file mode 100644
index 0000000..8ca66dd
--- /dev/null
+++ b/t/t2302/diff-cc.expected
@@ -0,0 +1,9 @@
+diff --cc work/modified
+index 4c7442b,a9c7698..0000000
+--- a/work/modified
++++ b/work/modified
+@@@ -1,2 -1,2 +1,2 @@@
+  one
+- three
+ -four
+++two
diff --git a/t/t2302/grep-work.expected b/t/t2302/grep-work.expected
new file mode 100644
index 0000000..c081ddd
--- /dev/null
+++ b/t/t2302/grep-work.expected
@@ -0,0 +1,2 @@
+modified
+tracked
diff --git a/t/t2302/grep.expected b/t/t2302/grep.expected
new file mode 100644
index 0000000..4ddbe70
--- /dev/null
+++ b/t/t2302/grep.expected
@@ -0,0 +1,2 @@
+work/modified
+work/tracked
diff --git a/t/t2302/ls-files.expected b/t/t2302/ls-files.expected
new file mode 100644
index 0000000..4ddbe70
--- /dev/null
+++ b/t/t2302/ls-files.expected
@@ -0,0 +1,2 @@
+work/modified
+work/tracked
diff --git a/t/t2303-sparse-worktree-apply.sh b/t/t2303-sparse-worktree-apply.sh
new file mode 100755
index 0000000..4aeaf53
--- /dev/null
+++ b/t/t2303-sparse-worktree-apply.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+test_description='git-apply in subtree checkout'
+
+. ./test-lib.sh
+
+mkdir work
+
+test_apply() {
+	test_expect_success "$1" '
+		git checkout . &&
+		git config core.sparsecheckout work &&
+		git apply ../t2303/apply-'$2'.patch &&
+		git config --unset core.sparsecheckout &&
+		git diff > apply.result &&
+		cmp apply.result ../t2303/apply-'$2'.patch
+	'
+}
+
+test_expect_success 'apply on empty tree' '
+		git config core.sparsecheckout work &&
+		! git apply ../t2303/apply-initial.patch &&
+		git config --unset core.sparsecheckout
+'
+
+test_expect_success 'apply successfully without sparse checkout' '
+	git apply ../t2303/apply-initial.patch &&
+	git apply --cached ../t2303/apply-initial.patch
+'
+
+test_expect_success 'apply on modified tree outside' '
+	git checkout . &&
+	git config core.sparsecheckout work &&
+	! git apply ../t2303/apply-outside.patch &&
+	git config --unset core.sparsecheckout
+'
+
+test_apply 'apply on modified tree' inside
+
+test_apply 'apply removing file' remove
+
+test_expect_success 'apply creating leading directories' '
+	git config core.sparsecheckout work &&
+	git apply ../t2303/apply-leading-dirs.patch &&
+	git config --unset core.sparsecheckout &&
+	test -f work/for/me/now
+'
+
+test_expect_success 'apply renaming file' '
+	git checkout . &&
+	touch a &&
+	empty_sha1=$(git hash-object -w a) &&
+	rm a &&
+	git update-index --add --cacheinfo 100644 $empty_sha1 work/two &&
+	git config core.sparsecheckout work &&
+	git apply ../t2303/apply-rename.patch &&
+	git config --unset core.sparsecheckout &&
+	git diff > apply.result &&
+	cmp apply.result ../t2303/apply-rename.expected
+'
+
+test_done
diff --git a/t/t2303/apply-initial.patch b/t/t2303/apply-initial.patch
new file mode 100644
index 0000000..72bfd82
--- /dev/null
+++ b/t/t2303/apply-initial.patch
@@ -0,0 +1,14 @@
+diff --git a/one b/one
+new file mode 100644
+index 0000000..5626abf
+--- /dev/null
++++ b/one
+@@ -0,0 +1 @@
++one
+diff --git a/work/one b/work/one
+new file mode 100644
+index 0000000..da327ae
+--- /dev/null
++++ b/work/one
+@@ -0,0 +1 @@
++work/one
diff --git a/t/t2303/apply-inside.patch b/t/t2303/apply-inside.patch
new file mode 100644
index 0000000..4f5c310
--- /dev/null
+++ b/t/t2303/apply-inside.patch
@@ -0,0 +1,7 @@
+diff --git a/work/one b/work/one
+index da327ae..6317017 100644
+--- a/work/one
++++ b/work/one
+@@ -1 +1,2 @@
+ work/one
++more
diff --git a/t/t2303/apply-leading-dirs.patch b/t/t2303/apply-leading-dirs.patch
new file mode 100644
index 0000000..b55809f
--- /dev/null
+++ b/t/t2303/apply-leading-dirs.patch
@@ -0,0 +1,3 @@
+diff --git a/work/for/me/now b/work/for/me/now
+new file mode 100644
+index 0000000..e69de29
diff --git a/t/t2303/apply-outside.patch b/t/t2303/apply-outside.patch
new file mode 100644
index 0000000..8a8d625
--- /dev/null
+++ b/t/t2303/apply-outside.patch
@@ -0,0 +1,7 @@
+diff --git a/one b/one
+index 5626abf..9a72323 100644
+--- a/one
++++ b/one
+@@ -1 +1,2 @@
+ one
++more
diff --git a/t/t2303/apply-remove.patch b/t/t2303/apply-remove.patch
new file mode 100644
index 0000000..781c743
--- /dev/null
+++ b/t/t2303/apply-remove.patch
@@ -0,0 +1,7 @@
+diff --git a/work/one b/work/one
+deleted file mode 100644
+index da327ae..0000000
+--- a/work/one
++++ /dev/null
+@@ -1 +0,0 @@
+-work/one
diff --git a/t/t2303/apply-rename.expected b/t/t2303/apply-rename.expected
new file mode 100644
index 0000000..d19f719
--- /dev/null
+++ b/t/t2303/apply-rename.expected
@@ -0,0 +1,13 @@
+diff --git a/work/one b/work/one
+deleted file mode 100644
+index da327ae..0000000
+--- a/work/one
++++ /dev/null
+@@ -1 +0,0 @@
+-work/one
+diff --git a/work/two b/work/two
+index e69de29..da327ae 100644
+--- a/work/two
++++ b/work/two
+@@ -0,0 +1 @@
++work/one
diff --git a/t/t2303/apply-rename.patch b/t/t2303/apply-rename.patch
new file mode 100644
index 0000000..6f21947
--- /dev/null
+++ b/t/t2303/apply-rename.patch
@@ -0,0 +1,4 @@
+diff --git a/work/one b/work/two
+similarity index 100%
+rename from work/one
+rename to work/two
diff --git a/t/t2304-sparse-worktree-merge-recursive.sh b/t/t2304-sparse-worktree-merge-recursive.sh
new file mode 100755
index 0000000..b1708d4
--- /dev/null
+++ b/t/t2304-sparse-worktree-merge-recursive.sh
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+test_description='merge-recursive backend test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup 1' '
+
+	echo hello >a &&
+	o0=$(git hash-object a) &&
+	cp a b &&
+	cp a c &&
+	mkdir d &&
+	cp a d/e &&
+
+	test_tick &&
+	git add a b c d/e &&
+	git commit -m initial &&
+	c0=$(git rev-parse --verify HEAD) &&
+	git branch noconflict &&
+	git branch conflict &&
+	git branch conflict-inside &&
+
+	echo hello >>a &&
+	cp a d/e &&
+	o1=$(git hash-object a) &&
+
+	git add a d/e &&
+
+	test_tick &&
+	git commit -m "master modifies a and d/e" &&
+	c1=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o1	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o1	d/e"
+		echo "100644 $o1 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o1 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 2' '
+
+	rm -rf [abcd] &&
+	git checkout conflict &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	echo goodbye >>a &&
+	o2=$(git hash-object a) &&
+
+	git add a &&
+
+	test_tick &&
+	git commit -m "conflict modifies a" &&
+	c2=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o2	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o2 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 3' '
+
+	rm -rf [abcd] &&
+	git checkout noconflict &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	echo hello >>a &&
+	o3=$(git hash-object a) &&
+
+	git add a &&
+
+	test_tick &&
+	git commit -m "noconflict modifies a" &&
+	c3=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o3	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o3 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 4' '
+
+	rm -rf [abcd] &&
+	git checkout conflict-inside &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	mkdir d &&
+	echo goodbye >>d/e &&
+	o4=$(git hash-object d/e) &&
+
+	git add d/e &&
+
+	test_tick &&
+	git commit -m "conflict-inside modifies d/e" &&
+	c4=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o4	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o4 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+echo "gitdir: $(pwd)/.git" > gitfile
+
+test_expect_success 'merge-recursive conflict inside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c4" &&
+
+	cp gitfile d/.git && cd d/
+	git config core.sparsecheckout d
+	git-merge-recursive "$c0" -- "$c4" "$c1"
+	status=$?
+	git config --unset core.sparsecheckout
+	rm .git && cd ..
+	case "$status" in
+	1)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_expect_success 'merge-recursive no conflict outside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c3" &&
+
+	cp gitfile d/.git && cd d
+	git config core.sparsecheckout d
+	git-merge-recursive "$c0" -- "$c3" "$c1"
+	status=$?
+	git config --unset core.sparsecheckout
+	rm .git && cd ..
+	case "$status" in
+	0)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_expect_success 'merge-recursive conflict outside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c2" &&
+
+	cp gitfile d/.git && cd d
+	git config core.sparsecheckout d
+	git-merge-recursive "$c0" -- "$c2" "$c1"
+	status=$?
+	git config --unset core.sparsecheckout
+	rm .git && cd ..
+	case "$status" in
+	128)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_done
-- 
1.5.5.GIT

^ permalink raw reply related

* Re: q: faster way to integrate/merge lots of topic branches?
From: Miklos Vajna @ 2008-07-23 14:57 UTC (permalink / raw)
  To: Andreas Ericsson; +Cc: Ingo Molnar, git
In-Reply-To: <488734D9.9070703@op5.se>

[-- Attachment #1: Type: text/plain, Size: 172 bytes --]

On Wed, Jul 23, 2008 at 03:40:41PM +0200, Andreas Ericsson <ae@op5.se> wrote:
> With the builtin merge (which is in next)

Just a small correction: it's already in master.

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* [PATCH 06/12] Avoid accessing working directory outside sparse prefix
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:56 UTC (permalink / raw)
  To: git

Low-level functions such as checkout_entry() will refuse to write
outside sparse prefix. Other functions need lstat() will simply
assume files are good. The rest should die()

On UI side, all pathspec input will be filtered by filter_pathspec()
or filter_pathspec_1() to:
 - keep pathspec if it's inside sparse prefix
 - expand pathspec if it's outside sparse prefix, but is a parent
   directory of sparse prefix
 - otherwise, die()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-add.c             |    4 +-
 builtin-apply.c           |    5 ++
 builtin-checkout-index.c  |    2 +
 builtin-checkout.c        |    2 +-
 builtin-clean.c           |    3 +
 builtin-commit.c          |    2 +-
 builtin-grep.c            |   26 +++++++------
 builtin-ls-files.c        |    4 +-
 builtin-ls-tree.c         |    2 +-
 builtin-merge-recursive.c |    2 +-
 builtin-mv.c              |    2 +-
 builtin-rm.c              |    7 +++-
 builtin-update-index.c    |   37 ++++++++++++++----
 cache.h                   |    3 +
 diff-lib.c                |    2 +-
 diff.c                    |    3 +-
 entry.c                   |    3 +
 read-cache.c              |    5 ++
 setup.c                   |   90 ++++++++++++++++++++++++++++++++++++++++++++-
 sha1_file.c               |    3 +
 unpack-trees.c            |   29 +++++++++++++-
 21 files changed, 199 insertions(+), 37 deletions(-)

diff --git a/builtin-add.c b/builtin-add.c
index fc3f96e..8f6efca 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -151,7 +151,7 @@ static void refresh(int verbose, const char **pathspec)
 
 static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
 {
-	const char **pathspec = get_pathspec(prefix, argv);
+	const char **pathspec = get_filtered_pathspec(prefix, argv);
 
 	return pathspec;
 }
@@ -278,7 +278,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 		fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
 		return 0;
 	}
-	pathspec = get_pathspec(prefix, argv);
+	pathspec = get_filtered_pathspec(prefix, argv);
 
 	/*
 	 * If we are adding new files, we need to scan the working
diff --git a/builtin-apply.c b/builtin-apply.c
index 2216a0b..25d3523 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -2389,6 +2389,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
 	if (!old_name)
 		return 0;
 
+	if (outside_sparse_prefix(old_name))
+		return error("%s: could not apply outside sparse checkout", old_name);
+
 	assert(patch->is_new <= 0);
 
 	if (!(patch->is_copy || patch->is_rename) &&
@@ -2491,6 +2494,8 @@ static int check_patch(struct patch *patch)
 		    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
 		    !ok_if_exists)
 			return error("%s: already exists in index", new_name);
+		if (outside_sparse_prefix(new_name))
+			return error("%s: could not apply outside sparse checkout", new_name);
 		if (!cached) {
 			int err = check_to_create_blob(new_name, ok_if_exists);
 			if (err)
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index 71ebabf..f0e829c 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -134,6 +134,8 @@ static void checkout_all(const char *prefix, int prefix_length)
 		    (ce_namelen(ce) <= prefix_length ||
 		     memcmp(prefix, ce->name, prefix_length)))
 			continue;
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (last_ce && to_tempfile) {
 			if (ce_namelen(last_ce) != ce_namelen(ce)
 			    || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
diff --git a/builtin-checkout.c b/builtin-checkout.c
index fbd5105..eec1bde 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -472,7 +472,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		die("git checkout: -f and -m are incompatible");
 
 	if (argc) {
-		const char **pathspec = get_pathspec(prefix, argv);
+		const char **pathspec = get_filtered_pathspec(prefix, argv);
 
 		if (!pathspec)
 			die("invalid path specification");
diff --git a/builtin-clean.c b/builtin-clean.c
index 48bf29f..5f0a632 100644
--- a/builtin-clean.c
+++ b/builtin-clean.c
@@ -76,6 +76,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 		setup_standard_excludes(&dir);
 
 	pathspec = get_pathspec(prefix, argv);
+	pathspec = filter_pathspec(pathspec);
+	if (!pathspec && sparse_checkout_enabled())
+		pathspec = (const char **)get_sparse_prefix();
 	read_cache();
 
 	/*
diff --git a/builtin-commit.c b/builtin-commit.c
index f2d301a..aaa208d 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -237,7 +237,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		die("index file corrupt");
 
 	if (*argv)
-		pathspec = get_pathspec(prefix, argv);
+		pathspec = get_filtered_pathspec(prefix, argv);
 
 	/*
 	 * Non partial, non as-is commit.
diff --git a/builtin-grep.c b/builtin-grep.c
index ee96d01..c9dc30a 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -776,19 +776,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			verify_filename(prefix, argv[j]);
 	}
 
-	if (i < argc) {
-		paths = get_pathspec(prefix, argv + i);
-		if (opt.prefix_length && opt.relative) {
-			/* Make sure we do not get outside of paths */
-			for (i = 0; paths[i]; i++)
-				if (strncmp(prefix, paths[i], opt.prefix_length))
-					die("git-grep: cannot generate relative filenames containing '..'");
-		}
-	}
+	if (i < argc)
+		paths = get_filtered_pathspec(prefix, argv + i);
 	else if (prefix) {
-		paths = xcalloc(2, sizeof(const char *));
-		paths[0] = prefix;
-		paths[1] = NULL;
+		static const char *fake_argv[2];
+		fake_argv[0] = prefix;
+		fake_argv[1] = NULL;
+		paths = get_filtered_pathspec(NULL, fake_argv);
+	}
+	if (!paths && sparse_checkout_enabled())
+		paths = (const char **)get_sparse_prefix();
+	if (paths && opt.prefix_length && opt.relative) {
+		/* Make sure we do not get outside of paths */
+		for (i = 0; paths[i]; i++)
+			if (strncmp(prefix, paths[i], opt.prefix_length))
+				die("git-grep: cannot generate relative filenames containing '..'");
 	}
 
 	if (!list.nr)
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 4d18873..37041cd 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -571,7 +571,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 	if (require_work_tree && !is_inside_work_tree())
 		setup_work_tree();
 
-	pathspec = get_pathspec(prefix, argv + i);
+	pathspec = get_filtered_pathspec(prefix, argv + i);
+	if (!pathspec && sparse_checkout_enabled())
+		pathspec = (const char **)get_sparse_prefix();
 
 	/* Verify that the pathspec matches the prefix */
 	if (pathspec)
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index d25767a..45b1ef5 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -185,7 +185,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_sha1(argv[1], sha1))
 		die("Not a valid object name %s", argv[1]);
 
-	pathspec = get_pathspec(prefix, argv + 2);
+	pathspec = get_filtered_pathspec(prefix, argv + 2);
 	tree = parse_tree_indirect(sha1);
 	if (!tree)
 		die("not a tree object");
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 7ce015b..5c62743 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -526,7 +526,7 @@ static void update_file_flags(const unsigned char *sha,
 	if (index_only)
 		update_wd = 0;
 
-	if (update_wd) {
+	if (update_wd && !outside_sparse_prefix(path)) {
 		enum object_type type;
 		void *buf;
 		unsigned long size;
diff --git a/builtin-mv.c b/builtin-mv.c
index 736a0b8..0591aa2 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -33,7 +33,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
 				result[i] = last_slash + 1;
 		}
 	}
-	return get_pathspec(prefix, result);
+	return get_filtered_pathspec(prefix, result);
 }
 
 static void show_list(const char *label, struct string_list *list)
diff --git a/builtin-rm.c b/builtin-rm.c
index ee8247b..082dd95 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -158,7 +158,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 	if (read_cache() < 0)
 		die("index file corrupt");
 
-	pathspec = get_pathspec(prefix, argv);
+	pathspec = get_filtered_pathspec(prefix, argv);
+	if (!pathspec)
+		return 0;
+
 	seen = NULL;
 	for (i = 0; pathspec[i] ; i++)
 		/* nothing */;
@@ -166,6 +169,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
 	for (i = 0; i < active_nr; i++) {
 		struct cache_entry *ce = active_cache[i];
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
 			continue;
 		add_list(ce->name);
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 38eb53c..25e3e98 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -242,6 +242,9 @@ static void chmod_path(int flip, const char *path)
 	struct cache_entry *ce;
 	unsigned int mode;
 
+	if (outside_sparse_prefix(path))
+		die("%s: cannot update sparse outside %s", path, get_sparse_prefix_str());
+
 	pos = cache_name_pos(path, strlen(path));
 	if (pos < 0)
 		goto fail;
@@ -521,7 +524,7 @@ static int do_reupdate(int ac, const char **av,
 	 */
 	int pos;
 	int has_head = 1;
-	const char **pathspec = get_pathspec(prefix, av + 1);
+	const char **pathspec = get_filtered_pathspec(prefix, av + 1);
 
 	if (read_ref("HEAD", head_sha1))
 		/* If there is no HEAD, that means it is an initial
@@ -582,7 +585,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 
 	for (i = 1 ; i < argc; i++) {
 		const char *path = argv[i];
-		const char *p;
+		const char *p, **pathspec;
 
 		if (allow_options && *path == '-') {
 			if (!strcmp(path, "--")) {
@@ -703,9 +706,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 			die("unknown option %s", path);
 		}
 		p = prefix_path(prefix, prefix_length, path);
-		update_one(p, NULL, 0);
-		if (set_executable_bit)
-			chmod_path(set_executable_bit, p);
+		pathspec = filter_pathspec_1(p);
+		if (pathspec) {
+			const char **pp = pathspec;
+			while (*pp) {
+				update_one(*pp, NULL, 0);
+				if (set_executable_bit)
+					chmod_path(set_executable_bit, *pp);
+				pp ++;
+			}
+			free(pathspec);
+		}
 		if (p < path || p > path + strlen(path))
 			free((char*)p);
 	}
@@ -715,7 +726,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 		strbuf_init(&buf, 0);
 		strbuf_init(&nbuf, 0);
 		while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
-			const char *p;
+			const char *p, **pathspec;
 			if (line_termination && buf.buf[0] == '"') {
 				strbuf_reset(&nbuf);
 				if (unquote_c_style(&nbuf, buf.buf, NULL))
@@ -723,9 +734,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				strbuf_swap(&buf, &nbuf);
 			}
 			p = prefix_path(prefix, prefix_length, buf.buf);
-			update_one(p, NULL, 0);
-			if (set_executable_bit)
-				chmod_path(set_executable_bit, p);
+			pathspec = filter_pathspec_1(p);
+			if (pathspec) {
+				const char **pp = pathspec;
+				while (*pp) {
+					update_one(*pp, NULL, 0);
+					if (set_executable_bit)
+						chmod_path(set_executable_bit, *pp);
+					pp++;
+				}
+				free(pathspec);
+			}
 			if (p < buf.buf || p > buf.buf + buf.len)
 				free((char *)p);
 		}
diff --git a/cache.h b/cache.h
index f6bbadc..b9a1d96 100644
--- a/cache.h
+++ b/cache.h
@@ -336,6 +336,9 @@ extern int check_index_prefix(const struct index_state *index, int is_merge);
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern const char **filter_pathspec(const char **pathspec);
+extern const char **filter_pathspec_1(const char *path);
+extern const char **get_filtered_pathspec(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
diff --git a/diff-lib.c b/diff-lib.c
index 2908146..9382114 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -356,7 +356,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
 	 * But with the revision flag parsing, that's found in
 	 * "!revs->ignore_merges".
 	 */
-	cached = o->index_only;
+	cached = outside_sparse_prefix(idx->name) || o->index_only;
 	match_missing = !revs->ignore_merges;
 
 	if (cached && idx && ce_stage(idx)) {
diff --git a/diff.c b/diff.c
index a07812c..e344916 100644
--- a/diff.c
+++ b/diff.c
@@ -1721,7 +1721,8 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
 	 * If ce matches the file in the work tree, we can reuse it.
 	 */
 	if (ce_uptodate(ce) ||
-	    (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
+	    (!outside_sparse_prefix(name) &&
+	     !lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
 		return 1;
 
 	return 0;
diff --git a/entry.c b/entry.c
index 222aaa3..e2921c2 100644
--- a/entry.c
+++ b/entry.c
@@ -201,6 +201,9 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
 
 	memcpy(path, state->base_dir, len);
 	strcpy(path + len, ce->name);
+	if (outside_sparse_prefix(path))
+		die("%s: unable to checkout entry outside sparse checkout", path);
+
 
 	if (!lstat(path, &st)) {
 		unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
diff --git a/read-cache.c b/read-cache.c
index 4fea40e..dd42c7c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -232,6 +232,8 @@ int ie_match_stat(const struct index_state *istate,
 	int ignore_valid = options & CE_MATCH_IGNORE_VALID;
 	int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
 
+	if (outside_sparse_prefix(ce->name))
+		return 0;
 	/*
 	 * If it's marked as always valid in the index, it's
 	 * valid whatever the checked-out copy says.
@@ -953,6 +955,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
 		return ce;
 	}
 
+	if (outside_sparse_prefix(ce->name))
+		return ce;
+
 	if (lstat(ce->name, &st) < 0) {
 		if (err)
 			*err = errno;
diff --git a/setup.c b/setup.c
index 6cf9094..4ae68fd 100644
--- a/setup.c
+++ b/setup.c
@@ -164,7 +164,7 @@ void verify_filename(const char *prefix, const char *arg)
 	if (*arg == '-')
 		die("bad flag '%s' used after filename", arg);
 	name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
-	if (!lstat(name, &st))
+	if (!outside_sparse_prefix(name) && !lstat(name, &st))
 		return;
 	if (errno == ENOENT)
 		die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
@@ -187,7 +187,7 @@ void verify_non_filename(const char *prefix, const char *arg)
 	if (*arg == '-')
 		return; /* flag */
 	name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
-	if (!lstat(name, &st))
+	if (!outside_sparse_prefix(name) && !lstat(name, &st))
 		die("ambiguous argument '%s': both revision and filename\n"
 		    "Use '--' to separate filenames from revisions", arg);
 	if (errno != ENOENT && errno != ENOTDIR)
@@ -229,6 +229,92 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
 }
 
 /*
+ * if path is inside sparse prefix, leave it as-is
+ * if path is parent of sparse prefix, expand it to sparse prefix
+ * if path is outside sparse prefix, die()
+ */
+static void filter_pathspec_internal(const char *path, const char ***dst, int *dst_alloc, int *dst_nr)
+{
+	char **iprefix;
+	int filtered = 1;
+
+	for (iprefix = get_sparse_prefix(); *iprefix; iprefix++) {
+		const char *new_dst = NULL;
+		if (!prefixcmp(path, *iprefix))
+			new_dst = path;
+		else if (!prefixcmp(*iprefix, path))
+			new_dst = *iprefix;
+
+		if (!new_dst)
+			continue;
+
+		if (*dst_alloc <= *dst_nr) {
+			*dst_alloc = alloc_nr(*dst_alloc);
+			*dst = xrealloc(*dst, *dst_alloc * sizeof(**dst));
+		}
+		(*dst)[(*dst_nr)++] = new_dst;
+		filtered = 0;
+
+		/*
+		 * if it's inside sparse prefix,
+		 * no need to search more
+		 */
+		if (new_dst == path)
+			break;
+	}
+	if (filtered)
+		die("pathspec %s is outside sparse checkout %s", path, get_sparse_prefix_str());
+}
+
+const char **filter_pathspec(const char **pathspec)
+{
+	const char **src, **dst;
+	int dst_alloc = 0;
+	int dst_nr = 0;
+
+	if (!sparse_checkout_enabled())
+		return pathspec;
+	if (!pathspec || !*pathspec)
+		return NULL;
+
+	src = pathspec;
+	dst = NULL;
+	while (*src) {
+		filter_pathspec_internal(*src, &dst, &dst_alloc, &dst_nr);
+		src++;
+	}
+	if (!dst)
+		return NULL;
+	dst[dst_nr] = NULL;
+	return dst;
+}
+
+const char **filter_pathspec_1(const char *path)
+{
+	int dst_alloc = 0, dst_nr = 0;
+	const char **dst = NULL;
+
+	if (!path)
+		return NULL;
+	if (!sparse_checkout_enabled()) {
+		dst = xmalloc(2*sizeof(*dst));
+		dst[0] = path;
+		dst[1] = NULL;
+		return dst;
+	}
+	filter_pathspec_internal(path, &dst, &dst_alloc, &dst_nr);
+	if (!dst)
+		return NULL;
+	dst[dst_nr] = NULL;
+	return dst;
+}
+
+const char **get_filtered_pathspec(const char *prefix, const char **pathspec)
+{
+	return filter_pathspec(get_pathspec(prefix,pathspec));
+}
+
+/*
  * Test if it looks like we're at a git directory.
  * We want to see:
  *
diff --git a/sha1_file.c b/sha1_file.c
index e281c14..60f9e2a 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2422,6 +2422,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
 	char *target;
 	size_t len;
 
+	if (outside_sparse_prefix(path))
+		die("%s: cannot index a file outside sparse checkout", path);
+
 	switch (st->st_mode & S_IFMT) {
 	case S_IFREG:
 		fd = open(path, O_RDONLY);
diff --git a/unpack-trees.c b/unpack-trees.c
index 0a6723b..0a30d68 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -84,6 +84,19 @@ static void unlink_entry(struct cache_entry *ce)
 	}
 }
 
+static int check_sparse_checkout(struct unpack_trees_options *o)
+{
+	unsigned cnt = 0;
+	struct index_state *index = &o->result;
+
+	for (cnt = 0; cnt < index->cache_nr; cnt++) {
+		struct cache_entry *ce = index->cache[cnt];
+		if (ce_stage(ce) && outside_sparse_prefix(ce->name))
+			return 0;
+	}
+	return 1;
+}
+
 static struct checkout state;
 static int check_updates(struct unpack_trees_options *o)
 {
@@ -96,6 +109,8 @@ static int check_updates(struct unpack_trees_options *o)
 	if (o->update && o->verbose_update) {
 		for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
 			struct cache_entry *ce = index->cache[cnt];
+			if (outside_sparse_prefix(ce->name))
+				continue;
 			if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
 				total++;
 		}
@@ -108,6 +123,8 @@ static int check_updates(struct unpack_trees_options *o)
 	for (i = 0; i < index->cache_nr; i++) {
 		struct cache_entry *ce = index->cache[i];
 
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (ce->ce_flags & CE_REMOVE) {
 			display_progress(progress, ++cnt);
 			if (o->update)
@@ -121,6 +138,8 @@ static int check_updates(struct unpack_trees_options *o)
 	for (i = 0; i < index->cache_nr; i++) {
 		struct cache_entry *ce = index->cache[i];
 
+		if (outside_sparse_prefix(ce->name))
+			continue;
 		if (ce->ce_flags & CE_UPDATE) {
 			display_progress(progress, ++cnt);
 			ce->ce_flags &= ~CE_UPDATE;
@@ -411,6 +430,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))
 		return unpack_failed(o, "Merge outside index prefix");
 
+	if (sparse_checkout_enabled() && !check_sparse_checkout(o))
+		return unpack_failed(o, "Merge conflicts outside sparse checkout");
+
 	o->src_index = NULL;
 	ret = check_updates(o) ? (-2) : 0;
 	if (o->dst_index)
@@ -445,7 +467,7 @@ static int verify_uptodate(struct cache_entry *ce,
 {
 	struct stat st;
 
-	if (o->index_only || o->reset)
+	if (o->index_only || o->reset || outside_sparse_prefix(ce->name))
 		return 0;
 
 	if (!lstat(ce->name, &st)) {
@@ -583,7 +605,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
 {
 	struct stat st;
 
-	if (o->index_only || o->reset || !o->update)
+	if (o->index_only || o->reset || !o->update || outside_sparse_prefix(ce->name))
 		return 0;
 
 	if (has_symlink_leading_path(ce_namelen(ce), ce->name))
@@ -994,7 +1016,8 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 		int update = 0;
 		if (o->reset) {
 			struct stat st;
-			if (lstat(old->name, &st) ||
+			if (outside_sparse_prefix(old->name) ||
+			    lstat(old->name, &st) ||
 			    ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
 				update |= CE_UPDATE;
 		}
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 05/12] tests for sparse checkout, index protection
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:56 UTC (permalink / raw)
  To: git


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t2300-sparse-index.sh                 |  156 +++++++++++++++++++++
 t/t2300/diff-index-sub.expected         |    3 +
 t/t2300/log.expected                    |   64 +++++++++
 t/t2301-sparse-index-merge-recursive.sh |  226 +++++++++++++++++++++++++++++++
 4 files changed, 449 insertions(+), 0 deletions(-)
 create mode 100755 t/t2300-sparse-index.sh
 create mode 100644 t/t2300/diff-index-sub.expected
 create mode 100644 t/t2300/log.expected
 create mode 100755 t/t2301-sparse-index-merge-recursive.sh

diff --git a/t/t2300-sparse-index.sh b/t/t2300-sparse-index.sh
new file mode 100755
index 0000000..8b9d61f
--- /dev/null
+++ b/t/t2300-sparse-index.sh
@@ -0,0 +1,156 @@
+#!/bin/sh
+
+test_description='sparse checkout -- index update
+
+This test makes sure all commands that touch index will not
+be able to write outside sparse prefix, once set.
+
+It also makes sure some full index operation like git-checkout
+or git-read-tree can function even with index prefix set.
+'
+
+. ./test-lib.sh
+
+setup_repo() {
+	test "$#" = 1 ||
+	error 'bug in the test script: not 1 parameter to setup_repo'
+	test_create_repo "$1" &&
+	(
+		cd "$1" &&
+		mkdir -p work/sub/dir &&
+		touch untracked tracked modified added &&
+		touch work/untracked work/tracked work/modified work/added &&
+		git add tracked work/tracked &&
+		git add modified work/modified &&
+		git commit -m initial &&
+		git add added work/added &&
+		echo modified > modified &&
+		echo work/modified > work/modified
+	)
+}
+
+# this prevent sparse checkout's worktree protection
+# so you can access everything in worktree
+# index modification is limited though
+export GIT_SPARSE_CHECKOUT_INDEX_ONLY=1
+
+test_expect_success 'setup update-index and --show-parse-prefix' '
+	setup_repo update-index &&
+	cd update-index &&
+	git config core.sparsecheckout work:nonwork &&
+	test nonwork:work = "$(git rev-parse --show-sparse-prefix)" &&
+	git config core.sparsecheckout work &&
+	test work = "$(git rev-parse --show-sparse-prefix)"
+'
+
+test_expect_success 'ls-files --with-tree does not bail out' '
+	git ls-files --with-tree=HEAD
+'
+
+test_expect_success 'update-index --refresh with new mtime outside index prefix' '
+	touch tracked &&
+	test -z "$(git update-index --refresh 2>&1 >/dev/null)"
+'
+
+test_expect_success 'update inside' 'git update-index work/modified'
+test_expect_success 'remove inside' 'git update-index --force-remove work/tracked'
+test_expect_success 'add inside' 'git update-index --add work/untracked'
+
+test_expect_success 'update outside' '! git update-index modified'
+test_expect_success 'remove outside' '! git update-index --force-remove modified'
+test_expect_success 'add outside' '! git update-index --add untracked'
+
+test_expect_success 'setup add' '
+	cd .. &&
+	setup_repo add &&
+	cd add
+	git config core.sparsecheckout work &&
+	test work = "$(git rev-parse --show-sparse-prefix)"
+'
+
+test_expect_success 'add inside' 'git add work/modified'
+test_expect_success 'add outside' '! git add untracked'
+
+test_expect_success 'setup rm' '
+	cd .. &&
+	setup_repo rm &&
+	cd rm &&
+	git config core.sparsecheckout work
+	test work = "$(git rev-parse --show-sparse-prefix)"
+'
+
+test_expect_success 'inside:  rm' 'git rm work/tracked'
+test_expect_success 'outside:  rm' '! git rm tracked'
+
+test_expect_success 'setup apply' '
+	cd .. &&
+	test_create_repo apply &&
+	cd apply &&
+	mkdir -p work/sub/dir &&
+	touch modified work/modified &&
+	git add modified work/modified &&
+	git config core.sparsecheckout work &&
+	test work = "$(git rev-parse --show-sparse-prefix)"
+'
+
+cat <<EOF > outside.patch
+diff --git a/modified b/modified
+index e69de29..2e09960 100644
+--- a/modified
++++ b/modified
+@@ -0,0 +1 @@
++modified
+EOF
+
+cat <<EOF > inside.patch
+diff --git a/work/modified b/work/modified
+index e69de29..4bd2893 100644
+--- a/work/modified
++++ b/work/modified
+@@ -0,0 +1 @@
++work/modified
+EOF
+
+
+test_expect_success 'inside:  apply --cached' 'git apply --cached inside.patch'
+test_expect_success 'outside:  apply --cached' '! git apply --cached outside.patch'
+
+test_expect_success 'setup read-tree (clean worktree)' '
+	cd ..
+	test_create_repo read-tree &&
+	cd read-tree &&
+	test_tick &&
+	mkdir -p work/sub/dir &&
+	echo one > one && git add one && git commit -m one &&
+	echo two >> one && git add one && git commit -m two &&
+	echo one > work/one && git add work/one && git commit -m work/one &&
+	echo two >> work/one && git add work/one && git commit -m work/two &&
+	echo one > work/sub/one && git add work/sub/one && git commit -m work/sub/one &&
+	echo two >> work/sub/one && git add work/sub/one && git commit -m work/sub/two &&
+	echo one > work/sub/dir/one && echo three >> work/one &&
+		git add work/sub/dir/one work/one &&
+		git commit -m work/sub/dir/one &&
+	echo two >> work/sub/dir/one &&
+		git add work/sub/dir/one &&
+		git commit -m work/sub/dir/two &&
+	git log --raw > ../log.result &&
+	cmp ../../t2300/log.expected ../log.result &&
+	git config core.sparsecheckout work/sub &&
+	test work/sub = "$(git rev-parse --show-sparse-prefix)"
+'
+
+test_expect_success 'read-tree inside index prefix' '
+	git read-tree 47b2996ef7d1dd7d5fd146c520ed90995e2d7e0d &&
+	test -z "$(git diff-index --cached 47b2996ef7d1dd7d5fd146c520ed90995e2d7e0d)"'
+
+test_expect_success 'read-tree outside index prefix' '
+	! git read-tree b64ae04f6be47b3d729f731de1ce804223f45113'
+
+test_expect_success 'read-tree with --prefix outside' '
+	! git read-tree --prefix=work/newsub/ b64ae04f6be47b3d729f731de1ce804223f45113'
+
+test_expect_success 'read-tree with --prefix inside' '
+	git read-tree --prefix=work/sub/dir/newsub/ b64ae04f6be47b3d729f731de1ce804223f45113 &&
+	git diff-index --cached 47b2996ef7d1dd7d5fd146c520ed90995e2d7e0d|cmp ../../t2300/diff-index-sub.expected'
+
+test_done
diff --git a/t/t2300/diff-index-sub.expected b/t/t2300/diff-index-sub.expected
new file mode 100644
index 0000000..49b1a47
--- /dev/null
+++ b/t/t2300/diff-index-sub.expected
@@ -0,0 +1,3 @@
+:000000 100644 0000000000000000000000000000000000000000 814f4a422927b82f5f8a43f8fab6d3839e3983f2 A	work/sub/dir/newsub/one
+:000000 100644 0000000000000000000000000000000000000000 814f4a422927b82f5f8a43f8fab6d3839e3983f2 A	work/sub/dir/newsub/work/one
+:000000 100644 0000000000000000000000000000000000000000 5626abf0f72e58d7a153368ba57db4c673c0e171 A	work/sub/dir/newsub/work/sub/one
diff --git a/t/t2300/log.expected b/t/t2300/log.expected
new file mode 100644
index 0000000..58b69bd
--- /dev/null
+++ b/t/t2300/log.expected
@@ -0,0 +1,64 @@
+commit 69798ebb265c266292b4919f7a943bc30d72dfa3
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/sub/dir/two
+
+:100644 100644 5626abf... 814f4a4... M	work/sub/dir/one
+
+commit 47b2996ef7d1dd7d5fd146c520ed90995e2d7e0d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/sub/dir/one
+
+:100644 100644 814f4a4... 4cb29ea... M	work/one
+:000000 100644 0000000... 5626abf... A	work/sub/dir/one
+
+commit dd5fc8b46ac73c49bb046e16b0d9980a8142de51
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/sub/two
+
+:100644 100644 5626abf... 814f4a4... M	work/sub/one
+
+commit b64ae04f6be47b3d729f731de1ce804223f45113
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/sub/one
+
+:000000 100644 0000000... 5626abf... A	work/sub/one
+
+commit 69da5948890ae9caff70388451ba5ede78b77d08
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/two
+
+:100644 100644 5626abf... 814f4a4... M	work/one
+
+commit e63957619e33be2985dacdbb077f4c2ca7210319
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    work/one
+
+:000000 100644 0000000... 5626abf... A	work/one
+
+commit e6742a1800d08c749ebbc01d1f4ca03b66ff44e1
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    two
+
+:100644 100644 5626abf... 814f4a4... M	one
+
+commit 1c60dc5aa63dc2956ceb3ccc399d04245bc2cc1b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:13:13 2005 -0700
+
+    one
+
+:000000 100644 0000000... 5626abf... A	one
diff --git a/t/t2301-sparse-index-merge-recursive.sh b/t/t2301-sparse-index-merge-recursive.sh
new file mode 100755
index 0000000..61b3766
--- /dev/null
+++ b/t/t2301-sparse-index-merge-recursive.sh
@@ -0,0 +1,226 @@
+#!/bin/sh
+
+test_description='merge-recursive backend test'
+
+. ./test-lib.sh
+
+# this prevent sparse checkout's worktree protection
+# so you can access everything in worktree
+# index modification is limited though
+export GIT_SPARSE_CHECKOUT_INDEX_ONLY=1
+
+test_expect_success 'setup 1' '
+
+	echo hello >a &&
+	o0=$(git hash-object a) &&
+	cp a b &&
+	cp a c &&
+	mkdir d &&
+	cp a d/e &&
+
+	test_tick &&
+	git add a b c d/e &&
+	git commit -m initial &&
+	c0=$(git rev-parse --verify HEAD) &&
+	git branch noconflict &&
+	git branch conflict &&
+	git branch conflict-inside &&
+
+	echo hello >>a &&
+	cp a d/e &&
+	o1=$(git hash-object a) &&
+
+	git add a d/e &&
+
+	test_tick &&
+	git commit -m "master modifies a and d/e" &&
+	c1=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o1	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o1	d/e"
+		echo "100644 $o1 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o1 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 2' '
+
+	rm -rf [abcd] &&
+	git checkout conflict &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	echo goodbye >>a &&
+	o2=$(git hash-object a) &&
+
+	git add a &&
+
+	test_tick &&
+	git commit -m "conflict modifies a" &&
+	c2=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o2	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o2 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 3' '
+
+	rm -rf [abcd] &&
+	git checkout noconflict &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	echo hello >>a &&
+	o3=$(git hash-object a) &&
+
+	git add a &&
+
+	test_tick &&
+	git commit -m "noconflict modifies a" &&
+	c3=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o3	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o3 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup 4' '
+
+	rm -rf [abcd] &&
+	git checkout conflict-inside &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o0	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o0 0	d/e"
+	) >expected &&
+	test_cmp expected actual &&
+
+	mkdir d &&
+	echo goodbye >>d/e &&
+	o4=$(git hash-object d/e) &&
+
+	git add d/e &&
+
+	test_tick &&
+	git commit -m "conflict-inside modifies d/e" &&
+	c4=$(git rev-parse --verify HEAD) &&
+	( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+	(
+		echo "100644 blob $o0	a"
+		echo "100644 blob $o0	b"
+		echo "100644 blob $o0	c"
+		echo "100644 blob $o4	d/e"
+		echo "100644 $o0 0	a"
+		echo "100644 $o0 0	b"
+		echo "100644 $o0 0	c"
+		echo "100644 $o4 0	d/e"
+	) >expected &&
+	test_cmp expected actual
+'
+
+export GIT_INDEX_PREFIX=d/
+
+test_expect_success 'merge-recursive conflict inside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c4" &&
+
+	git-merge-recursive "$c0" -- "$c4" "$c1"
+	status=$?
+	case "$status" in
+	1)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_expect_success 'merge-recursive no conflict outside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c3" &&
+
+	git-merge-recursive "$c0" -- "$c3" "$c1"
+	status=$?
+	case "$status" in
+	0)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_expect_success 'merge-recursive conflict outside' '
+
+	rm -fr [abcd] &&
+	git checkout -f "$c2" &&
+
+	git-merge-recursive "$c0" -- "$c2" "$c1"
+	status=$?
+	case "$status" in
+	1)
+		: happy
+		;;
+	*)
+		echo >&2 "why status $status!!!"
+		false
+		;;
+	esac
+'
+
+test_done
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 04/12] Protect index with sparse prefix
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:56 UTC (permalink / raw)
  To: git

This ensures no one can write to index outside sparse prefix.
Protection is usually applied to the_index only. For more
complicated cases where temporary index is required, thorough
check will be done by check_index_prefix()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-commit.c          |   11 +++++
 builtin-ls-files.c        |    2 +
 builtin-merge-recursive.c |    4 +-
 builtin-read-tree.c       |    5 ++
 cache.h                   |    1 +
 diff-lib.c                |    3 +
 read-cache.c              |  107 ++++++++++++++++++++++++++++++++++++++++++++-
 unpack-trees.c            |    3 +
 unpack-trees.h            |    3 +-
 9 files changed, 135 insertions(+), 4 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 97e64de..f2d301a 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -223,6 +223,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	int fd;
 	struct string_list partial;
 	const char **pathspec = NULL;
+	char **saved_sparse_prefix;
 
 	if (interactive) {
 		interactive_add(argc, argv, prefix);
@@ -306,6 +307,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 
 	memset(&partial, 0, sizeof(partial));
 	partial.strdup_strings = 1;
+
+	/*
+	 * this code modifies index but won't write down anything
+	 * so it's safe to turn of sparse_prefix protection for a while
+	 */
+	saved_sparse_prefix = save_sparse_prefix();
+
 	if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
 		exit(1);
 
@@ -313,6 +321,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	if (read_cache() < 0)
 		die("cannot read the index");
 
+	/* re-enable sparse_prefix again */
+	restore_sparse_prefix(saved_sparse_prefix);
+
 	fd = hold_locked_index(&index_lock, 1);
 	add_remove_files(&partial);
 	refresh_cache(REFRESH_QUIET);
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index e8d568e..4d18873 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -606,6 +606,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 		 */
 		if (show_stage || show_unmerged)
 			die("ls-files --with-tree is incompatible with -s or -u");
+		/* no need to restore as ls-files never writes index */
+		save_sparse_prefix();
 		overlay_tree_on_cache(with_tree, prefix);
 	}
 	show_files(&dir, prefix);
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 43e55bf..7ce015b 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -201,8 +201,10 @@ static int git_merge_trees(int index_only,
 	memset(&opts, 0, sizeof(opts));
 	if (index_only)
 		opts.index_only = 1;
-	else
+	else {
 		opts.update = 1;
+		opts.check_index_prefix = 1;
+	}
 	opts.merge = 1;
 	opts.head_idx = 2;
 	opts.fn = threeway_merge;
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 72a6de3..0883b41 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -218,6 +218,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
 			opts.head_idx = 1;
 	}
 
+	if (get_sparse_prefix()) {
+		read_cache();
+		opts.check_index_prefix = 1;
+	}
+
 	for (i = 0; i < nr_trees; i++) {
 		struct tree *tree = trees[i];
 		parse_tree(tree);
diff --git a/cache.h b/cache.h
index 4687096..f6bbadc 100644
--- a/cache.h
+++ b/cache.h
@@ -331,6 +331,7 @@ extern char **split_prefix(const char *env);
 extern char **combine_prefix_list(char **p1, char **p2);
 extern void free_prefix_list(char **prefix_list);
 extern int outside_prefix_list(char **iprefix, const char *prefix);
+extern int check_index_prefix(const struct index_state *index, int is_merge);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
diff --git a/diff-lib.c b/diff-lib.c
index e7eaff9..2908146 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -73,6 +73,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 		struct cache_entry *ce = active_cache[i];
 		int changed;
 
+		if (index_outside_sparse_prefix(ce->name))
+			continue;
+
 		if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
 			DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
 			break;
diff --git a/read-cache.c b/read-cache.c
index a50a851..4fea40e 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -23,6 +23,11 @@
 
 struct index_state the_index;
 
+static int outside_cache_prefix(const struct index_state *istate, const char *ce_name)
+{
+	return istate == &the_index && index_outside_sparse_prefix(ce_name);
+}
+
 static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
 {
 	istate->cache[nr] = ce;
@@ -396,6 +401,8 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
 	struct cache_entry *ce = istate->cache[pos];
 
+	if (outside_cache_prefix(istate, ce->name))
+		die("%s: cannot remove from index outside %s", ce->name, get_sparse_prefix_str());
 	remove_name_hash(ce);
 	istate->cache_changed = 1;
 	istate->cache_nr--;
@@ -410,6 +417,10 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 int remove_file_from_index(struct index_state *istate, const char *path)
 {
 	int pos = index_name_pos(istate, path, strlen(path));
+
+	if (outside_cache_prefix(istate, path))
+		die("%s: cannot remove from index from outside %s", path, get_sparse_prefix_str());
+
 	if (pos < 0)
 		pos = -pos-1;
 	cache_tree_invalidate_path(istate->cache_tree, path);
@@ -809,21 +820,43 @@ static int check_file_directory_conflict(struct index_state *istate,
 	return retval + has_dir_name(istate, ce, pos, ok_to_replace);
 }
 
+/*
+ * ce_compare only considers stuff that will be written to a tree object
+ * or index stages
+ *
+ * On CE_* flags CE_HASHED, CE_UNHASHED and CE_UPTODATE are out because they
+ * are in-core only, will not be written out
+ */
+#define CE_COMPARE_MASK (CE_STATE_MASK | CE_UPTODATE)
+static int ce_compare(const struct cache_entry *ce1, const struct cache_entry *ce2)
+{
+	return ce1->ce_mode == ce2->ce_mode &&
+		((ce1->ce_flags ^ ce2->ce_flags) & ~CE_COMPARE_MASK) == 0 &&
+		!memcmp(ce1->sha1, ce2->sha1, 20) &&
+		!strcmp(ce1->name, ce2->name);
+}
+
 static int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option)
 {
 	int pos;
 	int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
 	int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
 	int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+	int is_outside = outside_cache_prefix(istate, ce->name);
 
 	cache_tree_invalidate_path(istate->cache_tree, ce->name);
 	pos = index_name_pos(istate, ce->name, ce->ce_flags);
 
 	/* existing match? Just replace it. */
 	if (pos >= 0) {
+		if (is_outside && !ce_compare(istate->cache[pos], ce))
+			die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 		replace_index_entry(istate, pos, ce);
 		return 0;
 	}
+
+	if (is_outside)
+		die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 	pos = -pos-1;
 
 	/*
@@ -858,9 +891,11 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
 {
 	int pos;
 
-	if (option & ADD_CACHE_JUST_APPEND)
+	if (option & ADD_CACHE_JUST_APPEND) {
+		if (outside_cache_prefix(istate, ce->name))
+			die("%s: cannot add to index outside %s", ce->name, get_sparse_prefix_str());
 		pos = istate->cache_nr;
-	else {
+	} else {
 		int ret;
 		ret = add_index_entry_with_check(istate, ce, option);
 		if (ret <= 0)
@@ -1444,3 +1479,71 @@ int read_index_unmerged(struct index_state *istate)
 	istate->cache_nr = dst - istate->cache;
 	return !!last;
 }
+
+int check_index_prefix(const struct index_state *index, int is_merge)
+{
+	unsigned i;
+	char **index_prefix = get_sparse_prefix();
+
+	if (!index_prefix)
+		return 1;
+
+	/* Check for unmerged entries first */
+	for (i = 0; i < index->cache_nr; i++) {
+		struct cache_entry *ce = index->cache[i];
+		if (ce_stage(ce) && index_outside_sparse_prefix(ce->name))
+			return 0;
+	}
+
+	/*
+	 * if is_merge is true, caller has decided that there
+	 * could be changes outside index prefix, return now
+	 */
+	if (is_merge)
+		return 1;
+	else {
+		const struct index_state *idx[2];
+		int prefix_i[2];
+		int index_prefix_nr;
+		unsigned entry_i[2];
+
+		idx[0] = &the_index;
+		idx[1] = index;
+		prefix_i[0] = prefix_i[1] = 0;
+		entry_i[0] = entry_i[1] = 0;
+		index_prefix_nr = 0;
+		while (index_prefix[index_prefix_nr])
+			index_prefix_nr++;
+
+		while (entry_i[0] < idx[0]->cache_nr &&
+		       entry_i[1] < idx[1]->cache_nr) {
+			/* ignore anything inside index prefix */
+			int what_index;
+			for (what_index = 0; what_index < 2; what_index++) {
+				const struct index_state *idx_p = idx[what_index];
+				const char *prefix = index_prefix[prefix_i[what_index]];
+				unsigned *entry_ip = entry_i+what_index;
+				if (prefix_i[what_index] < index_prefix_nr &&
+				    !prefixcmp(idx_p->cache[*entry_ip]->name, prefix)) {
+					while (*entry_ip < idx_p->cache_nr &&
+					       !prefixcmp(idx_p->cache[*entry_ip]->name, prefix))
+						entry_ip[0]++;
+					prefix_i[what_index]++;
+				}
+			}
+
+			if (entry_i[0] == idx[0]->cache_nr || entry_i[1] == idx[1]->cache_nr)
+				break;
+
+			/* does not match */
+			if (!ce_compare(idx[0]->cache[entry_i[0]], idx[1]->cache[entry_i[1]]))
+				return 0;
+
+			entry_i[0]++;
+			entry_i[1]++;
+		}
+
+		return entry_i[0] == idx[0]->cache_nr &&
+			entry_i[1] == idx[1]->cache_nr;
+	}
+}
diff --git a/unpack-trees.c b/unpack-trees.c
index cba0aca..0a6723b 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -408,6 +408,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 	if (o->trivial_merges_only && o->nontrivial_merge)
 		return unpack_failed(o, "Merge requires file-level merging");
 
+	if (o->check_index_prefix && !check_index_prefix(&o->result, o->merge && !o->prefix))
+		return unpack_failed(o, "Merge outside index prefix");
+
 	o->src_index = NULL;
 	ret = check_updates(o) ? (-2) : 0;
 	if (o->dst_index)
diff --git a/unpack-trees.h b/unpack-trees.h
index 94e5672..a1b46f9 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -26,7 +26,8 @@ struct unpack_trees_options {
 		     verbose_update:1,
 		     aggressive:1,
 		     skip_unmerged:1,
-		     gently:1;
+		     gently:1,
+		     check_index_prefix:1;
 	const char *prefix;
 	int pos;
 	struct dir_struct *dir;
-- 
1.5.5.GIT

^ permalink raw reply related

* Re: q: faster way to integrate/merge lots of topic branches?
From: Ingo Molnar @ 2008-07-23 14:56 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git
In-Reply-To: <76718490807230747w5d0350b8v6feba00fb8837617@mail.gmail.com>


* Jay Soffian <jaysoffian@gmail.com> wrote:

> > CACHE=$MERGECACHE/$HEAD_SHA1/$BRANCH_SHA1
> >
> > [ -f "$CACHE" -a "$CACHE" -nt .git/refs/heads/$BRANCH_SHA1 ] && {
> 
> Shouldn't this be:
> 
> [ -f "$CACHE" -a "$CACHE" -nt .git/refs/heads/$BRANCH ] && {
> 
> ?

yeah, i just figured it out too ... the hard way :)

Updated script below. This works fine across resets in the master 
branch.

While it's fast in the empty-merge case, it's not as fast as i'd like it 
to be in the almost-empty-merge case.

	Ingo

--------------{ git-fastmerge }-------------------->
#!/bin/bash

usage () {
  echo 'usage: tip-fastmerge <refspec>..'
  exit -1
}

[ $# = 0 ] && usage

BRANCH=$1

MERGECACHE=.git/mergecache

[ ! -d $MERGECACHE ] && { mkdir $MERGECACHE || usage; }

HEADREF=.git/$(cut -d' ' -f2 .git/HEAD)

HEAD_SHA1=$(git-log -1 --pretty=format:"%H")
BRANCH_SHA1=$(git-log -1 --pretty=format:"%H" $BRANCH)

CACHE=$MERGECACHE/$HEAD_SHA1/$BRANCH_SHA1

[ -f "$CACHE" -a "$CACHE" -nt "$HEADREF" ] && {
# echo "merge-cache hit on HEAD <= $1"
  exit 0
}

git-merge $1 && {
  mkdir -p $(dirname $CACHE)
  touch $CACHE
}

^ permalink raw reply

* [PATCH 03/12] Introduce sparse prefix
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:56 UTC (permalink / raw)
  To: git

Sparse prefix is actually a list of prefixes separated by colons,
that will limit worktree usage within it.

This patch adds core.sparsecheckout and
"git rev-parse --show-sparse-prefix". This also adds manipulation
functions that will get used later.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-rev-parse.c |    4 +
 cache.h             |   13 +++
 config.c            |    7 ++
 environment.c       |  221 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 245 insertions(+), 0 deletions(-)

diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index aa71f4a..4200f14 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -499,6 +499,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 					puts(prefix);
 				continue;
 			}
+			if (!strcmp(arg, "--show-sparse-prefix")) {
+				puts(get_sparse_prefix_str());
+				continue;
+			}
 			if (!strcmp(arg, "--show-cdup")) {
 				const char *pfx = prefix;
 				if (!is_inside_work_tree()) {
diff --git a/cache.h b/cache.h
index 38985aa..4687096 100644
--- a/cache.h
+++ b/cache.h
@@ -319,6 +319,19 @@ extern const char *get_git_work_tree(void);
 extern const char *read_gitfile_gently(const char *path);
 extern void set_git_work_tree(const char *tree);
 
+extern int sparse_checkout_enabled();
+extern char **get_sparse_prefix(void);
+extern const char *get_sparse_prefix_str(void);
+extern char **save_sparse_prefix();
+extern char **restore_sparse_prefix(char **prefix);
+extern int outside_sparse_prefix(const char *prefix);
+extern int index_outside_sparse_prefix(const char *prefix);
+extern void set_sparse_prefix(const char *prefix);
+extern char **split_prefix(const char *env);
+extern char **combine_prefix_list(char **p1, char **p2);
+extern void free_prefix_list(char **prefix_list);
+extern int outside_prefix_list(char **iprefix, const char *prefix);
+
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
diff --git a/config.c b/config.c
index 1e066c7..e7b457b 100644
--- a/config.c
+++ b/config.c
@@ -467,6 +467,13 @@ static int git_default_core_config(const char *var, const char *value)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.sparsecheckout")) {
+		if (!value)
+			return config_error_nonbool(var);
+		set_sparse_prefix(value);
+		return 0;
+	}
+
 	/* Add other config variables here and to Documentation/config.txt. */
 	return 0;
 }
diff --git a/environment.c b/environment.c
index 4a88a17..94e39b8 100644
--- a/environment.c
+++ b/environment.c
@@ -46,9 +46,132 @@ enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
 char *git_work_tree_cfg;
 static char *work_tree;
 
+static char **sparse_prefix;
+
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
 
+static char *append_slash(const char *prefix, int len)
+{
+	char *new_prefix;
+
+	if (!*prefix)
+		return NULL;
+
+	if (prefix[len-1] == '/')
+		return xstrndup(prefix, len);
+
+	new_prefix = xmalloc(len+2);
+	memcpy(new_prefix, prefix, len);
+	new_prefix[len] = '/';
+	new_prefix[len+1] = '\0';
+	return new_prefix;
+}
+
+/* this should be sorted as same order as index */
+static int prefix_compare(const void *prefix1_, const void *prefix2_)
+{
+	const char *prefix1 = *(const char**)prefix1_;
+	const char *prefix2 = *(const char**)prefix2_;
+	int len1 = strlen(prefix1);
+	int len2 = strlen(prefix2);
+	int len = len1 < len2 ? len1 : len2;
+	int cmp = memcmp(prefix1, prefix2, len);
+	if (cmp)
+		return cmp;
+	if (len1 < len2)
+		return -1;
+	if (len1 > len2)
+		return 1;
+	return 0;
+}
+
+void free_prefix_list(char **prefix_list)
+{
+	char **prefix = prefix_list;
+
+	if (!prefix)
+		return;
+
+	while (*prefix) {
+		free(*prefix);
+		prefix++;
+	}
+	free(prefix_list);
+}
+
+char **split_prefix(const char *env)
+{
+	int prefix_nr = 0;
+	int prefix_alloc = 0;
+	char **prefix_list = NULL;
+
+	if (!env)
+		return NULL;
+
+	do {
+		const char *sep = strchr(env, ':');
+		int len = sep ? sep - env : strlen(env);
+		if (prefix_alloc <= prefix_nr+1) {
+			prefix_alloc = alloc_nr(prefix_alloc);
+			prefix_list = xrealloc(prefix_list,
+						prefix_alloc * sizeof(*prefix_list));
+		}
+		prefix_list[prefix_nr++] = append_slash(env, len);
+		env += sep ? len+1 : len;
+	} while (*env);
+	prefix_list[prefix_nr] = NULL;
+	qsort(prefix_list, prefix_nr, sizeof(*prefix_list), prefix_compare);
+	return prefix_list;
+}
+
+char **combine_prefix_list(char **p1, char **p2)
+{
+	int len1 = 0, len2 = 0;
+	char **p, **p12, *last_prefix;
+	char **result, **result_p;
+
+	/*
+	 * if either of them is null, full access,
+	 * combining them would give full access as well
+	 */
+	if (!p1 || !p2)
+		return NULL;
+
+	for (p = p1; *p; p++)
+		len1++;
+	for (p = p2; *p; p++)
+		len2++;
+
+	p12 = xmalloc((len1+len2+1)*sizeof(*p12));
+	result = xmalloc((len1+len2+1)*sizeof(*result));
+	memcpy(p12, p1, len1*sizeof(*p12));
+	memcpy(p12+len1, p2, (len2+1)*sizeof(*p12));
+	qsort(p12, len1+len2, sizeof(*p12), prefix_compare);
+
+	p = p12;
+	last_prefix = *p;
+	p++;
+	result_p = result;
+	*result_p = xstrdup(last_prefix);
+	result_p++;
+	while (*p) {
+		if (!strcmp(*p, last_prefix)) {
+			p++;
+			continue;
+		}
+		if (!prefixcmp(*p, last_prefix)) {
+			p++;
+			continue;
+		}
+		*result_p = xstrdup(*p);
+		result_p++;
+		p++;
+	}
+	*result_p = NULL;
+	return result;
+}
+
 static void setup_git_env(void)
 {
 	git_dir = getenv(GIT_DIR_ENVIRONMENT);
@@ -122,6 +245,104 @@ const char *get_git_work_tree(void)
 	return work_tree;
 }
 
+void set_sparse_prefix(const char *flat_sparse_prefix)
+{
+	free_prefix_list(sparse_prefix);
+	sparse_prefix = split_prefix(flat_sparse_prefix);
+}
+
+char **get_sparse_prefix()
+{
+	return sparse_prefix;
+}
+
+const char *get_sparse_prefix_str()
+{
+	static char **sparse_prefix = NULL;
+	static char *sparse_prefix_str = NULL;
+	int len;
+	char **prefix;
+
+	if (sparse_prefix == get_sparse_prefix())
+		return sparse_prefix ? sparse_prefix_str : "";
+
+	sparse_prefix = get_sparse_prefix();
+
+	if (!sparse_prefix)
+		return "";
+
+	len = 0;
+	for (prefix = sparse_prefix; *prefix; prefix++)
+		len += strlen(*prefix)+1;
+	if (sparse_prefix_str)
+		free(sparse_prefix_str);
+	sparse_prefix_str = xmalloc(len);
+
+	prefix = sparse_prefix;
+	len = strlen(*prefix);
+	if ((*prefix)[len-1] == '/')
+		len--;
+	memcpy(sparse_prefix_str, *prefix, len);
+	prefix++;
+	while (*prefix) {
+		int len2 = strlen(*prefix);
+		sparse_prefix_str[len++] = ':';
+		if ((*prefix)[len2-1] == '/')
+			len2--;
+		memcpy(sparse_prefix_str+len, *prefix, len2);
+		len += len2;
+		prefix++;
+	}
+	sparse_prefix_str[len] = 0;
+	return sparse_prefix_str;
+}
+
+char **save_sparse_prefix()
+{
+	char **prefix = sparse_prefix;
+	sparse_prefix = NULL;
+	return prefix;
+}
+
+char **restore_sparse_prefix(char **prefix)
+{
+	char **old_prefix = sparse_prefix;
+	sparse_prefix = prefix;
+	return old_prefix;
+}
+
+int outside_prefix_list(char **iprefix, const char *prefix)
+{
+	if (!iprefix)
+		return 0;
+
+	while (*iprefix) {
+		if (!prefixcmp(prefix, *iprefix))
+			return 0;
+		iprefix++;
+	}
+	return 1;
+}
+
+int sparse_checkout_enabled()
+{
+	static int disable_sparse_checkout = -1;
+	if (disable_sparse_checkout == -1)
+		disable_sparse_checkout = getenv("GIT_SPARSE_CHECKOUT_INDEX_ONLY") != NULL;
+	return !disable_sparse_checkout && get_sparse_prefix();
+}
+
+
+int outside_sparse_prefix(const char *prefix)
+{
+	return sparse_checkout_enabled() && outside_prefix_list(sparse_prefix, prefix);
+}
+
+int index_outside_sparse_prefix(const char *prefix)
+{
+	return outside_prefix_list(sparse_prefix, prefix);
+}
+
 char *get_object_directory(void)
 {
 	if (!git_object_dir)
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 02/12] git-grep: support --no-external-grep
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:55 UTC (permalink / raw)
  To: git

this one is mainly for testing builtin grep

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-grep.c |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)

diff --git a/builtin-grep.c b/builtin-grep.c
index cc9ba6b..ee96d01 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -20,6 +20,8 @@
 #endif
 #endif
 
+static int no_external_grep;
+
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
  * pathname wildcards are allowed.
@@ -386,7 +388,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 	 * we grep through the checked-out files. It tends to
 	 * be a lot more optimized
 	 */
-	if (!cached) {
+	if (!cached && !no_external_grep) {
 		hit = external_grep(opt, paths, cached);
 		if (hit >= 0)
 			return hit;
@@ -544,6 +546,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			cached = 1;
 			continue;
 		}
+		if (!strcmp("--no-external-grep", arg)) {
+			no_external_grep = 1;
+			continue;
+		}
 		if (!strcmp("-a", arg) ||
 		    !strcmp("--text", arg)) {
 			opt.binary = GREP_BINARY_TEXT;
-- 
1.5.5.GIT

^ permalink raw reply related

* [PATCH 01/12] git-grep: read config
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:55 UTC (permalink / raw)
  To: git


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin-grep.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/builtin-grep.c b/builtin-grep.c
index 631129d..cc9ba6b 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -517,6 +517,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 	const char **paths = NULL;
 	int i;
 
+	git_config(git_default_config, NULL);
+
 	memset(&opt, 0, sizeof(opt));
 	opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
 	opt.relative = 1;
-- 
1.5.5.GIT

^ permalink raw reply related

* [RFC PATCH 00/12] Sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-07-23 14:55 UTC (permalink / raw)
  To: git

I have not looked at non-builtin commands yet, but I think it's not
a big deal. We have several rounds before this series is good enough ;)
So in short, sparse prefix will be stored in config, core.sparsecheckout.
you have three new commands to enter/update/leave sparse checkout:

git clone --path=prefix       # clone with sparse checkout
git checkout --path=prefix    # limit/update checkout paths
git checkout --full           # stop sparse checkout

Other operations should be normal. User attempts to do anything outside
sparse checkout will get flagged. Git itself should not touch anything
outside sparse checkout.

One more thing. As files outside sparse checkout will not be checking
out, .gitignore and .gitattributes from parent directories (outside
sparse checkout) will be gone too. This may lead to surprise.

Comments are welcome.

Nguyễn Thái Ngọc Duy (12):
  git-grep: read config
  git-grep: support --no-external-grep
  Introduce sparse prefix
  Protect index with sparse prefix
  tests for sparse checkout, index protection
  Avoid accessing working directory outside sparse prefix
  tests for sparse checkout, worktree protection
  git-clone: support --path to do sparse clone
  tests for sparse clone
  git-checkout: support --full and --path to manipulate sparse checkout
  tests for checkout [--full|--path]
  git-status: print sparse checkout status

 builtin-add.c                              |    4 +-
 builtin-apply.c                            |    5 +
 builtin-checkout-index.c                   |    2 +
 builtin-checkout.c                         |   46 ++++++-
 builtin-clean.c                            |    3 +
 builtin-clone.c                            |   13 ++
 builtin-commit.c                           |   13 ++-
 builtin-grep.c                             |   36 +++--
 builtin-ls-files.c                         |    6 +-
 builtin-ls-tree.c                          |    2 +-
 builtin-merge-recursive.c                  |    6 +-
 builtin-mv.c                               |    2 +-
 builtin-read-tree.c                        |    5 +
 builtin-rev-parse.c                        |    4 +
 builtin-rm.c                               |    7 +-
 builtin-update-index.c                     |   37 ++++-
 cache.h                                    |   19 +++
 config.c                                   |    7 +
 diff-lib.c                                 |    5 +-
 diff.c                                     |    3 +-
 entry.c                                    |    3 +
 environment.c                              |  221 ++++++++++++++++++++++++++
 read-cache.c                               |  112 +++++++++++++-
 setup.c                                    |   90 +++++++++++-
 sha1_file.c                                |    3 +
 t/t2010-checkout-sparse.sh                 |   71 +++++++++
 t/t2300-sparse-index.sh                    |  156 +++++++++++++++++++
 t/t2300/diff-index-sub.expected            |    3 +
 t/t2300/log.expected                       |   64 ++++++++
 t/t2301-sparse-index-merge-recursive.sh    |  226 +++++++++++++++++++++++++++
 t/t2302-sparse-worktree.sh                 |  113 ++++++++++++++
 t/t2302/add-u.expected                     |    1 +
 t/t2302/add.expected                       |    2 +
 t/t2302/commit.expected                    |   14 ++
 t/t2302/diff-1.expected                    |    7 +
 t/t2302/diff-cc.expected                   |    9 +
 t/t2302/grep-work.expected                 |    2 +
 t/t2302/grep.expected                      |    2 +
 t/t2302/ls-files.expected                  |    2 +
 t/t2303-sparse-worktree-apply.sh           |   62 ++++++++
 t/t2303/apply-initial.patch                |   14 ++
 t/t2303/apply-inside.patch                 |    7 +
 t/t2303/apply-leading-dirs.patch           |    3 +
 t/t2303/apply-outside.patch                |    7 +
 t/t2303/apply-remove.patch                 |    7 +
 t/t2303/apply-rename.expected              |   13 ++
 t/t2303/apply-rename.patch                 |    4 +
 t/t2304-sparse-worktree-merge-recursive.sh |  233 ++++++++++++++++++++++++++++
 t/t5703-clone-sparse.sh                    |   40 +++++
 unpack-trees.c                             |  152 +++++++++++++++++--
 unpack-trees.h                             |    4 +-
 wt-status.c                                |   12 +-
 52 files changed, 1831 insertions(+), 53 deletions(-)
 create mode 100755 t/t2010-checkout-sparse.sh
 create mode 100755 t/t2300-sparse-index.sh
 create mode 100644 t/t2300/diff-index-sub.expected
 create mode 100644 t/t2300/log.expected
 create mode 100755 t/t2301-sparse-index-merge-recursive.sh
 create mode 100755 t/t2302-sparse-worktree.sh
 create mode 100644 t/t2302/add-u.expected
 create mode 100644 t/t2302/add.expected
 create mode 100644 t/t2302/commit.expected
 create mode 100644 t/t2302/diff-1.expected
 create mode 100644 t/t2302/diff-cc.expected
 create mode 100644 t/t2302/grep-work.expected
 create mode 100644 t/t2302/grep.expected
 create mode 100644 t/t2302/ls-files.expected
 create mode 100755 t/t2303-sparse-worktree-apply.sh
 create mode 100644 t/t2303/apply-initial.patch
 create mode 100644 t/t2303/apply-inside.patch
 create mode 100644 t/t2303/apply-leading-dirs.patch
 create mode 100644 t/t2303/apply-outside.patch
 create mode 100644 t/t2303/apply-remove.patch
 create mode 100644 t/t2303/apply-rename.expected
 create mode 100644 t/t2303/apply-rename.patch
 create mode 100755 t/t2304-sparse-worktree-merge-recursive.sh
 create mode 100755 t/t5703-clone-sparse.sh

^ permalink raw reply

* Re: [RFC] Git User's Survey 2008
From: Dmitry Potapov @ 2008-07-23 14:54 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jakub Narebski, git, Stephan Beyer
In-Reply-To: <alpine.DEB.1.00.0807231128090.2830@eeepc-johanness>

On Wed, Jul 23, 2008 at 11:53:27AM +0200, Johannes Schindelin wrote:
> 
> On Wed, 23 Jul 2008, Jakub Narebski wrote:
> 
> 
> >    11. Why did you choose Git? (if you use Git)
> >        What do you like about using Git?
> >        (free form, not to be tabulated)
> 
> Again, to avoid hassles with free-form:
> 
> 	Mandatory: work, mandatory: open source project I am participating 
> 	in, speed, scalability, It's What Linus Uses, Other.

If we move away from free-form, it should be much more choices here.

- Ability to work offline
- Cryptographic authentication of history.
- Distributed development (pull/push from/to more than one remote repo)
- Easy to extend functionality through scripting
- Efficient storage model
- Elegant design
- Fast
- Good community support
- Rewriting patches before publishing (git rebase, commit --amend)
- Scalability (Efficient handling of large projects)
- Strong support for non-linear development
- Support of wide range of protocols for synchronization.
...

Dmitry

^ permalink raw reply

* Re: [RFC] Git User's Survey 2008
From: Robin Rosenberg @ 2008-07-23 14:54 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jakub Narebski, git
In-Reply-To: <alpine.DEB.1.00.0807231414480.8986@racer>

onsdagen den 23 juli 2008 15.18.40 skrev Johannes Schindelin:
> Hi,
> 
> On Wed, 23 Jul 2008, Jakub Narebski wrote:
> 
> > On Wed, 23 Jul 2008, Johannes Schindelin wrote:
> > > On Wed, 23 Jul 2008, Jakub Narebski wrote:
> > > 
> > > Some people prefer to stay anonymous, so I think email is out.
> > > 
> > > >    04. Which programming languages you are proficient with?
> > > >        (The choices include programming languages used by git)
> > > >        (zero or more: multiple choice)
> > > >      - C, shell, Perl, Python, Tcl/Tk
> > > >      + (should we include other languages, like C++, Java, PHP,
> > > >         Ruby,...?)
> > > 
> > > Yes, I think this should be a long list.
> > 
> > I'd rather not have a "laundry list" of languages.  I have put C++
> > because QGit uses it, Java because of egit/jgit, PHP for web
> > interfaces, Ruby because of GitHub and because of Ruby comminity
> > choosing Git.  I should perhaps add Emacs Lisp, HTML+CSS and
> > JavaScript here.  What other languages should be considered?
> 
> C# at least, since we had one (pretty unsuccessful) attempt at 
> reimplementing Git in it.

What is the reason for the question? Do we want to know what languages
people would like to contribute to Git in or do we want to know what "kind"
of programmers are attracted by Git?  Making it a long list should make
it easier to tabulate the responses.

-- robin

^ permalink raw reply

* Re: [PATCH] Rename ".dotest/" to ".git/rebase" and ".dotest-merge" to "rebase-merge"
From: Olivier Marin @ 2008-07-23 14:54 UTC (permalink / raw)
  To: Stephan Beyer
  Cc: Junio C Hamano, Theodore Tso, Nanako Shiraishi,
	Johannes Schindelin, René Scharfe, Joe Fiorini, git,
	Jari Aalto
In-Reply-To: <20080723011341.GE5904@leksak.fem-net>

Stephan Beyer a écrit :
> 
>>> Perhaps I am confused, but ...

I can understand. ;-)

>>> Why is there "HEAD" and "ORIG_HEAD" and not only "ORIG_HEAD"?
>> Just being a bit defensive -- in this case I think it might be Ok to say
>> "read-tree --reset -u ORIG_HEAD", but I haven't checked in a conflicted
>> case.

git read-tree --reset -u ORIG_HEAD clears local changes which is not good.

> Well, the test suite fails:
> * FAIL 4: am --abort goes back after failed am
> 
>                         git-am --abort &&
>                         git rev-parse HEAD >actual &&
>                         git rev-parse initial >expect &&
>                         test_cmp expect actual &&
>   here>                 test_cmp file-2-expect file-2 &&

Local changes have been lost.

> The reason of my question was that I *blindly* incorporated the change into
> sequencer to make it able to work on a dirty working tree and thus to be
> able to migrate am onto it without losing the ability to apply patches
> on a dirty working tree....

Are you talking about your seq-proto-dev3 branch?

> All am tests applied afterwards, but the sequencer and the rebase-i
> test suite failed in a place where I didn't expect it. I *then* had
> a deeper look at the read-tree line and I was wondering what the "HEAD"
> should achieve.
> I removed it and all tests passed. (I didn't have t4151 in my branch
> at that point.)
> 
> Now, because t4151 does not pass, I am wondering what's the best thing
> I could do...

I looked at your code. You use reset_almost_hard() instead of "reset --hard",
it's fine but you does not update require_clean_work_tree() to be less
restrictive and let the sequencer work with local modifications. Those two
lines must be removed, I think:
  git update-index --ignore-submodules --refresh &&
  git diff-files --quiet --ignore-submodules &&

Try that with the original read-tree line and t4151 should pass.

Ah, you should change "Applying 6" with "Applying \"6\"" in t4151-am-abort.sh
too.

Olivier.

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Jay Soffian @ 2008-07-23 14:47 UTC (permalink / raw)
  To: Ingo Molnar; +Cc: git
In-Reply-To: <20080723134926.GA12888@elte.hu>

On Wed, Jul 23, 2008 at 9:49 AM, Ingo Molnar <mingo@elte.hu> wrote:
> #!/bin/bash
>
> usage () {
>  echo 'usage: git-fastmerge <refspec>..'
>  exit -1
> }
>
> [ $# = 0 ] && usage
>
> BRANCH=$1
>
> MERGECACHE=.git/mergecache
>
> [ ! -d $MERGECACHE ] && { mkdir $MERGECACHE || usage; }
>
> HEAD_SHA1=$(git-log -1 --pretty=format:"%H")
> BRANCH_SHA1=$(git-log -1 --pretty=format:"%H" $BRANCH)
>
> CACHE=$MERGECACHE/$HEAD_SHA1/$BRANCH_SHA1
>
> [ -f "$CACHE" -a "$CACHE" -nt .git/refs/heads/$BRANCH_SHA1 ] && {

Shouldn't this be:

[ -f "$CACHE" -a "$CACHE" -nt .git/refs/heads/$BRANCH ] && {

?

j.

^ permalink raw reply

* Re: [PATCH] index-pack: never prune base_cache.
From: Johannes Schindelin @ 2008-07-23 14:41 UTC (permalink / raw)
  To: Björn Steinbrink; +Cc: Pierre Habouzit, spearce, Git ML, Junio C Hamano
In-Reply-To: <20080723134448.GB11679@atjola.homenet>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 1070 bytes --]

Hi,

On Wed, 23 Jul 2008, Björn Steinbrink wrote:

> diff --git a/index-pack.c b/index-pack.c
> index ac20a46..33ba8ef 100644
> --- a/index-pack.c
> +++ b/index-pack.c
> @@ -699,6 +699,9 @@ static struct object_entry *append_obj_to_pack(
>  	write_or_die(output_fd, header, n);
>  	obj[0].idx.crc32 = crc32(0, Z_NULL, 0);
>  	obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n);
> +	obj[0].hdr_size = n;
> +	obj[0].type = type;
> +	obj[0].size = size;
>  	obj[1].idx.offset = obj[0].idx.offset + n;
>  	obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
>  	hashcpy(obj->idx.sha1, sha1);

I confirm that the issues I saw went away with this patch, and it looks 
obviously like the correct approach.

The only things valgrind is still complaining about (apart from libz, 
which I will not bother commenting about) are uninitialized parts of the 
data being written to disk, and a crc over them.

Judging from the addresses, those are probably the bytes that are padded 
for 4- or 8-byte alignment, so they are probably fine.

Thanks,
Dscho

^ permalink raw reply

* Re: [RFC] Git User's Survey 2008
From: Dmitry Potapov @ 2008-07-23 14:38 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git, Stephan Beyer
In-Reply-To: <200807230325.04184.jnareb@gmail.com>

On Wed, Jul 23, 2008 at 03:25:03AM +0200, Jakub Narebski wrote:
>    02. What is your preferred non-programming language?
>   (or) What is the language you want computer communicate with you?

IMHO, the later wording of the question is much better.

>    05. How did you hear about Git?
>        (single choice?, in 2007 it was free-form)
>      - Linux kernel news (LKML, LWN, KernelTrap, KernelTraffic,...),
>        news site or magazine, blog entry, some project uses it,
>        presentation or seminar (real life, not on-line), SCM research,
>        IRC, mailing list, other Internet, other off-line, other(*)

I think "friend" would be a reasonable choice here too.

>    09. When did you start using git? From which version?
>      - pre 1.0, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5
>      + might be important when checking "what did you find hardest" etc.
>      + perhaps we should ask in addition to this question, or in place
>        of this question (replacing it) what git version one uses; it
>        should be multiple choice, and allow 'master', 'next', 'pu',
>        'dirty (with own modifications)' versions in addition.

I think: "What version do you use now?" and "How log do you use git?"
may be more useful here. From which version may give rather confusing
results because someone may "start" with 1.4 a week ago just because
that is the version included in Debian Etch and after realizing that
version 1.4 has serious usability issues upgraded git to 1.5. Besides,
1.5 is around for a long time now (as most as long as all previous
versions), so 1.5 can mean either one month of usage or 18 months...


Dmitry

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Ingo Molnar @ 2008-07-23 14:14 UTC (permalink / raw)
  To: Sergey Vlasov; +Cc: git
In-Reply-To: <20080723140959.GB9537@elte.hu>


* Ingo Molnar <mingo@elte.hu> wrote:

> Even assuming that the filesystem is sane, is my merge-cache 
> implementation semantically equivalent to a git-merge? One detail is 
> that i suspect it is not equivalent in the git-merge --no-ff case. 
> (but that is a not too interesting non-default case anyway)

actually, since --no-ff creates a merge commit and thus propagates the 
head sha1, this should work fine as well.

(besides the small detail that my script has $1 hardcoded so parameters 
are not properly passed onto.)

	Ingo

^ permalink raw reply

* Re: [RFC] Git User's Survey 2008
From: Stephan Beyer @ 2008-07-23 14:12 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <200807230325.04184.jnareb@gmail.com>

Hi,

Jakub Narebski wrote:
> I'd rather avoid free-form questions, even if they are more interesting,
> as they are PITA to analyse and summarize, especially to create some 
> kind of histogram from free-form replies data (some of 2007 free-form
> responses are not fully summarized even now).

Then we should use a web-based survey, because e-mail-based will always
be used to write free-form answers, I think.

So for multiple choice questions an "Other" item is often useful, but
-- if the survey service allows it -- I'd prefer if the "Other" item
enables a text input field (free-form) to let the user be more concrete.
Those values are informational only, but could be added to Git User's
Survey 2009, if they occur more than once. :)

> Third, where to send survey to / where to publish information about the 
> survey?  Last year the announcement was send to git mailing list, to
> LKML (Linux kernel mailing list), and mailing list for git projects 
> found on GitProjects page on GIT wiki.  Now that the number of projects 
> using Git as version control system has grown, I don't think it would 
> be good idea to "spam" all those mailing list; and if we don't send 
> notice to all other projects I'm not sure if we should include LKML.

Hmm, perhaps we could spam some news sites[1] on the web and keep the lists
clean.  Of course, this is advertising for git, too ;-)

[1] I could write something for German-speaking pro-linux.de and symlink.ch
    though I don't know if they take it as news.

> Last year survey announcement was put on Git Homepage (thanks Pasky), 
> and on front page of Git Wiki; info about survey was also put on two 
> git hosting sites: kernel.org and repo.or.cz.

Nice. That should be done again ;-)

> Last year the survey was meant to take three weeks, but was up longer.

Perhaps this is much too much, but my first thought was: 8 weeks.
Hmm, perhaps 5 weeks?

>    04. Which programming languages you are proficient with?

This is a really nasty multiple choice question.

>        (The choices include programming languages used by git)
>        (zero or more: multiple choice)
>      - C, shell, Perl, Python, Tcl/Tk
>      + (should we include other languages, like C++, Java, PHP,
>         Ruby,...?)

Perhaps yes.
The programming language list
	https://www.ohloh.net/tools
could be a start %)

>      - Linux kernel news (LKML, LWN, KernelTrap, KernelTraffic,...),
>        news site or magazine, blog entry, some project uses it,
>        presentation or seminar (real life, not on-line), SCM research,
>        IRC, mailing list, other Internet, other off-line, other(*)

- other off-line: told by friend, must be used at job, ...

>      + the problem is with having not very long list (not too many
>        choices), but spanning all possibilities.

Hmmm.
Is this a limitation by the free web-based services or is this due to
survey usability issues?

>    09. When did you start using git? From which version?
>      - pre 1.0, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5
>      + might be important when checking "what did you find hardest" etc.
>      + perhaps we should ask in addition to this question, or in place
>        of this question (replacing it) what git version one uses; it
>        should be multiple choice, and allow 'master', 'next', 'pu',
>        'dirty (with own modifications)' versions in addition.

Hmm, the master/next/pu/dirty question will be a mystery to most git users
that have never cared about git source code.

> How you use Git

>    16. Which porcelains / interfaces / implementations do you use?
>        (zero or more: multiple choice)
>      - core-git, Cogito (deprecated), StGIT, Guilt, pg (deprecated),
>        Pyrite, Easy Git, IsiSetup, jgit, my own scripts, other

I wonder if this could be extended to get an idea how many people use
plumbing directly or, even better, add a question like:

	Which of the following git commands or extra git tools do you use regularly?

	[list of all plumbing, porcelain and tools like stgit, guilt, etc]

or "... have you never used?", or "...have you ever used?"...
Just to get an idea of what commands are often used by the users.

Perhaps it is even useful to extend that list by some behavior-changing
options, like:

	[ ] git add
	[ ] git add -i / -p
	...
	[ ] git am
	[ ] git am -i
	...
	[ ] git merge
	[ ] git merge with strategy
	...
	[ ] git rebase
	[ ] git rebase -i

...though this is also handled by question 27 (see below).

Yes, this will be a long list :-)
And don't forget the [ ] other :)

Of course this question could be split into
 - extra tools
 - guis (like question 17.)
 - helpers
 - porcelain
 - plumbings

Question 27. should be in this section, too:

>    27. Which of the following features do you use?
>        (zero or more: multiple choice)
>      - git-gui or other commit tool, gitk or other history viewer, patch
>        management interface (e.g. StGIT), bundle, eol conversion,
>        gitattributes, submodules, separate worktree, reflog, stash,
>        shallow clone, detaching HEAD, mergetool, interactive rebase,
>        add --interactive or other partial commit helper, commit
>        templates, bisect, other (not mentioned here)
>      + should probably be sorted in some resemblance of order
>      + are there any new features which should be listed here?

Hmm, I'd remove "git-gui or other commit tool, gitk or other history
viewer, patch management interface (e.g. StGIT)".
And depending of the question I just proposed, "interactive rebase",
"add --interactive [...]", "bisect" could be removed, too.

>    18. Which (main) git web interface do you use for your projects?
>        (zero or more: multiple choice)
>      - gitweb, cgit, wit (Ruby), git-php, viewgit (PHP), other
>      + should there be a question about web server (Apache, IIS, ...)
>        used to host git web interface?

No, why should we care about the web server? :)

>    22. How does Git compare to other SCM tools you have used?
>      - worse/equal (or comparable)/better
>    23. What would you most like to see improved about Git?
>        (features, bugs, plug-ins, documentation, ...)
>    24. If you want to see Git more widely used, what do you
>        think we could do to make this happen?
>      + Is this question necessary/useful?  Do we need wider adoption?

Hmmm,
"Do you miss features in git that you know from other SCMs?"
"If yes, what features are these?"

>    26. How do you compare current version with version from year ago?
>      - current version is: better/worse/no changes

Since this is single-choice, a "don't know"/"cannot say" option should 
be added.

>    28. If you use some important Git features not mentioned above,
>        what are it?

"what are it" sounds somehow funny. Is it correct?
"what are them?" or "what are those?"
Or
"If you use some important Git feature not mentioned above, what is it?"

> Documentation
> 
>    29. Do you use the Git wiki?
>     -  yes/no
>    30. Do you find Git wiki useful?
>     -  yes/no/somewhat
>    31. Do you contribute to Git wiki?
>     -  yes/no/only corrections or spam removal
>    32. Do you find Git's on-line help (homepage, documentation) useful?
>     -  yes/no/somewhat
>    33. Do you find help distributed with Git useful
>        (manpages, manual, tutorial, HOWTO, release notes)?
>     -  yes/no/somewhat
>    34. What could be improved on the Git homepage?
>        (free form)
>    35. What could be improved in Git documentation?
>        (free form)

36. Do you think there is too few documentation on the web?
37. Do you think there is too much documentation on the web?

No ;-) Perhaps:

36. Do you think it is easy to find out how to do a specific task with
    git?

> Open forum
> 
>    46. What other comments or suggestions do you have that are not
>        covered by the questions above?
>        (free form)

About the survey

47. Do you have any comments about the survey?
48. Should such a survey be repeated next year?
(Or: Would you take part in such a survey next year again?)
    [ ] Yes
    [ ] No, but 2010 again.
    [ ] No, never again.

Just a poor idea to get "feedback" if people like to take part in this
survey or not.


Regards,
  Stephan

-- 
Stephan Beyer <s-beyer@gmx.net>, PGP 0x6EDDD207FCC5040F

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Ingo Molnar @ 2008-07-23 14:09 UTC (permalink / raw)
  To: Sergey Vlasov; +Cc: git
In-Reply-To: <20080723174140.b749191a.vsu@altlinux.ru>

* Sergey Vlasov <vsu@altlinux.ru> wrote:

> On Wed, 23 Jul 2008 15:05:18 +0200 Ingo Molnar wrote:
> 
> > Anyone can simulate it by switching to the linus/master branch of the
> > current Linux kernel tree, and doing:
> >
> >    time for ((i=0; i<140; i++)); do git-merge v2.6.26; done
> >
> >    real    1m26.397s
> >    user    1m10.048s
> >    sys     0m13.944s
> 
> Timing results here (E6750 @ 2.66GHz):
> 41.61s user 3.71s system 99% cpu 45.530 total
> 
> However, testing whether there is something new to merge could be
> performed significantly faster:
> 
> $ time sh -c 'for ((i=0; i<140; i++)); do [ -n "$(git rev-list --max-count=1 v2.6.26 ^HEAD)" ]; done'
> sh -c   5.49s user 0.26s system 99% cpu 5.786 total
> 
> The same loop with "git merge-base v2.6.26 HEAD" takes about 40 
> seconds here - apparently finding the merge base is the expensive 
> part, and it makes sense to avoid it if you expect that most of your 
> branches do not contain anything new to merge.

using git-fastmerge i get 2.4 seconds:

  $ time for ((i=0; i<140; i++)); do git-fastmerge v2.6.26; done
  [...]
  real    0m2.388s
  user    0m1.211s
  sys     0m1.131s

for something that 'progresses' in a forward manner (which merges do 
fundamentally) nothing beats the performance of a timestamped cache i 
think.

at least for my usecase.

Even assuming that the filesystem is sane, is my merge-cache 
implementation semantically equivalent to a git-merge? One detail is 
that i suspect it is not equivalent in the git-merge --no-ff case. (but 
that is a not too interesting non-default case anyway)

	Ingo

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Santi Béjar @ 2008-07-23 14:06 UTC (permalink / raw)
  To: Ingo Molnar; +Cc: git
In-Reply-To: <20080723130518.GA17462@elte.hu>

On Wed, Jul 23, 2008 at 15:05, Ingo Molnar <mingo@elte.hu> wrote:
>
> I've got the following, possibly stupid question: is there a way to
> merge a healthy number of topic branches into the master branch in a
> quicker way, when most of the branches are already merged up?

You could filter upfront the branches that are already merged up with:

git show-branch --independent <commits>

but it has a limit of 25 refs.

Santi

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Björn Steinbrink @ 2008-07-23 14:06 UTC (permalink / raw)
  To: Ingo Molnar; +Cc: git
In-Reply-To: <20080723130518.GA17462@elte.hu>

On 2008.07.23 15:05:18 +0200, Ingo Molnar wrote:
> 
> I've got the following, possibly stupid question: is there a way to 
> merge a healthy number of topic branches into the master branch in a 
> quicker way, when most of the branches are already merged up?
> 
> Right now i've got something like this scripted up:
> 
>   for B in $(git-branch | cut -c3- ); do git-merge $B; done 

Not yet in any release (AFAICT), but with git.git master, you could use:

for B in $(git branch --no-merged); do git-merge $B; done


Or with earlier versions, this should work, but it's a lot slower:

for B in $(git branch | cut -c3- ); do
	[[ -n "$(git rev-list -1 HEAD..$B)" ]] && git merge $B;
done

Björn

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Ingo Molnar @ 2008-07-23 14:04 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: git
In-Reply-To: <20080723135621.GJ22606@neumann>


* SZEDER Gábor <szeder@ira.uka.de> wrote:

> Hi,
> 
> On Wed, Jul 23, 2008 at 03:05:18PM +0200, Ingo Molnar wrote:
> > I've got the following, possibly stupid question: is there a way to 
> > merge a healthy number of topic branches into the master branch in a 
> > quicker way, when most of the branches are already merged up?
> > 
> > Right now i've got something like this scripted up:
> > 
> >   for B in $(git-branch | cut -c3- ); do git-merge $B; done 
> you cound use 'git branch --no-merged' to list only those branches
> that have not been merged into your current HEAD.

hm, it's very slow:

  $ time git branch --no-merged
  [...]

  real    0m9.177s
  user    0m9.027s
  sys     0m0.129s

when running it on tip/master:

  http://people.redhat.com/mingo/tip.git/README

	Ingo

^ permalink raw reply

* Re: q: faster way to integrate/merge lots of topic branches?
From: Ingo Molnar @ 2008-07-23 14:02 UTC (permalink / raw)
  To: Andreas Ericsson; +Cc: git
In-Reply-To: <488734D9.9070703@op5.se>


* Andreas Ericsson <ae@op5.se> wrote:

> Ingo Molnar wrote:
>> I've got the following, possibly stupid question: is there a way to  
>> merge a healthy number of topic branches into the master branch in a  
>> quicker way, when most of the branches are already merged up?
>>
>> Right now i've got something like this scripted up:
>>
>>   for B in $(git-branch | cut -c3- ); do git-merge $B; done 
>>
>> It takes a lot of time to run on even a 3.45GHz box:
>>
>>   real    0m53.228s
>>   user    0m41.134s
>>   sys     0m11.405s
>>
>> I just had a workflow incident where i forgot that this script was  
>> running in one window (53 seconds are a _long_ time to start doing some 
>> other stuff :-), i switched branches and the script merrily chugged 
>> away merging branches into a topic branch i did not intend.
>>
>> It iterates over 140 branches - but all of them are already merged up.
>>
>
> With the builtin merge (which is in next), this should be doable with 
> an octopus merge, which will eliminate the branches that are already 
> fully merged, resulting in a less-than-140-way merge (thank gods...). 
> It also doesn't have the 24-way cap that the scripted version suffers 
> from.
>
> If it does a good job at your rather extreme use-case, I'd say it's 
> good enough for 'master' pretty soon :-)

hm, while i do love octopus merges [*] for release and bisection-quality 
purposes, for throw-away (delta-)integration runs it's more manageable 
to do a predictable series of one-on-one merges.

It results in better git-rerere behavior, has easier (to the human) 
conflict resolutions and the octopus merge also falls apart quite easily 
when it runs into conflicts. Furthermore, i've often seen octopus merges 
fail while a series of 1:1 merges succeeded.

What i could try is to do a speculative octopus merge, in the hope of it 
just going fine - and then fall back to the serial merge if it fails?

The git-fastmerge approach is probably still faster though - and 
certainly simpler from a workflow POV.

	Ingo

[*] take a look at these in the Linux kernel -git repo:

      gitk 3c1ca43fafea41e38cb2d0c1684119af4c1de547
      gitk 6924d1ab8b7bbe5ab416713f5701b3316b2df85b

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox