Welcome to the fourth round of sparse checkout this year, dubbed "The
mummy^W^W^W^W^Wno-checkout returns", although the bit now comes under
a new name "skip-worktree" [1]. This series has two parts: sparse
worktree and sparse checkout. Details will be given below.
For brave users of this series: I have pushed it to my sparse-checkout
branch [2] so you can just clone it and test away. Visible changes:
- the spec is now .git/info/sparse-checkout
- the spec is positive matching, that is it matches what _is_ in
worktree, not what is out of worktree like the last series
- you need to set core.sparsecheckout no additional command argument
is needed.
Except from running tests, I have only used it a little bit, so be
careful, it may burn your trees.
Nguyá»
n Thái Ngá»c Duy (8):
update-index: refactor mark_valid() in preparation for new options
Add test-index-version
Introduce "skip-worktree" bit in index, teach Git to get/set this bit
Teach Git to respect skip-worktree bit (reading part)
Teach Git to respect skip-worktree bit (writing part)
Avoid writing to buffer in add_excludes_from_file_1()
Read .gitignore from index if it is skip-worktree
unpack-trees(): carry skip-worktree bit over in merged_entry()
The three main patches in this part are 3, 4 and 5, which define how
"skip-worktree" should work (and I'd love to have feedback whether I
get it right) and implement it.
.gitignore patches are from last series and do not change much.
Nguyá»
n Thái Ngá»c Duy (11):
excluded_1(): support exclude files in index
dir.c: export excluded_1() and add_excludes_from_file_1()
Introduce "sparse checkout"
unpack-trees(): add CE_WT_REMOVE to remove on worktree alone
unpack-trees.c: generalize verify_* functions
unpack-trees(): "enable" sparse checkout and load
$GIT_DIR/info/sparse-checkout
unpack_trees(): apply $GIT_DIR/info/sparse-checkout to the final
index
unpack-trees(): ignore worktree check outside checkout area
read-tree: add --no-sparse-checkout to disable sparse checkout
support
Add tests for sparse checkout
sparse checkout: inhibit empty worktree
The recent assume-unchanged "breakage" that lets Git overwrite
assume-unchanged files worried me. I sat back, checked and wrote tests
to catch similar situations. As a result, core sparse checkout code
becomes more complicated and is splitted into smaller parts for easier
read.
Interesting patches are 11/19 (OK not that interesting, it's just
boring text), 14/19-16/19. Patch 18/19 also covers "foo" vs "foo/"
matching bug in tests.
[1] There are a few reasons behind this rename:
- there is nothing about "checkout" in the first part, it's about
"sparse worktree"
- the double negation issue with "no-" part of "no-checkout"
- new name makes cache.h diff align beautifully
[2] http://repo.or.cz/w/git/pclouds.git?a=shortlog;h=refs/heads/sparse-checkout
.gitignore | 1 +
Documentation/config.txt | 4 +
Documentation/git-ls-files.txt | 1 +
Documentation/git-read-tree.txt | 52 ++++++-
Documentation/git-update-index.txt | 29 ++++
Documentation/technical/api-directory-listing.txt | 3 +
Makefile | 1 +
builtin-clean.c | 4 +-
builtin-commit.c | 5 +
builtin-grep.c | 2 +-
builtin-ls-files.c | 11 +-
builtin-read-tree.c | 4 +-
builtin-update-index.c | 78 ++++++----
cache.h | 8 +-
config.c | 5 +
diff-lib.c | 5 +-
diff.c | 2 +-
dir.c | 100 ++++++++----
dir.h | 4 +
environment.c | 1 +
read-cache.c | 8 +-
t/t1009-read-tree-sparse-checkout.sh | 150 ++++++++++++++++++
t/t2104-update-index-skip-worktree.sh | 57 +++++++
t/t3001-ls-files-others-exclude.sh | 22 +++
t/t7011-skip-worktree-reading.sh | 163 +++++++++++++++++++
t/t7012-skip-worktree-writing.sh | 146 +++++++++++++++++
t/t7300-clean.sh | 19 +++
test-index-version.c | 14 ++
unpack-trees.c | 175 +++++++++++++++++++--
unpack-trees.h | 6 +
30 files changed, 983 insertions(+), 97 deletions(-)
create mode 100755 t/t1009-read-tree-sparse-checkout.sh
create mode 100755 t/t2104-update-index-skip-worktree.sh
create mode 100755 t/t7011-skip-worktree-reading.sh
create mode 100755 t/t7012-skip-worktree-writing.sh
create mode 100644 test-index-version.c
Commit 06aaaa0bf70fe37d198893f4e25fa73b6516f8a9 may step index format
version up and down, depends on whether extended flags present in the
index. This adds a test to check for index format version.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
.gitignore | 1 +
Makefile | 1 +
test-index-version.c | 14 ++++++++++++++
3 files changed, 16 insertions(+), 0 deletions(-)
create mode 100644 test-index-version.c
@@ -107,6 +107,7 @@ OPTIONS Identify the file status with the following tags (followed by a space) at the start of each line: H:: cached+ S:: skip-worktree M:: unmerged R:: removed/deleted C:: modified/changed
@@ -99,6 +100,13 @@ 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.+--skip-worktree::+--no-skip-worktree::+ When one of these flags is specified, the object name recorded+ for the paths are not updated. Instead, these options+ set and unset the "skip-worktree" bit for the paths. See+ section "Skip-worktree bit" below for more information.+ -g:: --again:: Runs 'git-update-index' itself on the paths whose index
@@ -304,6 +312,27 @@ M foo.c <9> now it checks with lstat(2) and finds it has been changed.+Skip-worktree bit+-----------------++Skip-worktree bit can be defined in one (long) sentence: When reading+an entry, if it is marked as skip-worktree, then Git pretends its+working directory version is up to date and read the index version+instead.++To elaborate, "reading" means checking for file existence, reading+file attributes or file content. The working directory version may be+present or absent. If present, its content may match against the index+version or not. Writing is not affected by this bit, content safety+is still first priority. Note that Git _can_ update working directory+file, that is marked skip-worktree, if it is safe to do so (i.e.+working directory version matches index version)++Although this bit looks similar to assume-unchanged bit, its goal is+different from assume-unchanged bit's. Skip-worktree also takes+precedence over assume-unchanged bit when both are set.++ Configuration -------------
@@ -276,6 +277,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length)die("Unable to mark file %s",path);gotofree_return;}+if(mark_skip_worktree_only){+if(mark_ce_flags(p,CE_SKIP_WORKTREE,mark_skip_worktree_only==MARK_FLAG))+die("Unable to mark file %s",path);+gotofree_return;+}if(force_remove){if(remove_file_from_cache(p))
@@ -0,0 +1,57 @@+#!/bin/sh+#+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy+#++test_description='skip-worktree bit test'++../test-lib.sh++cat>expect.full<<EOF+H1+H2+Hsub/1+Hsub/2+EOF++cat>expect.skip<<EOF+S1+H2+Ssub/1+Hsub/2+EOF++test_expect_success'setup''+mkdirsub&&+touch./1./2sub/1sub/2&&+gitadd12sub/1sub/2&&+gitls-files-t|test_cmpexpect.full-+'++test_expect_success'index is at version 2''+test"$(test-index-version<.git/index)"=2+'++test_expect_success'update-index --skip-worktree''+gitupdate-index--skip-worktree1sub/1&&+gitls-files-t|test_cmpexpect.skip-+'++test_expect_success'index is at version 3 after having some skip-worktree entries''+test"$(test-index-version<.git/index)"=3+'++test_expect_success'ls-files -t''+gitls-files-t|test_cmpexpect.skip-+'++test_expect_success'update-index --no-skip-worktree''+gitupdate-index--no-skip-worktree1sub/1&&+gitls-files-t|test_cmpexpect.full-+'++test_expect_success'index version is back to 2 when there is no skip-worktree entry''+test"$(test-index-version<.git/index)"=2+'++test_done
@@ -172,29 +172,29 @@ static int process_directory(const char *path, int len, struct stat *st)returnerror("%s: is a directory - add files inside instead",path);}-/*-*Processaregularfile-*/-staticintprocess_file(constchar*path,intlen,structstat*st)-{-intpos=cache_name_pos(path,len);-structcache_entry*ce=pos<0?NULL:active_cache[pos];--if(ce&&S_ISGITLINK(ce->ce_mode))-returnerror("%s is already a gitlink, not replacing",path);--returnadd_one_path(ce,path,len,st);-}-staticintprocess_path(constchar*path){-intlen;+intpos,len;structstatst;+structcache_entry*ce;len=strlen(path);if(has_symlink_leading_path(path,len))returnerror("'%s' is beyond a symbolic link",path);+pos=cache_name_pos(path,len);+ce=pos<0?NULL:active_cache[pos];+if(ce&&ce_skip_worktree(ce)){+/*+*workingdirectoryversionisassumed"good"+*soupdatingitdoesnotmakesense.+*Ontheotherhand,removingitfromindexshouldwork+*/+if(allow_remove&&remove_file_from_cache(path))+returnerror("%s: cannot remove from the index",path);+return0;+}+/**Firstthingsfirst:getthestatinformation,todecide*whattodoaboutthepathname!
@@ -205,7 +205,13 @@ static int process_path(const char *path)if(S_ISDIR(st.st_mode))returnprocess_directory(path,len,&st);-returnprocess_file(path,len,&st);+/*+*Processaregularfile+*/+if(ce&&S_ISGITLINK(ce->ce_mode))+returnerror("%s is already a gitlink, not replacing",path);++returnadd_one_path(ce,path,len,&st);}staticintadd_cacheinfo(unsignedintmode,constunsignedchar*sha1,
@@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)continue;}-if(ce_uptodate(ce))+if(ce_uptodate(ce)||ce_skip_worktree(ce))continue;changed=check_removed(ce,&st);
@@ -321,6 +321,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,structrev_info*revs=o->unpack_data;intmatch_missing,cached;+/* if the entry is not checked out, don't examine work tree */+cached=o->index_only||(idx&&ce_skip_worktree(idx));/**Backwardcompatibilitywart-"diff-index -m"does*notmean"do not ignore merges",but"match_missing".
@@ -0,0 +1,163 @@+#!/bin/sh+#+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy+#++test_description='skip-worktree bit test'++../test-lib.sh++cat>expect.full<<EOF+H1+H2+Hinit.t+Hsub/1+Hsub/2+EOF++cat>expect.skip<<EOF+S1+H2+Hinit.t+Ssub/1+Hsub/2+EOF++NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391+ZERO_SHA0=0000000000000000000000000000000000000000+setup_absent(){+test-f1&&rm1+gitupdate-index--remove1&&+gitupdate-index--add--cacheinfo100644$NULL_SHA11&&+gitupdate-index--skip-worktree1+}++test_absent(){+echo"100644 $NULL_SHA1 0 1">expected&&+gitls-files--stage1>result&&+test_cmpexpectedresult&&+test!-f1+}++setup_dirty(){+gitupdate-index--force-remove1&&+echodirty>1&&+gitupdate-index--add--cacheinfo100644$NULL_SHA11&&+gitupdate-index--skip-worktree1+}++test_dirty(){+echo"100644 $NULL_SHA1 0 1">expected&&+gitls-files--stage1>result&&+test_cmpexpectedresult&&+echodirty>expected+test_cmpexpected1+}++test_expect_success'setup''+test_commitinit&&+mkdirsub&&+touch./1./2sub/1sub/2&&+gitadd12sub/1sub/2&&+gitupdate-index--skip-worktree1sub/1&&+gitls-files-t>result&&+test_cmpexpect.skipresult+'++test_expect_success'update-index''+setup_absent&&+gitupdate-index1&&+test_absent+'++test_expect_success'update-index''+setup_dirty&&+gitupdate-index1&&+test_dirty+'++test_expect_success'update-index --remove''+setup_absent&&+gitupdate-index--remove1&&+test-z"$(gitls-files1)"&&+test!-f1+'++test_expect_success'update-index --remove''+setup_dirty&&+gitupdate-index--remove1&&+test-z"$(gitls-files1)"&&+echodirty>expected&&+test_cmpexpected1+'++test_expect_success'ls-files --delete''+setup_absent&&+test-z"$(gitls-files-d)"+'++test_expect_success'ls-files --delete''+setup_dirty&&+test-z"$(gitls-files-d)"+'++test_expect_success'ls-files --modified''+setup_absent&&+test-z"$(gitls-files-m)"+'++test_expect_success'ls-files --modified''+setup_dirty&&+test-z"$(gitls-files-m)"+'++test_expect_success'grep with skip-worktree file''+gitupdate-index--no-skip-worktree1&&+echotest>1&&+gitupdate-index1&&+gitupdate-index--skip-worktree1&&+rm1&&+test"$(gitgrep--no-ext-greptest)"="1:test"+'++echo":000000 100644 $ZERO_SHA0$NULL_SHA1 A 1">expected+test_expect_success'diff-index does not examine skip-worktree absent entries''+setup_absent&&+gitdiff-indexHEAD--1>result&&+test_cmpexpectedresult+'++test_expect_success'diff-index does not examine skip-worktree dirty entries''+setup_dirty&&+gitdiff-indexHEAD--1>result&&+test_cmpexpectedresult+'++test_expect_success'diff-files does not examine skip-worktree absent entries''+setup_absent&&+test-z"$(gitdiff-files--one)"+'++test_expect_success'diff-files does not examine skip-worktree dirty entries''+setup_dirty&&+test-z"$(gitdiff-files--one)"+'++test_expect_success'git-rm succeeds on skip-worktree absent entries''+setup_absent&&+gitrm1+'++test_expect_failure'commit on skip-worktree absent entries''+gitreset&&+setup_absent&&+test_must_failgitcommit-mnull1+'++test_expect_failure'commit on skip-worktree dirty entries''+gitreset&&+setup_dirty&&+test_must_failgitcommit-mnull1+'++test_done
This part is mainly to remove CE_VALID shortcuts (and as a
consequence, ce_uptodate() shortcuts as it may be turned on by
CE_VALID) in writing code path if skip-worktree is used. Various tests
are added to avoid future breakages.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
t/t7012-skip-worktree-writing.sh | 146 ++++++++++++++++++++++++++++++++++++++
unpack-trees.c | 4 +-
2 files changed, 148 insertions(+), 2 deletions(-)
create mode 100755 t/t7012-skip-worktree-writing.sh
In the next patch, the buffer that is being used within
add_excludes_from_file_1() comes from another function and does not
have extra space to put \n at the end.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
dir.c | 5 ++---
1 files changed, 2 insertions(+), 3 deletions(-)
This adds index as a prerequisite for directory listing (with
exclude). At the moment directory listing is used by "git clean",
"git add", "git ls-files" and "git status"/"git commit" and
unpack_trees()-related commands. These commands have been
checked/modified to populate index before doing directory listing.
add_excludes_from_file() does not enable this feature, because it
is used to read .git/info/exclude and some explicit files specified
by "git ls-files".
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/technical/api-directory-listing.txt | 3 +
builtin-clean.c | 4 +-
builtin-ls-files.c | 4 +-
dir.c | 65 ++++++++++++++------
t/t3001-ls-files-others-exclude.sh | 22 +++++++
t/t7300-clean.sh | 19 ++++++
6 files changed, 95 insertions(+), 22 deletions(-)
@@ -58,6 +58,9 @@ The result of the enumeration is left in these fields:: Calling sequence ----------------+Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE+marked. If you to exclude files, make sure you have loaded index first.+ * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`.
@@ -513,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)pathspec=get_pathspec(prefix,argv);/* be nice with submodule paths ending in a slash */-read_cache();if(pathspec)strip_trailing_slash_from_submodules();
@@ -212,20 +236,26 @@ static int add_excludes_from_file_1(const char *fname,char*buf,*entry;fd=open(fname,O_RDONLY);-if(fd<0||fstat(fd,&st)<0)-gotoerr;-size=xsize_t(st.st_size);-if(size==0){-close(fd);-return0;+if(fd<0||fstat(fd,&st)<0){+if(0<=fd)+close(fd);+if(!check_index||+(buf=read_skip_worktree_file_from_index(fname,&size))==NULL)+return-1;}-buf=xmalloc(size+1);-if(read_in_full(fd,buf,size)!=size)-{-free(buf);-gotoerr;+else{+size=xsize_t(st.st_size);+if(size==0){+close(fd);+return0;+}+buf=xmalloc(size);+if(read_in_full(fd,buf,size)!=size){+close(fd);+return-1;+}+close(fd);}-close(fd);if(buf_p)*buf_p=buf;
@@ -240,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname,}}return0;--err:-if(0<=fd)-close(fd);-return-1;}voidadd_excludes_from_file(structdir_struct*dir,constchar*fname){if(add_excludes_from_file_1(fname,"",0,NULL,-&dir->exclude_list[EXC_FILE])<0)+&dir->exclude_list[EXC_FILE],0)<0)die("cannot use %s as an exclude file",fname);}
In this code path, we would remove "old" and replace it with "merge".
"old" may have skip-worktree bit, so re-add it to "merge".
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
unpack-trees.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
These functions are used to handle .gitignore. They are now exported
so that sparse checkout can reuse.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
dir.c | 32 ++++++++++++++++----------------
dir.h | 4 ++++
2 files changed, 20 insertions(+), 16 deletions(-)
@@ -274,8 +274,8 @@ static int add_excludes_from_file_1(const char *fname,voidadd_excludes_from_file(structdir_struct*dir,constchar*fname){-if(add_excludes_from_file_1(fname,"",0,NULL,-&dir->exclude_list[EXC_FILE],0)<0)+if(add_excludes_from_file_to_list(fname,"",0,NULL,+&dir->exclude_list[EXC_FILE],0)<0)die("cannot use %s as an exclude file",fname);}
@@ -336,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)/* Scan the list and let the last match determine the fate.*Return1forexclude,0forincludeand-1forundecided.*/-staticintexcluded_1(constchar*pathname,-intpathlen,constchar*basename,int*dtype,-structexclude_list*el)+intexcluded_from_list(constchar*pathname,+intpathlen,constchar*basename,int*dtype,+structexclude_list*el){inti;
@@ -412,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)prep_exclude(dir,pathname,basename-pathname);for(st=EXC_CMDL;st<=EXC_FILE;st++){-switch(excluded_1(pathname,pathlen,basename,-dtype_p,&dir->exclude_list[st])){+switch(excluded_from_list(pathname,pathlen,basename,+dtype_p,&dir->exclude_list[st])){case0:return0;case1:
Index does not really have "directories", attempts to match "foo/"
against index will fail unless someone tries to reconstruct directories
from a list of file.
Observing that dtype in this function can never be NULL (otherwise
it would segfault), dtype NULL will be used to say "hey.. you are
matching against index" and behave properly.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
dir.c | 6 ++++++
1 files changed, 6 insertions(+), 0 deletions(-)
With skip-worktree bit, you can manually set it to unwanted files,
then remove them: you would have the so-called sparse checkout. The
disadvantages are:
- Porcelain tools are not aware of this. Everytime you do an
operation that may update working directory, skip-worktree may be
cleared out. You have to set them again.
- You still have to remove skip-worktree'd files manually, which is
boring and ineffective.
These will be addressed in the following patches. This patch gives an
idea what is "sparse checkout" in Documentation/git-read-tree.txt.
This file is chosen instead of git-checkout.txt because it is quite
technical and user-unfriendly. I'd expect git-checkout.txt to have
something when Porcelain support is done.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/git-read-tree.txt | 44 +++++++++++++++++++++++++++++++++++++++
1 files changed, 44 insertions(+), 0 deletions(-)
@@ -360,6 +360,50 @@ middle of doing, and when your working tree is ready (i.e. you have finished your work-in-progress), attempt the merge again.+Sparse checkout+---------------++"Sparse checkout" allows to sparsely populate working directory.+It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell+Git whether a file on working directory is worth looking at.++"git read-tree" and other merge-based commands ("git merge", "git+checkout"...) can help maintaining skip-worktree bitmap and working+directory update. `$GIT_DIR/info/sparse-checkout` is used to+define the skip-worktree reference bitmap. When "git read-tree" needs+to update working directory, it will reset skip-worktree bit in index+based on this file, which uses the same syntax as .gitignore files.+If an entry matches a pattern in this file, skip-worktree will be+set on that entry. Otherwise, skip-worktree will be unset.++Then it compares the new skip-worktree value with the previous one. If+skip-worktree turns from unset to set, it will add the corresponding+file back. If it turns from set to unset, that file will be removed.++While `$GIT_DIR/info/sparse-checkout` is usually used to specify what+files are in. You can also specify what files are _not_ in, using+negate patterns. For example, to remove file "unwanted":++----------------+*+!unwanted+----------------++Another tricky thing is fully repopulating working directory when you+no longer want sparse checkout. You cannot just disable "sparse+checkout" because skip-worktree are still in the index and you working+directory is still sparsely populated. You should re-populate working+directory with the `$GIT_DIR/info/sparse-checkout` file content as+follows:++----------------+*+----------------++Then you can disable sparse checkout. Sparse checkout support in "git+read-tree" and similar commands is disabled by default.++ SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
CE_REMOVE now removes both worktree and index versions. Sparse
checkout must be able to remove worktree version while keep the
index intact when checkout area is narrowed.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
cache.h | 3 +++
unpack-trees.c | 9 ++++++++-
2 files changed, 11 insertions(+), 1 deletions(-)
@@ -177,6 +177,9 @@ struct cache_entry {#define CE_HASHED (0x100000)#define CE_UNHASHED (0x200000)+/* Only remove in work directory, not index */+#define CE_WT_REMOVE (0x400000)+/**Extendedon-diskflags*/
verify_absent() and verify_uptodate() are used to ensure worktree
is safe to be updated, then CE_REMOVE or CE_UPDATE will be set.
Finally check_updates() bases on CE_REMOVE, CE_UPDATE and the
recently added CE_WT_REMOVE to update working directory accordingly.
The entries that are checked may eventually be left out of checkout
area (done later in apply_sparse_checkout()). We don't want to update
outside checkout area. This patch teaches Git to assume "good",
skip these checks when it's sure those entries will be outside checkout
area, and clear CE_REMOVE|CE_UPDATE that could be set due to this
assumption.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
unpack-trees.c | 12 ++++++++++++
1 files changed, 12 insertions(+), 0 deletions(-)
The way sparse checkout works, users may empty their worktree
completely, because of non-matching sparse-checkout spec, or empty
spec. I believe this is not desired. This patch makes Git refuse to
produce such worktree.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
t/t1009-read-tree-sparse-checkout.sh | 10 +++-------
unpack-trees.c | 7 +++++++
2 files changed, 10 insertions(+), 7 deletions(-)
@@ -512,8 +513,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options*/if(ce_skip_worktree(ce))ce->ce_flags&=~(CE_UPDATE|CE_REMOVE);+else+empty_worktree=0;}+if(o->result.cache_nr&&empty_worktree){+ret=unpack_failed(o,"Sparse checkout leaves no entry on working directory");+gotodone;+}}o->src_index=NULL;
This patch introduces core.sparseCheckout, which will control whether
sparse checkout support is enabled in unpack_trees()
It also loads sparse-checkout file that will be used in the next patch.
I split it out so the next patch will be shorter, easier to read.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/config.txt | 4 ++++
Documentation/git-read-tree.txt | 4 +++-
cache.h | 1 +
config.c | 5 +++++
environment.c | 1 +
unpack-trees.c | 36 ++++++++++++++++++++++++++++++------
unpack-trees.h | 4 ++++
7 files changed, 48 insertions(+), 7 deletions(-)
@@ -439,6 +439,10 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten.+core.sparseCheckout::+ Enable "sparse checkout" feature. See section "Sparse checkout" in+ linkgit:git-read-tree[1] for more information.+ add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors'
@@ -401,7 +401,9 @@ follows: ---------------- Then you can disable sparse checkout. Sparse checkout support in "git-read-tree" and similar commands is disabled by default.+read-tree" and similar commands is disabled by default. You need to+turn `core.sparseCheckout` on in order to have sparse checkout+support. SEE ALSO
@@ -503,6 +503,11 @@ static int git_default_core_config(const char *var, const char *value)return0;}+if(!strcmp(var,"core.sparsecheckout")){+core_apply_sparse_checkout=git_config_bool(var,value);+return0;+}+/* Add other config variables here and to Documentation/config.txt. */return0;}
@@ -50,6 +50,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;#endifenumobject_creation_modeobject_creation_mode=OBJECT_CREATION_MODE;intgrafts_replace_parents=1;+intcore_apply_sparse_checkout;/* Parallel index stat data preload? */intcore_preload_index=0;
@@ -0,0 +1,154 @@+#!/bin/sh++test_description='sparse checkout tests'++../test-lib.sh++cat>expected<<EOF+10064477f0ba1734ed79d12881f81b36ee134de6a3327b0init.t+100644e69de29bb2d1d6434b8b29ae775ad8c2e48c53910sub/added+EOF+test_expect_success'setup''+test_commitinit&&+echomodified>>init.t&&+mkdirsub&&+touchsub/added&&+gitaddinit.tsub/added&&+gitcommit-m"modified and added"&&+gittagtop&&+gitrmsub/added&&+gitcommit-mremoved&&+gittagremoved&&+gitcheckouttop&&+gitls-files--stage>result&&+test_cmpexpectedresult+'++cat>expected.swt<<EOF+Hinit.t+Hsub/added+EOF+test_expect_success'read-tree without .git/info/sparse-checkout''+gitread-tree-m-uHEAD&&+gitls-files--stage>result&&+test_cmpexpectedresult&&+gitls-files-t>result&&+test_cmpexpected.swtresult+'++test_expect_success'read-tree with .git/info/sparse-checkout but disabled''+echo>.git/info/sparse-checkout+gitread-tree-m-uHEAD&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test-finit.t&&+test-fsub/added+'++test_expect_success'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled''+gitconfigcore.sparsecheckouttrue&&+echo>.git/info/sparse-checkout&&+gitread-tree--no-sparse-checkout-m-uHEAD&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test-finit.t&&+test-fsub/added+'++cat>expected.swt<<EOF+Sinit.t+Ssub/added+EOF+test_expect_success'read-tree with empty .git/info/sparse-checkout''+gitconfigcore.sparsecheckouttrue&&+echo>.git/info/sparse-checkout&&+gitread-tree-m-uHEAD&&+gitls-files--stage>result&&+test_cmpexpectedresult&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test!-finit.t&&+test!-fsub/added+'++cat>expected.swt<<EOF+Sinit.t+Hsub/added+EOF+test_expect_success'match directories with trailing slash''+echosub/>.git/info/sparse-checkout&&+gitread-tree-m-uHEAD&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test!-finit.t&&+test-fsub/added+'++cat>expected.swt<<EOF+Hinit.t+Hsub/added+EOF+test_expect_failure'match directories without trailing slash''+echoinit.t>.git/info/sparse-checkout&&+echosub>>.git/info/sparse-checkout&&+gitread-tree-m-uHEAD&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test!-finit.t&&+test-fsub/added+'++cat>expected.swt<<EOF+Hinit.t+Ssub/added+EOF+test_expect_success'checkout area changes''+echoinit.t>.git/info/sparse-checkout&&+gitread-tree-m-uHEAD&&+gitls-files-t>result&&+test_cmpexpected.swtresult&&+test-finit.t&&+test!-fsub/added+'++test_expect_success'read-tree updates worktree, absent case''+echosub/added>.git/info/sparse-checkout&&+gitcheckout-ftop&&+gitread-tree-m-uHEAD^&&+test!-finit.t+'++test_expect_success'read-tree updates worktree, dirty case''+echosub/added>.git/info/sparse-checkout&&+gitcheckout-ftop&&+echodirty>init.t&&+gitread-tree-m-uHEAD^&&+grep-qdirtyinit.t&&+rminit.t+'++test_expect_success'read-tree removes worktree, dirty case''+echoinit.t>.git/info/sparse-checkout&&+gitcheckout-ftop&&+echodirty>added&&+gitread-tree-m-uHEAD^&&+grep-qdirtyadded+'++test_expect_success'read-tree adds to worktree, absent case''+echoinit.t>.git/info/sparse-checkout&&+gitcheckout-fremoved&&+gitread-tree-u-mHEAD^&&+test!-fsub/added+'++test_expect_success'read-tree adds to worktree, dirty case''+echoinit.t>.git/info/sparse-checkout&&+gitcheckout-fremoved&&+mkdirsub&&+echodirty>sub/added&&+gitread-tree-u-mHEAD^&&+grep-qdirtysub/added+'++test_done
@@ -110,6 +110,10 @@ OPTIONS directories the index file and index output file are located in.+--no-sparse-checkout::+ Disable sparse checkout support even if `core.sparseCheckout`+ is true.+ <tree-ish#>:: The id of the tree object(s) to be read/merged.
@@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)PARSE_OPT_NONEG,exclude_per_directory_cb},OPT_SET_INT('i',NULL,&opts.index_only,"don't check the working tree after merging",1),+OPT_SET_INT(0,"no-sparse-checkout",&opts.skip_sparse_checkout,+"skip applying sparse checkout filter",1),OPT_END()};
From: Junio C Hamano <hidden> Date: 2016-06-15 22:47:17
Nguyễn Thái Ngọc Duy [off-list ref] writes:
The recent assume-unchanged "breakage" that lets Git overwrite
assume-unchanged files worried me. I sat back, checked and wrote tests
Yeah, it worries me, too. Does the fix to make sure the sparse stuff
won't be broken apply equally to assume-unchanged? Does the series have
such fixes to assume-unchanged bit as well?
By the way, I think the first patch in the earlier series, 540e694
(Prevent diff machinery from examining assume-unchanged entries on
worktree, 2009-08-11), is a good change regardless of the sparse
implementation, and I'm inclined to say that we should merge that part
(and I suspect there will be similar fixes to really ignore differences to
CE_VALID entries) first and then queue this new series on top.
The recent assume-unchanged "breakage" that lets Git overwrite
assume-unchanged files worried me. I sat back, checked and wrote tests
Yeah, it worries me, too. Does the fix to make sure the sparse stuff
won't be broken apply equally to assume-unchanged? Does the series have
such fixes to assume-unchanged bit as well?
This series does not fix assume-unchanged bit. I'd like to focus on
skip-worktree bit now. I still need to write a few more tests for
git-apply, git-checkout... but I think they are safe. It's up to you
to see if changes apply to assume-unchanged bit, in patches 4/19 and
5/19. I don't know if I understand assume-unchanged semantics
correctly anymore :-)
Anyway I think we could put a big fat warning above ce_uptodate()
macro definition, saying that this bit/macro could be faked by
assume-unchanged or skip-worktree bit, so don't rely on that macro
when it comes to writing (at least for skip-worktree part).
Hmm.. _or_ we could make it clear whether it is truly uptodate, or
faked uptodate. Some code path will be updated to only trust "truly
uptodate" bit, which is clearer and easier to grasp than messy logics
like "if (ce_uptodate(ce) && !ce_skip_worktree(ce))". Something like
this as a starting point (for demonstration only, I don't think it
compiles)
@@ -179,6 +179,7 @@ struct cache_entry {/* Only remove in work directory, not index */#define CE_WT_REMOVE (0x400000)+#define CE_ASSUME_UPTODATE (0x800000)/**Extendedon-diskflags
*refresh_cache_ent(struct index_state *istate,
return ce;
if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) {
- ce_mark_uptodate(ce);
+ ce_mark_assume_uptodate(ce);
return ce;
}
Still thinking of it. Seems like a good change...
By the way, I think the first patch in the earlier series, 540e694
(Prevent diff machinery from examining assume-unchanged entries on
worktree, 2009-08-11), is a good change regardless of the sparse
implementation, and I'm inclined to say that we should merge that part
(and I suspect there will be similar fixes to really ignore differences to
CE_VALID entries) first and then queue this new series on top.
grep: turn on --cached for files that is marked skip-worktree
ls-files: do not check for deleted file that is marked skip-worktree
update-index: ignore update request if it's skip-worktree, while still allows removing
diff*: skip worktree version
I kept reminding myself and still managed to miss it. skip-worktree
bit is not completely independent from assume-unchanged. It shares
CE_MATCH_IGNORE_VALID flag in ie_match_stat(). Should have mentioned
that in commit log.
--
Duy