Git development
 help / color / mirror / Atom feed
* Re: [PATCH v2 00/14] Sparse checkout
From: Santi Béjar @ 2008-09-20 10:48 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git
In-Reply-To: <1221904913-25887-1-git-send-email-pclouds@gmail.com>

On Sat, Sep 20, 2008 at 12:01 PM, Nguyễn Thái Ngọc Duy
<pclouds@gmail.com> wrote:
>
> Nguyễn Thái Ngọc Duy (14):
>  Extend index to save more flags
>  Introduce CE_NO_CHECKOUT bit
>  ls-files: add options to support sparse checkout
>  update-index: refactor mark_valid() in preparation for new options
>  update-index: add --checkout/--no-checkout to update CE_NO_CHECKOUT bit
>  ls-files: Add tests for --sparse and friends
>  Prevent diff machinery from examining worktree outside sparse checkout
>  checkout_entry(): CE_NO_CHECKOUT on checked out entries.
>  grep: skip files outside sparse checkout area
>  ls-files: support "sparse patterns", used to form sparse checkout areas
>  unpack_trees(): add support for sparse checkout
>  clone: support sparse checkout with --narrow-path option
>  checkout: add new options to support sparse checkout
>  wt-status: Show orphaned entries in "git status" output
>

I would like to test it, do you have a public repo to fetch it?

Santi

^ permalink raw reply

* Re: TopGit: how to deal with upstream inclusion
From: martin f krafft @ 2008-09-19 17:04 UTC (permalink / raw)
  To: Petr Baudis, git
In-Reply-To: <20080914210316.GJ10360@machine.or.cz>

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

also sprach Petr Baudis <pasky@suse.cz> [2008.09.14.2203 +0100]:
> 	tg depend fold RETIRED VANILLA
> 
>   (i) Take the RETIRED _base_ branch
> 
>   (ii) Merge in VANILLA
> 
>   (iii) Merge in RETIRED head branch with -s ours
> 
>   (iv) Now we have a commit that contains RETIRED topic branch, but with
> the RETIRED's changes taken from VANILLA instead of RETIRED
> 
>   (v) Merge this into the base of your current branch
> 
>   (vi) Merge base to the head of your current branch, replacing RETIRED
> with VANILLA (if not already there) in the .topdeps
> 
>   (vii) You do not have to add VANILLA if you depend on it recursively
> and ran tg update before to get it from your dependencies
> 
>   Maybe in your case this could be even simpler but this should be the
> general process. Does that sound right?

Yes, it does. One might want to consider to make the use of -s ours
in (iiii) configurable, but otherwise that's it.

-- 
martin | http://madduck.net/ | http://two.sentenc.es/
 
all information contained in the above is false,
for reasons of military security.
 
spamtraps: madduck.bogus@madduck.net

[-- Attachment #2: Digital signature (see http://martin-krafft.net/gpg/) --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* [PATCH 14/14] wt-status: Show orphaned entries in "git status" output
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-14-git-send-email-pclouds@gmail.com>


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

diff --git a/wt-status.c b/wt-status.c
index 7cf890f..4c237e2 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -20,6 +20,7 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
 	"\033[31m", /* WT_STATUS_CHANGED: red */
 	"\033[31m", /* WT_STATUS_UNTRACKED: red */
 	"\033[31m", /* WT_STATUS_NOBRANCH: red */
+	"\033[31m", /* WT_STATUS_ORPHANED: red */
 };
 
 enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
@@ -83,6 +84,16 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 	color_fprintf_ln(s->fp, c, "#");
 }
 
+static void wt_status_print_orphaned_header(struct wt_status *s)
+{
+	const char *c = color(WT_STATUS_HEADER);
+	color_fprintf_ln(s->fp, c, "# Orphaned files:");
+	color_fprintf_ln(s->fp, c, "#   (these are tracked, but marked no-checkout and should not be present)");
+	color_fprintf_ln(s->fp, c, "#   (use \"git update-index --checkout\" to remove no-checkout status)");
+	color_fprintf_ln(s->fp, c, "#   (otherwise remove them to avoid confusion because git will ignore them)");
+	color_fprintf_ln(s->fp, c, "#");
+}
+
 static void wt_status_print_untracked_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER);
@@ -233,6 +244,33 @@ static void wt_status_print_changed(struct wt_status *s)
 	run_diff_files(&rev, 0);
 }
 
+static void wt_status_print_orphaned(struct wt_status *s)
+{
+	int i, show_header = 0;
+	struct stat st;
+	struct strbuf buf;
+
+	strbuf_init(&buf, 0);
+	for (i = 0; i < the_index.cache_nr; i++) {
+		struct cache_entry *ce = the_index.cache[i];
+
+		if (ce_checkout(ce) || lstat(ce->name, &st))
+			continue;
+
+		if (!show_header) {
+			wt_status_print_orphaned_header(s);
+			show_header = 1;
+		}
+
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+		color_fprintf_ln(s->fp, color(WT_STATUS_ORPHANED), "%s",
+				 quote_path(ce->name, -1, &buf, s->prefix));
+	}
+
+	if (show_header)
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),"#");
+}
+
 static void wt_status_print_submodule_summary(struct wt_status *s)
 {
 	struct child_process sm_summary;
@@ -373,6 +411,7 @@ void wt_status_print(struct wt_status *s)
 	}
 
 	wt_status_print_changed(s);
+	wt_status_print_orphaned(s);
 	if (wt_status_submodule_summary)
 		wt_status_print_submodule_summary(s);
 	if (show_untracked_files)
diff --git a/wt-status.h b/wt-status.h
index 78add09..52f1eb5 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -9,6 +9,7 @@ enum color_wt_status {
 	WT_STATUS_CHANGED,
 	WT_STATUS_UNTRACKED,
 	WT_STATUS_NOBRANCH,
+	WT_STATUS_ORPHANED,
 };
 
 enum untracked_status_type {
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 13/14] checkout: add new options to support sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-13-git-send-email-pclouds@gmail.com>

This patch adds main interface to manipulate sparse checkout.
New options are added to support entering/updating/leaving sparse
checkout:

 --full: return to full checkout (default)
 --reset-path: set checkout area according to given spec
 --add-path/--remove-path: adjust current sparse checkout area

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt |   54 +++++++++++++++++++-
 builtin-checkout.c             |   37 ++++++++++++++
 t/t2011-checkout-sparse.sh     |  108 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 196 insertions(+), 3 deletions(-)
 create mode 100755 t/t2011-checkout-sparse.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index d6f94a6..571e2a4 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -8,8 +8,10 @@ git-checkout - Checkout a branch or paths to the working tree
 SYNOPSIS
 --------
 [verse]
-'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
-'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
+'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m]
+	  [<sparse checkout options>] [<branch>]
+'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>]
+	  [<sparse checkout options>] [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -34,6 +36,10 @@ used to specify a specific tree-ish (i.e. commit, tag or tree)
 to update the index for the given paths before updating the
 working tree.
 
+<sparse checkout options> include --full, --reset-path, --add-path
+and --remove-path. The last three require sparse patterns. Please refer
+to "sparse checkout" section for more information about this mode.
+
 The index may contain unmerged entries after a failed merge.  By
 default, if you try to check out such an entry from the index, the
 checkout operation will fail and nothing will be checked out.
@@ -117,6 +123,32 @@ should result in deletion of the path).
 When checking out paths from the index, this option lets you recreate
 the conflicted merge in the specified paths.
 
+--full::
+	Quit sparse checkout mode. Return to full checkout. This option
+	cannot be used with either --reset-path, --add-path,
+	--remove-path or <paths>.
+
+--reset-path=<sparse patterns>::
+	Re-apply new sparse patterns on current working directory to
+	form new checkout area. All no-checkout bits will be wiped
+	out before applying the patterns. This option cannot be used
+	with --full, --add-path, --remove-path or <paths>. Multiple
+	--reset-path is not allowed.
+
+--add-path=<sparse patterns>::
+	Checkout more areas specified by sparse patterns to current
+	checkout area. Already checked out entries are not affected.
+	This option cannot be used with --full, --reset-path,
+	--remove-path or <paths>. Multiple --add-path is not allowed.
+
+--remove-path=<sparse patterns>::
+	Narrow checkout area by removing files specified by sparse patterns
+	from current checkout area. This operation will fail if there
+	are unmerged or modified files in the removing areas. No-checkout
+	entries are not affected. This option cannot be used with --full,
+	--reset-path, --add-path or <paths>. Multiple --remove-path is not
+	allowed.
+
 --conflict=<style>::
 	The same as --merge option above, but changes the way the
 	conflicting hunks are presented, overriding the
@@ -186,7 +218,10 @@ Because sparse checkout uses a new index format, it will be
 incompatible with git prior to 1.6.0 regarding worktree operations.
 Operations that only need access to the repository itself, such as
 clone, push, or pull/fetch from another (normal) repository... should
-not be affected by sparse checkout.
+not be affected by sparse checkout. In order to make your working
+directory work again with those versions, you can use
+`git checkout --full` to return to normal mode (and compatible index
+format).
 
 In sparse checkout mode, checkout status of every files in your
 working directory will be recorded in index. If a file is marked
@@ -251,6 +286,19 @@ Patterns have the following format:
  - Because colons are used to separate patterns, you cannot put them
    in patterns directly. You must quote them using backslash.
 
+When you apply new sparse patterns to your working directory using either
+--reset-path, --add-path or --remove-path, it will update "checkout" status
+in index accordingly. Moreover, if a file is marked "no-checkout" and
+is present in working directory, it will be removed. If a file is
+turned from "no-checkout" to "checkout", then it will be added again
+to working directory. Modified and unmerged entries can't bear
+"no-checkout" status, if sparse patterns apply to them, "git checkout"
+will refuse to update working directory.
+
+Sparse patterns are not saved by "git checkout" anywhere in the repository.
+You can form your checkout area in one go with --reset-path option,
+or do it incrementally with --add-path and --remove-path.
+
 EXAMPLES
 --------
 
