summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/helper/.gitignore2
-rw-r--r--t/helper/test-drop-caches.c164
-rw-r--r--t/helper/test-dump-fsmonitor.c21
-rw-r--r--t/helper/test-ref-store.c28
-rwxr-xr-xt/lib-gpg.sh2
-rwxr-xr-xt/lib-submodule-update.sh251
-rwxr-xr-xt/perf/p4211-line-log.sh4
-rwxr-xr-xt/perf/p7519-fsmonitor.sh184
-rwxr-xr-xt/t0000-basic.sh25
-rw-r--r--t/t0021/rot13-filter.pl127
-rwxr-xr-xt/t0025-crlf-renormalize.sh30
-rwxr-xr-xt/t0027-auto-crlf.sh2
-rwxr-xr-xt/t1409-avoid-packing-refs.sh118
-rwxr-xr-xt/t1430-bad-ref-name.sh43
-rwxr-xr-xt/t1700-split-index.sh1
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh8
-rwxr-xr-xt/t3320-notes-merge-worktrees.sh2
-rwxr-xr-xt/t3400-rebase.sh22
-rwxr-xr-xt/t3512-cherry-pick-submodule.sh36
-rwxr-xr-xt/t3600-rm.sh5
-rwxr-xr-xt/t3700-add.sh5
-rwxr-xr-xt/t4015-diff-whitespace.sh28
-rwxr-xr-xt/t4051-diff-function-context.sh4
-rw-r--r--t/t4051/hello.c3
-rwxr-xr-xt/t4107-apply-ignore-whitespace.sh14
-rwxr-xr-xt/t5521-pull-options.sh45
-rwxr-xr-xt/t5526-fetch-submodules.sh77
-rwxr-xr-xt/t5545-push-options.sh77
-rwxr-xr-xt/t6030-bisect-porcelain.sh17
-rwxr-xr-xt/t6300-for-each-ref.sh32
-rwxr-xr-xt/t7006-pager.sh38
-rwxr-xr-xt/t7519-status-fsmonitor.sh317
-rwxr-xr-xt/t7519/fsmonitor-all24
-rwxr-xr-xt/t7519/fsmonitor-none22
-rwxr-xr-xt/t7519/fsmonitor-watchman133
-rwxr-xr-xt/t7520-ignored-hook-warning.sh41
-rwxr-xr-xt/t7521-ignored-mode.sh233
-rwxr-xr-xt/t7810-grep.sh41
38 files changed, 1889 insertions, 337 deletions
diff --git a/t/helper/.gitignore b/t/helper/.gitignore
index 7c9d28a834..d02f9b39ac 100644
--- a/t/helper/.gitignore
+++ b/t/helper/.gitignore
@@ -3,7 +3,9 @@
/test-config
/test-date
/test-delta
+/test-drop-caches
/test-dump-cache-tree
+/test-dump-fsmonitor
/test-dump-split-index
/test-dump-untracked-cache
/test-fake-ssh
diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c
new file mode 100644
index 0000000000..bd1a857d52
--- /dev/null
+++ b/t/helper/test-drop-caches.c
@@ -0,0 +1,164 @@
+#include "git-compat-util.h"
+
+#if defined(GIT_WINDOWS_NATIVE)
+
+static int cmd_sync(void)
+{
+ char Buffer[MAX_PATH];
+ DWORD dwRet;
+ char szVolumeAccessPath[] = "\\\\.\\X:";
+ HANDLE hVolWrite;
+ int success = 0;
+
+ dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
+ if ((0 == dwRet) || (dwRet > MAX_PATH))
+ return error("Error getting current directory");
+
+ if ((Buffer[0] < 'A') || (Buffer[0] > 'Z'))
+ return error("Invalid drive letter '%c'", Buffer[0]);
+
+ szVolumeAccessPath[4] = Buffer[0];
+ hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (INVALID_HANDLE_VALUE == hVolWrite)
+ return error("Unable to open volume for writing, need admin access");
+
+ success = FlushFileBuffers(hVolWrite);
+ if (!success)
+ error("Unable to flush volume");
+
+ CloseHandle(hVolWrite);
+
+ return !success;
+}
+
+#define STATUS_SUCCESS (0x00000000L)
+#define STATUS_PRIVILEGE_NOT_HELD (0xC0000061L)
+
+typedef enum _SYSTEM_INFORMATION_CLASS {
+ SystemMemoryListInformation = 80,
+} SYSTEM_INFORMATION_CLASS;
+
+typedef enum _SYSTEM_MEMORY_LIST_COMMAND {
+ MemoryCaptureAccessedBits,
+ MemoryCaptureAndResetAccessedBits,
+ MemoryEmptyWorkingSets,
+ MemoryFlushModifiedList,
+ MemoryPurgeStandbyList,
+ MemoryPurgeLowPriorityStandbyList,
+ MemoryCommandMax
+} SYSTEM_MEMORY_LIST_COMMAND;
+
+static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags)
+{
+ BOOL bResult;
+ DWORD dwBufferLength;
+ LUID luid;
+ TOKEN_PRIVILEGES tpPreviousState;
+ TOKEN_PRIVILEGES tpNewState;
+
+ dwBufferLength = 16;
+ bResult = LookupPrivilegeValueA(0, lpName, &luid);
+ if (bResult) {
+ tpNewState.PrivilegeCount = 1;
+ tpNewState.Privileges[0].Luid = luid;
+ tpNewState.Privileges[0].Attributes = 0;
+ bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState,
+ (DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState),
+ &tpPreviousState, &dwBufferLength);
+ if (bResult) {
+ tpPreviousState.PrivilegeCount = 1;
+ tpPreviousState.Privileges[0].Luid = luid;
+ tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0;
+ bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState,
+ dwBufferLength, 0, 0);
+ }
+ }
+ return bResult;
+}
+
+static int cmd_dropcaches(void)
+{
+ HANDLE hProcess = GetCurrentProcess();
+ HANDLE hToken;
+ HMODULE ntdll;
+ DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG);
+ SYSTEM_MEMORY_LIST_COMMAND command;
+ int status;
+
+ if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
+ return error("Can't open current process token");
+
+ if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1))
+ return error("Can't get SeProfileSingleProcessPrivilege");
+
+ CloseHandle(hToken);
+
+ ntdll = LoadLibrary("ntdll.dll");
+ if (!ntdll)
+ return error("Can't load ntdll.dll, wrong Windows version?");
+
+ NtSetSystemInformation =
+ (DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation");
+ if (!NtSetSystemInformation)
+ return error("Can't get function addresses, wrong Windows version?");
+
+ command = MemoryPurgeStandbyList;
+ status = NtSetSystemInformation(
+ SystemMemoryListInformation,
+ &command,
+ sizeof(SYSTEM_MEMORY_LIST_COMMAND)
+ );
+ if (status == STATUS_PRIVILEGE_NOT_HELD)
+ error("Insufficient privileges to purge the standby list, need admin access");
+ else if (status != STATUS_SUCCESS)
+ error("Unable to execute the memory list command %d", status);
+
+ FreeLibrary(ntdll);
+
+ return status;
+}
+
+#elif defined(__linux__)
+
+static int cmd_sync(void)
+{
+ return system("sync");
+}
+
+static int cmd_dropcaches(void)
+{
+ return system("echo 3 | sudo tee /proc/sys/vm/drop_caches");
+}
+
+#elif defined(__APPLE__)
+
+static int cmd_sync(void)
+{
+ return system("sync");
+}
+
+static int cmd_dropcaches(void)
+{
+ return system("sudo purge");
+}
+
+#else
+
+static int cmd_sync(void)
+{
+ return 0;
+}
+
+static int cmd_dropcaches(void)
+{
+ return error("drop caches not implemented on this platform");
+}
+
+#endif
+
+int cmd_main(int argc, const char **argv)
+{
+ cmd_sync();
+ return cmd_dropcaches();
+}
diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c
new file mode 100644
index 0000000000..ad452707e8
--- /dev/null
+++ b/t/helper/test-dump-fsmonitor.c
@@ -0,0 +1,21 @@
+#include "cache.h"
+
+int cmd_main(int ac, const char **av)
+{
+ struct index_state *istate = &the_index;
+ int i;
+
+ setup_git_directory();
+ if (do_read_index(istate, get_index_file(), 0) < 0)
+ die("unable to read index file");
+ if (!istate->fsmonitor_last_update) {
+ printf("no fsmonitor\n");
+ return 0;
+ }
+ printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update);
+
+ for (i = 0; i < istate->cache_nr; i++)
+ printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-");
+
+ return 0;
+}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 6ec2670044..7120634b04 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -72,12 +72,12 @@ static int cmd_pack_refs(struct ref_store *refs, const char **argv)
static int cmd_peel_ref(struct ref_store *refs, const char **argv)
{
const char *refname = notnull(*argv++, "refname");
- unsigned char sha1[20];
+ struct object_id oid;
int ret;
- ret = refs_peel_ref(refs, refname, sha1);
+ ret = refs_peel_ref(refs, refname, &oid);
if (!ret)
- puts(sha1_to_hex(sha1));
+ puts(oid_to_hex(&oid));
return ret;
}
@@ -127,15 +127,15 @@ static int cmd_for_each_ref(struct ref_store *refs, const char **argv)
static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
{
- unsigned char sha1[20];
+ struct object_id oid;
const char *refname = notnull(*argv++, "refname");
int resolve_flags = arg_flags(*argv++, "resolve-flags");
int flags;
const char *ref;
ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- sha1, &flags);
- printf("%s %s 0x%x\n", sha1_to_hex(sha1), ref ? ref : "(null)", flags);
+ &oid, &flags);
+ printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags);
return ref ? 0 : 1;
}
@@ -218,12 +218,12 @@ static int cmd_delete_ref(struct ref_store *refs, const char **argv)
const char *refname = notnull(*argv++, "refname");
const char *sha1_buf = notnull(*argv++, "old-sha1");
unsigned int flags = arg_flags(*argv++, "flags");
- unsigned char old_sha1[20];
+ struct object_id old_oid;
- if (get_sha1_hex(sha1_buf, old_sha1))
+ if (get_oid_hex(sha1_buf, &old_oid))
die("not sha-1");
- return refs_delete_ref(refs, msg, refname, old_sha1, flags);
+ return refs_delete_ref(refs, msg, refname, &old_oid, flags);
}
static int cmd_update_ref(struct ref_store *refs, const char **argv)
@@ -233,15 +233,15 @@ static int cmd_update_ref(struct ref_store *refs, const char **argv)
const char *new_sha1_buf = notnull(*argv++, "old-sha1");
const char *old_sha1_buf = notnull(*argv++, "old-sha1");
unsigned int flags = arg_flags(*argv++, "flags");
- unsigned char old_sha1[20];
- unsigned char new_sha1[20];
+ struct object_id old_oid;
+ struct object_id new_oid;
- if (get_sha1_hex(old_sha1_buf, old_sha1) ||
- get_sha1_hex(new_sha1_buf, new_sha1))
+ if (get_oid_hex(old_sha1_buf, &old_oid) ||
+ get_oid_hex(new_sha1_buf, &new_oid))
die("not sha-1");
return refs_update_ref(refs, msg, refname,
- new_sha1, old_sha1,
+ &new_oid, &old_oid,
flags, UPDATE_REFS_DIE_ON_ERR);
}
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
index 43679a4c64..a5d3b2cbaa 100755
--- a/t/lib-gpg.sh
+++ b/t/lib-gpg.sh
@@ -31,7 +31,7 @@ then
chmod 0700 ./gpghome &&
GNUPGHOME="$(pwd)/gpghome" &&
export GNUPGHOME &&
- (gpgconf --kill gpg-agent 2>&1 >/dev/null || : ) &&
+ (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 2d26f86800..bb94c23209 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -306,9 +306,9 @@ test_submodule_content () {
# to protect the history!
#
-# Test that submodule contents are currently not updated when switching
-# between commits that change a submodule.
-test_submodule_switch () {
+# Internal function; use test_submodule_switch() or
+# test_submodule_forced_switch() instead.
+test_submodule_switch_common() {
command="$1"
######################### Appearing submodule #########################
# Switching to a commit letting a submodule appear creates empty dir ...
@@ -332,7 +332,7 @@ test_submodule_switch () {
test_submodule_content sub1 origin/add_sub1
)
'
- # ... and doesn't care if it already exists ...
+ # ... and doesn't care if it already exists.
test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" '
prolog &&
reset_work_tree_to no_submodule &&
@@ -347,19 +347,6 @@ test_submodule_switch () {
test_submodule_content sub1 origin/add_sub1
)
'
- # ... unless there is an untracked file in its place.
- test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
- prolog &&
- reset_work_tree_to no_submodule &&
- (
- cd submodule_update &&
- git branch -t add_sub1 origin/add_sub1 &&
- >sub1 &&
- test_must_fail $command add_sub1 &&
- test_superproject_content origin/no_submodule &&
- test_must_be_empty sub1
- )
- '
# Replacing a tracked file with a submodule produces an empty
# directory ...
test_expect_$RESULT "$command: replace tracked file with submodule creates empty directory" '
@@ -441,6 +428,11 @@ test_submodule_switch () {
# submodule files with the newly checked out ones in the
# directory of the same name while it shouldn't.
RESULT="failure"
+ elif test "$KNOWN_FAILURE_FORCED_SWITCH_TESTS" = 1
+ then
+ # All existing tests that use test_submodule_forced_switch()
+ # require this.
+ RESULT="failure"
else
RESULT="success"
fi
@@ -522,7 +514,6 @@ test_submodule_switch () {
test_submodule_content sub1 origin/modify_sub1
)
'
-
# Updating a submodule to an invalid sha1 doesn't update the
# submodule's work tree, subsequent update will fail
test_expect_$RESULT "$command: modified submodule does not update submodule work tree to invalid commit" '
@@ -555,42 +546,51 @@ test_submodule_switch () {
'
}
-# Test that submodule contents are currently not updated when switching
-# between commits that change a submodule, but throwing away local changes in
-# the superproject is allowed.
-test_submodule_forced_switch () {
+# Declares and invokes several tests that, in various situations, checks that
+# the provided transition function:
+# - succeeds in updating the worktree and index of a superproject to a target
+# commit, or fails atomically (depending on the test situation)
+# - if succeeds, the contents of submodule directories are unchanged
+# - if succeeds, once "git submodule update" is invoked, the contents of
+# submodule directories are updated
+#
+# Use as follows:
+#
+# my_func () {
+# target=$1
+# # Do something here that updates the worktree and index to match target,
+# # but not any submodule directories.
+# }
+# test_submodule_switch "my_func"
+test_submodule_switch () {
command="$1"
- ######################### Appearing submodule #########################
- # Switching to a commit letting a submodule appear creates empty dir ...
- test_expect_success "$command: added submodule creates empty directory" '
- prolog &&
- reset_work_tree_to no_submodule &&
- (
- cd submodule_update &&
- git branch -t add_sub1 origin/add_sub1 &&
- $command add_sub1 &&
- test_superproject_content origin/add_sub1 &&
- test_dir_is_empty sub1 &&
- git submodule update --init --recursive &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # ... and doesn't care if it already exists ...
- test_expect_success "$command: added submodule leaves existing empty directory alone" '
+ test_submodule_switch_common "$command"
+
+ # An empty directory does not prevent the creation of a submodule of
+ # the same name, but a file does.
+ test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
prolog &&
reset_work_tree_to no_submodule &&
(
cd submodule_update &&
git branch -t add_sub1 origin/add_sub1 &&
- mkdir sub1 &&
- $command add_sub1 &&
- test_superproject_content origin/add_sub1 &&
- test_dir_is_empty sub1 &&
- git submodule update --init --recursive &&
- test_submodule_content sub1 origin/add_sub1
+ >sub1 &&
+ test_must_fail $command add_sub1 &&
+ test_superproject_content origin/no_submodule &&
+ test_must_be_empty sub1
)
'
- # ... unless there is an untracked file in its place.
+}
+
+# Same as test_submodule_switch(), except that throwing away local changes in
+# the superproject is allowed.
+test_submodule_forced_switch () {
+ command="$1"
+ KNOWN_FAILURE_FORCED_SWITCH_TESTS=1
+ test_submodule_switch_common "$command"
+
+ # When forced, a file in the superproject does not prevent creating a
+ # submodule of the same name.
test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
prolog &&
reset_work_tree_to no_submodule &&
@@ -603,165 +603,6 @@ test_submodule_forced_switch () {
test_dir_is_empty sub1
)
'
- # Replacing a tracked file with a submodule produces an empty
- # directory ...
- test_expect_success "$command: replace tracked file with submodule creates empty directory" '
- prolog &&
- reset_work_tree_to replace_sub1_with_file &&
- (
- cd submodule_update &&
- git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
- $command replace_file_with_sub1 &&
- test_superproject_content origin/replace_file_with_sub1 &&
- test_dir_is_empty sub1 &&
- git submodule update --init --recursive &&
- test_submodule_content sub1 origin/replace_file_with_sub1
- )
- '
- # ... as does removing a directory with tracked files with a
- # submodule.
- test_expect_success "$command: replace directory with submodule" '
- prolog &&
- reset_work_tree_to replace_sub1_with_directory &&
- (
- cd submodule_update &&
- git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
- $command replace_directory_with_sub1 &&
- test_superproject_content origin/replace_directory_with_sub1 &&
- test_dir_is_empty sub1 &&
- git submodule update --init --recursive &&
- test_submodule_content sub1 origin/replace_directory_with_sub1
- )
- '
-
- ######################## Disappearing submodule #######################
- # Removing a submodule doesn't remove its work tree ...
- test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t remove_sub1 origin/remove_sub1 &&
- $command remove_sub1 &&
- test_superproject_content origin/remove_sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # ... especially when it contains a .git directory.
- test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t remove_sub1 origin/remove_sub1 &&
- replace_gitfile_with_git_dir sub1 &&
- $command remove_sub1 &&
- test_superproject_content origin/remove_sub1 &&
- test_git_directory_is_unchanged sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # Replacing a submodule with files in a directory must fail as the
- # submodule work tree isn't removed ...
- test_expect_failure "$command: replace submodule with a directory must fail" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
- test_must_fail $command replace_sub1_with_directory &&
- test_superproject_content origin/add_sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # ... especially when it contains a .git directory.
- test_expect_failure "$command: replace submodule containing a .git directory with a directory must fail" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
- replace_gitfile_with_git_dir sub1 &&
- test_must_fail $command replace_sub1_with_directory &&
- test_superproject_content origin/add_sub1 &&
- test_git_directory_is_unchanged sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # Replacing it with a file must fail as it could throw away any local
- # work tree changes ...
- test_expect_failure "$command: replace submodule with a file must fail" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
- test_must_fail $command replace_sub1_with_file &&
- test_superproject_content origin/add_sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # ... or even destroy unpushed parts of submodule history if that
- # still uses a .git directory.
- test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
- replace_gitfile_with_git_dir sub1 &&
- test_must_fail $command replace_sub1_with_file &&
- test_superproject_content origin/add_sub1 &&
- test_git_directory_is_unchanged sub1 &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
-
- ########################## Modified submodule #########################
- # Updating a submodule sha1 doesn't update the submodule's work tree
- test_expect_success "$command: modified submodule does not update submodule work tree" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t modify_sub1 origin/modify_sub1 &&
- $command modify_sub1 &&
- test_superproject_content origin/modify_sub1 &&
- test_submodule_content sub1 origin/add_sub1 &&
- git submodule update &&
- test_submodule_content sub1 origin/modify_sub1
- )
- '
- # Updating a submodule to an invalid sha1 doesn't update the
- # submodule's work tree, subsequent update will fail
- test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
- prolog &&
- reset_work_tree_to add_sub1 &&
- (
- cd submodule_update &&
- git branch -t invalid_sub1 origin/invalid_sub1 &&
- $command invalid_sub1 &&
- test_superproject_content origin/invalid_sub1 &&
- test_submodule_content sub1 origin/add_sub1 &&
- test_must_fail git submodule update &&
- test_submodule_content sub1 origin/add_sub1
- )
- '
- # Updating a submodule from an invalid sha1 doesn't update the
- # submodule's work tree, subsequent update will succeed
- test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" '
- prolog &&
- reset_work_tree_to invalid_sub1 &&
- (
- cd submodule_update &&
- git branch -t valid_sub1 origin/valid_sub1 &&
- $command valid_sub1 &&
- test_superproject_content origin/valid_sub1 &&
- test_dir_is_empty sub1 &&
- git submodule update --init --recursive &&
- test_submodule_content sub1 origin/valid_sub1
- )
- '
}
# Test that submodule contents are correctly updated when switching
diff --git a/t/perf/p4211-line-log.sh b/t/perf/p4211-line-log.sh
index b7ff68d4fa..e0ed05907c 100755
--- a/t/perf/p4211-line-log.sh
+++ b/t/perf/p4211-line-log.sh
@@ -31,4 +31,8 @@ test_perf 'git log -L (renames on)' '
git log -M -L 1:"$file" >/dev/null
'
+test_perf 'git log --oneline --raw --parents' '
+ git log --oneline --raw --parents >/dev/null
+'
+
test_done
diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh
new file mode 100755
index 0000000000..16d1bf72e5
--- /dev/null
+++ b/t/perf/p7519-fsmonitor.sh
@@ -0,0 +1,184 @@
+#!/bin/sh
+
+test_description="Test core.fsmonitor"
+
+. ./perf-lib.sh
+
+#
+# Performance test for the fsmonitor feature which enables git to talk to a
+# file system change monitor and avoid having to scan the working directory
+# for new or modified files.
+#
+# By default, the performance test will utilize the Watchman file system
+# monitor if it is installed. If Watchman is not installed, it will use a
+# dummy integration script that does not report any new or modified files.
+# The dummy script has very little overhead which provides optimistic results.
+#
+# The performance test will also use the untracked cache feature if it is
+# available as fsmonitor uses it to speed up scanning for untracked files.
+#
+# There are 3 environment variables that can be used to alter the default
+# behavior of the performance test:
+#
+# GIT_PERF_7519_UNTRACKED_CACHE: used to configure core.untrackedCache
+# GIT_PERF_7519_SPLIT_INDEX: used to configure core.splitIndex
+# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor
+#
+# The big win for using fsmonitor is the elimination of the need to scan the
+# working directory looking for changed and untracked files. If the file
+# information is all cached in RAM, the benefits are reduced.
+#
+# GIT_PERF_7519_DROP_CACHE: if set, the OS caches are dropped between tests
+#
+
+test_perf_large_repo
+test_checkout_worktree
+
+test_lazy_prereq UNTRACKED_CACHE '
+ { git update-index --test-untracked-cache; ret=$?; } &&
+ test $ret -ne 1
+'
+
+test_lazy_prereq WATCHMAN '
+ { command -v watchman >/dev/null 2>&1; ret=$?; } &&
+ test $ret -ne 1
+'
+
+if test_have_prereq WATCHMAN
+then
+ # Convert unix style paths to escaped Windows style paths for Watchman
+ case "$(uname -s)" in
+ MSYS_NT*)
+ GIT_WORK_TREE="$(cygpath -aw "$PWD" | sed 's,\\,/,g')"
+ ;;
+ *)
+ GIT_WORK_TREE="$PWD"
+ ;;
+ esac
+fi
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"
+then
+ # When using GIT_PERF_7519_DROP_CACHE, GIT_PERF_REPEAT_COUNT must be 1 to
+ # generate valid results. Otherwise the caching that happens for the nth
+ # run will negate the validity of the comparisons.
+ if test "$GIT_PERF_REPEAT_COUNT" -ne 1
+ then
+ echo "warning: Setting GIT_PERF_REPEAT_COUNT=1" >&2
+ GIT_PERF_REPEAT_COUNT=1
+ fi
+fi
+
+test_expect_success "setup for fsmonitor" '
+ # set untrackedCache depending on the environment
+ if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
+ then
+ git config core.untrackedCache "$GIT_PERF_7519_UNTRACKED_CACHE"
+ else
+ if test_have_prereq UNTRACKED_CACHE
+ then
+ git config core.untrackedCache true
+ else
+ git config core.untrackedCache false
+ fi
+ fi &&
+
+ # set core.splitindex depending on the environment
+ if test -n "$GIT_PERF_7519_SPLIT_INDEX"
+ then
+ git config core.splitIndex "$GIT_PERF_7519_SPLIT_INDEX"
+ fi &&
+
+ # set INTEGRATION_SCRIPT depending on the environment
+ if test -n "$GIT_PERF_7519_FSMONITOR"
+ then
+ INTEGRATION_SCRIPT="$GIT_PERF_7519_FSMONITOR"
+ else
+ #
+ # Choose integration script based on existence of Watchman.
+ # If Watchman exists, watch the work tree and attempt a query.
+ # If everything succeeds, use Watchman integration script,
+ # else fall back to an empty integration script.
+ #
+ mkdir .git/hooks &&
+ if test_have_prereq WATCHMAN
+ then
+ INTEGRATION_SCRIPT=".git/hooks/fsmonitor-watchman" &&
+ cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" &&
+ watchman watch "$GIT_WORK_TREE" &&
+ watchman watch-list | grep -q -F "$GIT_WORK_TREE"
+ else
+ INTEGRATION_SCRIPT=".git/hooks/fsmonitor-empty" &&
+ write_script "$INTEGRATION_SCRIPT"<<-\EOF
+ EOF
+ fi
+ fi &&
+
+ git config core.fsmonitor "$INTEGRATION_SCRIPT" &&
+ git update-index --fsmonitor
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status -uno
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status -uall
+'
+
+test_expect_success "setup without fsmonitor" '
+ unset INTEGRATION_SCRIPT &&
+ git config --unset core.fsmonitor &&
+ git update-index --no-fsmonitor
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status -uno
+'
+
+if test -n "$GIT_PERF_7519_DROP_CACHE"; then
+ test-drop-caches
+fi
+
+test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
+ git status -uall
+'
+
+if test_have_prereq WATCHMAN
+then
+ watchman watch-del "$GIT_WORK_TREE" >/dev/null 2>&1 &&
+
+ # Work around Watchman bug on Windows where it holds on to handles
+ # preventing the removal of the trash directory
+ watchman shutdown-server >/dev/null 2>&1
+fi
+
+test_done
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 1aa5093f36..7fd87dd544 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -20,6 +20,31 @@ modification *should* take notice and update the test vectors here.
. ./test-lib.sh
+try_local_x () {
+ local x="local" &&
+ echo "$x"
+}
+
+# This test is an experiment to check whether any Git users are using
+# Shells that don't support the "local" keyword. "local" is not
+# POSIX-standard, but it is very widely supported by POSIX-compliant
+# shells, and if it doesn't cause problems for people, we would like
+# to be able to use it in Git code.
+#
+# For now, this is the only test that requires "local". If your shell
+# fails this test, you can ignore the failure, but please report the
+# problem to the Git mailing list <git@vger.kernel.org>, as it might
+# convince us to continue avoiding the use of "local".
+test_expect_success 'verify that the running shell supports "local"' '
+ x="notlocal" &&
+ echo "local" >expected1 &&
+ try_local_x >actual1 &&
+ test_cmp expected1 actual1 &&
+ echo "notlocal" >expected2 &&
+ echo "$x" >actual2 &&
+ test_cmp expected2 actual2
+'
+
################################################################
# git init has been done in an empty repository.
# make sure it is empty.
diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl
index ad685d92f8..6fd7fa476b 100644
--- a/t/t0021/rot13-filter.pl
+++ b/t/t0021/rot13-filter.pl
@@ -30,9 +30,12 @@
# to the "list_available_blobs" response.
#
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
use strict;
use warnings;
use IO::File;
+use Git::Packet;
my $MAX_PACKET_CONTENT_SIZE = 65516;
my $log_file = shift @ARGV;
@@ -55,89 +58,30 @@ sub rot13 {
return $str;
}
-sub packet_bin_read {
- my $buffer;
- my $bytes_read = read STDIN, $buffer, 4;
- if ( $bytes_read == 0 ) {
- # EOF - Git stopped talking to us!
- print $debug "STOP\n";
- exit();
- }
- elsif ( $bytes_read != 4 ) {
- die "invalid packet: '$buffer'";
- }
- my $pkt_size = hex($buffer);
- if ( $pkt_size == 0 ) {
- return ( 1, "" );
- }
- elsif ( $pkt_size > 4 ) {
- my $content_size = $pkt_size - 4;
- $bytes_read = read STDIN, $buffer, $content_size;
- if ( $bytes_read != $content_size ) {
- die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
- }
- return ( 0, $buffer );
- }
- else {
- die "invalid packet size: $pkt_size";
- }
-}
-
-sub packet_txt_read {
- my ( $res, $buf ) = packet_bin_read();
- unless ( $buf eq '' or $buf =~ s/\n$// ) {
- die "A non-binary line MUST be terminated by an LF.";
- }
- return ( $res, $buf );
-}
-
-sub packet_bin_write {
- my $buf = shift;
- print STDOUT sprintf( "%04x", length($buf) + 4 );
- print STDOUT $buf;
- STDOUT->flush();
-}
-
-sub packet_txt_write {
- packet_bin_write( $_[0] . "\n" );
-}
-
-sub packet_flush {
- print STDOUT sprintf( "%04x", 0 );
- STDOUT->flush();
-}
-
print $debug "START\n";
$debug->flush();
-( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize";
-( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version";
-( packet_bin_read() eq ( 1, "" ) ) || die "bad version end";
+packet_initialize("git-filter", 2);
-packet_txt_write("git-filter-server");
-packet_txt_write("version=2");
-packet_flush();
+my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay");
+packet_check_and_write_capabilities(\%remote_caps, @capabilities);
-( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability";
-( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability";
-( packet_txt_read() eq ( 0, "capability=delay" ) ) || die "bad capability";
-( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end";
-
-foreach (@capabilities) {
- packet_txt_write( "capability=" . $_ );
-}
-packet_flush();
print $debug "init handshake complete\n";
$debug->flush();
while (1) {
- my ( $command ) = packet_txt_read() =~ /^command=(.+)$/;
+ my ( $res, $command ) = packet_required_key_val_read("command");
+ if ( $res == -1 ) {
+ print $debug "STOP\n";
+ exit();
+ }
print $debug "IN: $command";
$debug->flush();
if ( $command eq "list_available_blobs" ) {
# Flush
- packet_bin_read();
+ packet_compare_lists([1, ""], packet_bin_read()) ||
+ die "bad list_available_blobs end";
foreach my $pathname ( sort keys %DELAY ) {
if ( $DELAY{$pathname}{"requested"} >= 1 ) {
@@ -161,16 +105,14 @@ while (1) {
$debug->flush();
packet_txt_write("status=success");
packet_flush();
- }
- else {
- my ( $pathname ) = packet_txt_read() =~ /^pathname=(.+)$/;
+ } else {
+ my ( $res, $pathname ) = packet_required_key_val_read("pathname");
+ if ( $res == -1 ) {
+ die "unexpected EOF while expecting pathname";
+ }
print $debug " $pathname";
$debug->flush();
- if ( $pathname eq "" ) {
- die "bad pathname '$pathname'";
- }
-
# Read until flush
my ( $done, $buffer ) = packet_txt_read();
while ( $buffer ne '' ) {
@@ -184,6 +126,9 @@ while (1) {
( $done, $buffer ) = packet_txt_read();
}
+ if ( $done == -1 ) {
+ die "unexpected EOF after pathname '$pathname'";
+ }
my $input = "";
{
@@ -194,6 +139,9 @@ while (1) {
( $done, $buffer ) = packet_bin_read();
$input .= $buffer;
}
+ if ( $done == -1 ) {
+ die "unexpected EOF while reading input for '$pathname'";
+ }
print $debug " " . length($input) . " [OK] -- ";
$debug->flush();
}
@@ -201,17 +149,13 @@ while (1) {
my $output;
if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) {
$output = $DELAY{$pathname}{"output"}
- }
- elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
+ } elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
$output = "";
- }
- elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
+ } elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
$output = rot13($input);
- }
- elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
+ } elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
$output = rot13($input);
- }
- else {
+ } else {
die "bad command '$command'";
}
@@ -220,25 +164,21 @@ while (1) {
$debug->flush();
packet_txt_write("status=error");
packet_flush();
- }
- elsif ( $pathname eq "abort.r" ) {
+ } elsif ( $pathname eq "abort.r" ) {
print $debug "[ABORT]\n";
$debug->flush();
packet_txt_write("status=abort");
packet_flush();
- }
- elsif ( $command eq "smudge" and
+ } elsif ( $command eq "smudge" and
exists $DELAY{$pathname} and
- $DELAY{$pathname}{"requested"} == 1
- ) {
+ $DELAY{$pathname}{"requested"} == 1 ) {
print $debug "[DELAYED]\n";
$debug->flush();
packet_txt_write("status=delayed");
packet_flush();
$DELAY{$pathname}{"requested"} = 2;
$DELAY{$pathname}{"output"} = $output;
- }
- else {
+ } else {
packet_txt_write("status=success");
packet_flush();
@@ -258,8 +198,7 @@ while (1) {
print $debug ".";
if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
$output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
- }
- else {
+ } else {
$output = "";
}
}
diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh
new file mode 100755
index 0000000000..9d9e02a211
--- /dev/null
+++ b/t/t0025-crlf-renormalize.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+test_description='CRLF renormalization'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ git config core.autocrlf false &&
+ printf "LINEONE\nLINETWO\nLINETHREE\n" >LF.txt &&
+ printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >CRLF.txt &&
+ printf "LINEONE\r\nLINETWO\nLINETHREE\n" >CRLF_mix_LF.txt &&
+ git add . &&
+ git commit -m initial
+'
+
+test_expect_success 'renormalize CRLF in repo' '
+ echo "*.txt text=auto" >.gitattributes &&
+ git add --renormalize "*.txt" &&
+ cat >expect <<-\EOF &&
+ i/lf w/crlf attr/text=auto CRLF.txt
+ i/lf w/lf attr/text=auto LF.txt
+ i/lf w/mixed attr/text=auto CRLF_mix_LF.txt
+ EOF
+ git ls-files --eol |
+ sed -e "s/ / /g" -e "s/ */ /g" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index deb3ae7813..68108d956a 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -315,7 +315,7 @@ test_expect_success 'setup master' '
echo >.gitattributes &&
git checkout -b master &&
git add .gitattributes &&
- git commit -m "add .gitattributes" "" &&
+ git commit -m "add .gitattributes" . &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\nLINETWO\nLINETHREE" >LF &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\r\nLINETHREE" >CRLF &&
printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\r\nLINETWO\nLINETHREE" >CRLF_mix_LF &&
diff --git a/t/t1409-avoid-packing-refs.sh b/t/t1409-avoid-packing-refs.sh
new file mode 100755
index 0000000000..e5cb8a252d
--- /dev/null
+++ b/t/t1409-avoid-packing-refs.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+test_description='avoid rewriting packed-refs unnecessarily'
+
+. ./test-lib.sh
+
+# Add an identifying mark to the packed-refs file header line. This
+# shouldn't upset readers, and it should be omitted if the file is
+# ever rewritten.
+mark_packed_refs () {
+ sed -e "s/^\(#.*\)/\1 t1409 /" <.git/packed-refs >.git/packed-refs.new &&
+ mv .git/packed-refs.new .git/packed-refs
+}
+
+# Verify that the packed-refs file is still marked.
+check_packed_refs_marked () {
+ grep -q '^#.* t1409 ' .git/packed-refs
+}
+
+test_expect_success 'setup' '
+ git commit --allow-empty -m "Commit A" &&
+ A=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m "Commit B" &&
+ B=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m "Commit C" &&
+ C=$(git rev-parse HEAD)
+'
+
+test_expect_success 'do not create packed-refs file gratuitously' '
+ test_must_fail test -f .git/packed-refs &&
+ git update-ref refs/heads/foo $A &&
+ test_must_fail test -f .git/packed-refs &&
+ git update-ref refs/heads/foo $B &&
+ test_must_fail test -f .git/packed-refs &&
+ git update-ref refs/heads/foo $C $B &&
+ test_must_fail test -f .git/packed-refs &&
+ git update-ref -d refs/heads/foo &&
+ test_must_fail test -f .git/packed-refs
+'
+
+test_expect_success 'check that marking the packed-refs file works' '
+ git for-each-ref >expected &&
+ git pack-refs --all &&
+ mark_packed_refs &&
+ check_packed_refs_marked &&
+ git for-each-ref >actual &&
+ test_cmp expected actual &&
+ git pack-refs --all &&
+ test_must_fail check_packed_refs_marked &&
+ git for-each-ref >actual2 &&
+ test_cmp expected actual2
+'
+
+test_expect_success 'leave packed-refs untouched on update of packed' '
+ git update-ref refs/heads/packed-update $A &&
+ git pack-refs --all &&
+ mark_packed_refs &&
+ git update-ref refs/heads/packed-update $B &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on checked update of packed' '
+ git update-ref refs/heads/packed-checked-update $A &&
+ git pack-refs --all &&
+ mark_packed_refs &&
+ git update-ref refs/heads/packed-checked-update $B $A &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on verify of packed' '
+ git update-ref refs/heads/packed-verify $A &&
+ git pack-refs --all &&
+ mark_packed_refs &&
+ echo "verify refs/heads/packed-verify $A" | git update-ref --stdin &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'touch packed-refs on delete of packed' '
+ git update-ref refs/heads/packed-delete $A &&
+ git pack-refs --all &&
+ mark_packed_refs &&
+ git update-ref -d refs/heads/packed-delete &&
+ test_must_fail check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on update of loose' '
+ git pack-refs --all &&
+ git update-ref refs/heads/loose-update $A &&
+ mark_packed_refs &&
+ git update-ref refs/heads/loose-update $B &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on checked update of loose' '
+ git pack-refs --all &&
+ git update-ref refs/heads/loose-checked-update $A &&
+ mark_packed_refs &&
+ git update-ref refs/heads/loose-checked-update $B $A &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on verify of loose' '
+ git pack-refs --all &&
+ git update-ref refs/heads/loose-verify $A &&
+ mark_packed_refs &&
+ echo "verify refs/heads/loose-verify $A" | git update-ref --stdin &&
+ check_packed_refs_marked
+'
+
+test_expect_success 'leave packed-refs untouched on delete of loose' '
+ git pack-refs --all &&
+ git update-ref refs/heads/loose-delete $A &&
+ mark_packed_refs &&
+ git update-ref -d refs/heads/loose-delete &&
+ check_packed_refs_marked
+'
+
+test_done
diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index e88349c8a0..c7878a60ed 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -331,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '
grep "fatal: invalid ref format: ~a" err
'
+test_expect_success 'branch rejects HEAD as a branch name' '
+ test_must_fail git branch HEAD HEAD^ &&
+ test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'checkout -b rejects HEAD as a branch name' '
+ test_must_fail git checkout -B HEAD HEAD^ &&
+ test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'update-ref can operate on refs/heads/HEAD' '
+ git update-ref refs/heads/HEAD HEAD^ &&
+ git show-ref refs/heads/HEAD &&
+ git update-ref -d refs/heads/HEAD &&
+ test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -d can remove refs/heads/HEAD' '
+ git update-ref refs/heads/HEAD HEAD^ &&
+ git branch -d HEAD &&
+ test_must_fail git show-ref refs/heads/HEAD
+'
+
+test_expect_success 'branch -m can rename refs/heads/HEAD' '
+ git update-ref refs/heads/HEAD HEAD^ &&
+ git branch -m HEAD tail &&
+ test_must_fail git show-ref refs/heads/HEAD &&
+ git show-ref refs/heads/tail
+'
+
+test_expect_success 'branch -d can remove refs/heads/-dash' '
+ git update-ref refs/heads/-dash HEAD^ &&
+ git branch -d -- -dash &&
+ test_must_fail git show-ref refs/heads/-dash
+'
+
+test_expect_success 'branch -m can rename refs/heads/-dash' '
+ git update-ref refs/heads/-dash HEAD^ &&
+ git branch -m -- -dash dash &&
+ test_must_fail git show-ref refs/heads/-dash &&
+ git show-ref refs/heads/dash
+'
+
test_done
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index 22f69a410b..af9b847761 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -6,6 +6,7 @@ test_description='split index mode tests'
# We need total control of index splitting here
sane_unset GIT_TEST_SPLIT_INDEX
+sane_unset GIT_FSMONITOR_TEST
test_expect_success 'enable split index' '
git config splitIndex.maxPercentChange 100 &&
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index baef2d6924..9c1bf6eb3d 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -176,7 +176,7 @@ git rev-parse refs/notes/z > pre_merge_z
test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
git update-ref refs/notes/m refs/notes/y &&
git config core.notesRef refs/notes/m &&
- test_must_fail git notes merge z >output &&
+ test_must_fail git notes merge z >output 2>&1 &&
# Output should point to where to resolve conflicts
test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
# Inspect merge conflicts
@@ -379,7 +379,7 @@ git rev-parse refs/notes/z > pre_merge_z
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
git update-ref refs/notes/m refs/notes/y &&
git config core.notesRef refs/notes/m &&
- test_must_fail git notes merge z >output &&
+ test_must_fail git notes merge z >output 2>&1 &&
# Output should point to where to resolve conflicts
test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
# Inspect merge conflicts
@@ -413,7 +413,7 @@ git rev-parse refs/notes/y > pre_merge_y
git rev-parse refs/notes/z > pre_merge_z
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
- test_must_fail git notes merge z >output &&
+ test_must_fail git notes merge z >output 2>&1 &&
# Output should point to where to resolve conflicts
test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
# Inspect merge conflicts
@@ -494,7 +494,7 @@ cp expect_log_y expect_log_m
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
git update-ref refs/notes/m refs/notes/y &&
- test_must_fail git notes merge z >output &&
+ test_must_fail git notes merge z >output 2>&1 &&
# Output should point to where to resolve conflicts
test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
# Inspect merge conflicts
diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh
index b9c3bc2487..10bfc8b947 100755
--- a/t/t3320-notes-merge-worktrees.sh
+++ b/t/t3320-notes-merge-worktrees.sh
@@ -61,7 +61,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
(
cd worktree2 &&
git config core.notesRef refs/notes/x &&
- test_must_fail git notes merge z 2>&1 >out &&
+ test_must_fail git notes merge z >out 2>&1 &&
test_i18ngrep "Automatic notes merge failed" out &&
grep -v "A notes merge into refs/notes/x is already in-progress in" out
) &&
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index f5fd15e559..8ac58d5ea5 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -255,4 +255,26 @@ test_expect_success 'rebase commit with an ancient timestamp' '
grep "author .* 34567 +0600$" actual
'
+test_expect_success 'rebase with "From " line in commit message' '
+ git checkout -b preserve-from master~1 &&
+ cat >From_.msg <<EOF &&
+Somebody embedded an mbox in a commit message
+
+This is from so-and-so:
+
+From a@b Mon Sep 17 00:00:00 2001
+From: John Doe <nobody@example.com>
+Date: Sat, 11 Nov 2017 00:00:00 +0000
+Subject: not this message
+
+something
+EOF
+ >From_ &&
+ git add From_ &&
+ git commit -F From_.msg &&
+ git rebase master &&
+ git log -1 --pretty=format:%B >out &&
+ test_cmp From_.msg out
+'
+
test_done
diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh
index 6863b7bb6f..ce48c4fcca 100755
--- a/t/t3512-cherry-pick-submodule.sh
+++ b/t/t3512-cherry-pick-submodule.sh
@@ -10,4 +10,40 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
test_submodule_switch "git cherry-pick"
+test_expect_success 'unrelated submodule/file conflict is ignored' '
+ test_create_repo sub &&
+
+ touch sub/file &&
+ git -C sub add file &&
+ git -C sub commit -m "add a file in a submodule" &&
+
+ test_create_repo a_repo &&
+ (
+ cd a_repo &&
+ >a_file &&
+ git add a_file &&
+ git commit -m "add a file" &&
+
+ git branch test &&
+ git checkout test &&
+
+ mkdir sub &&
+ >sub/content &&
+ git add sub/content &&
+ git commit -m "add a regular folder with name sub" &&
+
+ echo "123" >a_file &&
+ git add a_file &&
+ git commit -m "modify a file" &&
+
+ git checkout master &&
+
+ git submodule add ../sub sub &&
+ git submodule update sub &&
+ git commit -m "add a submodule info folder with name sub" &&
+
+ git cherry-pick test
+ )
+'
+
test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index ab5500db44..46f15169f5 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -858,9 +858,8 @@ test_expect_success 'rm files with two different errors' '
test_i18ncmp expect actual
'
-test_expect_success 'rm empty string should invoke warning' '
- git rm -rf "" 2>output &&
- test_i18ngrep "warning: empty strings" output
+test_expect_success 'rm empty string should fail' '
+ test_must_fail git rm -rf ""
'
test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 0aae21d698..2748805642 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -331,9 +331,8 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file out
test_i18ncmp expect.err actual.err
'
-test_expect_success 'git add empty string should invoke warning' '
- git add "" 2>output &&
- test_i18ngrep "warning: empty strings" output
+test_expect_success 'git add empty string should fail' '
+ test_must_fail git add ""
'
test_expect_success 'git add --chmod=[+-]x stages correctly' '
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 6c9a93b734..559a7541a8 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -106,6 +106,8 @@ test_expect_success 'another test, without options' '
git diff -w -b --ignore-space-at-eol >out &&
test_cmp expect out &&
+ git diff -w --ignore-cr-at-eol >out &&
+ test_cmp expect out &&
tr "Q_" "\015 " <<-\EOF >expect &&
diff --git a/x b/x
@@ -128,6 +130,9 @@ test_expect_success 'another test, without options' '
git diff -b --ignore-space-at-eol >out &&
test_cmp expect out &&
+ git diff -b --ignore-cr-at-eol >out &&
+ test_cmp expect out &&
+
tr "Q_" "\015 " <<-\EOF >expect &&
diff --git a/x b/x
index d99af23..22d9f73 100644
@@ -145,6 +150,29 @@ test_expect_success 'another test, without options' '
CR at end
EOF
git diff --ignore-space-at-eol >out &&
+ test_cmp expect out &&
+
+ git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
+ test_cmp expect out &&
+
+ tr "Q_" "\015 " <<-\EOF >expect &&
+ diff --git a/x b/x
+ index_d99af23..22d9f73 100644
+ --- a/x
+ +++ b/x
+ @@ -1,6 +1,6 @@
+ -whitespace at beginning
+ -whitespace change
+ -whitespace in the middle
+ -whitespace at end
+ +_ whitespace at beginning
+ +whitespace_ _change
+ +white space in the middle
+ +whitespace at end__
+ unchanged line
+ CR at end
+ EOF
+ git diff --ignore-cr-at-eol >out &&
test_cmp expect out
'
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
index 3e6b485ecb..2d76a971c4 100755
--- a/t/t4051-diff-function-context.sh
+++ b/t/t4051-diff-function-context.sh
@@ -85,6 +85,10 @@ test_expect_success 'setup' '
check_diff changed_hello 'changed function'
+test_expect_success ' context includes comment' '
+ grep "^ .*Hello comment" changed_hello.diff
+'
+
test_expect_success ' context includes begin' '
grep "^ .*Begin of hello" changed_hello.diff
'
diff --git a/t/t4051/hello.c b/t/t4051/hello.c
index 63b1a1e4ef..73e767e178 100644
--- a/t/t4051/hello.c
+++ b/t/t4051/hello.c
@@ -1,4 +1,7 @@
+/*
+ * Hello comment.
+ */
static void hello(void) // Begin of hello
{
/*
diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh
index 9e29b5262d..ac72eeaf27 100755
--- a/t/t4107-apply-ignore-whitespace.sh
+++ b/t/t4107-apply-ignore-whitespace.sh
@@ -178,4 +178,18 @@ test_expect_success 'patch5 fails (--no-ignore-whitespace)' '
test_must_fail git apply --no-ignore-whitespace patch5.patch
'
+test_expect_success 'apply --ignore-space-change --inaccurate-eof' '
+ echo 1 >file &&
+ git apply --ignore-space-change --inaccurate-eof <<-\EOF &&
+ diff --git a/file b/file
+ --- a/file
+ +++ b/file
+ @@ -1 +1 @@
+ -1
+ +2
+ EOF
+ printf 2 >expect &&
+ test_cmp expect file
+'
+
test_done
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index ded8f98dbe..c19d8dbc9d 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -165,4 +165,49 @@ test_expect_success 'git pull --allow-unrelated-histories' '
)
'
+test_expect_success 'git pull does not add a sign-off line' '
+ test_when_finished "rm -fr src dst actual" &&
+ git init src &&
+ test_commit -C src one &&
+ git clone src dst &&
+ test_commit -C src two &&
+ git -C dst pull --no-ff &&
+ git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'git pull --no-signoff does not add sign-off line' '
+ test_when_finished "rm -fr src dst actual" &&
+ git init src &&
+ test_commit -C src one &&
+ git clone src dst &&
+ test_commit -C src two &&
+ git -C dst pull --no-signoff --no-ff &&
+ git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+ test_must_be_empty actual
+'
+
+test_expect_success 'git pull --signoff add a sign-off line' '
+ test_when_finished "rm -fr src dst expected actual" &&
+ echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
+ git init src &&
+ test_commit -C src one &&
+ git clone src dst &&
+ test_commit -C src two &&
+ git -C dst pull --signoff --no-ff &&
+ git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git pull --no-signoff flag cancels --signoff flag' '
+ test_when_finished "rm -fr src dst actual" &&
+ git init src &&
+ test_commit -C src one &&
+ git clone src dst &&
+ test_commit -C src two &&
+ git -C dst pull --signoff --no-signoff --no-ff &&
+ git -C dst show -s --pretty="format:%(trailers)" HEAD >actual &&
+ test_must_be_empty actual
+'
+
test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
index 42251f7f3a..a552ad4ead 100755
--- a/t/t5526-fetch-submodules.sh
+++ b/t/t5526-fetch-submodules.sh
@@ -478,7 +478,47 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
git fetch >../actual.out 2>../actual.err
) &&
! test -s actual.out &&
- test_i18ncmp expect.err actual.err
+ test_i18ncmp expect.err actual.err &&
+ (
+ cd submodule &&
+ git checkout -q master
+ )
+'
+
+test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodule entry" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules
+ ) &&
+ add_upstream_commit &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git rm .gitmodules &&
+ git commit -m "new submodule without .gitmodules" &&
+ printf "" >expect.out &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." >expect.err.2 &&
+ echo " $head1..$head2 master -> origin/master" >>expect.err.2 &&
+ head -3 expect.err >>expect.err.2 &&
+ (
+ cd downstream &&
+ rm .gitmodules &&
+ git config fetch.recurseSubmodules on-demand &&
+ # fake submodule configuration to avoid skipping submodule handling
+ git config -f .gitmodules submodule.fake.path fake &&
+ git config -f .gitmodules submodule.fake.url fakeurl &&
+ git add .gitmodules &&
+ git config --unset submodule.submodule.url &&
+ git fetch >../actual.out 2>../actual.err &&
+ # cleanup
+ git config --unset fetch.recurseSubmodules &&
+ git reset --hard
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err.2 actual.err &&
+ git checkout HEAD^ -- .gitmodules &&
+ git add .gitmodules &&
+ git commit -m "new submodule restored .gitmodules"
'
test_expect_success 'fetching submodules respects parallel settings' '
@@ -530,4 +570,39 @@ test_expect_success 'fetching submodule into a broken repository' '
test_must_fail git -C dst fetch --recurse-submodules
'
+test_expect_success "fetch new commits when submodule got renamed" '
+ git clone . downstream_rename &&
+ (
+ cd downstream_rename &&
+ git submodule update --init &&
+# NEEDSWORK: we omitted --recursive for the submodule update here since
+# that does not work. See test 7001 for mv "moving nested submodules"
+# for details. Once that is fixed we should add the --recursive option
+# here.
+ git checkout -b rename &&
+ git mv submodule submodule_renamed &&
+ (
+ cd submodule_renamed &&
+ git checkout -b rename_sub &&
+ echo a >a &&
+ git add a &&
+ git commit -ma &&
+ git push origin rename_sub &&
+ git rev-parse HEAD >../../expect
+ ) &&
+ git add submodule_renamed &&
+ git commit -m "update renamed submodule" &&
+ git push origin rename
+ ) &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules=on-demand &&
+ (
+ cd submodule &&
+ git rev-parse origin/rename_sub >../../actual
+ )
+ ) &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
index 90a4b0d2fe..463783789c 100755
--- a/t/t5545-push-options.sh
+++ b/t/t5545-push-options.sh
@@ -140,6 +140,83 @@ test_expect_success 'push options and submodules' '
test_cmp expect parent_upstream/.git/hooks/post-receive.push_options
'
+test_expect_success 'default push option' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git -c push.pushOption=default push up master
+ ) &&
+ test_refs master master &&
+ echo "default" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'two default push options' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git -c push.pushOption=default1 -c push.pushOption=default2 push up master
+ ) &&
+ test_refs master master &&
+ printf "default1\ndefault2\n" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'push option from command line overrides from-config push option' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git -c push.pushOption=default push --push-option=manual up master
+ ) &&
+ test_refs master master &&
+ echo "manual" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'empty value of push.pushOption in config clears the list' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git -c push.pushOption=default1 -c push.pushOption= -c push.pushOption=default2 push up master
+ ) &&
+ test_refs master master &&
+ echo "default2" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'invalid push option in config' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ test_must_fail git -c push.pushOption push up master
+ ) &&
+ test_refs master HEAD@{1}
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 8c2c6eaef8..f84ff941c3 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -894,4 +894,21 @@ test_expect_success 'bisect start takes options and revs in any order' '
test_cmp expected actual
'
+test_expect_success 'git bisect reset cleans bisection state properly' '
+ git bisect reset &&
+ git bisect start &&
+ git bisect good $HASH1 &&
+ git bisect bad $HASH4 &&
+ git bisect reset &&
+ test -z "$(git for-each-ref "refs/bisect/*")" &&
+ test_path_is_missing "$GIT_DIR/BISECT_EXPECTED_REV" &&
+ test_path_is_missing "$GIT_DIR/BISECT_ANCESTORS_OK" &&
+ test_path_is_missing "$GIT_DIR/BISECT_LOG" &&
+ test_path_is_missing "$GIT_DIR/BISECT_RUN" &&
+ test_path_is_missing "$GIT_DIR/BISECT_TERMS" &&
+ test_path_is_missing "$GIT_DIR/head-name" &&
+ test_path_is_missing "$GIT_DIR/BISECT_HEAD" &&
+ test_path_is_missing "$GIT_DIR/BISECT_START"
+'
+
test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 3aa534933e..c128dfc579 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -766,4 +766,36 @@ test_expect_success 'Verify usage of %(symref:rstrip) atom' '
test_cmp expected actual
'
+test_expect_success ':remotename and :remoteref' '
+ git init remote-tests &&
+ (
+ cd remote-tests &&
+ test_commit initial &&
+ git remote add from fifth.coffee:blub &&
+ git config branch.master.remote from &&
+ git config branch.master.merge refs/heads/stable &&
+ git remote add to southridge.audio:repo &&
+ git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+ git config branch.master.pushRemote to &&
+ for pair in "%(upstream)=refs/remotes/from/stable" \
+ "%(upstream:remotename)=from" \
+ "%(upstream:remoteref)=refs/heads/stable" \
+ "%(push)=refs/remotes/to/pushed/master" \
+ "%(push:remotename)=to" \
+ "%(push:remoteref)=refs/heads/pushed/master"
+ do
+ echo "${pair#*=}" >expect &&
+ git for-each-ref --format="${pair%=*}" \
+ refs/heads/master >actual &&
+ test_cmp expect actual
+ done &&
+ git branch push-simple &&
+ git config branch.push-simple.pushRemote from &&
+ actual="$(git for-each-ref \
+ --format="%(push:remotename),%(push:remoteref)" \
+ refs/heads/push-simple)" &&
+ test from, = "$actual"
+ )
+'
+
test_done
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index 865168ec6a..f5f46a95b4 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -214,6 +214,44 @@ test_expect_success TTY 'git tag as alias respects pager.tag with -l' '
! test -e paginated.out
'
+test_expect_success TTY 'git branch defaults to paging' '
+ rm -f paginated.out &&
+ test_terminal git branch &&
+ test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects pager.branch' '
+ rm -f paginated.out &&
+ test_terminal git -c pager.branch=false branch &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch respects --no-pager' '
+ rm -f paginated.out &&
+ test_terminal git --no-pager branch &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git branch --edit-description ignores pager.branch' '
+ rm -f paginated.out editor.used &&
+ write_script editor <<-\EOF &&
+ echo "New description" >"$1"
+ touch editor.used
+ EOF
+ EDITOR=./editor test_terminal git -c pager.branch branch --edit-description &&
+ ! test -e paginated.out &&
+ test -e editor.used
+'
+
+test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
+ rm -f paginated.out &&
+ git branch other &&
+ test_when_finished "git branch -D other" &&
+ test_terminal git -c pager.branch branch --set-upstream-to=other &&
+ test_when_finished "git branch --unset-upstream" &&
+ ! test -e paginated.out
+'
+
# A colored commit log will begin with an appropriate ANSI escape
# for the first color; the text "commit" comes later.
colorful() {
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
new file mode 100755
index 0000000000..eb2d13bbcf
--- /dev/null
+++ b/t/t7519-status-fsmonitor.sh
@@ -0,0 +1,317 @@
+#!/bin/sh
+
+test_description='git status with file system watcher'
+
+. ./test-lib.sh
+
+#
+# To run the entire git test suite using fsmonitor:
+#
+# copy t/t7519/fsmonitor-all to a location in your path and then set
+# GIT_FSMONITOR_TEST=fsmonitor-all and run your tests.
+#
+
+# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE'
+# "git update-index --fsmonitor" can be used to get the extension written
+# before testing the results.
+
+clean_repo () {
+ git reset --hard HEAD &&
+ git clean -fd
+}
+
+dirty_repo () {
+ : >untracked &&
+ : >dir1/untracked &&
+ : >dir2/untracked &&
+ echo 1 >modified &&
+ echo 2 >dir1/modified &&
+ echo 3 >dir2/modified &&
+ echo 4 >new &&
+ echo 5 >dir1/new &&
+ echo 6 >dir2/new
+}
+
+write_integration_script () {
+ write_script .git/hooks/fsmonitor-test<<-\EOF
+ if test "$#" -ne 2
+ then
+ echo "$0: exactly 2 arguments expected"
+ exit 2
+ fi
+ if test "$1" != 1
+ then
+ echo "Unsupported core.fsmonitor hook version." >&2
+ exit 1
+ fi
+ printf "untracked\0"
+ printf "dir1/untracked\0"
+ printf "dir2/untracked\0"
+ printf "modified\0"
+ printf "dir1/modified\0"
+ printf "dir2/modified\0"
+ printf "new\0"
+ printf "dir1/new\0"
+ printf "dir2/new\0"
+ EOF
+}
+
+test_lazy_prereq UNTRACKED_CACHE '
+ { git update-index --test-untracked-cache; ret=$?; } &&
+ test $ret -ne 1
+'
+
+test_expect_success 'setup' '
+ mkdir -p .git/hooks &&
+ : >tracked &&
+ : >modified &&
+ mkdir dir1 &&
+ : >dir1/tracked &&
+ : >dir1/modified &&
+ mkdir dir2 &&
+ : >dir2/tracked &&
+ : >dir2/modified &&
+ git -c core.fsmonitor= add . &&
+ git -c core.fsmonitor= commit -m initial &&
+ git config core.fsmonitor .git/hooks/fsmonitor-test &&
+ cat >.gitignore <<-\EOF
+ .gitignore
+ expect*
+ actual*
+ marker*
+ EOF
+'
+
+# test that the fsmonitor extension is off by default
+test_expect_success 'fsmonitor extension is off by default' '
+ test-dump-fsmonitor >actual &&
+ grep "^no fsmonitor" actual
+'
+
+# test that "update-index --fsmonitor" adds the fsmonitor extension
+test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' '
+ git update-index --fsmonitor &&
+ test-dump-fsmonitor >actual &&
+ grep "^fsmonitor last update" actual
+'
+
+# test that "update-index --no-fsmonitor" removes the fsmonitor extension
+test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' '
+ git update-index --no-fsmonitor &&
+ test-dump-fsmonitor >actual &&
+ grep "^no fsmonitor" actual
+'
+
+cat >expect <<EOF &&
+h dir1/modified
+H dir1/tracked
+h dir2/modified
+H dir2/tracked
+h modified
+H tracked
+EOF
+
+# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
+test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
+ git update-index --fsmonitor &&
+ git update-index --fsmonitor-valid dir1/modified &&
+ git update-index --fsmonitor-valid dir2/modified &&
+ git update-index --fsmonitor-valid modified &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+H dir1/tracked
+H dir2/modified
+H dir2/tracked
+H modified
+H tracked
+EOF
+
+# test that "update-index --no-fsmonitor-valid" clears the fsmonitor valid bit
+test_expect_success 'update-index --no-fsmonitor-valid" clears the fsmonitor valid bit' '
+ git update-index --no-fsmonitor-valid dir1/modified &&
+ git update-index --no-fsmonitor-valid dir2/modified &&
+ git update-index --no-fsmonitor-valid modified &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+H dir1/tracked
+H dir2/modified
+H dir2/tracked
+H modified
+H tracked
+EOF
+
+# test that all files returned by the script get flagged as invalid
+test_expect_success 'all files returned by integration script get flagged as invalid' '
+ write_integration_script &&
+ dirty_repo &&
+ git update-index --fsmonitor &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/new
+H dir1/tracked
+H dir2/modified
+h dir2/new
+H dir2/tracked
+H modified
+h new
+H tracked
+EOF
+
+# test that newly added files are marked valid
+test_expect_success 'newly added files are marked valid' '
+ git add new &&
+ git add dir1/new &&
+ git add dir2/new &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/new
+h dir1/tracked
+H dir2/modified
+h dir2/new
+h dir2/tracked
+H modified
+h new
+h tracked
+EOF
+
+# test that all unmodified files get marked valid
+test_expect_success 'all unmodified files get marked valid' '
+ # modified files result in update-index returning 1
+ test_must_fail git update-index --refresh --force-write-index &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF &&
+H dir1/modified
+h dir1/tracked
+h dir2/modified
+h dir2/tracked
+h modified
+h tracked
+EOF
+
+# test that *only* files returned by the integration script get flagged as invalid
+test_expect_success '*only* files returned by the integration script get flagged as invalid' '
+ write_script .git/hooks/fsmonitor-test<<-\EOF &&
+ printf "dir1/modified\0"
+ EOF
+ clean_repo &&
+ git update-index --refresh --force-write-index &&
+ echo 1 >modified &&
+ echo 2 >dir1/modified &&
+ echo 3 >dir2/modified &&
+ test_must_fail git update-index --refresh --force-write-index &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+# Ensure commands that call refresh_index() to move the index back in time
+# properly invalidate the fsmonitor cache
+test_expect_success 'refresh_index() invalidates fsmonitor cache' '
+ write_script .git/hooks/fsmonitor-test<<-\EOF &&
+ EOF
+ clean_repo &&
+ dirty_repo &&
+ git add . &&
+ git commit -m "to reset" &&
+ git reset HEAD~1 &&
+ git status >actual &&
+ git -c core.fsmonitor= status >expect &&
+ test_i18ncmp expect actual
+'
+
+# test fsmonitor with and without preloadIndex
+preload_values="false true"
+for preload_val in $preload_values
+do
+ test_expect_success "setup preloadIndex to $preload_val" '
+ git config core.preloadIndex $preload_val &&
+ if test $preload_val = true
+ then
+ GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST
+ else
+ unset GIT_FORCE_PRELOAD_TEST
+ fi
+ '
+
+ # test fsmonitor with and without the untracked cache (if available)
+ uc_values="false"
+ test_have_prereq UNTRACKED_CACHE && uc_values="false true"
+ for uc_val in $uc_values
+ do
+ test_expect_success "setup untracked cache to $uc_val" '
+ git config core.untrackedcache $uc_val
+ '
+
+ # Status is well tested elsewhere so we'll just ensure that the results are
+ # the same when using core.fsmonitor.
+ test_expect_success 'compare status with and without fsmonitor' '
+ write_integration_script &&
+ clean_repo &&
+ dirty_repo &&
+ git add new &&
+ git add dir1/new &&
+ git add dir2/new &&
+ git status >actual &&
+ git -c core.fsmonitor= status >expect &&
+ test_i18ncmp expect actual
+ '
+
+ # Make sure it's actually skipping the check for modified and untracked
+ # (if enabled) files unless it is told about them.
+ test_expect_success "status doesn't detect unreported modifications" '
+ write_script .git/hooks/fsmonitor-test<<-\EOF &&
+ :>marker
+ EOF
+ clean_repo &&
+ git status &&
+ test_path_is_file marker &&
+ dirty_repo &&
+ rm -f marker &&
+ git status >actual &&
+ test_path_is_file marker &&
+ test_i18ngrep ! "Changes not staged for commit:" actual &&
+ if test $uc_val = true
+ then
+ test_i18ngrep ! "Untracked files:" actual
+ fi &&
+ if test $uc_val = false
+ then
+ test_i18ngrep "Untracked files:" actual
+ fi &&
+ rm -f marker
+ '
+ done
+done
+
+# test that splitting the index dosn't interfere
+test_expect_success 'splitting the index results in the same state' '
+ write_integration_script &&
+ dirty_repo &&
+ git update-index --fsmonitor &&
+ git ls-files -f >expect &&
+ test-dump-fsmonitor >&2 && echo &&
+ git update-index --fsmonitor --split-index &&
+ test-dump-fsmonitor >&2 && echo &&
+ git ls-files -f >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t7519/fsmonitor-all b/t/t7519/fsmonitor-all
new file mode 100755
index 0000000000..691bc94dc2
--- /dev/null
+++ b/t/t7519/fsmonitor-all
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+
+if test "$#" -ne 2
+then
+ echo "$0: exactly 2 arguments expected" >&2
+ exit 2
+fi
+
+if test "$1" != 1
+then
+ echo "Unsupported core.fsmonitor hook version." >&2
+ exit 1
+fi
+
+echo "/"
diff --git a/t/t7519/fsmonitor-none b/t/t7519/fsmonitor-none
new file mode 100755
index 0000000000..ed9cf5a6a9
--- /dev/null
+++ b/t/t7519/fsmonitor-none
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# An test hook script to integrate with git to test fsmonitor.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+#echo "$0 $*" >&2
+
+if test "$#" -ne 2
+then
+ echo "$0: exactly 2 arguments expected" >&2
+ exit 2
+fi
+
+if test "$1" != 1
+then
+ echo "Unsupported core.fsmonitor hook version." >&2
+ exit 1
+fi
diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman
new file mode 100755
index 0000000000..5514edcf68
--- /dev/null
+++ b/t/t7519/fsmonitor-watchman
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 1) and a time in nanoseconds
+# formatted as a string and outputs to stdout all files that have been
+# modified since the given time. Paths must be relative to the root of
+# the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $time) = @ARGV;
+#print STDERR "$0 $version $time\n";
+
+# Check the hook interface version
+
+if ($version == 1) {
+ # convert nanoseconds to seconds
+ $time = int $time / 1000000000;
+} else {
+ die "Unsupported query-fsmonitor hook version '$version'.\n" .
+ "Falling back to scanning...\n";
+}
+
+my $git_work_tree;
+if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+ $git_work_tree = Win32::GetCwd();
+ $git_work_tree =~ tr/\\/\//;
+} else {
+ require Cwd;
+ $git_work_tree = Cwd::cwd();
+}
+
+my $retry = 1;
+
+launch_watchman();
+
+sub launch_watchman {
+
+ my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j')
+ or die "open2() failed: $!\n" .
+ "Falling back to scanning...\n";
+
+ # In the query expression below we're asking for names of files that
+ # changed since $time but were not transient (ie created after
+ # $time but no longer exist).
+ #
+ # To accomplish this, we're using the "since" generator to use the
+ # recency index to select candidate nodes and "fields" to limit the
+ # output to file names only. Then we're using the "expression" term to
+ # further constrain the results.
+ #
+ # The category of transient files that we want to ignore will have a
+ # creation clock (cclock) newer than $time_t value and will also not
+ # currently exist.
+
+ my $query = <<" END";
+ ["query", "$git_work_tree", {
+ "since": $time,
+ "fields": ["name"],
+ "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
+ }]
+ END
+
+ open (my $fh, ">", ".git/watchman-query.json");
+ print $fh $query;
+ close $fh;
+
+ print CHLD_IN $query;
+ close CHLD_IN;
+ my $response = do {local $/; <CHLD_OUT>};
+
+ open ($fh, ">", ".git/watchman-response.json");
+ print $fh $response;
+ close $fh;
+
+ die "Watchman: command returned no output.\n" .
+ "Falling back to scanning...\n" if $response eq "";
+ die "Watchman: command returned invalid output: $response\n" .
+ "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+ my $json_pkg;
+ eval {
+ require JSON::XS;
+ $json_pkg = "JSON::XS";
+ 1;
+ } or do {
+ require JSON::PP;
+ $json_pkg = "JSON::PP";
+ };
+
+ my $o = $json_pkg->new->utf8->decode($response);
+
+ if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
+ print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
+ $retry--;
+ qx/watchman watch "$git_work_tree"/;
+ die "Failed to make watchman watch '$git_work_tree'.\n" .
+ "Falling back to scanning...\n" if $? != 0;
+
+ # Watchman will always return all files on the first query so
+ # return the fast "everything is dirty" flag to git and do the
+ # Watchman query just to get it over with now so we won't pay
+ # the cost in git to look up each individual file.
+
+ open ($fh, ">", ".git/watchman-output.out");
+ print "/\0";
+ close $fh;
+
+ print "/\0";
+ eval { launch_watchman() };
+ exit 0;
+ }
+
+ die "Watchman: $o->{error}.\n" .
+ "Falling back to scanning...\n" if $o->{error};
+
+ open ($fh, ">", ".git/watchman-output.out");
+ binmode $fh, ":utf8";
+ print $fh @{$o->{files}};
+ close $fh;
+
+ binmode STDOUT, ":utf8";
+ local $, = "\0";
+ print @{$o->{files}};
+}
diff --git a/t/t7520-ignored-hook-warning.sh b/t/t7520-ignored-hook-warning.sh
new file mode 100755
index 0000000000..634fb7f23a
--- /dev/null
+++ b/t/t7520-ignored-hook-warning.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='ignored hook warning'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ hookdir="$(git rev-parse --git-dir)/hooks" &&
+ hook="$hookdir/pre-commit" &&
+ mkdir -p "$hookdir" &&
+ write_script "$hook" <<-\EOF
+ exit 0
+ EOF
+'
+
+test_expect_success 'no warning if hook is not ignored' '
+ git commit --allow-empty -m "more" 2>message &&
+ test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_expect_success POSIXPERM 'warning if hook is ignored' '
+ chmod -x "$hook" &&
+ git commit --allow-empty -m "even more" 2>message &&
+ test_i18ngrep -e "hook was ignored" message
+'
+
+test_expect_success POSIXPERM 'no warning if advice.ignoredHook set to false' '
+ test_config advice.ignoredHook false &&
+ chmod -x "$hook" &&
+ git commit --allow-empty -m "even more" 2>message &&
+ test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_expect_success 'no warning if unset advice.ignoredHook and hook removed' '
+ rm -f "$hook" &&
+ test_unconfig advice.ignoredHook &&
+ git commit --allow-empty -m "even more" 2>message &&
+ test_i18ngrep ! -e "hook was ignored" message
+'
+
+test_done
diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh
new file mode 100755
index 0000000000..91790943c3
--- /dev/null
+++ b/t/t7521-ignored-mode.sh
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+test_description='git status ignored modes'
+
+. ./test-lib.sh
+
+test_expect_success 'setup initial commit and ignore file' '
+ cat >.gitignore <<-\EOF &&
+ *.ign
+ ignored_dir/
+ !*.unignore
+ EOF
+ git add . &&
+ git commit -m "Initial commit"
+'
+
+test_expect_success 'Verify behavior of status on directories with ignored files' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! dir/ignored/ignored_1.ign
+ ! dir/ignored/ignored_2.ign
+ ! ignored/ignored_1.ign
+ ! ignored/ignored_2.ign
+ EOF
+
+ mkdir -p ignored dir/ignored &&
+ touch ignored/ignored_1.ign ignored/ignored_2.ign \
+ dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on directory with tracked & ignored files' '
+ test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! dir/tracked_ignored/ignored_1.ign
+ ! dir/tracked_ignored/ignored_2.ign
+ ! tracked_ignored/ignored_1.ign
+ ! tracked_ignored/ignored_2.ign
+ EOF
+
+ mkdir -p tracked_ignored dir/tracked_ignored &&
+ touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+ tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \
+ dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \
+ dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign &&
+
+ git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
+ dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 &&
+ git commit -m "commit tracked files" &&
+
+ git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on directory with untracked and ignored files' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? dir/untracked_ignored/untracked_1
+ ? dir/untracked_ignored/untracked_2
+ ? expect
+ ? output
+ ? untracked_ignored/untracked_1
+ ? untracked_ignored/untracked_2
+ ! dir/untracked_ignored/ignored_1.ign
+ ! dir/untracked_ignored/ignored_2.ign
+ ! untracked_ignored/ignored_1.ign
+ ! untracked_ignored/ignored_2.ign
+ EOF
+
+ mkdir -p untracked_ignored dir/untracked_ignored &&
+ touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \
+ untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \
+ dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \
+ dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status matching ignored files on ignored directory' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! ignored_dir/
+ EOF
+
+ mkdir ignored_dir &&
+ touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+ ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
+ test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! ignored_dir/ignored_1
+ ! ignored_dir/ignored_1.ign
+ ! ignored_dir/ignored_2
+ ! ignored_dir/ignored_2.ign
+ EOF
+
+ mkdir ignored_dir &&
+ touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+ ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
+ ignored_dir/tracked &&
+ git add -f ignored_dir/tracked &&
+ git commit -m "Force add file in ignored directory" &&
+ git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ? untracked_dir/
+ ! ignored_dir/
+ ! ignored_files/ignored_1.ign
+ ! ignored_files/ignored_2.ign
+ EOF
+
+ mkdir ignored_dir ignored_files untracked_dir &&
+ touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+ ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
+ untracked_dir/untracked &&
+ git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ? untracked_dir/
+ ! ignored_dir/
+ ! ignored_files/ignored_1.ign
+ ! ignored_files/ignored_2.ign
+ EOF
+
+ mkdir ignored_dir ignored_files untracked_dir &&
+ touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+ ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
+ untracked_dir/untracked &&
+ git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
+ test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! ignored_dir/ignored_1
+ ! ignored_dir/ignored_1.ign
+ ! ignored_dir/ignored_2
+ ! ignored_dir/ignored_2.ign
+ EOF
+
+ mkdir ignored_dir &&
+ touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
+ ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
+ ignored_dir/tracked &&
+ git add -f ignored_dir/tracked &&
+ git commit -m "Force add file in ignored directory" &&
+ git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=no' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ EOF
+
+ mkdir -p ignored dir/ignored &&
+ touch ignored/ignored_1.ign ignored/ignored_2.ign \
+ dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=no --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=all' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! dir/ignored/ignored_1.ign
+ ! dir/ignored/ignored_2.ign
+ ! ignored/ignored_1.ign
+ ! ignored/ignored_2.ign
+ EOF
+
+ mkdir -p ignored dir/ignored &&
+ touch ignored/ignored_1.ign ignored/ignored_2.ign \
+ dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=traditional --untracked-files=all >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=normal' '
+ test_when_finished "git clean -fdx" &&
+ cat >expect <<-\EOF &&
+ ? expect
+ ? output
+ ! dir/
+ ! ignored/
+ EOF
+
+ mkdir -p ignored dir/ignored &&
+ touch ignored/ignored_1.ign ignored/ignored_2.ign \
+ dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
+
+ git status --porcelain=v2 --ignored=traditional --untracked-files=normal >output &&
+ test_i18ncmp expect output
+'
+
+test_done
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 2a6679c2f5..c02ca735b9 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -60,6 +60,18 @@ test_expect_success setup '
echo " line with leading space3"
echo "line without leading space2"
} >space &&
+ cat >hello.ps1 <<-\EOF &&
+ # No-op.
+ function dummy() {}
+
+ # Say hello.
+ function hello() {
+ echo "Hello world."
+ } # hello
+
+ # Still a no-op.
+ function dummy() {}
+ EOF
git add . &&
test_tick &&
git commit -m initial
@@ -766,18 +778,27 @@ test_expect_success 'grep -W shows no trailing empty lines' '
test_cmp expected actual
'
-cat >expected <<EOF
-hello.c= printf("Hello world.\n");
-hello.c: return 0;
-hello.c- /* char ?? */
-EOF
-
test_expect_success 'grep -W with userdiff' '
test_when_finished "rm -f .gitattributes" &&
- git config diff.custom.xfuncname "(printf.*|})$" &&
- echo "hello.c diff=custom" >.gitattributes &&
- git grep -W return >actual &&
- test_cmp expected actual
+ git config diff.custom.xfuncname "^function .*$" &&
+ echo "hello.ps1 diff=custom" >.gitattributes &&
+ git grep -W echo >function-context-userdiff-actual
+'
+
+test_expect_success ' includes preceding comment' '
+ grep "# Say hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes function line' '
+ grep "=function hello" function-context-userdiff-actual
+'
+
+test_expect_success ' includes matching line' '
+ grep ": echo" function-context-userdiff-actual
+'
+
+test_expect_success ' includes last line of the function' '
+ grep "} # hello" function-context-userdiff-actual
'
for threads in $(test_seq 0 10)