diff --git a/builtin-checkout.c b/builtin-checkout.c
index c7b0aad..d459bc6 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -33,6 +33,12 @@ struct checkout_opts {
 	const char *new_branch;
 	int new_branch_log;
 	enum branch_track track;
+
+	const char *prefix;
+	char *new_path;
+	char *add_path;
+	char *remove_path;
+	int all_path;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -412,6 +418,23 @@ static int merge_working_tree(struct checkout_opts *opts,
 		tree = parse_tree_indirect(new->commit->object.sha1);
 		init_tree_desc(&trees[1], tree->buffer, tree->size);
 
+		if (opts->all_path) {
+			/* leave narrow_spec NULL */
+			topts.new_narrow_path = 1;
+		}
+		else if (opts->new_path) {
+			topts.narrow_spec = parse_narrow_spec(opts->new_path, opts->prefix);
+			topts.new_narrow_path = 1;
+		}
+		else if (opts->add_path) {
+			topts.narrow_spec = parse_narrow_spec(opts->add_path, opts->prefix);
+			topts.add_narrow_path = 1;
+		}
+		else if (opts->remove_path) {
+			topts.narrow_spec = parse_narrow_spec(opts->remove_path, opts->prefix);
+			topts.remove_narrow_path = 1;
+		}
+
 		ret = unpack_trees(2, trees, &topts);
 		if (ret == -1) {
 			/*
@@ -600,6 +623,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
 		OPT_STRING(0, "conflict", &conflict_style, "style",
 			   "conflict style (merge or diff3)"),
+		OPT_BOOLEAN(0, "full", &opts.all_path, "quit sparse checkout"),
+		OPT_STRING(0, "reset-path", &opts.new_path, "prefixes", "reset to new sparse checkout"),
+		OPT_STRING(0, "add-path", &opts.add_path, "prefixes", "widen checkout area"),
+		OPT_STRING(0, "remove-path", &opts.remove_path, "prefixes", "narrow checkout area"),
 		OPT_END(),
 	};
 	int has_dash_dash;
@@ -610,6 +637,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	git_config(git_checkout_config, NULL);
 
 	opts.track = BRANCH_TRACK_UNSPECIFIED;
+	opts.prefix = prefix;
 
 	argc = parse_options(argc, argv, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
@@ -639,6 +667,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	if (!opts.new_branch && (opts.track != git_branch_track))
 		die("git checkout: --track and --no-track require -b");
 
+	if (((opts.all_path ? 1 : 0) +
+	     (opts.new_path ? 1 : 0) +
+	     (opts.add_path ? 1 : 0) +
+	     (opts.remove_path ? 1 : 0)) > 1)
+		die("git checkout: --reset-path, --full, --add-path and --remove-path are incompatible");
+
 	if (opts.force && opts.merge)
 		die("git checkout: -f and -m are incompatible");
 
@@ -732,6 +766,9 @@ no_reference:
 		if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
 			die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
 
+		if (opts.all_path || opts.new_path || opts.add_path || opts.remove_path)
+			die("git checkout: updating paths is incompatible with setting sparse checkout");
+
 		return checkout_paths(source_tree, pathspec, &opts);
 	}
 
diff --git a/t/t2011-checkout-sparse.sh b/t/t2011-checkout-sparse.sh
new file mode 100755
index 0000000..b5ccfe4
--- /dev/null
+++ b/t/t2011-checkout-sparse.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+test_description='sparse checkout'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	mkdir work1 work2 work3
+	touch one two three
+	touch work1/one work2/two work3/three
+	git add one work1/one
+	git commit -m work1
+	git add two work2/two
+	git commit -m work2
+	git add three work3/three
+	git commit -m work3
+'
+
+test_expect_success '--full on no-narrow checkout' '
+	git checkout --full
+'
+
+test_expect_success '--full and --reset-path incompatible' '
+	test_must_fail git checkout --full --reset-path=work1
+'
+
+test_expect_success 'limit worktree to work1 and work2' '
+	git checkout --reset-path=work1/:work2/ &&
+	test -f work1/one &&
+	test -f work2/two &&
+	! test -f work3/three
+'
+
+test_expect_success 'update worktree to work2 and work3' '
+	git checkout --reset-path=work2/:work3/ &&
+	! test -f work1/one &&
+	test -f work2/two &&
+	test -f work3/three
+'
+
+test_expect_success 'update narrow prefix with modification' '
+	echo modified >> work2/two &&
+	git checkout --reset-path=work1/:work2/ &&
+	test -f work1/one &&
+	test -f work2/two &&
+	! test -f work3/three &&
+	grep -q modified work2/two
+'
+
+test_expect_success 'update checkout should not lose modification' '
+	! git checkout --reset-path=work1/:work3/ &&
+	test -f work1/one &&
+	test -f work2/two &&
+	! test -f work3/three &&
+	grep -q modified work2/two
+'
+
+test_expect_success 'widen checkout area' '
+	git checkout --add-path=work3/ &&
+	test -f work1/one &&
+	test -f work2/two &&
+	test -f work3/three
+'
+
+test_expect_success 'narrow checkout area' '
+	git checkout --remove-path=work3/ &&
+	test -f work1/one &&
+	test -f work2/two &&
+	! test -f work3/three
+'
+
+test_expect_success 'update outside checkout area' '
+	echo one >> work1/one &&
+	git add work1/one &&
+	git commit -m update &&
+	git checkout --reset-path=work2/ &&
+	git checkout HEAD^ &&
+	git checkout master
+'
+
+test_expect_success 'conflict outside checkout area' '
+	git checkout --add-path=work1/one -b conflict HEAD~2 &&
+	echo two >> work1/one &&
+	git add work1/one &&
+	git commit -m conflict-update &&
+	git checkout --reset-path=work2/ master &&
+	test -z "$(git ls-files --sparse work1/one)"
+	git merge conflict
+	test $? = 1 &&
+	test -n "$(git ls-files --sparse work1/one)" &&
+	git reset --hard HEAD
+'
+
+test_expect_success 'removal outside checkout area' '
+	git rm work1/one &&
+	git commit -m remove &&
+	git checkout --reset-path=work2/ HEAD^
+'
+
+test_expect_success 'exit sparse checkout' '
+	git checkout --full &&
+	test -f work1/one &&
+	test -f work2/two &&
+	test -f work3/three &&
+	test one
+'
+
+test_done
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 12/14] clone: support sparse checkout with --narrow-path option
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-12-git-send-email-pclouds@gmail.com>


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-clone.txt |   10 +++++++++-
 builtin-clone.c             |   13 +++++++++++++
 t/t5703-clone-narrow.sh     |   39 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 61 insertions(+), 1 deletions(-)
 create mode 100755 t/t5703-clone-narrow.sh

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 0e14e73..c283cf4 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -12,7 +12,8 @@ SYNOPSIS
 'git clone' [--template=<template_directory>]
 	  [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
 	  [-o <name>] [-u <upload-pack>] [--reference <repository>]
-	  [--depth <depth>] [--] <repository> [<directory>]
+	  [--depth <depth>] [--narrow-path=<sparse patterns>] [--]
+	  <repository> [<directory>]
 
 DESCRIPTION
 -----------
@@ -94,6 +95,13 @@ then the cloned repository will become corrupt.
 -n::
 	No checkout of HEAD is performed after the clone is complete.
 
+--narrow-path=<sparse patterns>::
+	Make a sparse checkout instead of full one. The checkout area
+	will be narrowed to specific areas based on given sparse
+	patterns. This option will not work with either --no-checkout
+	or --bare.  Please refer to linkgit:git-checkout[1] for more
+	detail on sparse checkout and sparse patterns.
+
 --bare::
 	Make a 'bare' GIT repository.  That is, instead of
 	creating `<directory>` and placing the administrative
diff --git a/builtin-clone.c b/builtin-clone.c
index a4b8790..5ee8362 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, option_mirror;
 static int option_local, option_no_hardlinks, option_shared;
 static char *option_template, *option_reference, *option_depth;
+static char *option_narrow_path;
 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, "narrow-path", &option_narrow_path, "prefixes",
+		    "limit checkout to specified areas (sparse checkout)"),
 	OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
 	OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
 	OPT_BOOLEAN(0, "mirror", &option_mirror,
@@ -378,10 +381,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		if (option_origin)
 			die("--bare and --origin %s options are incompatible.",
 			    option_origin);
+		if (option_narrow_path)
+			die("--bare and --narrow-path options are incompatible.");
 		option_no_checkout = 1;
 		use_separate_remote = 0;
 	}
 
+	if (option_no_checkout && option_narrow_path)
+		die("--no-checkout and --narrow-path options are incompatible.");
+
 	if (!option_origin)
 		option_origin = "origin";
 
@@ -590,6 +598,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		opts.src_index = &the_index;
 		opts.dst_index = &the_index;
 
+		if (option_narrow_path) {
+			opts.new_narrow_path = 1;
+			opts.narrow_spec = parse_narrow_spec(option_narrow_path, NULL);
+		}
+
 		tree = parse_tree_indirect(remote_head->old_sha1);
 		parse_tree(tree);
 		init_tree_desc(&t, tree->buffer, tree->size);
diff --git a/t/t5703-clone-narrow.sh b/t/t5703-clone-narrow.sh
new file mode 100755
index 0000000..66f9191
--- /dev/null
+++ b/t/t5703-clone-narrow.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='narrow 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 'narrow clone incompatible with --bare' '
+	rm -fr dst &&
+	test_must_fail git clone --narrow-path=work --bare src dst
+'
+
+test_expect_success 'narrow clone incompatible with --no-checkout' '
+	rm -fr dst &&
+	test_must_fail git clone --narrow-path=work -n src dst
+'
+
+test_expect_success 'clone with --narrow-path' '
+	rm -fr dst &&
+	git clone --narrow-path=work src dst &&
+	cd dst &&
+	test -z "$(git ls-files --sparse | grep -v ^work/)"
+'
+
+test_done
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 11/14] unpack_trees(): add support for sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-11-git-send-email-pclouds@gmail.com>

This patch teaches unpack_trees() to checkout/remove entries
on working directories appropriately when sparse checkout area is
changed. There are three kind of changes:

 - new_narrow_path: reset workdir to a completely new checkout area
 - add_narrow_path: keep current areas and add more entries
 - remove_narrow_path: remove some entries from current areas

CE_WD_REMOVE is introduced to remove entries from working directories,
but still keep them in index

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h        |    3 ++
 unpack-trees.c |   74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 unpack-trees.h |    4 +++
 3 files changed, 80 insertions(+), 1 deletions(-)

diff --git a/cache.h b/cache.h
index 6e875d5..f4025b5 100644
--- a/cache.h
+++ b/cache.h
@@ -167,6 +167,9 @@ struct cache_entry {
 #define CE_HASHED    (0x100000)
 #define CE_UNHASHED  (0x200000)
 
+/* Only remove in work directory, not index */
+#define CE_WD_REMOVE (0x400000)
+
 /*
  * Extended on-disk flags
  */
diff --git a/unpack-trees.c b/unpack-trees.c
index ce4c826..10f377c 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -96,7 +96,7 @@ 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 (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
+			if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WD_REMOVE))
 				total++;
 		}
 
@@ -108,6 +108,13 @@ 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 (ce->ce_flags & CE_WD_REMOVE) {
+			display_progress(progress, ++cnt);
+			if (o->update)
+				unlink_entry(ce);
+			continue;
+		}
+
 		if (ce->ce_flags & CE_REMOVE) {
 			display_progress(progress, ++cnt);
 			if (o->update)
@@ -133,6 +140,66 @@ static int check_updates(struct unpack_trees_options *o)
 	return errs != 0;
 }
 
+static int verify_uptodate(struct cache_entry *ce, struct unpack_trees_options *o);
+static int apply_narrow_spec(struct unpack_trees_options *o)
+{
+	struct index_state *index = &o->result;
+	int i;
+
+	if (!(o->new_narrow_path | o->add_narrow_path | o->remove_narrow_path))
+		return 0;
+
+	for (i = 0; i < index->cache_nr; i++) {
+		struct cache_entry *ce = index->cache[i];
+		int was_checkout = ce_checkout(ce);
+		int match = match_narrow_spec(o->narrow_spec, ce->name);
+
+		if (ce_stage(ce))
+			continue;
+
+		if (o->new_narrow_path) {
+			if (match)
+				ce_mark_checkout(ce);
+			else
+				ce_mark_no_checkout(ce);
+		}
+
+		if (o->add_narrow_path && match)
+			ce_mark_checkout(ce);
+
+		if (o->remove_narrow_path && match)
+			ce_mark_no_checkout(ce);
+
+		/* Update worktree, add/remove entries if needed */
+
+		/*
+		 * We only care about files getting into the checkout area
+		 * If merge strategies want to remove some, go ahead
+		 */
+		if (ce->ce_flags & CE_REMOVE)
+			continue;
+
+		if (was_checkout && ce_no_checkout(ce)) {
+			/*
+			 * If CE_UPDATE is set, verify_uptodate() must be called already
+			 * also stat info may have lost after merged_entry() so calling
+			 * verify_uptodate() again may fail
+			 */
+			if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate(ce, o))
+				return -1;
+			ce->ce_flags |= CE_WD_REMOVE;
+		}
+		if (!was_checkout && ce_checkout(ce))
+			ce->ce_flags |= CE_UPDATE;
+
+		/* merge strategies may set CE_UPDATE outside checkout area */
+		if (ce_no_checkout(ce))
+			ce->ce_flags &= ~CE_UPDATE;
+
+	}
+	return 0;
+}
+
 static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
 {
 	int ret = o->fn(src, o);
@@ -409,6 +476,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 (apply_narrow_spec(o))
+		return unpack_failed(o, NULL);
+
 	o->src_index = NULL;
 	ret = check_updates(o) ? (-2) : 0;
 	if (o->dst_index)
@@ -677,6 +747,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
 				return -1;
 			invalidate_ce_path(old, o);
 		}
+		if (ce_no_checkout(old))
+			update |= CE_NO_CHECKOUT;
 	}
 	else {
 		if (verify_absent(merge, "overwritten", o))
diff --git a/unpack-trees.h b/unpack-trees.h
index 6b1971f..0d899b6 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -43,6 +43,9 @@ struct unpack_trees_options {
 		     aggressive:1,
 		     skip_unmerged:1,
 		     initial_checkout:1,
+		     new_narrow_path:1,
+		     add_narrow_path:2,
+		     remove_narrow_path:2,
 		     gently:1;
 	const char *prefix;
 	int pos;
@@ -54,6 +57,7 @@ struct unpack_trees_options {
 	int merge_size;
 
 	struct cache_entry *df_conflict_entry;
+	struct narrow_spec *narrow_spec;
 	void *unpack_data;
 
 	struct index_state *dst_index;
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 10/14] ls-files: support "sparse patterns", used to form sparse checkout areas
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-10-git-send-email-pclouds@gmail.com>

This implements sparse patterns and adds --narrow-match option in order
to test the patterns.

Sparse patterns are basically like .gitignore patterns, but they can be
combined in one line, separating by colons like $PATH.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt   |   45 +++++++++++++
 Documentation/git-ls-files.txt   |    8 ++-
 builtin-ls-files.c               |   21 +++++-
 t/t3003-ls-files-narrow-match.sh |   39 +++++++++++
 t/t3003/1                        |    3 +
 t/t3003/12                       |    6 ++
 t/t3003/clone-escape             |    4 +
 t/t3003/cur-12                   |    2 +
 t/t3003/root-sub-1               |    1 +
 t/t3003/slash-1                  |    1 +
 t/t3003/sub-1                    |    2 +
 t/t3003/sub-only                 |    3 +
 t/t3003/subsub-slash             |    3 +
 unpack-trees.c                   |  136 ++++++++++++++++++++++++++++++++++++++
 unpack-trees.h                   |   18 +++++
 15 files changed, 288 insertions(+), 4 deletions(-)
 create mode 100755 t/t3003-ls-files-narrow-match.sh
 create mode 100644 t/t3003/1
 create mode 100644 t/t3003/12
 create mode 100644 t/t3003/clone-escape
 create mode 100644 t/t3003/cur-12
 create mode 100644 t/t3003/root-sub-1
 create mode 100644 t/t3003/slash-1
 create mode 100644 t/t3003/sub
 create mode 100644 t/t3003/sub-1
 create mode 100644 t/t3003/sub-only
 create mode 100644 t/t3003/subsub-slash

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 2b344e1..d6f94a6 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -205,6 +205,51 @@ is "assume unchanged" bit just ignores corresponding files in working
 directory while sparse checkout goes a bit farther, remove those files
 when it is safe to do so.
 
+Sparse patterns
+---------------
+
+Sparse patterns specify how do you want to form your checkout area.
+Many patterns can be specified on one line, separated by colons.
+The patterns specify what files should or should not be checked out
+on working directory (depends on the option used with the patterns).
+Patterns have the following format:
+
+ - An optional prefix '!' which negates the pattern; any
+   matching file by a previous pattern will become
+   unmatched again.  If a negated pattern matches, this will
+   override lower precedence patterns sources.
+
+ - If the pattern ends with a slash, it is removed for the
+   purpose of the following description, but it would only find
+   a match with a directory.  In other words, `foo/` will match a
+   directory `foo` and paths underneath it, but will not match a
+   regular file or a symbolic link `foo` (this is consistent
+   with the way how pathspec works in general in git).
+
+ - If the pattern does not contain a slash '/', git treats it as
+   a shell glob pattern and checks for a match against the
+   pathname without leading directories.
+
+ - Otherwise, git treats the pattern as a shell glob suitable
+   for consumption by fnmatch(3) with the FNM_PATHNAME flag:
+   wildcards in the pattern will not match a / in the pathname.
+   For example, "Documentation/\*.html" matches
+   "Documentation/git.html" but not
+   "Documentation/ppc/ppc.html".  A leading slash matches the
+   beginning of the pathname; for example, "/*.c" matches
+   "cat-file.c" but not "mozilla-sha1/sha1.c".
+
+ - Patterns begin with a slash will match against full pathname,
+   as opposed to normal case when it only matches pathnames relative
+   to current working directory.
+
+ - Patterns begin with "./" are treated like normal patterns. That is
+   it will follow above rules. But since it has a slash inside,
+   "fnmatch rule" will apply. This is a work-around when you do not
+   want to apply "no slash" rule.
+
+ - Because colons are used to separate patterns, you cannot put them
+   in patterns directly. You must quote them using backslash.
 
 EXAMPLES
 --------
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 1de68e2..fbed73b 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -12,7 +12,7 @@ SYNOPSIS
 'git ls-files' [-z] [-t] [-v]
 		(--[cached|deleted|others|ignored|stage|unmerged|killed|modified|orphaned|no-checkout])\*
 		(-[c|d|o|i|s|u|k|m])\*
-		[--sparse]
+		[--sparse] [--narrow-match=<sparse patterns>]
 		[-x <pattern>|--exclude=<pattern>]
 		[-X <file>|--exclude-from=<file>]
 		[--exclude-per-directory=<file>]
@@ -90,6 +90,12 @@ OPTIONS
 	No-checkout entries can be shown using --orphaned or
 	--no-checkout (or both).
 
+--narrow-match=<sparse patterns>::
+	This option can be used to test sparse patterns. The given sparse patterns will
+	be used to filter ls-files output. Entries not matching the spec will be
+	ignored. This option can only be used with --cached or --stage.
+	See linkgit:git-checkout[1] for more information about sparse patterns.
+
 -z::
 	\0 line termination on output.
 
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 873de15..1c81022 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -10,6 +10,8 @@
 #include "dir.h"
 #include "builtin.h"
 #include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
 
 static int abbrev;
 static int show_deleted;
@@ -31,6 +33,7 @@ static const char **pathspec;
 static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
+static struct narrow_spec *narrow_spec;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -187,7 +190,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
 	int len = prefix_len;
 	int offset = prefix_offset;
 
-	if (len >= ce_namelen(ce))
+	if (len >= ce_namelen(ce) && !narrow_spec)
 		die("git ls-files: internal error - cache entry not superset of prefix");
 
 	if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
@@ -260,6 +263,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
 			}
 			if (!(show_cached | show_stage))
 				continue;
+			if (narrow_spec && !match_narrow_spec(narrow_spec, ce->name))
+				continue;
 			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
 		}
 	}
@@ -439,7 +444,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
 
 static const char ls_files_usage[] =
 	"git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified|orphaned|no-checkout])* "
-	"[ --sparse ] "
+	"[ --sparse ] [--narrow-match=<narrow_spec>] "
 	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
 	"[ --exclude-per-directory=<filename> ] [--exclude-standard] "
 	"[--full-name] [--abbrev] [--] [<file>]*";
@@ -498,6 +503,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 			sparse_checkout = 1;
 			continue;
 		}
+		if (!prefixcmp(arg, "--narrow-match=")) {
+			narrow_spec = parse_narrow_spec(arg+15, prefix);
+			continue;
+		}
 		if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
 			show_deleted = 1;
 			continue;
@@ -629,8 +638,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 	      show_killed | show_modified | show_orphaned | show_no_checkout))
 		show_cached = 1;
 
+	if (narrow_spec && !show_cached && !show_stage)
+		die("ls-files: --narrow-match can only be used with either --cached or --stage");
+
+	if (narrow_spec && narrow_spec->has_root && prefix_offset != 0)
+		die("ls-files: --narrow-match with root matching patterns requires --full-name");
+
 	read_cache();
-	if (prefix)
+	if (prefix && (!narrow_spec || !narrow_spec->has_root))
 		prune_cache(prefix);
 	if (with_tree) {
 		/*
diff --git a/t/t3003-ls-files-narrow-match.sh b/t/t3003-ls-files-narrow-match.sh
new file mode 100755
index 0000000..5611cab
--- /dev/null
+++ b/t/t3003-ls-files-narrow-match.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='This test is for narrow spec matching'
+
+. test-lib.sh
+
+D="$(cd ..;pwd)"/t3003
+
+test_pattern() {
+	test_expect_success "pattern $1" '
+		(
+		if [ -n "'$3'" ]; then cd '$3'; fi
+		git ls-files --full-name --narrow-match="'"$2"'" > result &&
+		diff -u result "'"$D/$1"'"
+		)
+	'
+}
+
+test_expect_success 'setup' '
+	touch 1 2 3 "1:2" &&
+	mkdir -p sub/subsub &&
+	touch sub/1 sub/2 sub/3 &&
+	touch sub/subsub/1 sub/subsub/2 sub/subsub/3 &&
+	git add .
+'
+
+test_pattern 1 1
+test_pattern sub sub
+test_pattern sub-1 1 sub
+test_pattern root-sub-1 /1 sub
+test_pattern subsub-slash subsub/ sub
+test_pattern sub-only 'sub/:!sub/subsub/'
+test_pattern 12 1:2
+test_pattern cur-12 ./1:./2
+test_pattern slash-1 'sub/*1'
+test_pattern clone-escape '1\:2:1'
+
+test_done
+
diff --git a/t/t3003/1 b/t/t3003/1
new file mode 100644
index 0000000..9b73321
--- /dev/null
+++ b/t/t3003/1
@@ -0,0 +1,3 @@
+1
+sub/1
+sub/subsub/1
diff --git a/t/t3003/12 b/t/t3003/12
new file mode 100644
index 0000000..5d71811
--- /dev/null
+++ b/t/t3003/12
@@ -0,0 +1,6 @@
+1
+2
+sub/1
+sub/2
+sub/subsub/1
+sub/subsub/2
diff --git a/t/t3003/clone-escape b/t/t3003/clone-escape
new file mode 100644
index 0000000..11cdf68
--- /dev/null
+++ b/t/t3003/clone-escape
@@ -0,0 +1,4 @@
+1
+1:2
+sub/1
+sub/subsub/1
diff --git a/t/t3003/cur-12 b/t/t3003/cur-12
new file mode 100644
index 0000000..1191247
--- /dev/null
+++ b/t/t3003/cur-12
@@ -0,0 +1,2 @@
+1
+2
diff --git a/t/t3003/root-sub-1 b/t/t3003/root-sub-1
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/t/t3003/root-sub-1
@@ -0,0 +1 @@
+1
diff --git a/t/t3003/slash-1 b/t/t3003/slash-1
new file mode 100644
index 0000000..5798e42
--- /dev/null
+++ b/t/t3003/slash-1
@@ -0,0 +1 @@
+sub/1
diff --git a/t/t3003/sub b/t/t3003/sub
new file mode 100644
index 0000000..e69de29
diff --git a/t/t3003/sub-1 b/t/t3003/sub-1
new file mode 100644
index 0000000..3ef951a
--- /dev/null
+++ b/t/t3003/sub-1
@@ -0,0 +1,2 @@
+sub/1
+sub/subsub/1
diff --git a/t/t3003/sub-only b/t/t3003/sub-only
new file mode 100644
index 0000000..3115212
--- /dev/null
+++ b/t/t3003/sub-only
@@ -0,0 +1,3 @@
+sub/1
+sub/2
+sub/3
diff --git a/t/t3003/subsub-slash b/t/t3003/subsub-slash
new file mode 100644
index 0000000..bc585b0
--- /dev/null
+++ b/t/t3003/subsub-slash
@@ -0,0 +1,3 @@
+sub/subsub/1
+sub/subsub/2
+sub/subsub/3
diff --git a/unpack-trees.c b/unpack-trees.c
index e59d144..ce4c826 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -726,6 +726,142 @@ static void show_stage_entry(FILE *o,
 }
 #endif
 
+struct narrow_spec *parse_narrow_spec(const char *spec, const char *prefix)
+{
+	struct narrow_spec *ns;
+	struct narrow_pattern *p;
+	const char *start = spec, *end;
+	int has_wildcards, has_slashes;
+
+	ns = xmalloc(sizeof(*ns));
+	memset(ns, 0, sizeof(*ns));
+	if (prefix)
+		ns->prefix = xstrdup(prefix);
+
+	while (*start) {
+		end = start;
+		has_slashes = has_wildcards = 0;
+		while (*end && *end != ':') {
+			if (*end == '*' || *end == '[' || *end == '?')
+				has_wildcards = 1;
+			if (*end == '/')
+				has_slashes = 1;
+			if (*end == '\\') {
+				end++;
+				has_wildcards = 1;
+				if (*end == '\0') /* trailing backslash */
+					break;
+			}
+			end++;
+		}
+		if (start == end)
+			continue;
+
+		p = xmalloc(offsetof(struct narrow_pattern, pattern)+(end-start)+1);
+		p->negative = *start == '!';
+		if (p->negative)
+			start++;
+		p->has_slashes = has_slashes;
+		p->has_wildcards = has_wildcards;
+		p->has_trailing_slash = end[-1] == '/';
+		p->has_root = *start == '/';
+		if (p->has_root)
+			start++;
+		else if (*start == '.' && start[1] == '/')
+			start += 2;
+		p->len = end-start;
+		memcpy(p->pattern, start, p->len);
+		p->pattern[p->len] = '\0';
+
+		ALLOC_GROW(ns->patterns, ns->nr + 1, ns->alloc);
+		ns->patterns[ns->nr++] = p;
+		ns->has_root |= p->has_root;
+
+		if (*end != ':')
+			break;
+		start = end + 1;
+	}
+	return ns;
+}
+
+int match_narrow_spec(struct narrow_spec *spec, const char *path)
+{
+	int i, prefix_len = 0;
+
+	if (!spec || !spec->nr)
+		return 1; /* always match if spec is NULL */
+
+	if (spec->prefix) {
+		/*
+		 * optimization:
+		 * if there is no pattern with leading slash
+		 * then it is safe to only match inside prefix
+		 */
+		if (!spec->has_root && prefixcmp(path, spec->prefix))
+			return 0;
+		prefix_len = strlen(spec->prefix);
+	}
+
+	for (i = spec->nr - 1;i >= 0; i--) {
+		struct narrow_pattern *p = spec->patterns[i];
+		const char *new_path = path + prefix_len;
+		int match;
+
+		if (p->has_root)
+			new_path = path; /* match full path */
+		else if (spec->has_root) {
+			if (prefixcmp(path, spec->prefix))
+				continue;
+		}
+		/* !spec->has_root case has been handled above */
+
+		if (p->has_trailing_slash) {
+			/* the only "wildcard" here is backslash escape */
+			if (p->has_wildcards) {
+				char *unescaped_pattern = xstrdup(p->pattern);
+				char *src, *dst;
+
+				src = dst = unescaped_pattern;
+				while (*src) {
+					if (*src == '\\')
+						src++;
+					if (src != dst)
+						*dst = *src;
+					src++;
+					dst++;
+				}
+				*dst = '\0';
+				match = prefixcmp(new_path, unescaped_pattern) == 0;
+				free(unescaped_pattern);
+			}
+			else
+				match = prefixcmp(new_path, p->pattern) == 0;
+		}
+		else if (p->has_slashes) {
+			if (p->has_wildcards)
+				match = fnmatch(p->pattern, new_path, FNM_PATHNAME) == 0;
+			else
+				match = strcmp(p->pattern, new_path) == 0;
+		}
+		else {
+			const char *basename = strrchr(path + prefix_len, '/');
+			if (basename)
+				basename++;
+			else
+				basename = path + prefix_len;
+			if (p->has_wildcards)
+				match = fnmatch(p->pattern, basename, 0) == 0;
+			else
+				match = strcmp(p->pattern, basename) == 0;
+		}
+		if (match)
+			return p->negative ? 0 : 1;
+	}
+
+	/* no pattern is matched */
+	return 0;
+}
+
 int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
 {
 	struct cache_entry *index;
diff --git a/unpack-trees.h b/unpack-trees.h
index 0d26f3d..6b1971f 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -16,6 +16,22 @@ struct unpack_trees_error_msgs {
 	const char *bind_overlap;
 };
 
+struct narrow_spec {
+	int nr;
+	int alloc;
+	int has_root:1;
+	const char *prefix;
+	struct narrow_pattern {
+		int len;
+		int has_root:1;
+		int has_slashes:1;
+		int has_wildcards:1;
+		int has_trailing_slash:1;
+		int negative:1;
+		char pattern[FLEX_ARRAY];
+	} **patterns;
+};
+
 struct unpack_trees_options {
 	unsigned int reset:1,
 		     merge:1,
@@ -48,6 +64,8 @@ struct unpack_trees_options {
 extern int unpack_trees(unsigned n, struct tree_desc *t,
 		struct unpack_trees_options *options);
 
+struct narrow_spec *parse_narrow_spec(const char *spec, const char *prefix);
+int match_narrow_spec(struct narrow_spec *spec, const char *path);
 int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o);
 int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o);
 int bind_merge(struct cache_entry **src, struct unpack_trees_options *o);
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 08/14] checkout_entry(): CE_NO_CHECKOUT on checked out entries.
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-8-git-send-email-pclouds@gmail.com>

With this you can just do "git checkout some-files" to
widen your checkout. One caveat though: caller must save
the index.

For all of its callers (unpack_trees(), checkout-index, checkout
and apply), only "git apply" does not write index back.

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

diff --git a/entry.c b/entry.c
index aa2ee46..305f8d3 100644
--- a/entry.c
+++ b/entry.c
@@ -230,5 +230,6 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
 	} else if (state->not_new)
 		return 0;
 	create_directories(path, state);
+	ce_mark_checkout(ce);
 	return write_entry(ce, path, state, 0);
 }
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 09/14] grep: skip files outside sparse checkout area
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-9-git-send-email-pclouds@gmail.com>


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-grep.txt |    4 +++-
 builtin-grep.c             |    7 ++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index fa4d133..ee359c9 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -24,7 +24,9 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Look for specified patterns in the working tree files, blobs
-registered in the index file, or given tree objects.
+registered in the index file, or given tree objects. By default
+it will search in the working tree files. When in sparse checkout
+mode, it only searches checked-out files.
 
 
 OPTIONS
diff --git a/builtin-grep.c b/builtin-grep.c
index 3a51662..d5507d7 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -343,6 +343,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 			continue;
 		if (!pathspec_matches(paths, ce->name))
 			continue;
+		if (ce_no_checkout(ce))
+			continue;
 		name = ce->name;
 		if (name[0] == '-') {
 			int len = ce_namelen(ce);
@@ -404,8 +406,11 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 				continue;
 			hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
 		}
-		else
+		else {
+			if (ce_no_checkout(ce))
+				continue;
 			hit |= grep_file(opt, ce->name);
+		}
 		if (ce_stage(ce)) {
 			do {
 				nr++;
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 07/14] Prevent diff machinery from examining worktree outside sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-7-git-send-email-pclouds@gmail.com>


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 diff-lib.c |    5 +++--
 diff.c     |    4 +++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/diff-lib.c b/diff-lib.c
index ae96c64..992280b 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -161,7 +161,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 				continue;
 		}
 
-		if (ce_uptodate(ce))
+		if (ce_uptodate(ce) || ce_no_checkout(ce))
 			continue;
 
 		changed = check_removed(ce, &st);
@@ -348,6 +348,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
 	struct rev_info *revs = cbdata->revs;
 	int match_missing, cached;
 
+	/* if the entry is not checked out, don't examine work tree */
+	cached = o->index_only || (idx && ce_no_checkout(idx));
 	/*
 	 * Backward compatibility wart - "diff-index -m" does
 	 * not mean "do not ignore merges", but "match_missing".
@@ -355,7 +357,6 @@ 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;
 	match_missing = !revs->ignore_merges;
 
 	if (cached && idx && ce_stage(idx)) {
diff --git a/diff.c b/diff.c
index a2dd931..b5b7249 100644
--- a/diff.c
+++ b/diff.c
@@ -1793,8 +1793,10 @@ 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.
+	 * For sparse checkout case, ce_uptodate() may be true although
+	 * the file may or may not exist in the work tree.
 	 */
-	if (ce_uptodate(ce) ||
+	if ((ce_uptodate(ce) && ce_checkout(ce)) ||
 	    (!lstat(name, &st) && !ce_match_stat(ce, &st, 0)))
 		return 1;
 
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 06/14] ls-files: Add tests for --sparse and friends
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-6-git-send-email-pclouds@gmail.com>


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 t/t3004-ls-files-sparse.sh            |   40 +++++++++++++++++++++++++++++++++
 t/t3004/cached.expected               |    5 ++++
 t/t3004/deleted.expected              |    1 +
 t/t3004/everything.expected           |   10 ++++++++
 t/t3004/modified.expected             |    2 +
 t/t3004/no-checkout.expected          |    2 +
 t/t3004/orphaned-no-checkout.expected |    3 ++
 t/t3004/orphaned.expected             |    1 +
 t/t3004/others.expected               |    2 +
 t/t3004/sparse-cached.expected        |    3 ++
 t/t3004/sparse-everything.expected    |   11 +++++++++
 11 files changed, 80 insertions(+), 0 deletions(-)
 create mode 100755 t/t3004-ls-files-sparse.sh
 create mode 100644 t/t3004/cached.expected
 create mode 100644 t/t3004/deleted.expected
 create mode 100644 t/t3004/everything.expected
 create mode 100644 t/t3004/modified.expected
 create mode 100644 t/t3004/no-checkout.expected
 create mode 100644 t/t3004/orphaned-no-checkout.expected
 create mode 100644 t/t3004/orphaned.expected
 create mode 100644 t/t3004/others.expected
 create mode 100644 t/t3004/sparse-cached.expected
 create mode 100644 t/t3004/sparse-everything.expected

diff --git a/t/t3004-ls-files-sparse.sh b/t/t3004-ls-files-sparse.sh
new file mode 100755
index 0000000..ec2c869
--- /dev/null
+++ b/t/t3004-ls-files-sparse.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description="test ls-files in --sparse mode"
+
+. test-lib.sh
+
+test_ls_files() {
+	T=../t3004/$1.expected
+	shift
+	test_expect_success "ls-files $*" "git ls-files $* > result && test_cmp $T result"
+}
+
+test_expect_success 'setup' '
+	touch other orphaned no-checkout cached modified deleted &&
+	git add orphaned no-checkout cached modified deleted &&
+	git update-index --no-checkout orphaned no-checkout &&
+	echo modified >> modified &&
+	rm no-checkout deleted
+'
+
+test_ls_files cached
+test_ls_files cached --cached
+test_ls_files sparse-cached --sparse
+test_ls_files sparse-cached --sparse --cached
+test_ls_files no-checkout --no-checkout
+test_ls_files no-checkout --sparse --no-checkout
+test_ls_files orphaned --orphaned
+test_ls_files orphaned --sparse --orphaned
+test_ls_files orphaned-no-checkout -v --no-checkout --orphaned
+test_ls_files orphaned-no-checkout -v --sparse --no-checkout --orphaned
+test_ls_files deleted --deleted
+test_ls_files deleted --sparse --deleted
+test_ls_files modified --modified
+test_ls_files modified --sparse --modified
+test_ls_files others --others
+test_ls_files others --sparse --others
+test_ls_files everything -v --cached --deleted --modified --others
+test_ls_files sparse-everything -v --cached --no-checkout --orphaned --deleted --modified --others
+
+test_done
\ No newline at end of file
diff --git a/t/t3004/cached.expected b/t/t3004/cached.expected
new file mode 100644
index 0000000..6fd0c78
--- /dev/null
+++ b/t/t3004/cached.expected
@@ -0,0 +1,5 @@
+cached
+deleted
+modified
+no-checkout
+orphaned
diff --git a/t/t3004/deleted.expected b/t/t3004/deleted.expected
new file mode 100644
index 0000000..71779d2
--- /dev/null
+++ b/t/t3004/deleted.expected
@@ -0,0 +1 @@
+deleted
diff --git a/t/t3004/everything.expected b/t/t3004/everything.expected
new file mode 100644
index 0000000..6000328
--- /dev/null
+++ b/t/t3004/everything.expected
@@ -0,0 +1,10 @@
+? other
+? result
+H cached
+H deleted
+H modified
+H no-checkout
+H orphaned
+R deleted
+C deleted
+C modified
diff --git a/t/t3004/modified.expected b/t/t3004/modified.expected
new file mode 100644
index 0000000..644a96e
--- /dev/null
+++ b/t/t3004/modified.expected
@@ -0,0 +1,2 @@
+deleted
+modified
diff --git a/t/t3004/no-checkout.expected b/t/t3004/no-checkout.expected
new file mode 100644
index 0000000..b2a429f
--- /dev/null
+++ b/t/t3004/no-checkout.expected
@@ -0,0 +1,2 @@
+no-checkout
+orphaned
diff --git a/t/t3004/orphaned-no-checkout.expected b/t/t3004/orphaned-no-checkout.expected
new file mode 100644
index 0000000..d687ef0
--- /dev/null
+++ b/t/t3004/orphaned-no-checkout.expected
@@ -0,0 +1,3 @@
+- no-checkout
+- orphaned
+O orphaned
diff --git a/t/t3004/orphaned.expected b/t/t3004/orphaned.expected
new file mode 100644
index 0000000..571b267
--- /dev/null
+++ b/t/t3004/orphaned.expected
@@ -0,0 +1 @@
+orphaned
diff --git a/t/t3004/others.expected b/t/t3004/others.expected
new file mode 100644
index 0000000..bf5bf2b
--- /dev/null
+++ b/t/t3004/others.expected
@@ -0,0 +1,2 @@
+other
+result
diff --git a/t/t3004/sparse-cached.expected b/t/t3004/sparse-cached.expected
new file mode 100644
index 0000000..3453483
--- /dev/null
+++ b/t/t3004/sparse-cached.expected
@@ -0,0 +1,3 @@
+cached
+deleted
+modified
diff --git a/t/t3004/sparse-everything.expected b/t/t3004/sparse-everything.expected
new file mode 100644
index 0000000..5df0599
--- /dev/null
+++ b/t/t3004/sparse-everything.expected
@@ -0,0 +1,11 @@
+? other
+? result
+H cached
+H deleted
+H modified
+- no-checkout
+- orphaned
+O orphaned
+R deleted
+C deleted
+C modified
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 05/14] update-index: add --checkout/--no-checkout to update CE_NO_CHECKOUT bit
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-5-git-send-email-pclouds@gmail.com>


Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 .gitignore                          |    1 +
 Documentation/git-checkout.txt      |    3 +-
 Documentation/git-update-index.txt  |   13 ++++++++++++
 Makefile                            |    2 +-
 builtin-update-index.c              |   16 ++++++++++++++-
 t/t2104-update-index-no-checkout.sh |   36 +++++++++++++++++++++++++++++++++++
 test-index-version.c                |   14 +++++++++++++
 7 files changed, 82 insertions(+), 3 deletions(-)
 create mode 100755 t/t2104-update-index-no-checkout.sh
 create mode 100644 test-index-version.c

diff --git a/.gitignore b/.gitignore
index bbaf9de..0c35577 100644
--- a/.gitignore
+++ b/.gitignore
@@ -147,6 +147,7 @@ test-date
 test-delta
 test-dump-cache-tree
 test-genrandom
+test-index-version
 test-match-trees
 test-parse-options
 test-path-utils
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 4bd9eba..2b344e1 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -196,7 +196,8 @@ to index, it will be marked "checkout" unless sparse patterns are
 applied.  Unmerged files are always "checkout". When you checkout new
 files using "git checkout <file>" they will be automatically marked
 "checkout". Other commands such as "git apply" can also checkout new
-files if they are needed.
+files if they are needed. linkgit:git-update-index[1] can be used to
+update "checkout/no-checkout" status in index.
 
 "No-checkout" status is very similar to "assume-unchanged bit"
 (see linkgit:git-update-index[1]). The main difference between them
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 1d9d81a..ec03e05 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -15,6 +15,7 @@ SYNOPSIS
 	     [--cacheinfo <mode> <object> <file>]\*
 	     [--chmod=(+|-)x]
 	     [--assume-unchanged | --no-assume-unchanged]
+	     [--checkout | --no-checkout]
 	     [--ignore-submodules]
 	     [--really-refresh] [--unresolve] [--again | -g]
 	     [--info-only] [--index-info]
@@ -99,6 +100,18 @@ in the index e.g. when merging in a commit;
 thus, in case the assumed-untracked file is changed upstream,
 you will need to handle the situation manually.
 
+--checkout::
+--no-checkout::
+	When one of these flags is specified, the object name recorded
+	for the paths are not updated. Instead, these options
+	set and unset the "no-checkout" bit for the paths. This
+	bit is used for marking files for sparse checkout. If
+	a path is marked "no-checkout", then it should not be
+	checked out unless requested by user or needed for a git
+	command to function.
+	See linkgit:git-checkout[1] for more information about
+	sparse checkout.
+
 -g::
 --again::
 	Runs 'git-update-index' itself on the paths whose index
diff --git a/Makefile b/Makefile
index e0c03c3..edb33cb 100644
--- a/Makefile
+++ b/Makefile
@@ -1327,7 +1327,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X test-index-version$X
 
 all:: $(TEST_PROGRAMS)
 
diff --git a/builtin-update-index.c b/builtin-update-index.c
index ae94739..7514aff 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -24,6 +24,7 @@ static int info_only;
 static int force_remove;
 static int verbose;
 static int mark_valid_only;
+static int mark_no_checkout_only;
 #define MARK_FLAG 1
 #define UNMARK_FLAG 2
 
@@ -276,6 +277,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
 			die("Unable to mark file %s", path);
 		goto free_return;
 	}
+	if (mark_no_checkout_only) {
+		if (mark_ce_flags(p, CE_NO_CHECKOUT, mark_no_checkout_only == MARK_FLAG))
+			die("Unable to mark file %s", path);
+		goto free_return;
+	}
 
 	if (force_remove) {
 		if (remove_file_from_cache(p))
@@ -386,7 +392,7 @@ static void read_index_info(int line_termination)
 }
 
 static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--checkout|--no-checkout] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
 
 static unsigned char head_sha1[20];
 static unsigned char merge_head_sha1[20];
@@ -652,6 +658,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				mark_valid_only = UNMARK_FLAG;
 				continue;
 			}
+			if (!strcmp(path, "--checkout")) {
+				mark_no_checkout_only = UNMARK_FLAG;
+				continue;
+			}
+			if (!strcmp(path, "--no-checkout")) {
+				mark_no_checkout_only = MARK_FLAG;
+				continue;
+			}
 			if (!strcmp(path, "--info-only")) {
 				info_only = 1;
 				continue;
diff --git a/t/t2104-update-index-no-checkout.sh b/t/t2104-update-index-no-checkout.sh
new file mode 100755
index 0000000..be9f913
--- /dev/null
+++ b/t/t2104-update-index-no-checkout.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='git update-index no-checkout bits (a.k.a sparse checkout)'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	mkdir sub &&
+	touch 1 2 sub/1 sub/2 &&
+	git add 1 2 sub/1 sub/2
+'
+
+test_expect_success 'index is at version 2' '
+	test "$(test-index-version < .git/index)" = 2
+'
+
+test_expect_success 'update-index --no-checkout' '
+	git update-index --no-checkout 1 sub/1 &&
+	test -z "$(git ls-files --sparse|grep 1)"'
+
+test_expect_success 'index is at version 3 after having some no-checkout entries' '
+	test "$(test-index-version < .git/index)" = 3
+'
+
+test_expect_success 'update-index --checkout' '
+	git update-index --checkout 1 sub/1 &&
+	test "$(git ls-files)" = "$(git ls-files --sparse)"'
+
+test_expect_success 'index version is back to 2 when there is no no-checkout entry' '
+	test "$(test-index-version < .git/index)" = 2
+'
+
+test_done
diff --git a/test-index-version.c b/test-index-version.c
new file mode 100644
index 0000000..bfaad9e
--- /dev/null
+++ b/test-index-version.c
@@ -0,0 +1,14 @@
+#include "cache.h"
+
+int main(int argc, const char **argv)
+{
+	struct cache_header hdr;
+	int version;
+
+	memset(&hdr,0,sizeof(hdr));
+	if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
+		return 0;
+	version = ntohl(hdr.hdr_version);
+	printf("%d\n", version);
+	return 0;
+}
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 04/14] update-index: refactor mark_valid() in preparation for new options
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-4-git-send-email-pclouds@gmail.com>


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

diff --git a/builtin-update-index.c b/builtin-update-index.c
index 417f972..ae94739 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -24,8 +24,8 @@ static int info_only;
 static int force_remove;
 static int verbose;
 static int mark_valid_only;
-#define MARK_VALID 1
-#define UNMARK_VALID 2
+#define MARK_FLAG 1
+#define UNMARK_FLAG 2
 
 static void report(const char *fmt, ...)
 {
@@ -40,19 +40,15 @@ static void report(const char *fmt, ...)
 	va_end(vp);
 }
 
-static int mark_valid(const char *path)
+static int mark_ce_flags(const char *path, int flag, int mark)
 {
 	int namelen = strlen(path);
 	int pos = cache_name_pos(path, namelen);
 	if (0 <= pos) {
-		switch (mark_valid_only) {
-		case MARK_VALID:
-			active_cache[pos]->ce_flags |= CE_VALID;
-			break;
-		case UNMARK_VALID:
-			active_cache[pos]->ce_flags &= ~CE_VALID;
-			break;
-		}
+		if (mark)
+			active_cache[pos]->ce_flags |= flag;
+		else
+			active_cache[pos]->ce_flags &= ~flag;
 		cache_tree_invalidate_path(active_cache_tree, path);
 		active_cache_changed = 1;
 		return 0;
@@ -276,7 +272,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
 		goto free_return;
 	}
 	if (mark_valid_only) {
-		if (mark_valid(p))
+		if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
 			die("Unable to mark file %s", path);
 		goto free_return;
 	}
@@ -649,11 +645,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 				continue;
 			}
 			if (!strcmp(path, "--assume-unchanged")) {
-				mark_valid_only = MARK_VALID;
+				mark_valid_only = MARK_FLAG;
 				continue;
 			}
 			if (!strcmp(path, "--no-assume-unchanged")) {
-				mark_valid_only = UNMARK_VALID;
+				mark_valid_only = UNMARK_FLAG;
 				continue;
 			}
 			if (!strcmp(path, "--info-only")) {
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 03/14] ls-files: add options to support sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-3-git-send-email-pclouds@gmail.com>

The first option to be introduced is --sparse, which puts ls-files
in "sparse mode". In this mode, cached entries are divided into
 - checkout entries: shown by --cached (new behavior with --sparse)
 - no-checkout entries: show by --no-checkout (new option)
 - orphaned entries: shown by --orphaned (new option)

Orphaned entries are themselves no-checkout ones but for some reasons
still be present in working directory.

While at it, fix "--deleted" running out of checkout area.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-ls-files.txt |   24 +++++++++++++++++++++-
 builtin-ls-files.c             |   41 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 59 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 9f85d60..1de68e2 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -10,8 +10,9 @@ SYNOPSIS
 --------
 [verse]
 'git ls-files' [-z] [-t] [-v]
-		(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
+		(--[cached|deleted|others|ignored|stage|unmerged|killed|modified|orphaned|no-checkout])\*
 		(-[c|d|o|i|s|u|k|m])\*
+		[--sparse]
 		[-x <pattern>|--exclude=<pattern>]
 		[-X <file>|--exclude-from=<file>]
 		[--exclude-per-directory=<file>]
@@ -32,7 +33,9 @@ OPTIONS
 -------
 -c::
 --cached::
-	Show cached files in the output (default)
+	Show cached files in the output (default). When used with --sparse,
+	show only cached files that are marked "checkout", no-checkout
+	entries will be excluded.
 
 -d::
 --deleted::
@@ -72,6 +75,21 @@ OPTIONS
 	to file/directory conflicts for checkout-index to
 	succeed.
 
+--no-checkout::
+	Show no-checkout entries. This option implies --sparse.
+
+--orphaned::
+	Show orphaned entries. Orphaned entries are no-checkout
+	entries that are present in working directory. This option
+	implies --sparse.
+
+--sparse::
+	When --sparse is passed, cached files will be divided into two
+	parts: checkout entries and no-checkout entries.
+	--cached will only show checkout entries.
+	No-checkout entries can be shown using --orphaned or
+	--no-checkout (or both).
+
 -z::
 	\0 line termination on output.
 
@@ -107,6 +125,8 @@ OPTIONS
 	Identify the file status with the following tags (followed by
 	a space) at the start of each line:
 	H::	cached
+	-::	no-checkout entries
+	O::	orphaned entries
 	M::	unmerged
 	R::	removed/deleted
 	C::	modified/changed
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 068f424..873de15 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -20,6 +20,9 @@ static int show_unmerged;
 static int show_modified;
 static int show_killed;
 static int show_valid_bit;
+static int show_orphaned;
+static int show_no_checkout;
+static int sparse_checkout;
 static int line_terminator = '\n';
 
 static int prefix_len;
@@ -35,6 +38,8 @@ static const char *tag_removed = "";
 static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
+static const char *tag_orphaned = "";
+static const char *tag_no_checkout = "";
 
 
 /*
@@ -235,7 +240,7 @@ static void show_files(struct dir_struct *dir, const char *prefix)
 		if (show_killed)
 			show_killed_files(dir);
 	}
-	if (show_cached | show_stage) {
+	if (show_cached | show_stage | show_orphaned | show_no_checkout) {
 		for (i = 0; i < active_nr; i++) {
 			struct cache_entry *ce = active_cache[i];
 			int dtype = ce_to_dtype(ce);
@@ -245,6 +250,16 @@ static void show_files(struct dir_struct *dir, const char *prefix)
 				continue;
 			if (ce->ce_flags & CE_UPDATE)
 				continue;
+			if (sparse_checkout && ce_no_checkout(ce)) {
+				struct stat st;
+				if (show_no_checkout)
+					show_ce_entry(tag_no_checkout, ce);
+				if (show_orphaned && !lstat(ce->name, &st))
+					show_ce_entry(tag_orphaned, ce);
+				continue;
+			}
+			if (!(show_cached | show_stage))
+				continue;
 			show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
 		}
 	}
@@ -257,7 +272,7 @@ static void show_files(struct dir_struct *dir, const char *prefix)
 			if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
 				continue;
 			err = lstat(ce->name, &st);
-			if (show_deleted && err)
+			if (show_deleted && err && ce_checkout(ce))
 				show_ce_entry(tag_removed, ce);
 			if (show_modified && ce_modified(ce, &st, 0))
 				show_ce_entry(tag_modified, ce);
@@ -423,7 +438,8 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
 }
 
 static const char ls_files_usage[] =
-	"git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+	"git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified|orphaned|no-checkout])* "
+	"[ --sparse ] "
 	"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
 	"[ --exclude-per-directory=<filename> ] [--exclude-standard] "
 	"[--full-name] [--abbrev] [--] [<file>]*";
@@ -457,6 +473,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 			tag_modified = "C ";
 			tag_other = "? ";
 			tag_killed = "K ";
+			tag_orphaned = "O ";
+			tag_no_checkout = "- ";
 			if (arg[1] == 'v')
 				show_valid_bit = 1;
 			continue;
@@ -465,6 +483,21 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 			show_cached = 1;
 			continue;
 		}
+		if (!strcmp(arg, "--sparse")) {
+			sparse_checkout = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--orphaned")) {
+			show_orphaned = 1;
+			sparse_checkout = 1;
+			require_work_tree = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--no-checkout")) {
+			show_no_checkout = 1;
+			sparse_checkout = 1;
+			continue;
+		}
 		if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
 			show_deleted = 1;
 			continue;
@@ -593,7 +626,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 
 	/* With no flags, we default to showing the cached files */
 	if (!(show_stage | show_deleted | show_others | show_unmerged |
-	      show_killed | show_modified))
+	      show_killed | show_modified | show_orphaned | show_no_checkout))
 		show_cached = 1;
 
 	read_cache();
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 02/14] Introduce CE_NO_CHECKOUT bit
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-2-git-send-email-pclouds@gmail.com>

This bit is the basis of sparse checkout. If this bit is on, the entry
is outside sparse checkout and therefore should be ignored (similar
to CE_VALID)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt |   33 +++++++++++++++++++++++++++++++++
 cache.h                        |   10 +++++++++-
 read-cache.c                   |    6 +++---
 3 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 82e154d..4bd9eba 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -171,6 +171,39 @@ the reflog for HEAD where you were, e.g.
 $ git log -g -2 HEAD
 ------------
 
+Sparse checkout
+---------------
+
+Normally when you checkout a branch, your working directory
+will be fully populated. In some situations, you just need to
+work on certain files, no full checkout is needed. Sparse
+checkout is a mode that limits the checkout area according to your
+needs. With sparse checkout, you can work on a single file, a
+collection of files, a subdirectory or a collection of separated
+subdirectories.
+
+Because sparse checkout uses a new index format, it will be
+incompatible with git prior to 1.6.0 regarding worktree operations.
+Operations that only need access to the repository itself, such as
+clone, push, or pull/fetch from another (normal) repository... should
+not be affected by sparse checkout.
+
+In sparse checkout mode, checkout status of every files in your
+working directory will be recorded in index. If a file is marked
+"no-checkout", it means that file is not needed to be present in
+working directory by user or any git command. When a new file is added
+to index, it will be marked "checkout" unless sparse patterns are
+applied.  Unmerged files are always "checkout". When you checkout new
+files using "git checkout <file>" they will be automatically marked
+"checkout". Other commands such as "git apply" can also checkout new
+files if they are needed.
+
+"No-checkout" status is very similar to "assume-unchanged bit"
+(see linkgit:git-update-index[1]). The main difference between them
+is "assume unchanged" bit just ignores corresponding files in working
+directory while sparse checkout goes a bit farther, remove those files
+when it is safe to do so.
+
 
 EXAMPLES
 --------
diff --git a/cache.h b/cache.h
index 77b6eb3..6e875d5 100644
--- a/cache.h
+++ b/cache.h
@@ -170,10 +170,11 @@ struct cache_entry {
 /*
  * Extended on-disk flags
  */
+#define CE_NO_CHECKOUT 0x40000000
 /* CE_EXTENDED2 is for future extension */
 #define CE_EXTENDED2 0x80000000
 
-#define CE_EXTENDED_FLAGS (0)
+#define CE_EXTENDED_FLAGS (CE_NO_CHECKOUT)
 
 /*
  * Safeguard to avoid saving wrong flags:
@@ -185,6 +186,9 @@ struct cache_entry {
 #error "CE_EXTENDED_FLAGS out of range"
 #endif
 
+/* "Assume unchanged" mask */
+#define CE_VALID_MASK (CE_VALID | CE_NO_CHECKOUT)
+
 /*
  * Copy the sha1 and stat state of a cache entry from one to
  * another. But we never change the name, or the hash state!
@@ -222,6 +226,10 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
 			    ondisk_cache_entry_size(ce_namelen(ce)))
 #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_no_checkout(ce) ((ce)->ce_flags & CE_NO_CHECKOUT)
+#define ce_checkout(ce) (!ce_no_checkout(ce))
+#define ce_mark_no_checkout(ce) ((ce)->ce_flags |= CE_NO_CHECKOUT)
+#define ce_mark_checkout(ce) ((ce)->ce_flags &= ~CE_NO_CHECKOUT)
 #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 
 #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
diff --git a/read-cache.c b/read-cache.c
index 667c36b..e965a4c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -254,7 +254,7 @@ int ie_match_stat(const struct index_state *istate,
 	 * If it's marked as always valid in the index, it's
 	 * valid whatever the checked-out copy says.
 	 */
-	if (!ignore_valid && (ce->ce_flags & CE_VALID))
+	if (!ignore_valid && (ce->ce_flags & CE_VALID_MASK))
 		return 0;
 
 	changed = ce_match_stat_basic(ce, st);
@@ -962,10 +962,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
 		return ce;
 
 	/*
-	 * CE_VALID means the user promised us that the change to
+	 * CE_VALID_MASK means the user promised us that the change to
 	 * the work tree does not matter and told us not to worry.
 	 */
-	if (!ignore_valid && (ce->ce_flags & CE_VALID)) {
+	if (!ignore_valid && (ce->ce_flags & CE_VALID_MASK)) {
 		ce_mark_uptodate(ce);
 		return ce;
 	}
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH 01/14] Extend index to save more flags
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1221904913-25887-1-git-send-email-pclouds@gmail.com>

The on-disk format of index only saves 16 bit flags, nearly all have
been used. The last bit (CE_EXTENDED) is used to for future extension.

This patch extends index entry format to save more flags in future.
The new entry format will be used when CE_EXTENDED bit is 1.

Because older implementation may not understand CE_EXTENDED bit and
misread the new format, if there is any extended entry in index, index
header version will turn 3, which makes it incompatible for older git.
If there is none, header version will return to 2 again.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h      |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 read-cache.c |   51 +++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 95 insertions(+), 14 deletions(-)

diff --git a/cache.h b/cache.h
index f4b8ddf..77b6eb3 100644
--- a/cache.h
+++ b/cache.h
@@ -109,6 +109,26 @@ struct ondisk_cache_entry {
 	char name[FLEX_ARRAY]; /* more */
 };
 
+/*
+ * This struct is used when CE_EXTENDED bit is 1
+ * The struct must match ondisk_cache_entry exactly from
+ * ctime till flags
+ */
+struct ondisk_cache_entry_extended {
+	struct cache_time ctime;
+	struct cache_time mtime;
+	unsigned int dev;
+	unsigned int ino;
+	unsigned int mode;
+	unsigned int uid;
+	unsigned int gid;
+	unsigned int size;
+	unsigned char sha1[20];
+	unsigned short flags;
+	unsigned short flags2;
+	char name[FLEX_ARRAY]; /* more */
+};
+
 struct cache_entry {
 	unsigned int ce_ctime;
 	unsigned int ce_mtime;
@@ -130,7 +150,15 @@ struct cache_entry {
 #define CE_VALID     (0x8000)
 #define CE_STAGESHIFT 12
 
-/* In-memory only */
+/*
+ * Range 0xFFFF0000 in ce_flags is divided into
+ * two parts: in-memory flags and on-disk ones.
+ * Flags in CE_EXTENDED_FLAGS will get saved on-disk
+ * if you want to save a new flag, add it in
+ * CE_EXTENDED_FLAGS
+ *
+ * In-memory only flags
+ */
 #define CE_UPDATE    (0x10000)
 #define CE_REMOVE    (0x20000)
 #define CE_UPTODATE  (0x40000)
@@ -140,6 +168,24 @@ struct cache_entry {
 #define CE_UNHASHED  (0x200000)
 
 /*
+ * Extended on-disk flags
+ */
+/* CE_EXTENDED2 is for future extension */
+#define CE_EXTENDED2 0x80000000
+
+#define CE_EXTENDED_FLAGS (0)
+
+/*
+ * Safeguard to avoid saving wrong flags:
+ *  - CE_EXTENDED2 won't get saved until its semantic is known
+ *  - Bits in 0x0000FFFF have been saved in ce_flags already
+ *  - Bits in 0x003F0000 are currently in-memory flags
+ */
+#if CE_EXTENDED_FLAGS & 0x80CFFFFF
+#error "CE_EXTENDED_FLAGS out of range"
+#endif
+
+/*
  * Copy the sha1 and stat state of a cache entry from one to
  * another. But we never change the name, or the hash state!
  */
@@ -171,7 +217,9 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
 }
 
 #define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
+#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
+			    ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
+			    ondisk_cache_entry_size(ce_namelen(ce)))
 #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
 #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
@@ -214,8 +262,10 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
 	(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
 	S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
 
-#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
-#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
+#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
+#define cache_entry_size(len) flexible_size(cache_entry,len)
+#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
+#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
 
 struct index_state {
 	struct cache_entry **cache;
diff --git a/read-cache.c b/read-cache.c
index c5a8659..667c36b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1096,7 +1096,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
 
 	if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
 		return error("bad signature");
-	if (hdr->hdr_version != htonl(2))
+	if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
 		return error("bad index version");
 	SHA1_Init(&c);
 	SHA1_Update(&c, hdr, size - 20);
@@ -1131,6 +1131,7 @@ int read_index(struct index_state *istate)
 static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
 {
 	size_t len;
+	const char *name;
 
 	ce->ce_ctime = ntohl(ondisk->ctime.sec);
 	ce->ce_mtime = ntohl(ondisk->mtime.sec);
@@ -1143,19 +1144,31 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
 	/* On-disk flags are just 16 bits */
 	ce->ce_flags = ntohs(ondisk->flags);
 
-	/* For future extension: we do not understand this entry yet */
-	if (ce->ce_flags & CE_EXTENDED)
-		die("Unknown index entry format");
 	hashcpy(ce->sha1, ondisk->sha1);
 
 	len = ce->ce_flags & CE_NAMEMASK;
+
+	if (ce->ce_flags & CE_EXTENDED) {
+		struct ondisk_cache_entry_extended *ondisk2;
+		int extended_flags;
+		ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
+		extended_flags = ntohs(ondisk2->flags2) << 16;
+		/* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
+		if (extended_flags & ~CE_EXTENDED_FLAGS)
+			die("Unknown index entry format %08x", extended_flags);
+		ce->ce_flags |= extended_flags;
+		name = ondisk2->name;
+	}
+	else
+		name = ondisk->name;
+
 	if (len == CE_NAMEMASK)
-		len = strlen(ondisk->name);
+		len = strlen(name);
 	/*
 	 * NEEDSWORK: If the original index is crafted, this copy could
 	 * go unchecked.
 	 */
-	memcpy(ce->name, ondisk->name, len + 1);
+	memcpy(ce->name, name, len + 1);
 }
 
 static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
@@ -1415,6 +1428,7 @@ static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce)
 {
 	int size = ondisk_ce_size(ce);
 	struct ondisk_cache_entry *ondisk = xcalloc(1, size);
+	char *name;
 
 	ondisk->ctime.sec = htonl(ce->ce_ctime);
 	ondisk->ctime.nsec = 0;
@@ -1428,7 +1442,15 @@ static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce)
 	ondisk->size = htonl(ce->ce_size);
 	hashcpy(ondisk->sha1, ce->sha1);
 	ondisk->flags = htons(ce->ce_flags);
-	memcpy(ondisk->name, ce->name, ce_namelen(ce));
+	if (ce->ce_flags & CE_EXTENDED) {
+		struct ondisk_cache_entry_extended *ondisk2;
+		ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
+		ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
+		name = ondisk2->name;
+	}
+	else
+		name = ondisk->name;
+	memcpy(name, ce->name, ce_namelen(ce));
 
 	return ce_write(c, fd, ondisk, size);
 }
@@ -1437,16 +1459,25 @@ int write_index(const struct index_state *istate, int newfd)
 {
 	SHA_CTX c;
 	struct cache_header hdr;
-	int i, err, removed;
+	int i, err, removed, extended;
 	struct cache_entry **cache = istate->cache;
 	int entries = istate->cache_nr;
 
-	for (i = removed = 0; i < entries; i++)
+	for (i = removed = extended = 0; i < entries; i++) {
 		if (cache[i]->ce_flags & CE_REMOVE)
 			removed++;
 
+		/* reduce extended entries if possible */
+		cache[i]->ce_flags &= ~CE_EXTENDED;
+		if (cache[i]->ce_flags & CE_EXTENDED_FLAGS) {
+			extended++;
+			cache[i]->ce_flags |= CE_EXTENDED;
+		}
+	}
+
 	hdr.hdr_signature = htonl(CACHE_SIGNATURE);
-	hdr.hdr_version = htonl(2);
+	/* for extended format, increase version so older git won't try to read it */
+	hdr.hdr_version = htonl(extended ? 3 : 2);
 	hdr.hdr_entries = htonl(entries - removed);
 
 	SHA1_Init(&c);
-- 
1.6.0.96.g2fad1.dirty

^ permalink raw reply related

* [PATCH v2 00/14] Sparse checkout
From: Nguyễn Thái Ngọc Duy @ 2008-09-20 10:01 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Main changes from the last round are:
 - The name is now "sparse checkout"
 - "git clone --path" => "git clone --narrow-path"
 - "git checkout --path" => "git checkout --reset-path"
 - New narrow spec (or "sparse patterns" from now) resembles
   .gitignore patterns
 - "git ls-files" now supports more fine-grained listing. It can now
   list checkout files, no-checkout files or orphaned (previously
   "overlay") files. --overlay is gone
 - "git status" shows orphaned entries and remedies
 - Documentation has been restructured to accompany code changes.
   Thanks to Jakub, Baz for lots of input.

For code changes, significant changes are:
  [03/14] ls-files: add options to support sparse checkout
  [10/14] ls-files: support "sparse patterns", used to form sparse checkout areas

I hope I have addressed all the issues. If I miss anything, please speak up.

Nguyễn Thái Ngọc Duy (14):
  Extend index to save more flags
  Introduce CE_NO_CHECKOUT bit
  ls-files: add options to support sparse checkout
  update-index: refactor mark_valid() in preparation for new options
  update-index: add --checkout/--no-checkout to update CE_NO_CHECKOUT bit
  ls-files: Add tests for --sparse and friends
  Prevent diff machinery from examining worktree outside sparse checkout
  checkout_entry(): CE_NO_CHECKOUT on checked out entries.
  grep: skip files outside sparse checkout area
  ls-files: support "sparse patterns", used to form sparse checkout areas
  unpack_trees(): add support for sparse checkout
  clone: support sparse checkout with --narrow-path option
  checkout: add new options to support sparse checkout
  wt-status: Show orphaned entries in "git status" output

 .gitignore                            |    1 +
 Documentation/git-checkout.txt        |  131 ++++++++++++++++++++-
 Documentation/git-clone.txt           |   10 ++-
 Documentation/git-grep.txt            |    4 +-
 Documentation/git-ls-files.txt        |   30 +++++-
 Documentation/git-update-index.txt    |   13 ++
 Makefile                              |    2 +-
 builtin-checkout.c                    |   37 ++++++
 builtin-clone.c                       |   13 ++
 builtin-grep.c                        |    7 +-
 builtin-ls-files.c                    |   60 +++++++++-
 builtin-update-index.c                |   40 ++++---
 cache.h                               |   69 ++++++++++-
 diff-lib.c                            |    5 +-
 diff.c                                |    4 +-
 entry.c                               |    1 +
 read-cache.c                          |   57 +++++++--
 t/t2011-checkout-sparse.sh            |  108 +++++++++++++++++
 t/t2104-update-index-no-checkout.sh   |   36 ++++++
 t/t3003-ls-files-narrow-match.sh      |   39 ++++++
 t/t3003/1                             |    3 +
 t/t3003/12                            |    6 +
 t/t3003/clone-escape                  |    4 +
 t/t3003/cur-12                        |    2 +
 t/t3003/root-sub-1                    |    1 +
 t/t3003/slash-1                       |    1 +
 t/t3003/sub-1                         |    2 +
 t/t3003/sub-only                      |    3 +
 t/t3003/subsub-slash                  |    3 +
 t/t3004-ls-files-sparse.sh            |   40 ++++++
 t/t3004/cached.expected               |    5 +
 t/t3004/deleted.expected              |    1 +
 t/t3004/everything.expected           |   10 ++
 t/t3004/modified.expected             |    2 +
 t/t3004/no-checkout.expected          |    2 +
 t/t3004/orphaned-no-checkout.expected |    3 +
 t/t3004/orphaned.expected             |    1 +
 t/t3004/others.expected               |    2 +
 t/t3004/sparse-cached.expected        |    3 +
 t/t3004/sparse-everything.expected    |   11 ++
 t/t5703-clone-narrow.sh               |   39 ++++++
 test-index-version.c                  |   14 ++
 unpack-trees.c                        |  210 ++++++++++++++++++++++++++++++++-
 unpack-trees.h                        |   22 ++++
 wt-status.c                           |   39 ++++++
 wt-status.h                           |    1 +
 46 files changed, 1047 insertions(+), 50 deletions(-)
 create mode 100755 t/t2011-checkout-sparse.sh
 create mode 100755 t/t2104-update-index-no-checkout.sh
 create mode 100755 t/t3003-ls-files-narrow-match.sh
 create mode 100644 t/t3003/1
 create mode 100644 t/t3003/12
 create mode 100644 t/t3003/clone-escape
 create mode 100644 t/t3003/cur-12
 create mode 100644 t/t3003/root-sub-1
 create mode 100644 t/t3003/slash-1
 create mode 100644 t/t3003/sub
 create mode 100644 t/t3003/sub-1
 create mode 100644 t/t3003/sub-only
 create mode 100644 t/t3003/subsub-slash
 create mode 100755 t/t3004-ls-files-sparse.sh
 create mode 100644 t/t3004/cached.expected
 create mode 100644 t/t3004/deleted.expected
 create mode 100644 t/t3004/everything.expected
 create mode 100644 t/t3004/modified.expected
 create mode 100644 t/t3004/no-checkout.expected
 create mode 100644 t/t3004/orphaned-no-checkout.expected
 create mode 100644 t/t3004/orphaned.expected
 create mode 100644 t/t3004/others.expected
 create mode 100644 t/t3004/sparse-cached.expected
 create mode 100644 t/t3004/sparse-everything.expected
 create mode 100755 t/t5703-clone-narrow.sh
 create mode 100644 test-index-version.c

^ permalink raw reply

* [PATCH (GIT-GUI)] git-gui: Reenable staging unmerged files by clicking the icon.
From: Alexander Gavrilov @ 2008-09-20  8:19 UTC (permalink / raw)
  To: git; +Cc: Shawn O. Pearce, Johannes Sixt

This restores functionality of the file icon for unmerged files.
Safety is enforced by loading the diff and checking for lines
that look like conflict markers. If such lines are found, or
the conflict involves deletion and/or symlinks, a confirmation
dialog is presented. Otherwise, the icon immediately stages the
working copy version of the file.

Includes a revert of 2fe5b2ee42897a3acc78e5ddaace3775eb2713ca
(Restore ability to Stage Working Copy for conflicts)

Signed-off-by: Alexander Gavrilov <angavrilov@gmail.com>
---
 git-gui.sh        |   18 ++++++++----------
 lib/diff.tcl      |   52 +++++++++++++++++++++++++++++++++++-----------------
 lib/mergetool.tcl |   33 +++++++++++++++++++++++++++------
 3 files changed, 70 insertions(+), 33 deletions(-)

diff --git a/git-gui.sh b/git-gui.sh
index 3bbb4f1..9e5b122 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1059,6 +1059,7 @@ set current_branch {}
 set is_detached 0
 set current_diff_path {}
 set is_3way_diff 0
+set is_conflict_diff 0
 set selected_commit_type new
 
 set nullid "0000000000000000000000000000000000000000"
@@ -2013,19 +2014,21 @@ proc toggle_or_diff {w x y} {
 	$ui_index tag remove in_sel 0.0 end
 	$ui_workdir tag remove in_sel 0.0 end
 
-	# Do not stage files with conflicts
+	# Determine the state of the file
 	if {[info exists file_states($path)]} {
 		set state [lindex $file_states($path) 0]
 	} else {
 		set state {__}
 	}
 
-	if {[string first {U} $state] >= 0} {
-		set col 1
-	}
-
 	# Restage the file, or simply show the diff
 	if {$col == 0 && $y > 1} {
+		# Conflicts need special handling
+		if {[string first {U} $state] >= 0} {
+			merge_stage_workdir $path $w $lno
+			return
+		}
+
 		if {[string index $state 1] eq {O}} {
 			set mmask {}
 		} else {
@@ -2984,11 +2987,6 @@ $ctxmmg add command \
 	-command {merge_resolve_one 1}
 lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
 $ctxmmg add separator
-$ctxmmg add command \
-	-label [mc "Stage Working Copy"] \
-	-command {merge_resolve_one 0}
-lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
-$ctxmmg add separator
 create_common_diff_popup $ctxmmg
 
 proc popup_diff_menu {ctxm ctxmmg x y X Y} {
diff --git a/lib/diff.tcl b/lib/diff.tcl
index b616296..abe502d 100644
--- a/lib/diff.tcl
+++ b/lib/diff.tcl
@@ -69,9 +69,9 @@ A rescan will be automatically started to find other files which may have the sa
 	rescan ui_ready 0
 }
 
-proc show_diff {path w {lno {}} {scroll_pos {}}} {
+proc show_diff {path w {lno {}} {scroll_pos {}} {callback {}}} {
 	global file_states file_lists
-	global is_3way_diff diff_active repo_config
+	global is_3way_diff is_conflict_diff diff_active repo_config
 	global ui_diff ui_index ui_workdir
 	global current_diff_path current_diff_side current_diff_header
 	global current_diff_queue
@@ -92,36 +92,42 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
 
 	set s $file_states($path)
 	set m [lindex $s 0]
+	set is_conflict_diff 0
 	set current_diff_path $path
 	set current_diff_side $w
 	set current_diff_queue {}
 	ui_status [mc "Loading diff of %s..." [escape_path $path]]
 
+	set cont_info [list $scroll_pos $callback]
+
 	if {[string first {U} $m] >= 0} {
-		merge_load_stages $path [list show_unmerged_diff $scroll_pos]
+		merge_load_stages $path [list show_unmerged_diff $cont_info]
 	} elseif {$m eq {_O}} {
-		show_other_diff $path $w $m $scroll_pos
+		show_other_diff $path $w $m $cont_info
 	} else {
-		start_show_diff $scroll_pos
+		start_show_diff $cont_info
 	}
 }
 
-proc show_unmerged_diff {scroll_pos} {
+proc show_unmerged_diff {cont_info} {
 	global current_diff_path current_diff_side
-	global merge_stages ui_diff
+	global merge_stages ui_diff is_conflict_diff
 	global current_diff_queue
 
 	if {$merge_stages(2) eq {}} {
+		set is_conflict_diff 1
 		lappend current_diff_queue \
 			[list "LOCAL: deleted\nREMOTE:\n" d======= \
 			    [list ":1:$current_diff_path" ":3:$current_diff_path"]]
 	} elseif {$merge_stages(3) eq {}} {
+		set is_conflict_diff 1
 		lappend current_diff_queue \
 			[list "REMOTE: deleted\nLOCAL:\n" d======= \
 			    [list ":1:$current_diff_path" ":2:$current_diff_path"]]
 	} elseif {[lindex $merge_stages(1) 0] eq {120000}
 		|| [lindex $merge_stages(2) 0] eq {120000}
 		|| [lindex $merge_stages(3) 0] eq {120000}} {
+		set is_conflict_diff 1
 		lappend current_diff_queue \
 			[list "LOCAL:\n" d======= \
 			    [list ":1:$current_diff_path" ":2:$current_diff_path"]]
@@ -129,14 +135,14 @@ proc show_unmerged_diff {scroll_pos} {
 			[list "REMOTE:\n" d======= \
 			    [list ":1:$current_diff_path" ":3:$current_diff_path"]]
 	} else {
-		start_show_diff $scroll_pos
+		start_show_diff $cont_info
 		return
 	}
 
-	advance_diff_queue $scroll_pos
+	advance_diff_queue $cont_info
 }
 
-proc advance_diff_queue {scroll_pos} {
+proc advance_diff_queue {cont_info} {
 	global current_diff_queue ui_diff
 
 	set item [lindex $current_diff_queue 0]
@@ -146,10 +152,10 @@ proc advance_diff_queue {scroll_pos} {
 	$ui_diff insert end [lindex $item 0] [lindex $item 1]
 	$ui_diff conf -state disabled
 
-	start_show_diff $scroll_pos [lindex $item 2]
+	start_show_diff $cont_info [lindex $item 2]
 }
 
-proc show_other_diff {path w m scroll_pos} {
+proc show_other_diff {path w m cont_info} {
 	global file_states file_lists
 	global is_3way_diff diff_active repo_config
 	global ui_diff ui_index ui_workdir
@@ -228,16 +234,21 @@ proc show_other_diff {path w m scroll_pos} {
 		$ui_diff conf -state disabled
 		set diff_active 0
 		unlock_index
+		set scroll_pos [lindex $cont_info 0]
 		if {$scroll_pos ne {}} {
 			update
 			$ui_diff yview moveto $scroll_pos
 		}
 		ui_ready
+		set callback [lindex $cont_info 1]
+		if {$callback ne {}} {
+			eval $callback
+		}
 		return
 	}
 }
 
-proc start_show_diff {scroll_pos {add_opts {}}} {
+proc start_show_diff {cont_info {add_opts {}}} {
 	global file_states file_lists
 	global is_3way_diff diff_active repo_config
 	global ui_diff ui_index ui_workdir
@@ -292,12 +303,12 @@ proc start_show_diff {scroll_pos {add_opts {}}} {
 		-blocking 0 \
 		-encoding [get_path_encoding $path] \
 		-translation lf
-	fileevent $fd readable [list read_diff $fd $scroll_pos]
+	fileevent $fd readable [list read_diff $fd $cont_info]
 }
 
-proc read_diff {fd scroll_pos} {
+proc read_diff {fd cont_info} {
 	global ui_diff diff_active
-	global is_3way_diff current_diff_header
+	global is_3way_diff is_conflict_diff current_diff_header
 	global current_diff_queue
 
 	$ui_diff conf -state normal
@@ -345,6 +356,7 @@ proc read_diff {fd scroll_pos} {
 			{--} {set tags d_--}
 			{++} {
 				if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
+					set is_conflict_diff 1
 					set line [string replace $line 0 1 {  }]
 					set tags d$op
 				} else {
@@ -364,6 +376,7 @@ proc read_diff {fd scroll_pos} {
 			{-} {set tags d_-}
 			{+} {
 				if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
+					set is_conflict_diff 1
 					set line [string replace $line 0 0 { }]
 					set tags d$op
 				} else {
@@ -388,12 +401,13 @@ proc read_diff {fd scroll_pos} {
 		close $fd
 
 		if {$current_diff_queue ne {}} {
-			advance_diff_queue $scroll_pos
+			advance_diff_queue $cont_info
 			return
 		}
 
 		set diff_active 0
 		unlock_index
+		set scroll_pos [lindex $cont_info 0]
 		if {$scroll_pos ne {}} {
 			update
 			$ui_diff yview moveto $scroll_pos
@@ -403,6 +417,10 @@ proc read_diff {fd scroll_pos} {
 		if {[$ui_diff index end] eq {2.0}} {
 			handle_empty_diff
 		}
+		set callback [lindex $cont_info 1]
+		if {$callback ne {}} {
+			eval $callback
+		}
 	}
 }
 
diff --git a/lib/mergetool.tcl b/lib/mergetool.tcl
index dd2315b..9693e47 100644
--- a/lib/mergetool.tcl
+++ b/lib/mergetool.tcl
@@ -5,12 +5,6 @@ proc merge_resolve_one {stage} {
 	global current_diff_path
 
 	switch -- $stage {
-		0 {	# Stage without confirmation, to minimize
-			# disruption of the rerere workflow
-			merge_add_resolution $current_diff_path
-			return
-		}
-
 		1 { set target [mc "the base version"] }
 		2 { set target [mc "this branch"] }
 		3 { set target [mc "the other branch"] }
@@ -29,6 +23,33 @@ This operation can be undone only by restarting the merge." \
 	}
 }
 
+proc merge_stage_workdir {path w lno} {
+	global current_diff_path diff_active
+
+	if {$diff_active} return
+
+	if {$path ne $current_diff_path} {
+		show_diff $path $w $lno {} [list do_merge_stage_workdir $path]
+	} else {
+		do_merge_stage_workdir $path
+	}
+}
+
+proc do_merge_stage_workdir {path} {
+	global current_diff_path is_conflict_diff
+
+	if {$path ne $current_diff_path} return;
+
+	if {$is_conflict_diff} {
+		if {[ask_popup [mc "File %s seems to have unresolved conflicts, still stage?" \
+				[short_path $path]]] ne {yes}} {
+			return
+		}
+	}
+
+	merge_add_resolution $path
+}
+
 proc merge_add_resolution {path} {
 	global current_diff_path ui_workdir
 
-- 
1.6.0.20.g6148bc

^ permalink raw reply related

* Re: [PATCH v2 4/4] diff.c: convert builtin funcname patterns to non-GNU extended regex syntax
From: Junio C Hamano @ 2008-09-20  7:51 UTC (permalink / raw)
  To: Brandon Casey
  Cc: Arjen Laarhoven, Mike Ralphson, Johannes Sixt, Jeff King,
	Boyd Lynn Gerber, Git Mailing List, Avery Pennarun, Johan Herland,
	Andreas Ericsson, Kirill Smelkov, Giuseppe Bilotta,
	Gustaf Hendeby, Jonathan del Strother
In-Reply-To: <7vtzcb47vh.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

> static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
> 	{ "bibtex", "(@[a-zA-Z]{1,}[ \t]*\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
> 	  REG_EXTENDED },
> 	{ "html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", REG_EXTENDED },
> 	{ "java",
> 	  "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
> 	  "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
> 	  REG_EXTENDED },
> 	{ "pascal",
> 	  "^((procedure|function|constructor|destructor|interface|"
> 		"implementation|initialization|finalization)[ \t]*.*)$"
> 	  "|"
> 	  "^(.*=[ \t]*(class|record).*)$",
> 	  REG_EXTENDED },
> 	{ "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
> 	{ "python", "^[ \t]*((class|def)[ \t].*)$", REG_EXTENDED },
> 	{ "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
> 	  REG_EXTENDED },
> 	{ "tex",
> 	  "^(\\\\((sub)*section|chapter|part)\\*{0,1}\{.*)$",
> 	  REG_EXTENDED },
> };

On the "Objective-C hunk header" topic, I mentioned that I wondered how a
construct like:

	"( A | B ) | ( C | D )"

could possibly work, given that xdiff-interface.c::ff_regexp() uses a
regmatch_t array with only two elements to capture (which means it can
only capture $0 and $1).

It turns out that the function is not really prepared to do this
properly.  In order to cope with a pattern without _any_ capturing
parentheses in the regexp, it has a trick to use $0 if $1 is undefined.

In other words, if you write:

	"junk ( A | B ) | garbage ( C | D )"

then lines "junk A", "junk B", "garbage C" and "garbage D" would all match
and be on the hunk header line.  However, "garbage" is not stripped out
(while "junk" does get left out of the capture).

This happens not to be a problem with any of the above built-in patterns.
The risky one is "pascal", but it's $2 captures the entire line anyway, so
it does not make any difference if the code used $0 instead.  E.g.  a line
that matches the second alternative, "foo = class bar", will be captured
as $0 and this is the same as (un)captured $2.

But it would be a good idea to fix this while our attention to the issue
is still fresh.  As we discussed earlier, we can replace the top-level "|"
with "\n" and fix the semantics of multiple positive regexp to grab $1 (or
$0 if $1 is unavaialble) of the first positive one.  If we did so, when
the pattern

	"junk ( A | B ) | garbage ( C | D )"

matches a line "garbage C", the initial garbage part will not be included
inside the capture.

Here is such a proposed fix.

-- >8 --
diff: fix "multiple regexp" semantics to find hunk header comment

When multiple regular expressions are concatenated with "\n", they were
traditionally AND'ed together, and only a line that matches _all_ of them
were taken as a match.  This however is unwieldy when multiple regexp
feature is used to specify alternatives.

This fixes the semantics to take the first match.  A nagative pattern, if
matches, makes the line to fail.  A positive pattern, if matches, will be
the final match and what it captures in $1 is used as the hunk header
comment.

We could write alternatives using "|" in ERE, but the machinery can only
use captured $1 as the hunk header comment (or $0 if there is no match in
$1), so you cannot write:

    "junk ( A | B ) | garbage ( C | D )"

and expect both "junk" and "garbage" to get stripped with the existing
code.  With this fix, you can write it as:

    "junk ( A | B ) \n garbage ( C | D )"

and the way capture works would match the user expectation more
naturally.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c            |    2 +-
 xdiff-interface.c |   17 ++++++++++-------
 2 files changed, 11 insertions(+), 8 deletions(-)

diff --git c/diff.c w/diff.c
index a733010..1bcbbd5 100644
--- c/diff.c
+++ w/diff.c
@@ -1414,7 +1414,7 @@ static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
 	{ "pascal",
 	  "^((procedure|function|constructor|destructor|interface|"
 		"implementation|initialization|finalization)[ \t]*.*)$"
-	  "|"
+	  "\n"
 	  "^(.*=[ \t]*(class|record).*)$",
 	  REG_EXTENDED },
 	{ "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
diff --git c/xdiff-interface.c w/xdiff-interface.c
index 7f1a7d3..6c6bb19 100644
--- c/xdiff-interface.c
+++ w/xdiff-interface.c
@@ -194,26 +194,29 @@ static long ff_regexp(const char *line, long len,
 	char *line_buffer = xstrndup(line, len); /* make NUL terminated */
 	struct ff_regs *regs = priv;
 	regmatch_t pmatch[2];
-	int result = 0, i;
+	int i;
+	int result = -1;
 
 	for (i = 0; i < regs->nr; i++) {
 		struct ff_reg *reg = regs->array + i;
-		if (reg->negate ^ !!regexec(&reg->re,
-					line_buffer, 2, pmatch, 0)) {
-			free(line_buffer);
-			return -1;
+		if (!regexec(&reg->re, line_buffer, 2, pmatch, 0)) {
+			if (reg->negate)
+				goto fail;
+			break;
 		}
 	}
+	if (regs->nr <= i)
+		goto fail;
 	i = pmatch[1].rm_so >= 0 ? 1 : 0;
 	line += pmatch[i].rm_so;
 	result = pmatch[i].rm_eo - pmatch[i].rm_so;
 	if (result > buffer_size)
 		result = buffer_size;
 	else
-		while (result > 0 && (isspace(line[result - 1]) ||
-					line[result - 1] == '\n'))
+		while (result > 0 && (isspace(line[result - 1])))
 			result--;
 	memcpy(buffer, line, result);
+ fail:
 	free(line_buffer);
 	return result;
 }

^ permalink raw reply related

* Re: [PATCH v2 4/4] diff.c: convert builtin funcname patterns to non-GNU extended regex syntax
From: Junio C Hamano @ 2008-09-20  7:02 UTC (permalink / raw)
  To: Brandon Casey
  Cc: Arjen Laarhoven, Mike Ralphson, Johannes Sixt, Jeff King,
	Boyd Lynn Gerber, Git Mailing List, Avery Pennarun, Johan Herland,
	Andreas Ericsson, Kirill Smelkov, Giuseppe Bilotta,
	Gustaf Hendeby, Jonathan del Strother
In-Reply-To: <7v7i97swv3.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

> Neither has [4/4] on it.  I'd like two patches so that:
>
>  * [PATCH 1/2] applies to bc/maint-diff-hunk-header-fix, so that the
>    languages in 1.6.0.2 are fixed for non GNU platforms;
>
>  * After applying [1/2] to bc/maint-diff-hunk-header-fix, I'll merge the
>    branch to bc/master-diff-hunk-header-fix and then...
>
>  * [PATCH 2/2] applies on top of it to convert new languages that are
>    supported only on 'master' to use xfuncname.

If you apply the previous one on top of 45d9414 (diff.*.xfuncname which
uses "extended" regex's for hunk header selection, 2008-09-18), and then
merge the result to dde4af4 (Merge branch 'bc/maint-diff-hunk-header-fix'
into bc/master-diff-hunk-header-fix, 2008-09-18), you will get some
trivial conflicts.  This patch is [2/2] that applies on top of that merge,
and should result in this:

static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
	{ "bibtex", "(@[a-zA-Z]{1,}[ \t]*\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
	  REG_EXTENDED },
	{ "html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", REG_EXTENDED },
	{ "java",
	  "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
	  "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
	  REG_EXTENDED },
	{ "pascal",
	  "^((procedure|function|constructor|destructor|interface|"
		"implementation|initialization|finalization)[ \t]*.*)$"
	  "|"
	  "^(.*=[ \t]*(class|record).*)$",
	  REG_EXTENDED },
	{ "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
	{ "python", "^[ \t]*((class|def)[ \t].*)$", REG_EXTENDED },
	{ "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
	  REG_EXTENDED },
	{ "tex",
	  "^(\\\\((sub)*section|chapter|part)\\*{0,1}\{.*)$",
	  REG_EXTENDED },
};

-- >8 --
Subject: [PATCH] diff: use extended regexp to find hunk headers

Using ERE elements such as "|" (alternation) by backquoting in BRE
is a GNU extension and should not be done in portable programs.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/diff.c b/diff.c
index 5b9b074..a733010 100644
--- a/diff.c
+++ b/diff.c
@@ -1406,7 +1406,7 @@ static const struct funcname_pattern_entry *funcname_pattern(const char *ident)
 static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
 	{ "bibtex", "(@[a-zA-Z]{1,}[ \t]*\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
 	  REG_EXTENDED },
-	{ "html", "^\\s*\\(<[Hh][1-6]\\s.*>.*\\)$", 0 },
+	{ "html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", REG_EXTENDED },
 	{ "java",
 	  "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
 	  "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
@@ -1417,8 +1417,8 @@ static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
 	  "|"
 	  "^(.*=[ \t]*(class|record).*)$",
 	  REG_EXTENDED },
-	{ "php", "^[\t ]*\\(\\(function\\|class\\).*\\)", 0 },
-	{ "python", "^\\s*\\(\\(class\\|def\\)\\s.*\\)$", 0 },
+	{ "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
+	{ "python", "^[ \t]*((class|def)[ \t].*)$", REG_EXTENDED },
 	{ "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
 	  REG_EXTENDED },
 	{ "tex",
-- 
1.6.0.2.414.g0bf11

^ permalink raw reply related

* Re: [PATCH v2 4/4] diff.c: convert builtin funcname patterns to non-GNU extended regex syntax
From: Junio C Hamano @ 2008-09-20  6:58 UTC (permalink / raw)
  To: Brandon Casey
  Cc: Arjen Laarhoven, Mike Ralphson, Johannes Sixt, Jeff King,
	Boyd Lynn Gerber, Git Mailing List, Avery Pennarun, Johan Herland,
	Andreas Ericsson, Kirill Smelkov, Giuseppe Bilotta,
	Gustaf Hendeby, Jonathan del Strother
In-Reply-To: <7v7i97swv3.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

> Neither has [4/4] on it.  I'd like two patches so that:
>
>  * [PATCH 1/2] applies to bc/maint-diff-hunk-header-fix, so that the
>    languages in 1.6.0.2 are fixed for non GNU platforms;
>
>  * After applying [1/2] to bc/maint-diff-hunk-header-fix, I'll merge the
>    branch to bc/master-diff-hunk-header-fix and then...
>
>  * [PATCH 2/2] applies on top of it to convert new languages that are
>    supported only on 'master' to use xfuncname.

Here is [1/2] to be applied on top of 45d9414 (diff.*.xfuncname which uses
"extended" regex's for hunk header selection, 2008-09-18).

Testing appreciated.

-- >8 --
Subject: [PATCH] diff: use extended regexp to find hunk headers

Using ERE elements such as "|" (alternation) by backquoting in BRE
is a GNU extension and should not be done in portable programs.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c |   31 +++++++++++++++++--------------
 1 files changed, 17 insertions(+), 14 deletions(-)

diff --git a/diff.c b/diff.c
index dabb4b4..175a044 100644
--- a/diff.c
+++ b/diff.c
@@ -1399,20 +1399,23 @@ static const struct funcname_pattern_entry *funcname_pattern(const char *ident)
 }
 
 static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
-	{ "java", "!^[ 	]*\\(catch\\|do\\|for\\|if\\|instanceof\\|"
-			"new\\|return\\|switch\\|throw\\|while\\)\n"
-			"^[ 	]*\\(\\([ 	]*"
-			"[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
-			"[ 	]*([^;]*\\)$", 0 },
-	{ "pascal", "^\\(\\(procedure\\|function\\|constructor\\|"
-			"destructor\\|interface\\|implementation\\|"
-			"initialization\\|finalization\\)[ \t]*.*\\)$"
-			"\\|"
-			"^\\(.*=[ \t]*\\(class\\|record\\).*\\)$",
-			0 },
-	{ "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$", 0 },
-	{ "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$", 0 },
-	{ "ruby", "^\\s*\\(\\(class\\|module\\|def\\)\\s.*\\)$", 0 },
+	{ "java",
+	  "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
+	  "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
+	  REG_EXTENDED },
+	{ "pascal",
+	  "^((procedure|function|constructor|destructor|interface|"
+		"implementation|initialization|finalization)[ \t]*.*)$"
+	  "|"
+	  "^(.*=[ \t]*(class|record).*)$",
+	  REG_EXTENDED },
+	{ "bibtex", "(@[a-zA-Z]{1,}[ \t]*\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+	  REG_EXTENDED },
+	{ "tex",
+	  "^(\\\\((sub)*section|chapter|part)\\*{0,1}\{.*)$",
+	  REG_EXTENDED },
+	{ "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+	  REG_EXTENDED },
 };
 
 static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_filespec *one)

^ permalink raw reply related

* Re: [PATCH] completion: git commit should list --interactive
From: Junio C Hamano @ 2008-09-20  2:44 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: Eric Raible, Git Mailing List
In-Reply-To: <20080920011654.GI13139@spearce.org>

"Shawn O. Pearce" <spearce@spearce.org> writes:

> Eric Raible <raible@gmail.com> wrote:
>> Signed-off-by: Eric Raible <raible@gmail.com>
>> ---
>>  contrib/completion/git-completion.bash |    2 +-
>>  1 files changed, 1 insertions(+), 1 deletions(-)
>
> Trivally-Acked-by: Shawn O. Pearce <spearce@spearce.org>
>
> Sorry, got backlogged at day-job.  I'm kind of surprised this isn't
> in-tree yet, its pretty simple...

Heh, I am kind of surprised this hasn't been Acked yet.  IOW, you do not
have monopoly on getting backlogged at dayjob.

Will apply, thanks both.

^ permalink raw reply

* Re: [PATCH] Use dashless git commands in setgitperms.perl
From: Junio C Hamano @ 2008-09-20  1:27 UTC (permalink / raw)
  To: Todd Zullinger; +Cc: Jakub Narebski, git
In-Reply-To: <20080920004814.GM2939@inocybe.teonanacatl.org>

Todd Zullinger <tmz@pobox.com> writes:

> Jakub Narebski wrote:
>> Todd Zullinger wrote:
>> 
>>>  # To save permissions/ownership data, place this script in your .git/hooks
>>>  # directory and enable a `pre-commit` hook with the following lines:
>>>  #      #!/bin/sh
>>> -#     SUBDIRECTORY_OK=1 . git-sh-setup
>>> +#     SUBDIRECTORY_OK=1 . git sh-setup
>>>  #     $GIT_DIR/hooks/setgitperms.perl -r
>>>  #
>>>  # To restore permissions/ownership data, place this script in your .git/hooks
>>>  # directory and enable a `post-merge` and `post-checkout` hook with the
>>>  # following lines:
>>>  #      #!/bin/sh
>>> -#     SUBDIRECTORY_OK=1 . git-sh-setup
>>> +#     SUBDIRECTORY_OK=1 . git sh-setup
>>>  #     $GIT_DIR/hooks/setgitperms.perl -w
>>>  #
>> 
>> The rest is probably O.K., but I don't think this change is good one.
>
> I don't see git-sh-setup in /usr/bin with a default install of git
> 1.6, so without this change, wouldn't the comments be suggesting a
> potentially broken hook configuration?

The patch is wrong on one point, and should be unnecessary.

First of all, you do not understand what "."  does in shell ;-)  That is
the "wrong" part.

About the "unnecessary" part, a hook should run under an environment where
exec-path is already added to the $PATH, so it should be able to find
git-sh-setup just fine without your change.

But I think the politically correct way would be:

	#!/bin/sh
	PATH=$(git --exec-path):$PATH
        SUBDIRECTORY_OK=1 . git-sh-setup
	$GIT_DIR/hooks/setgitperms.perl -r

especially if we envision that somebody may run the script by itself, not
from the hook.

The change to the perl script should not be strictly necessary (because
this is expected to be run from a hook), but to set a better example, I
think it is a good idea to do these s/git-/git / substitutions.

^ permalink raw reply

* Re: [PATCH] completion: git commit should list --interactive
From: Shawn O. Pearce @ 2008-09-20  1:16 UTC (permalink / raw)
  To: Eric Raible, Junio C Hamano; +Cc: Git Mailing List
In-Reply-To: <279b37b20809101740v4a8f19b8k395208c124af0de0@mail.gmail.com>

Eric Raible <raible@gmail.com> wrote:
> Signed-off-by: Eric Raible <raible@gmail.com>
> ---
>  contrib/completion/git-completion.bash |    2 +-
>  1 files changed, 1 insertions(+), 1 deletions(-)

Trivally-Acked-by: Shawn O. Pearce <spearce@spearce.org>

Sorry, got backlogged at day-job.  I'm kind of surprised this isn't
in-tree yet, its pretty simple...

> diff --git a/contrib/completion/git-completion.bash
> b/contrib/completion/git-completion.bash
> index d3fb6ae..2d8d1c3 100755
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -750,7 +750,7 @@ _git_commit ()
>  	--*)
>  		__gitcomp "
>  			--all --author= --signoff --verify --no-verify
> -			--edit --amend --include --only
> +			--edit --amend --include --only --interactive
>  			"
>  		return
>  	esac
> -- 
> 1.6.0.1.436.g09e16c.dirty

-- 
Shawn.

^ permalink raw reply

* Re: [PATCH] Use dashless git commands in setgitperms.perl
From: Todd Zullinger @ 2008-09-20  0:48 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <gb1ej9$bg2$1@ger.gmane.org>

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

Jakub Narebski wrote:
> Todd Zullinger wrote:
> 
>>  # To save permissions/ownership data, place this script in your .git/hooks
>>  # directory and enable a `pre-commit` hook with the following lines:
>>  #      #!/bin/sh
>> -#     SUBDIRECTORY_OK=1 . git-sh-setup
>> +#     SUBDIRECTORY_OK=1 . git sh-setup
>>  #     $GIT_DIR/hooks/setgitperms.perl -r
>>  #
>>  # To restore permissions/ownership data, place this script in your .git/hooks
>>  # directory and enable a `post-merge` and `post-checkout` hook with the
>>  # following lines:
>>  #      #!/bin/sh
>> -#     SUBDIRECTORY_OK=1 . git-sh-setup
>> +#     SUBDIRECTORY_OK=1 . git sh-setup
>>  #     $GIT_DIR/hooks/setgitperms.perl -w
>>  #
> 
> The rest is probably O.K., but I don't think this change is good one.

I don't see git-sh-setup in /usr/bin with a default install of git
1.6, so without this change, wouldn't the comments be suggesting a
potentially broken hook configuration?

-- 
Todd        OpenPGP -> KeyID: 0xBEAF0CE3 | URL: www.pobox.com/~tmz/pgp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Suppose I were a member of Congress, and suppose I were an idiot. But,
I repeat myself.
    -- Mark Twain


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

^ 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