diff options
103 files changed, 1672 insertions, 910 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index b6280692d2..77346a4929 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,7 +1,7 @@ env: CIRRUS_CLONE_DEPTH: 1 -freebsd_12_task: +freebsd_task: env: GIT_PROVE_OPTS: "--timer --jobs 10" GIT_TEST_OPTS: "--no-chain-lint --no-bin-wrappers" @@ -9,7 +9,7 @@ freebsd_12_task: DEFAULT_TEST_TARGET: prove DEVELOPER: 1 freebsd_instance: - image_family: freebsd-12-3 + image_family: freebsd-13-2 memory: 2G install_script: pkg install -y gettext gmake perl5 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9fdbd54028..4d97da57ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -309,6 +309,17 @@ jobs: with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} + fuzz-smoke-test: + name: fuzz smoke test + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' + env: + CC: clang + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: ci/install-dependencies.sh + - run: ci/run-build-and-minimal-fuzzers.sh dockerized: name: ${{matrix.vector.jobname}} (${{matrix.vector.image}}) needs: ci-config diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0215b1fd4c..e58917c50a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -130,11 +130,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. -Community Impact Guidelines were inspired by +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index 279f6a3e7c..f06563e981 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -35,8 +35,9 @@ announcements, design discussions, and more take place. Those interested in contributing are welcome to post questions here. The Git list requires plain-text-only emails and prefers inline and bottom-posting when replying to mail; you will be CC'd in all replies to you. Optionally, you can subscribe to -the list by sending an email to majordomo@vger.kernel.org with "subscribe git" -in the body. The https://lore.kernel.org/git[archive] of this mailing list is +the list by sending an email to <git+subscribe@vger.kernel.org> +(see https://subspace.kernel.org/subscribing.html for details). +The https://lore.kernel.org/git[archive] of this mailing list is available to view in a browser. ==== https://groups.google.com/forum/#!forum/git-mentoring[git-mentoring@googlegroups.com] diff --git a/Documentation/RelNotes/2.44.0.txt b/Documentation/RelNotes/2.44.0.txt index 9db3ea8241..8615306eed 100644 --- a/Documentation/RelNotes/2.44.0.txt +++ b/Documentation/RelNotes/2.44.0.txt @@ -59,6 +59,31 @@ UI, Workflows & Features maintenance" wrote into a wrong "global config" file, which have been corrected. + * Define "special ref" as a very narrow set that consists of + FETCH_HEAD and MERGE_HEAD, and clarify everything else that used to + be classified as such are actually just pseudorefs. + + * All conditional "advice" messages show how to turn them off, which + becomes repetitive. Setting advice.* configuration explicitly on + now omits the instruction part. + + * The "disable repository discovery of a bare repository" check, + triggered by setting safe.bareRepository configuration variable to + 'explicit', has been loosened to exclude the ".git/" directory inside + a non-bare repository from the check. So you can do "cd .git && + git cmd" to run a Git command that works on a bare repository without + explicitly specifying $GIT_DIR now. + + * The completion script (in contrib/) learned more options that can + be used with "git log". + + * The labels on conflict markers for the common ancestor, our version, + and the other version are available to custom 3-way merge driver + via %S, %X, and %Y placeholders. + + * The write codepath for the reftable data learned to honor + core.fsync configuration. + Performance, Internal Implementation, Development Support etc. @@ -110,6 +135,12 @@ Performance, Internal Implementation, Development Support etc. * A few tests to "git commit -o <pathspec>" and "git commit -i <pathspec>" has been added. + * Tests on ref API are moved around to prepare for reftable. + + * The Makefile often had to say "-L$(path) -R$(path)" that repeats + the path to the same library directory for link time and runtime. + A Makefile template is used to reduce such repetition. + Fixes since v2.43 ----------------- @@ -211,6 +242,48 @@ Fixes since v2.43 data from commit-graph too early, which has been corrected. (merge d70f554cdf jk/commit-graph-slab-clear-fix later to maint). + * Update to a new feature recently added, "git show-ref --exists". + (merge 0aabeaa562 tc/show-ref-exists-fix later to maint). + + * oss-fuzz tests are built and run in CI. + (merge c4a9cf1df3 js/oss-fuzz-build-in-ci later to maint). + + * Rename detection logic ignored the final line of a file if it is an + incomplete line. + (merge 1c5bc6971e en/diffcore-delta-final-line-fix later to maint). + + * GitHub CI update. + (merge 0188b2c8e0 pb/ci-github-skip-logs-for-broken-tests later to maint). + + * "git diff --no-rename A B" did not disable rename detection but did + not trigger an error from the command line parser. + (merge 457f96252f rs/parse-options-with-keep-unknown-abbrev-fix later to maint). + + * "git archive --remote=<remote>" learned to talk over the smart + http (aka stateless) transport. + (merge 176cd68634 jx/remote-archive-over-smart-http later to maint). + + * Fetching via protocol v0 over Smart HTTP transport sometimes failed + to correctly auto-follow tags. + (merge fba732c462 jk/fetch-auto-tag-following-fix later to maint). + + * The documentation for the --exclude-per-directory option marked it + as deprecated, which confused readers into thinking there may be a + plan to remove it in the future, which was not our intention. + (merge 0009542cab jc/ls-files-doc-update later to maint). + + * "git diff --no-index file1 file2" segfaulted while invoking the + external diff driver, which has been corrected. + (merge 85a9a63c92 jk/diff-external-with-no-index later to maint). + + * Rewrite //-comments to /* comments */ in files whose comments + prevalently use the latter. + (merge de65079d7b jc/comment-style-fixes later to maint). + + * Cirrus CI jobs started breaking because we specified version of + FreeBSD that is no longer available, which has been corrected. + (merge 81fffb66d3 cb/use-freebsd-13-2-at-cirrus-ci later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 50f1abcff6 js/packfile-h-typofix later to maint). (merge cbf498eb53 jb/reflog-expire-delete-dry-run-options later to maint). @@ -247,3 +320,11 @@ Fixes since v2.43 (merge 8f50984cf4 ne/doc-filter-blob-limit-fix later to maint). (merge f10b0989b8 la/strvec-comment-fix later to maint). (merge 8430b438f6 vd/fsck-submodule-url-test later to maint). + (merge f10031fadd nb/rebase-x-shell-docfix later to maint). + (merge af3d2c160f jc/majordomo-to-subspace later to maint). + (merge ee9895b0ff sd/negotiate-trace-fix later to maint). + (merge 976d0251ce jc/coc-whitespace-fix later to maint). + (merge 9023198280 jt/p4-spell-re-with-raw-string later to maint). + (merge 36c9c44fa4 tb/pack-bitmap-drop-unused-struct-member later to maint). + (merge 19ed0dff8f js/win32-retry-pipe-write-on-enospc later to maint). + (merge 3cb4384683 jc/t0091-with-unknown-git later to maint). diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index 25c0917524..c7ea70f2e2 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -1,7 +1,8 @@ advice.*:: These variables control various optional help messages designed to - aid new users. All 'advice.*' variables default to 'true', and you - can tell Git that you do not need help by setting these to 'false': + aid new users. When left unconfigured, Git will give the message + alongside instructions on how to squelch it. You can tell Git + that you do not need the help message by setting these to 'false': + -- addEmbeddedRepo:: diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 6486620c3d..5a20deefd5 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -79,8 +79,13 @@ OPTIONS to force the version for the generated pack index, and to force 64-bit index entries on objects located above the given offset. ---strict:: - Die, if the pack contains broken objects or links. +--strict[=<msg-id>=<severity>...]:: + Die, if the pack contains broken objects or links. An optional + comma-separated list of `<msg-id>=<severity>` can be passed to change + the severity of some possible issues, e.g., + `--strict="missingEmail=ignore,badTagName=error"`. See the entry for the + `fsck.<msg-id>` configuration options in linkgit:git-fsck[1] for more + information on the possible values of `<msg-id>` and `<severity>`. --progress-title:: For internal use only. @@ -91,13 +96,18 @@ default and "Indexing objects" when `--stdin` is specified. --check-self-contained-and-connected:: Die if the pack contains broken links. For internal use only. ---fsck-objects:: - For internal use only. +--fsck-objects[=<msg-id>=<severity>...]:: + Die if the pack contains broken objects, but unlike `--strict`, don't + choke on broken links. If the pack contains a tree pointing to a + .gitmodules blob that does not exist, prints the hash of that blob + (for the caller to check) after the hash that goes into the name of the + pack/idx file (see "Notes"). + -Die if the pack contains broken objects. If the pack contains a tree -pointing to a .gitmodules blob that does not exist, prints the hash of -that blob (for the caller to check) after the hash that goes into the -name of the pack/idx file (see "Notes"). +An optional comma-separated list of `<msg-id>=<severity>` can be passed to +change the severity of some possible issues, e.g., +`--fsck-objects="missingEmail=ignore,badTagName=ignore"`. See the entry for the +`fsck.<msg-id>` configuration options in linkgit:git-fsck[1] for more +information on the possible values of `<msg-id>` and `<severity>`. --threads=<n>:: Specifies the number of threads to spawn when resolving diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index f65a8cd91d..d08c7da8f4 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -119,8 +119,10 @@ OPTIONS --exclude-per-directory=<file>:: Read additional exclude patterns that apply only to the - directory and its subdirectories in <file>. Deprecated; use - --exclude-standard instead. + directory and its subdirectories in <file>. If you are + trying to emulate the way Porcelain commands work, using + the `--exclude-standard` option instead is easier and more + thorough. --exclude-standard:: Add the standard Git exclusions: .git/info/exclude, .gitignore @@ -298,9 +300,8 @@ traversing the directory tree and finding files to show when the flags --others or --ignored are specified. linkgit:gitignore[5] specifies the format of exclude patterns. -Generally, you should just use --exclude-standard, but for historical -reasons the exclude patterns can be specified from the following -places, in order: +These exclude patterns can be specified from the following places, +in order: 1. The command-line flag --exclude=<pattern> specifies a single pattern. Patterns are ordered in the same order @@ -322,6 +323,18 @@ top of the directory tree. A pattern read from a file specified by --exclude-per-directory is relative to the directory that the pattern file appears in. +Generally, you should be able to use `--exclude-standard` when you +want the exclude rules applied the same way as what Porcelain +commands do. To emulate what `--exclude-standard` specifies, you +can give `--exclude-per-directory=.gitignore`, and then specify: + + 1. The file specified by the `core.excludesfile` configuration + variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file. + + 2. The `$GIT_DIR/info/exclude` file. + +via the `--exclude-from=` option. + SEE ALSO -------- linkgit:git-read-tree[1], linkgit:gitignore[5] diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 8a8d32161b..06206521fc 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -966,10 +966,9 @@ The interactive rebase will stop when a command fails (i.e. exits with non-0 status) to give you an opportunity to fix the problem. You can continue with `git rebase --continue`. -The "exec" command launches the command in a shell (the one specified -in `$SHELL`, or the default shell if `$SHELL` is not set), so you can -use shell features (like "cd", ">", ";" ...). The command is run from -the root of the working tree. +The "exec" command launches the command in a shell (the default one, usually +/bin/sh), so you can use shell features (like "cd", ">", ";" ...). The command +is run from the root of the working tree. ---------------------------------- $ git rebase -i --exec "make test" diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 201bdf5edb..4338d023d9 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -1137,11 +1137,11 @@ The `merge.*.name` variable gives the driver a human-readable name. The `merge.*.driver` variable's value is used to construct a -command to run to merge ancestor's version (`%O`), current +command to run to common ancestor's version (`%O`), current version (`%A`) and the other branches' version (`%B`). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is -built. Additionally, %L will be replaced with the conflict marker +built. Additionally, `%L` will be replaced with the conflict marker size (see below). The merge driver is expected to leave the result of the merge in @@ -1159,8 +1159,9 @@ When left unspecified, the driver itself is used for both internal merge and the final merge. The merge driver can learn the pathname in which the merged result -will be stored via placeholder `%P`. - +will be stored via placeholder `%P`. The conflict labels to be used +for the common ancestor, local head and other head can be passed by +using '%S', '%X' and '%Y` respectively. `conflict-marker-size` ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index f7d98c11e3..d71b199955 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -638,6 +638,20 @@ The most notable example is `HEAD`. An <<def_object,object>> used to temporarily store the contents of a <<def_dirty,dirty>> working directory and the index for future reuse. +[[def_special_ref]]special ref:: + A ref that has different semantics than normal refs. These refs can be + accessed via normal Git commands but may not behave the same as a + normal ref in some cases. ++ +The following special refs are known to Git: + + - "`FETCH_HEAD`" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It + may refer to multiple object IDs. Each object ID is annotated with metadata + indicating where it was fetched from and its fetch status. + + - "`MERGE_HEAD`" is written by linkgit:git-merge[1] when resolving merge + conflicts. It contains all commit IDs which are being merged. + [[def_submodule]]submodule:: A <<def_repository,repository>> that holds the history of a separate project inside another repository (the latter of @@ -752,6 +752,10 @@ SCRIPTS = $(SCRIPT_SH_GEN) \ ETAGS_TARGET = TAGS +# If you add a new fuzzer, please also make sure to run it in +# ci/run-build-and-minimal-fuzzers.sh so that we make sure it still links and +# runs in the future. +FUZZ_OBJS += oss-fuzz/dummy-cmd-main.o FUZZ_OBJS += oss-fuzz/fuzz-commit-graph.o FUZZ_OBJS += oss-fuzz/fuzz-date.o FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o @@ -762,7 +766,7 @@ fuzz-objs: $(FUZZ_OBJS) # Always build fuzz objects even if not testing, to prevent bit-rot. all:: $(FUZZ_OBJS) -FUZZ_PROGRAMS += $(patsubst %.o,%,$(FUZZ_OBJS)) +FUZZ_PROGRAMS += $(patsubst %.o,%,$(filter-out %dummy-cmd-main.o,$(FUZZ_OBJS))) # Empty... EXTRA_PROGRAMS = @@ -827,7 +831,6 @@ TEST_BUILTINS_OBJS += test-partial-clone.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pcre2-config.o TEST_BUILTINS_OBJS += test-pkt-line.o -TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-proc-receive.o TEST_BUILTINS_OBJS += test-progress.o TEST_BUILTINS_OBJS += test-reach.o @@ -1342,6 +1345,7 @@ UNIT_TEST_PROGRAMS += t-basic UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-ctype +UNIT_TEST_PROGRAMS += t-prio-queue UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o @@ -1586,7 +1590,7 @@ endif ifdef LIBPCREDIR BASIC_CFLAGS += -I$(LIBPCREDIR)/include - EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) + EXTLIBS += $(call libpath_template,$(LIBPCREDIR)/$(lib)) endif ifdef HAVE_ALLOCA_H @@ -1606,7 +1610,7 @@ else ifdef CURLDIR # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case. CURL_CFLAGS = -I$(CURLDIR)/include - CURL_LIBCURL = -L$(CURLDIR)/$(lib) $(CC_LD_DYNPATH)$(CURLDIR)/$(lib) + CURL_LIBCURL = $(call libpath_template,$(CURLDIR)/$(lib)) else CURL_CFLAGS = CURL_LIBCURL = @@ -1642,7 +1646,7 @@ else ifndef NO_EXPAT ifdef EXPATDIR BASIC_CFLAGS += -I$(EXPATDIR)/include - EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat + EXPAT_LIBEXPAT = $(call libpath_template,$(EXPATDIR)/$(lib)) -lexpat else EXPAT_LIBEXPAT = -lexpat endif @@ -1655,7 +1659,7 @@ IMAP_SEND_LDFLAGS += $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO) ifdef ZLIB_PATH BASIC_CFLAGS += -I$(ZLIB_PATH)/include - EXTLIBS += -L$(ZLIB_PATH)/$(lib) $(CC_LD_DYNPATH)$(ZLIB_PATH)/$(lib) + EXTLIBS += $(call libpath_template,$(ZLIB_PATH)/$(lib)) endif EXTLIBS += -lz @@ -1663,7 +1667,7 @@ ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR BASIC_CFLAGS += -I$(OPENSSLDIR)/include - OPENSSL_LINK = -L$(OPENSSLDIR)/$(lib) $(CC_LD_DYNPATH)$(OPENSSLDIR)/$(lib) + OPENSSL_LINK = $(call libpath_template,$(OPENSSLDIR)/$(lib)) else OPENSSL_LINK = endif @@ -1690,7 +1694,7 @@ ifndef NO_ICONV ifdef NEEDS_LIBICONV ifdef ICONVDIR BASIC_CFLAGS += -I$(ICONVDIR)/include - ICONV_LINK = -L$(ICONVDIR)/$(lib) $(CC_LD_DYNPATH)$(ICONVDIR)/$(lib) + ICONV_LINK = $(call libpath_template,$(ICONVDIR)/$(lib)) else ICONV_LINK = endif @@ -3850,16 +3854,17 @@ cover_db_html: cover_db # # make CC=clang CXX=clang++ \ # CFLAGS="-fsanitize=fuzzer-no-link,address" \ -# LIB_FUZZING_ENGINE="-fsanitize=fuzzer" \ +# LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \ # fuzz-all # -FUZZ_CXXFLAGS ?= $(CFLAGS) +FUZZ_CXXFLAGS ?= $(ALL_CFLAGS) .PHONY: fuzz-all -$(FUZZ_PROGRAMS): all - $(QUIET_LINK)$(CXX) $(FUZZ_CXXFLAGS) $(LIB_OBJS) $(BUILTIN_OBJS) \ - $(XDIFF_OBJS) $(EXTLIBS) git.o $@.o $(LIB_FUZZING_ENGINE) -o $@ +$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS + $(QUIET_LINK)$(CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \ + -Wl,--allow-multiple-definition \ + $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE) fuzz-all: $(FUZZ_PROGRAMS) @@ -39,8 +39,8 @@ Those wishing to help with error message, usage and informational message string translations (localization l10) should see [po/README.md][] (a `po` file is a Portable Object file that holds the translations). -To subscribe to the list, send an email with just "subscribe git" in -the body to majordomo@vger.kernel.org (not the Git list). The mailing +To subscribe to the list, send an email to <git+subscribe@vger.kernel.org> +(see https://subspace.kernel.org/subscribing.html for details). The mailing list archives are available at <https://lore.kernel.org/git/>, <https://marc.info/?l=git> and other archival sites. @@ -33,50 +33,56 @@ static const char *advise_get_color(enum color_advice ix) return ""; } +enum advice_level { + ADVICE_LEVEL_NONE = 0, + ADVICE_LEVEL_DISABLED, + ADVICE_LEVEL_ENABLED, +}; + static struct { const char *key; - int enabled; + enum advice_level level; } advice_setting[] = { - [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 1 }, - [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 }, - [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 }, - [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec", 1 }, - [ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 }, - [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 }, - [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 }, - [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 }, - [ADVICE_DIVERGING] = { "diverging", 1 }, - [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 }, - [ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch", 1 }, - [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 }, - [ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 }, - [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity", 1 }, - [ADVICE_NESTED_TAG] = { "nestedTag", 1 }, - [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning", 1 }, - [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists", 1 }, - [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst", 1 }, - [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce", 1 }, - [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent", 1 }, - [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching", 1 }, - [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate", 1 }, - [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName", 1 }, - [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected", 1 }, - [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 }, /* backwards compatibility */ - [ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh", 1 }, - [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict", 1 }, - [ADVICE_RM_HINTS] = { "rmHints", 1 }, - [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 }, - [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 }, - [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks", 1 }, - [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 }, - [ADVICE_STATUS_HINTS] = { "statusHints", 1 }, - [ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 }, - [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 }, - [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 }, - [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead", 1 }, - [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, - [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, - [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan", 1 }, + [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo" }, + [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec" }, + [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile" }, + [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec" }, + [ADVICE_AM_WORK_DIR] = { "amWorkDir" }, + [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" }, + [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" }, + [ADVICE_DETACHED_HEAD] = { "detachedHead" }, + [ADVICE_DIVERGING] = { "diverging" }, + [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" }, + [ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" }, + [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" }, + [ADVICE_IGNORED_HOOK] = { "ignoredHook" }, + [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity" }, + [ADVICE_NESTED_TAG] = { "nestedTag" }, + [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning" }, + [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists" }, + [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst" }, + [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce" }, + [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent" }, + [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" }, + [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" }, + [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" }, + [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" }, + [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */ + [ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh" }, + [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict" }, + [ADVICE_RM_HINTS] = { "rmHints" }, + [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse" }, + [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure" }, + [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks" }, + [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning" }, + [ADVICE_STATUS_HINTS] = { "statusHints" }, + [ADVICE_STATUS_U_OPTION] = { "statusUoption" }, + [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated" }, + [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie" }, + [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead" }, + [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath" }, + [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor" }, + [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan" }, }; static const char turn_off_instructions[] = @@ -116,13 +122,13 @@ void advise(const char *advice, ...) int advice_enabled(enum advice_type type) { - switch(type) { - case ADVICE_PUSH_UPDATE_REJECTED: - return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled && - advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled; - default: - return advice_setting[type].enabled; - } + int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED; + + if (type == ADVICE_PUSH_UPDATE_REJECTED) + return enabled && + advice_enabled(ADVICE_PUSH_UPDATE_REJECTED_ALIAS); + + return enabled; } void advise_if_enabled(enum advice_type type, const char *advice, ...) @@ -133,7 +139,8 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...) return; va_start(params, advice); - vadvise(advice, 1, advice_setting[type].key, params); + vadvise(advice, !advice_setting[type].level, advice_setting[type].key, + params); va_end(params); } @@ -162,7 +169,9 @@ int git_default_advice_config(const char *var, const char *value) for (i = 0; i < ARRAY_SIZE(advice_setting); i++) { if (strcasecmp(k, advice_setting[i].key)) continue; - advice_setting[i].enabled = git_config_bool(var, value); + advice_setting[i].level = git_config_bool(var, value) + ? ADVICE_LEVEL_ENABLED + : ADVICE_LEVEL_DISABLED; return 0; } @@ -817,8 +817,9 @@ void remove_merge_branch_state(struct repository *r) unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); - unlink(git_path_auto_merge(r)); - save_autostash(git_path_merge_autostash(r)); + refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE", + NULL, REF_NO_DEREF); + save_autostash_ref(r, "MERGE_AUTOSTASH"); } void remove_branch_state(struct repository *r, int verbose) diff --git a/builtin/commit.c b/builtin/commit.c index 65196a2827..6d1fa71676 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1877,7 +1877,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) &oid, flags); } - apply_autostash(git_path_merge_autostash(the_repository)); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); cleanup: strbuf_release(&author_ident); diff --git a/builtin/config.c b/builtin/config.c index 08fe36d499..b55bfae7d6 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -710,6 +710,12 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (use_global_config) { given_config_source.file = git_global_config(); if (!given_config_source.file) + /* + * It is unknown if HOME/.gitconfig exists, so + * we do not know if we should write to XDG + * location; error out even if XDG_CONFIG_HOME + * is set and points at a sane location. + */ die(_("$HOME not set")); given_config_source.scope = CONFIG_SCOPE_GLOBAL; } else if (use_system_config) { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 1ea87e01f2..a3a37bd215 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -24,7 +24,7 @@ #include "setup.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--[no-]rev-index] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--[no-]rev-index] [--verify] [--strict[=<msg-id>=<severity>...]] [--fsck-objects[=<msg-id>=<severity>...]] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; struct object_entry { struct pack_idx_entry idx; @@ -1785,8 +1785,9 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) } else if (!strcmp(arg, "--check-self-contained-and-connected")) { strict = 1; check_self_contained_and_connected = 1; - } else if (!strcmp(arg, "--fsck-objects")) { + } else if (skip_to_optional_arg(arg, "--fsck-objects", &arg)) { do_fsck_object = 1; + fsck_set_msg_types(&fsck_options, arg); } else if (!strcmp(arg, "--verify")) { verify = 1; } else if (!strcmp(arg, "--verify-stat")) { diff --git a/builtin/merge.c b/builtin/merge.c index ebbe05033e..8f819781cc 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -476,7 +476,7 @@ static void finish(struct commit *head_commit, run_hooks_l("post-merge", squash ? "1" : "0", NULL); if (new_head) - apply_autostash(git_path_merge_autostash(the_repository)); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); strbuf_release(&reflog_message); } @@ -1315,7 +1315,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; - struct strbuf stash_oid = STRBUF_INIT; + char stash_oid_hex[GIT_MAX_HEXSZ + 1]; + struct object_id stash_oid = {0}; if (orig_argc != 2) usage_msg_opt(_("--abort expects no arguments"), @@ -1324,17 +1325,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); - if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository), - READ_ONELINER_SKIP_IF_EMPTY)) - unlink(git_path_merge_autostash(the_repository)); + if (!read_ref("MERGE_AUTOSTASH", &stash_oid)) + delete_ref("", "MERGE_AUTOSTASH", &stash_oid, REF_NO_DEREF); /* Invoke 'git reset --merge' */ ret = cmd_reset(nargc, nargv, prefix); - if (stash_oid.len) - apply_autostash_oid(stash_oid.buf); + if (!is_null_oid(&stash_oid)) { + oid_to_hex_r(stash_oid_hex, &stash_oid); + apply_autostash_oid(stash_oid_hex); + } - strbuf_release(&stash_oid); goto done; } @@ -1563,13 +1564,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (autostash) - create_autostash(the_repository, - git_path_merge_autostash(the_repository)); + create_autostash_ref(the_repository, "MERGE_AUTOSTASH"); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, overwrite_ignore)) { - apply_autostash(git_path_merge_autostash(the_repository)); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); ret = 1; goto done; } @@ -1655,8 +1655,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die_ff_impossible(); if (autostash) - create_autostash(the_repository, - git_path_merge_autostash(the_repository)); + create_autostash_ref(the_repository, "MERGE_AUTOSTASH"); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1741,7 +1740,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - apply_autostash(git_path_merge_autostash(the_repository)); + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); ret = 2; goto done; } else if (best_strategy == wt_strategy) diff --git a/builtin/rebase.c b/builtin/rebase.c index 995818c28d..5b086f651a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -515,7 +515,7 @@ static int finish_rebase(struct rebase_options *opts) int ret = 0; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - unlink(git_path_auto_merge(the_repository)); + delete_ref(NULL, "AUTO_MERGE", NULL, REF_NO_DEREF); apply_autostash(state_dir_path("autostash", opts)); /* * We ignore errors in 'git maintenance run --auto', since the diff --git a/builtin/show-ref.c b/builtin/show-ref.c index aaa2c39b2f..79955c2856 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -238,7 +238,7 @@ static int cmd_show_ref__exists(const char **refs) if (refs_read_raw_ref(get_main_ref_store(the_repository), ref, &unused_oid, &unused_referent, &unused_type, &failure_errno)) { - if (failure_errno == ENOENT) { + if (failure_errno == ENOENT || failure_errno == EISDIR) { error(_("reference does not exist")); ret = 2; } else { diff --git a/builtin/worktree.c b/builtin/worktree.c index 6d7da11746..9c76b62b02 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -848,21 +848,21 @@ static int add(int ac, const char **av, const char *prefix) const char *s = worktree_basename(path, &n); new_branch = xstrndup(s, n); } else if (opts.orphan) { - // No-op + ; /* no-op */ } else if (opts.detach) { - // Check HEAD + /* Check HEAD */ if (!strcmp(branch, "HEAD")) can_use_local_refs(&opts); } else if (ac < 2 && new_branch) { - // DWIM: Infer --orphan when repo has no refs. + /* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { - // DWIM: Guess branch name from path. + /* DWIM: Guess branch name from path. */ const char *s = dwim_branch(path, &new_branch); if (s) branch = s; - // DWIM: Infer --orphan when repo has no refs. + /* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1); } else if (ac == 2) { struct object_id oid; diff --git a/ci/run-build-and-minimal-fuzzers.sh b/ci/run-build-and-minimal-fuzzers.sh new file mode 100755 index 0000000000..8ba486f659 --- /dev/null +++ b/ci/run-build-and-minimal-fuzzers.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Build and test Git's fuzzers +# + +. ${0%/*}/lib.sh + +group "Build fuzzers" make \ + CC=clang \ + CXX=clang++ \ + CFLAGS="-fsanitize=fuzzer-no-link,address" \ + LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \ + fuzz-all + +for fuzzer in commit-graph date pack-headers pack-idx ; do + begin_group "fuzz-$fuzzer" + ./oss-fuzz/fuzz-$fuzzer -verbosity=0 -runs=1 || exit 1 + end_group "fuzz-$fuzzer" +done diff --git a/compat/mingw.c b/compat/mingw.c index 238a84ddba..320fb99a90 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -707,13 +707,24 @@ ssize_t mingw_write(int fd, const void *buf, size_t len) { ssize_t result = write(fd, buf, len); - if (result < 0 && errno == EINVAL && buf) { + if (result < 0 && (errno == EINVAL || errno == ENOSPC) && buf) { + int orig = errno; + /* check if fd is a pipe */ HANDLE h = (HANDLE) _get_osfhandle(fd); - if (GetFileType(h) == FILE_TYPE_PIPE) + if (GetFileType(h) != FILE_TYPE_PIPE) + errno = orig; + else if (orig == EINVAL) errno = EPIPE; - else - errno = EINVAL; + else { + DWORD buf_size; + + if (!GetNamedPipeInfo(h, NULL, NULL, &buf_size, NULL)) + buf_size = 4096; + if (len > buf_size) + return write(fd, buf, buf_size); + errno = orig; + } } return result; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 6662db221d..c3408d4143 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1808,7 +1808,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --output= --output-indicator-context= --output-indicator-new= --output-indicator-old= --ws-error-highlight= - --pickaxe-all --pickaxe-regex + --pickaxe-all --pickaxe-regex --patch-with-raw " # Options for diff/difftool @@ -2072,6 +2072,16 @@ __git_log_common_options=" --min-age= --until= --before= --min-parents= --max-parents= --no-min-parents --no-max-parents + --alternate-refs --ancestry-path + --author-date-order --basic-regexp + --bisect --boundary --exclude-first-parent-only + --exclude-hidden --extended-regexp + --fixed-strings --grep-reflog + --ignore-missing --left-only --perl-regexp + --reflog --regexp-ignore-case --remove-empty + --right-only --show-linear-break + --show-notes-by-default --show-pulls + --since-as-filter --single-worktree " # Options that go well for log and gitk (not shortlog) __git_log_gitk_options=" @@ -2087,6 +2097,7 @@ __git_log_shortlog_options=" # Options accepted by log and show __git_log_show_options=" --diff-merges --diff-merges= --no-diff-merges --dd --remerge-diff + --encoding= " __git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r" @@ -2170,6 +2181,8 @@ _git_log () --no-walk --no-walk= --do-walk --parents --children --expand-tabs --expand-tabs= --no-expand-tabs + --clear-decorations --decorate-refs= + --decorate-refs-exclude= $merge $__git_diff_common_options " diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 3028029ac2..5dab3f506c 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -787,6 +787,22 @@ ensure_valid_ref_format () { die "fatal: '$1' does not look like a ref" } +# Usage: check if a commit from another subtree should be +# ignored from processing for splits +should_ignore_subtree_split_commit () { + assert test $# = 1 + local rev="$1" + if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)" + then + if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" && + test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)" + then + return 0 + fi + fi + return 1 +} + # Usage: process_split_commit REV PARENTS process_split_commit () { assert test $# = 2 @@ -972,7 +988,19 @@ cmd_split () { eval "$grl" | while read rev parents do - process_split_commit "$rev" "$parents" + if should_ignore_subtree_split_commit "$rev" + then + continue + fi + parsedparents='' + for parent in $parents + do + if ! should_ignore_subtree_split_commit "$parent" + then + parsedparents="$parsedparents$parent " + fi + done + process_split_commit "$rev" "$parsedparents" done || exit $? latest_new=$(cache_get latest_new) || exit $? diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 49a21dd7c9..ca4df5be83 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -385,6 +385,46 @@ test_expect_success 'split sub dir/ with --rejoin' ' ) ' +# Tests that commits from other subtrees are not processed as +# part of a split. +# +# This test performs the following: +# - Creates Repo with subtrees 'subA' and 'subB' +# - Creates commits in the repo including changes to subtrees +# - Runs the following 'split' and commit' commands in order: +# - Perform 'split' on subtree A +# - Perform 'split' on subtree B +# - Create new commits with changes to subtree A and B +# - Perform split on subtree A +# - Check that the commits in subtree B are not processed +# as part of the subtree A split +test_expect_success 'split with multiple subtrees' ' + subtree_test_create_repo "$test_count" && + subtree_test_create_repo "$test_count/subA" && + subtree_test_create_repo "$test_count/subB" && + test_create_commit "$test_count" main1 && + test_create_commit "$test_count/subA" subA1 && + test_create_commit "$test_count/subA" subA2 && + test_create_commit "$test_count/subA" subA3 && + test_create_commit "$test_count/subB" subB1 && + git -C "$test_count" fetch ./subA HEAD && + git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD && + git -C "$test_count" fetch ./subB HEAD && + git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD && + test_create_commit "$test_count" subADir/main-subA1 && + test_create_commit "$test_count" subBDir/main-subB1 && + git -C "$test_count" subtree split --prefix=subADir \ + --squash --rejoin -m "Sub A Split 1" && + git -C "$test_count" subtree split --prefix=subBDir \ + --squash --rejoin -m "Sub B Split 1" && + test_create_commit "$test_count" subADir/main-subA2 && + test_create_commit "$test_count" subBDir/main-subB2 && + git -C "$test_count" subtree split --prefix=subADir \ + --squash --rejoin -m "Sub A Split 2" && + test "$(git -C "$test_count" subtree split --prefix=subBDir \ + --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = "" +' + test_expect_success 'split sub dir/ with --rejoin from scratch' ' subtree_test_create_repo "$test_count" && test_create_commit "$test_count" main1 && @@ -4384,7 +4384,8 @@ static void run_external_diff(const char *pgm, add_external_diff_name(o->repo, &cmd.args, two); if (other) { strvec_push(&cmd.args, other); - strvec_push(&cmd.args, xfrm_msg); + if (xfrm_msg) + strvec_push(&cmd.args, xfrm_msg); } } diff --git a/diffcore-delta.c b/diffcore-delta.c index 4927ab8fb0..ba6cbee76b 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -158,6 +158,10 @@ static struct spanhash_top *hash_chars(struct repository *r, n = 0; accum1 = accum2 = 0; } + if (n > 0) { + hashval = (accum1 + accum2 * 0x61) % HASHBASE; + hash = add_spanhash(hash, hashval, n); + } QSORT(hash->data, (size_t)1ul << hash->alloc_log2, spanhash_cmp); return hash; } diff --git a/fetch-pack.c b/fetch-pack.c index 5b8aa0adc7..091f9a80a9 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -2216,7 +2216,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, the_repository, "%d", negotiation_round); } - trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository); + trace2_region_leave("fetch-pack", "negotiate_using_fetch", the_repository); trace2_data_intmax("negotiate_using_fetch", the_repository, "total_rounds", negotiation_round); clear_common_flag(acked_commits); @@ -689,8 +689,8 @@ def setP4ExecBit(file, mode): if not isModeExec(mode): p4Type = getP4OpenedType(file) - p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type) - p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type) + p4Type = re.sub(r'^([cku]?)x(.*)', r'\1\2', p4Type) + p4Type = re.sub(r'(.*?\+.*?)x(.*?)', r'\1\2', p4Type) if p4Type[-1] == "+": p4Type = p4Type[0:-1] @@ -701,7 +701,7 @@ def getP4OpenedType(file): """Returns the perforce file type for the given file.""" result = p4_read_pipe(["opened", wildcard_encode(file)]) - match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result) + match = re.match(r".*\((.+)\)( \*exclusive\*)?\r?$", result) if match: return match.group(1) else: @@ -757,7 +757,7 @@ def parseDiffTreeEntry(entry): global _diff_tree_pattern if not _diff_tree_pattern: - _diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)') + _diff_tree_pattern = re.compile(r':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)') match = _diff_tree_pattern.match(entry) if match: @@ -918,9 +918,9 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, if len(result) > 0: data = result[0].get('data') if data: - m = re.search('Too many rows scanned \(over (\d+)\)', data) + m = re.search(r'Too many rows scanned \(over (\d+)\)', data) if not m: - m = re.search('Request too large \(over (\d+)\)', data) + m = re.search(r'Request too large \(over (\d+)\)', data) if m: limit = int(m.group(1)) @@ -1452,7 +1452,7 @@ def wildcard_encode(path): def wildcard_present(path): - m = re.search("[*#@%]", path) + m = re.search(r"[*#@%]", path) return m is not None @@ -3048,7 +3048,7 @@ class P4Sync(Command, P4UserMap): # Preserve everything in relative path name except leading # //depot/; just look at first prefix as they all should # be in the same depot. - depot = re.sub("^(//[^/]+/).*", r'\1', prefixes[0]) + depot = re.sub(r"^(//[^/]+/).*", r'\1', prefixes[0]) if p4PathStartsWith(path, depot): path = path[len(depot):] @@ -3603,7 +3603,7 @@ class P4Sync(Command, P4UserMap): commitFound = True else: gitCommit = read_pipe(["git", "rev-list", "--max-count=1", - "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) + "--reverse", r":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) if len(gitCommit) == 0: print("importing label %s: could not find git commit for changelist %d" % (name, changelist)) else: @@ -4182,7 +4182,7 @@ class P4Sync(Command, P4UserMap): if len(self.changesFile) == 0: revision = "#head" - p = re.sub("\.\.\.$", "", p) + p = re.sub(r"\.\.\.$", "", p) if not p.endswith("/"): p += "/" @@ -4292,7 +4292,7 @@ class P4Rebase(Command): die("Cannot find upstream branchpoint for rebase") # the branchpoint may be p4/foo~3, so strip off the parent - upstream = re.sub("~[0-9]+$", "", upstream) + upstream = re.sub(r"~[0-9]+$", "", upstream) print("Rebasing the current branch onto %s" % upstream) oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip() @@ -4321,8 +4321,8 @@ class P4Clone(P4Sync): def defaultDestination(self, args): # TODO: use common prefix of args? depotPath = args[0] - depotDir = re.sub("(@[^@]*)$", "", depotPath) - depotDir = re.sub("(#[^#]*)$", "", depotDir) + depotDir = re.sub(r"(@[^@]*)$", "", depotPath) + depotDir = re.sub(r"(#[^#]*)$", "", depotDir) depotDir = re.sub(r"\.\.\.$", "", depotDir) depotDir = re.sub(r"/$", "", depotDir) return os.path.split(depotDir)[1] diff --git a/http-backend.c b/http-backend.c index ff07b87e64..1ed1e29d07 100644 --- a/http-backend.c +++ b/http-backend.c @@ -38,6 +38,7 @@ struct rpc_service { static struct rpc_service rpc_service[] = { { "upload-pack", "uploadpack", 1, 1 }, { "receive-pack", "receivepack", 0, -1 }, + { "upload-archive", "uploadarchive", 0, -1 }, }; static struct string_list *get_parameters(void) @@ -639,10 +640,15 @@ static void check_content_type(struct strbuf *hdr, const char *accepted_type) static void service_rpc(struct strbuf *hdr, char *service_name) { - const char *argv[] = {NULL, "--stateless-rpc", ".", NULL}; + struct strvec argv = STRVEC_INIT; struct rpc_service *svc = select_service(hdr, service_name); struct strbuf buf = STRBUF_INIT; + strvec_push(&argv, svc->name); + if (strcmp(service_name, "git-upload-archive")) + strvec_push(&argv, "--stateless-rpc"); + strvec_push(&argv, "."); + strbuf_reset(&buf); strbuf_addf(&buf, "application/x-git-%s-request", svc->name); check_content_type(hdr, buf.buf); @@ -655,9 +661,9 @@ static void service_rpc(struct strbuf *hdr, char *service_name) end_headers(hdr); - argv[0] = svc->name; - run_service(argv, svc->buffer_input); + run_service(argv.v, svc->buffer_input); strbuf_release(&buf); + strvec_clear(&argv); } static int dead; @@ -723,6 +729,7 @@ static struct service_cmd { {"GET", "/objects/pack/pack-[0-9a-f]{64}\\.idx$", get_idx_file}, {"POST", "/git-upload-pack$", service_rpc}, + {"POST", "/git-upload-archive$", service_rpc}, {"POST", "/git-receive-pack$", service_rpc} }; diff --git a/merge-ll.c b/merge-ll.c index 1df58ebaac..5ffb045efb 100644 --- a/merge-ll.c +++ b/merge-ll.c @@ -185,9 +185,9 @@ static void create_temp(mmfile_t *src, char *path, size_t len) static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, mmbuffer_t *result, const char *path, - mmfile_t *orig, const char *orig_name UNUSED, - mmfile_t *src1, const char *name1 UNUSED, - mmfile_t *src2, const char *name2 UNUSED, + mmfile_t *orig, const char *orig_name, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, const struct ll_merge_options *opts, int marker_size) { @@ -222,6 +222,12 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, strbuf_addf(&cmd, "%d", marker_size); else if (skip_prefix(format, "P", &format)) sq_quote_buf(&cmd, path); + else if (skip_prefix(format, "S", &format)) + sq_quote_buf(&cmd, orig_name ? orig_name : ""); + else if (skip_prefix(format, "X", &format)) + sq_quote_buf(&cmd, name1 ? name1 : ""); + else if (skip_prefix(format, "Y", &format)) + sq_quote_buf(&cmd, name2 ? name2 : ""); else strbuf_addch(&cmd, '%'); } @@ -315,7 +321,12 @@ static int read_merge_config(const char *var, const char *value, * %B - temporary file name for the other branches' version. * %L - conflict marker length * %P - the original path (safely quoted for the shell) + * %S - the revision for the merge base + * %X - the revision for our version + * %Y - the revision for their version * + * If the file is not named indentically in all versions, then each + * revision is joined with the corresponding path, separated by a colon. * The external merge driver should write the results in the * file named by %A, and signal that it has done with zero exit * status. diff --git a/merge-ort.c b/merge-ort.c index 77ba7f3020..8617babee4 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -38,6 +38,7 @@ #include "path.h" #include "promisor-remote.h" #include "read-cache-ll.h" +#include "refs.h" #include "revision.h" #include "sparse-index.h" #include "strmap.h" @@ -2641,7 +2642,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, oidcpy(&ci->stages[i].oid, null_oid()); } - // Now we want to focus on new_ci, so reassign ci to it + /* Now we want to focus on new_ci, so reassign ci to it. */ ci = new_ci; } @@ -4659,9 +4660,6 @@ void merge_switch_to_result(struct merge_options *opt, { assert(opt->priv == NULL); if (result->clean >= 0 && update_worktree_and_index) { - const char *filename; - FILE *fp; - trace2_region_enter("merge", "checkout", opt->repo); if (checkout(opt, head, result->tree)) { /* failure to function */ @@ -4687,10 +4685,17 @@ void merge_switch_to_result(struct merge_options *opt, trace2_region_leave("merge", "record_conflicted", opt->repo); trace2_region_enter("merge", "write_auto_merge", opt->repo); - filename = git_path_auto_merge(opt->repo); - fp = xfopen(filename, "w"); - fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid)); - fclose(fp); + if (refs_update_ref(get_main_ref_store(opt->repo), "", "AUTO_MERGE", + &result->tree->object.oid, NULL, REF_NO_DEREF, + UPDATE_REFS_MSG_ON_ERR)) { + /* failure to function */ + opt->priv = NULL; + result->clean = -1; + merge_finalize(opt, result); + trace2_region_leave("merge", "write_auto_merge", + opt->repo); + return; + } trace2_region_leave("merge", "write_auto_merge", opt->repo); } if (display_update_msgs) diff --git a/oss-fuzz/dummy-cmd-main.c b/oss-fuzz/dummy-cmd-main.c new file mode 100644 index 0000000000..071cb231ba --- /dev/null +++ b/oss-fuzz/dummy-cmd-main.c @@ -0,0 +1,14 @@ +#include "git-compat-util.h" + +/* + * When linking the fuzzers, we link against common-main.o to pick up some + * symbols. However, even though we ignore common-main:main(), we still need to + * provide all the symbols it references. In the fuzzers' case, we need to + * provide a dummy cmd_main() for the linker to be happy. It will never be + * executed. + */ + +int cmd_main(int argc, const char **argv) { + BUG("We should not execute cmd_main() from a fuzz target"); + return 1; +} diff --git a/pack-bitmap.c b/pack-bitmap.c index 229a11fb00..2baeabacee 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -51,13 +51,6 @@ struct bitmap_index { struct packed_git *pack; struct multi_pack_index *midx; - /* - * Mark the first `reuse_objects` in the packfile as reused: - * they will be sent as-is without using them for repacking - * calculations - */ - uint32_t reuse_objects; - /* mmapped buffer of the whole bitmap index */ unsigned char *map; size_t map_size; /* size of the mmaped buffer */ diff --git a/parse-options.c b/parse-options.c index 4ce2b7ca16..63a99dea6e 100644 --- a/parse-options.c +++ b/parse-options.c @@ -357,6 +357,7 @@ static enum parse_opt_result parse_long_opt( const char *arg_end = strchrnul(arg, '='); const struct option *abbrev_option = NULL, *ambiguous_option = NULL; enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG; + int allow_abbrev = !(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT); for (; options->type != OPTION_END; options++) { const char *rest, *long_name = options->long_name; @@ -367,12 +368,16 @@ static enum parse_opt_result parse_long_opt( if (!long_name) continue; -again: + if (!starts_with(arg, "no-") && + !(options->flags & PARSE_OPT_NONEG) && + skip_prefix(long_name, "no-", &long_name)) + opt_flags |= OPT_UNSET; + if (!skip_prefix(arg, long_name, &rest)) rest = NULL; if (!rest) { /* abbreviated? */ - if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) && + if (allow_abbrev && !strncmp(long_name, arg, arg_end - arg)) { is_abbreviated: if (abbrev_option && @@ -396,22 +401,18 @@ is_abbreviated: if (options->flags & PARSE_OPT_NONEG) continue; /* negated and abbreviated very much? */ - if (starts_with("no-", arg)) { + if (allow_abbrev && starts_with("no-", arg)) { flags |= OPT_UNSET; goto is_abbreviated; } /* negated? */ - if (!starts_with(arg, "no-")) { - if (skip_prefix(long_name, "no-", &long_name)) { - opt_flags |= OPT_UNSET; - goto again; - } + if (!starts_with(arg, "no-")) continue; - } flags |= OPT_UNSET; if (!skip_prefix(arg + 3, long_name, &rest)) { /* abbreviated and negated? */ - if (starts_with(long_name, arg + 3)) + if (allow_abbrev && + starts_with(long_name, arg + 3)) goto is_abbreviated; else continue; @@ -1588,7 +1588,5 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE") REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") -REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH") -REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE") REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD") REPO_GIT_PATH_FUNC(shallow, "shallow") @@ -175,8 +175,6 @@ const char *git_path_merge_msg(struct repository *r); const char *git_path_merge_rr(struct repository *r); const char *git_path_merge_mode(struct repository *r); const char *git_path_merge_head(struct repository *r); -const char *git_path_merge_autostash(struct repository *r); -const char *git_path_auto_merge(struct repository *r); const char *git_path_fetch_head(struct repository *r); const char *git_path_shallow(struct repository *r); @@ -1839,13 +1839,10 @@ done: static int is_special_ref(const char *refname) { /* - * Special references get written and read directly via the filesystem - * by the subsystems that create them. Thus, they must not go through - * the reference backend but must instead be read directly. It is - * arguable whether this behaviour is sensible, or whether it's simply - * a leaky abstraction enabled by us only having a single reference - * backend implementation. But at least for a subset of references it - * indeed does make sense to treat them specially: + * Special references are refs that have different semantics compared + * to "normal" refs. These refs can thus not be stored in the ref + * backend, but must always be accessed via the filesystem. The + * following refs are special: * * - FETCH_HEAD may contain multiple object IDs, and each one of them * carries additional metadata like where it came from. @@ -1853,30 +1850,12 @@ static int is_special_ref(const char *refname) * - MERGE_HEAD may contain multiple object IDs when merging multiple * heads. * - * There are some exceptions that you might expect to see on this list - * but which are handled exclusively via the reference backend: - * - * - BISECT_EXPECTED_REV - * - * - CHERRY_PICK_HEAD - * - * - HEAD - * - * - ORIG_HEAD - * - * - "rebase-apply/" and "rebase-merge/" contain all of the state for - * rebases, including some reference-like files. These are - * exclusively read and written via the filesystem and never go - * through the refdb. - * - * Writing or deleting references must consistently go either through - * the filesystem (special refs) or through the reference backend - * (normal ones). + * Reading, writing or deleting references must consistently go either + * through the filesystem (special refs) or through the reference + * backend (normal ones). */ static const char * const special_refs[] = { - "AUTO_MERGE", "FETCH_HEAD", - "MERGE_AUTOSTASH", "MERGE_HEAD", }; size_t i; diff --git a/reftable/blocksource.c b/reftable/blocksource.c index a1ea304429..8c41e3c70f 100644 --- a/reftable/blocksource.c +++ b/reftable/blocksource.c @@ -76,8 +76,8 @@ struct reftable_block_source malloc_block_source(void) } struct file_block_source { - int fd; uint64_t size; + unsigned char *data; }; static uint64_t file_size(void *b) @@ -87,19 +87,12 @@ static uint64_t file_size(void *b) static void file_return_block(void *b, struct reftable_block *dest) { - if (dest->len) - memset(dest->data, 0xff, dest->len); - reftable_free(dest->data); } -static void file_close(void *b) +static void file_close(void *v) { - int fd = ((struct file_block_source *)b)->fd; - if (fd > 0) { - close(fd); - ((struct file_block_source *)b)->fd = 0; - } - + struct file_block_source *b = v; + munmap(b->data, b->size); reftable_free(b); } @@ -108,9 +101,7 @@ static int file_read_block(void *v, struct reftable_block *dest, uint64_t off, { struct file_block_source *b = v; assert(off + size <= b->size); - dest->data = reftable_malloc(size); - if (pread_in_full(b->fd, dest->data, size, off) != size) - return -1; + dest->data = b->data + off; dest->len = size; return size; } @@ -125,26 +116,26 @@ static struct reftable_block_source_vtable file_vtable = { int reftable_block_source_from_file(struct reftable_block_source *bs, const char *name) { - struct stat st = { 0 }; - int err = 0; - int fd = open(name, O_RDONLY); - struct file_block_source *p = NULL; + struct file_block_source *p; + struct stat st; + int fd; + + fd = open(name, O_RDONLY); if (fd < 0) { - if (errno == ENOENT) { + if (errno == ENOENT) return REFTABLE_NOT_EXIST_ERROR; - } return -1; } - err = fstat(fd, &st); - if (err < 0) { + if (fstat(fd, &st) < 0) { close(fd); return REFTABLE_IO_ERROR; } - p = reftable_calloc(sizeof(struct file_block_source)); + p = reftable_calloc(sizeof(*p)); p->size = st.st_size; - p->fd = fd; + p->data = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); assert(!bs->ops); bs->ops = &file_vtable; diff --git a/reftable/merged_test.c b/reftable/merged_test.c index 46908f738f..bf090b474e 100644 --- a/reftable/merged_test.c +++ b/reftable/merged_test.c @@ -42,7 +42,7 @@ static void write_test_table(struct strbuf *buf, } } - w = reftable_new_writer(&strbuf_add_void, buf, &opts); + w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts); reftable_writer_set_limits(w, min, max); for (i = 0; i < n; i++) { @@ -70,7 +70,7 @@ static void write_test_log_table(struct strbuf *buf, .exact_log_message = 1, }; struct reftable_writer *w = NULL; - w = reftable_new_writer(&strbuf_add_void, buf, &opts); + w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts); reftable_writer_set_limits(w, update_index, update_index); for (i = 0; i < n; i++) { @@ -412,7 +412,7 @@ static void test_default_write_opts(void) struct reftable_write_options opts = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record rec = { .refname = "master", diff --git a/reftable/pq_test.c b/reftable/pq_test.c index 011b5c7502..c202eff848 100644 --- a/reftable/pq_test.c +++ b/reftable/pq_test.c @@ -60,7 +60,7 @@ static void test_pq(void) if (last) { EXPECT(strcmp(last, rec->u.ref.refname) < 0); } - // this is names[i], so don't dealloc. + /* this is names[i], so don't dealloc. */ last = rec->u.ref.refname; rec->u.ref.refname = NULL; reftable_record_release(rec); diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c index b8a3224016..6b99daeaf2 100644 --- a/reftable/readwrite_test.c +++ b/reftable/readwrite_test.c @@ -51,7 +51,7 @@ static void write_table(char ***names, struct strbuf *buf, int N, .hash_id = hash_id, }; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts); struct reftable_ref_record ref = { NULL }; int i = 0, n; struct reftable_log_record log = { NULL }; @@ -130,7 +130,7 @@ static void test_log_buffer_size(void) .message = "commit: 9\n", } } }; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); /* This tests buffer extension for log compression. Must use a random hash, to ensure that the compressed part is larger than the original. @@ -171,7 +171,7 @@ static void test_log_overflow(void) .message = msg, } } }; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); uint8_t hash1[GIT_SHA1_RAWSZ] = {1}, hash2[GIT_SHA1_RAWSZ] = { 2 }; @@ -202,7 +202,7 @@ static void test_log_write_read(void) struct reftable_block_source source = { NULL }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); const struct reftable_stats *stats = NULL; reftable_writer_set_limits(w, 0, N); for (i = 0; i < N; i++) { @@ -294,7 +294,7 @@ static void test_log_zlib_corruption(void) struct reftable_block_source source = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); const struct reftable_stats *stats = NULL; uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 }; uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 }; @@ -535,7 +535,7 @@ static void test_table_refs_for(int indexed) struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_iterator it = { NULL }; int j; @@ -628,7 +628,7 @@ static void test_write_empty_table(void) struct reftable_write_options opts = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_block_source source = { NULL }; struct reftable_reader *rd = NULL; struct reftable_ref_record rec = { NULL }; @@ -666,7 +666,7 @@ static void test_write_object_id_min_length(void) }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -701,7 +701,7 @@ static void test_write_object_id_length(void) }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -735,7 +735,7 @@ static void test_write_empty_key(void) struct reftable_write_options opts = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record ref = { .refname = "", .update_index = 1, @@ -758,7 +758,7 @@ static void test_write_key_order(void) struct reftable_write_options opts = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record refs[2] = { { .refname = "b", @@ -801,7 +801,7 @@ static void test_write_multiple_indices(void) struct reftable_reader *reader; int err, i; - writer = reftable_new_writer(&strbuf_add_void, &writer_buf, &opts); + writer = reftable_new_writer(&strbuf_add_void, &noop_flush, &writer_buf, &opts); reftable_writer_set_limits(writer, 1, 1); for (i = 0; i < 100; i++) { struct reftable_ref_record ref = { diff --git a/reftable/refname_test.c b/reftable/refname_test.c index 699e1aea41..b9cc62554e 100644 --- a/reftable/refname_test.c +++ b/reftable/refname_test.c @@ -30,7 +30,7 @@ static void test_conflict(void) struct reftable_write_options opts = { 0 }; struct strbuf buf = STRBUF_INIT; struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &buf, &opts); + reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record rec = { .refname = "a/b", .value_type = REFTABLE_REF_SYMREF, diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index db8de197f6..7c7cae5f99 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -88,6 +88,7 @@ struct reftable_stats { /* reftable_new_writer creates a new writer */ struct reftable_writer * reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), + int (*flush_func)(void *), void *writer_arg, struct reftable_write_options *opts); /* Set the range of update indices for the records we will add. When writing a diff --git a/reftable/stack.c b/reftable/stack.c index 7ffeb3ee10..a1dd79fc06 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -8,6 +8,7 @@ https://developers.google.com/open-source/licenses/bsd #include "stack.h" +#include "../write-or-die.h" #include "system.h" #include "merged.h" #include "reader.h" @@ -16,7 +17,6 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-record.h" #include "reftable-merged.h" #include "writer.h" - #include "tempfile.h" static int stack_try_add(struct reftable_stack *st, @@ -47,6 +47,13 @@ static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz) return write_in_full(*fdp, data, sz); } +static int reftable_fd_flush(void *arg) +{ + int *fdp = (int *)arg; + + return fsync_component(FSYNC_COMPONENT_REFERENCE, *fdp); +} + int reftable_new_stack(struct reftable_stack **dest, const char *dir, struct reftable_write_options config) { @@ -66,6 +73,7 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir, strbuf_addstr(&list_file_name, "/tables.list"); p->list_file = strbuf_detach(&list_file_name, NULL); + p->list_fd = -1; p->reftable_dir = xstrdup(dir); p->config = config; @@ -175,6 +183,12 @@ void reftable_stack_destroy(struct reftable_stack *st) st->readers_len = 0; FREE_AND_NULL(st->readers); } + + if (st->list_fd >= 0) { + close(st->list_fd); + st->list_fd = -1; + } + FREE_AND_NULL(st->list_file); FREE_AND_NULL(st->reftable_dir); reftable_free(st); @@ -304,69 +318,134 @@ static int tv_cmp(struct timeval *a, struct timeval *b) static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, int reuse_open) { - struct timeval deadline = { 0 }; - int err = gettimeofday(&deadline, NULL); + char **names = NULL, **names_after = NULL; + struct timeval deadline; int64_t delay = 0; - int tries = 0; - if (err < 0) - return err; + int tries = 0, err; + int fd = -1; + err = gettimeofday(&deadline, NULL); + if (err < 0) + goto out; deadline.tv_sec += 3; + while (1) { - char **names = NULL; - char **names_after = NULL; - struct timeval now = { 0 }; - int err = gettimeofday(&now, NULL); - int err2 = 0; - if (err < 0) { - return err; - } + struct timeval now; + + err = gettimeofday(&now, NULL); + if (err < 0) + goto out; - /* Only look at deadlines after the first few times. This - simplifies debugging in GDB */ + /* + * Only look at deadlines after the first few times. This + * simplifies debugging in GDB. + */ tries++; - if (tries > 3 && tv_cmp(&now, &deadline) >= 0) { - break; - } + if (tries > 3 && tv_cmp(&now, &deadline) >= 0) + goto out; - err = read_lines(st->list_file, &names); - if (err < 0) { - free_names(names); - return err; - } - err = reftable_stack_reload_once(st, names, reuse_open); - if (err == 0) { - free_names(names); - break; - } - if (err != REFTABLE_NOT_EXIST_ERROR) { - free_names(names); - return err; - } + fd = open(st->list_file, O_RDONLY); + if (fd < 0) { + if (errno != ENOENT) { + err = REFTABLE_IO_ERROR; + goto out; + } - /* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent - writer. Check if there was one by checking if the name list - changed. - */ - err2 = read_lines(st->list_file, &names_after); - if (err2 < 0) { - free_names(names); - return err2; + names = reftable_calloc(sizeof(char *)); + } else { + err = fd_read_lines(fd, &names); + if (err < 0) + goto out; } + err = reftable_stack_reload_once(st, names, reuse_open); + if (!err) + break; + if (err != REFTABLE_NOT_EXIST_ERROR) + goto out; + + /* + * REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent + * writer. Check if there was one by checking if the name list + * changed. + */ + err = read_lines(st->list_file, &names_after); + if (err < 0) + goto out; if (names_equal(names_after, names)) { - free_names(names); - free_names(names_after); - return err; + err = REFTABLE_NOT_EXIST_ERROR; + goto out; } + free_names(names); + names = NULL; free_names(names_after); + names_after = NULL; + close(fd); + fd = -1; delay = delay + (delay * rand()) / RAND_MAX + 1; sleep_millisec(delay); } - return 0; +out: + /* + * Invalidate the stat cache. It is sufficient to only close the file + * descriptor and keep the cached stat info because we never use the + * latter when the former is negative. + */ + if (st->list_fd >= 0) { + close(st->list_fd); + st->list_fd = -1; + } + + /* + * Cache stat information in case it provides a useful signal to us. + * According to POSIX, "The st_ino and st_dev fields taken together + * uniquely identify the file within the system." That being said, + * Windows is not POSIX compliant and we do not have these fields + * available. So the information we have there is insufficient to + * determine whether two file descriptors point to the same file. + * + * While we could fall back to using other signals like the file's + * mtime, those are not sufficient to avoid races. We thus refrain from + * using the stat cache on such systems and fall back to the secondary + * caching mechanism, which is to check whether contents of the file + * have changed. + * + * On other systems which are POSIX compliant we must keep the file + * descriptor open. This is to avoid a race condition where two + * processes access the reftable stack at the same point in time: + * + * 1. A reads the reftable stack and caches its stat info. + * + * 2. B updates the stack, appending a new table to "tables.list". + * This will both use a new inode and result in a different file + * size, thus invalidating A's cache in theory. + * + * 3. B decides to auto-compact the stack and merges two tables. The + * file size now matches what A has cached again. Furthermore, the + * filesystem may decide to recycle the inode number of the file + * we have replaced in (2) because it is not in use anymore. + * + * 4. A reloads the reftable stack. Neither the inode number nor the + * file size changed. If the timestamps did not change either then + * we think the cached copy of our stack is up-to-date. + * + * By keeping the file descriptor open the inode number cannot be + * recycled, mitigating the race. + */ + if (!err && fd >= 0 && !fstat(fd, &st->list_st) && + st->list_st.st_dev && st->list_st.st_ino) { + st->list_fd = fd; + fd = -1; + } + + if (fd >= 0) + close(fd); + free_names(names); + free_names(names_after); + return err; } /* -1 = error @@ -375,8 +454,44 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, static int stack_uptodate(struct reftable_stack *st) { char **names = NULL; - int err = read_lines(st->list_file, &names); + int err; int i = 0; + + /* + * When we have cached stat information available then we use it to + * verify whether the file has been rewritten. + * + * Note that we explicitly do not want to use `stat_validity_check()` + * and friends here because they may end up not comparing the `st_dev` + * and `st_ino` fields. These functions thus cannot guarantee that we + * indeed still have the same file. + */ + if (st->list_fd >= 0) { + struct stat list_st; + + if (stat(st->list_file, &list_st) < 0) { + /* + * It's fine for "tables.list" to not exist. In that + * case, we have to refresh when the loaded stack has + * any readers. + */ + if (errno == ENOENT) + return !!st->readers_len; + return REFTABLE_IO_ERROR; + } + + /* + * When "tables.list" refers to the same file we can assume + * that it didn't change. This is because we always use + * rename(3P) to update the file and never write to it + * directly. + */ + if (st->list_st.st_dev == list_st.st_dev && + st->list_st.st_ino == list_st.st_ino) + return 0; + } + + err = read_lines(st->list_file, &names); if (err < 0) return err; @@ -545,6 +660,9 @@ int reftable_addition_commit(struct reftable_addition *add) goto done; } + fsync_component_or_die(FSYNC_COMPONENT_REFERENCE, lock_file_fd, + get_tempfile_path(add->lock_file)); + err = rename_tempfile(&add->lock_file, add->stack->list_file); if (err < 0) { err = REFTABLE_IO_ERROR; @@ -559,7 +677,7 @@ int reftable_addition_commit(struct reftable_addition *add) add->new_tables = NULL; add->new_tables_len = 0; - err = reftable_stack_reload(add->stack); + err = reftable_stack_reload_maybe_reuse(add->stack, 1); if (err) goto done; @@ -639,7 +757,7 @@ int reftable_addition_add(struct reftable_addition *add, goto done; } } - wr = reftable_new_writer(reftable_fd_write, &tab_fd, + wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, &add->stack->config); err = write_table(wr, arg); if (err < 0) @@ -731,7 +849,13 @@ static int stack_compact_locked(struct reftable_stack *st, int first, int last, strbuf_addstr(temp_tab, ".temp.XXXXXX"); tab_fd = mkstemp(temp_tab->buf); - wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config); + if (st->config.default_permissions && + chmod(temp_tab->buf, st->config.default_permissions) < 0) { + err = REFTABLE_IO_ERROR; + goto done; + } + + wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, &st->config); err = stack_write_compact(st, wr, first, last, config); if (err < 0) @@ -1008,6 +1132,14 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last, unlink(new_table_path.buf); goto done; } + + err = fsync_component(FSYNC_COMPONENT_REFERENCE, lock_file_fd); + if (err < 0) { + err = REFTABLE_IO_ERROR; + unlink(new_table_path.buf); + goto done; + } + err = close(lock_file_fd); lock_file_fd = -1; if (err < 0) { diff --git a/reftable/stack.h b/reftable/stack.h index f57005846e..c1e3efa899 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -14,7 +14,10 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-stack.h" struct reftable_stack { + struct stat list_st; char *list_file; + int list_fd; + char *reftable_dir; int disable_auto_compact; diff --git a/reftable/stack_test.c b/reftable/stack_test.c index 289e902146..5089392f7b 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -443,15 +443,16 @@ static void test_reftable_stack_add(void) int err = 0; struct reftable_write_options cfg = { .exact_log_message = 1, + .default_permissions = 0660, }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); - struct reftable_ref_record refs[2] = { { NULL } }; struct reftable_log_record logs[2] = { { NULL } }; + struct strbuf path = STRBUF_INIT; + struct stat stat_result; int N = ARRAY_SIZE(refs); - err = reftable_new_stack(&st, dir, cfg); EXPECT_ERR(err); st->disable_auto_compact = 1; @@ -509,12 +510,32 @@ static void test_reftable_stack_add(void) reftable_log_record_release(&dest); } +#ifndef GIT_WINDOWS_NATIVE + strbuf_addstr(&path, dir); + strbuf_addstr(&path, "/tables.list"); + err = stat(path.buf, &stat_result); + EXPECT(!err); + EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); + + strbuf_reset(&path); + strbuf_addstr(&path, dir); + strbuf_addstr(&path, "/"); + /* do not try at home; not an external API for reftable. */ + strbuf_addstr(&path, st->readers[0]->name); + err = stat(path.buf, &stat_result); + EXPECT(!err); + EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); +#else + (void) stat_result; +#endif + /* cleanup */ reftable_stack_destroy(st); for (i = 0; i < N; i++) { reftable_ref_record_release(&refs[i]); reftable_log_record_release(&logs[i]); } + strbuf_release(&path); clear_dir(dir); } diff --git a/reftable/test_framework.c b/reftable/test_framework.c index 04044fc1a0..4066924eee 100644 --- a/reftable/test_framework.c +++ b/reftable/test_framework.c @@ -20,3 +20,8 @@ ssize_t strbuf_add_void(void *b, const void *data, size_t sz) strbuf_add(b, data, sz); return sz; } + +int noop_flush(void *arg) +{ + return 0; +} diff --git a/reftable/test_framework.h b/reftable/test_framework.h index ee44f735ae..687390f9c2 100644 --- a/reftable/test_framework.h +++ b/reftable/test_framework.h @@ -56,4 +56,6 @@ void set_test_hash(uint8_t *p, int i); */ ssize_t strbuf_add_void(void *b, const void *data, size_t sz); +int noop_flush(void *); + #endif diff --git a/reftable/writer.c b/reftable/writer.c index ee4590e20f..92935baa70 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -121,6 +121,7 @@ static struct strbuf reftable_empty_strbuf = STRBUF_INIT; struct reftable_writer * reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), + int (*flush_func)(void *), void *writer_arg, struct reftable_write_options *opts) { struct reftable_writer *wp = @@ -136,6 +137,7 @@ reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), wp->write = writer_func; wp->write_arg = writer_arg; wp->opts = *opts; + wp->flush = flush_func; writer_reinit_block_writer(wp, BLOCK_TYPE_REF); return wp; @@ -603,6 +605,12 @@ int reftable_writer_close(struct reftable_writer *w) put_be32(p, crc32(0, footer, p - footer)); p += 4; + err = w->flush(w->write_arg); + if (err < 0) { + err = REFTABLE_IO_ERROR; + goto done; + } + err = padded_write(w, footer, footer_size(writer_version(w)), 0); if (err < 0) goto done; diff --git a/reftable/writer.h b/reftable/writer.h index 09b88673d9..8d0df9cc52 100644 --- a/reftable/writer.h +++ b/reftable/writer.h @@ -16,6 +16,7 @@ https://developers.google.com/open-source/licenses/bsd struct reftable_writer { ssize_t (*write)(void *, const void *, size_t); + int (*flush)(void *); void *write_arg; int pending_padding; struct strbuf last_key; diff --git a/remote-curl.c b/remote-curl.c index cb0182b582..1161dc7fed 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -1446,8 +1446,14 @@ static int stateless_connect(const char *service_name) * establish a stateless connection, otherwise we need to tell the * client to fallback to using other transport helper functions to * complete their request. + * + * The "git-upload-archive" service is a read-only operation. Fallback + * to use "git-upload-pack" service to discover protocol version. */ - discover = discover_refs(service_name, 0); + if (!strcmp(service_name, "git-upload-archive")) + discover = discover_refs("git-upload-pack", 0); + else + discover = discover_refs(service_name, 0); if (discover->version != protocol_v2) { printf("fallback\n"); fflush(stdout); @@ -1485,9 +1491,11 @@ static int stateless_connect(const char *service_name) /* * Dump the capability listing that we got from the server earlier - * during the info/refs request. + * during the info/refs request. This does not work with the + * "git-upload-archive" service. */ - write_or_die(rpc.in, discover->buf, discover->len); + if (strcmp(service_name, "git-upload-archive")) + write_or_die(rpc.in, discover->buf, discover->len); /* Until we see EOF keep sending POSTs */ while (1) { diff --git a/repository.c b/repository.c index d7d24d416a..7aacb51b65 100644 --- a/repository.c +++ b/repository.c @@ -262,8 +262,6 @@ static void repo_clear_path_cache(struct repo_path_cache *cache) FREE_AND_NULL(cache->merge_rr); FREE_AND_NULL(cache->merge_mode); FREE_AND_NULL(cache->merge_head); - FREE_AND_NULL(cache->merge_autostash); - FREE_AND_NULL(cache->auto_merge); FREE_AND_NULL(cache->fetch_head); FREE_AND_NULL(cache->shallow); } diff --git a/repository.h b/repository.h index f5269b3730..7a250a6605 100644 --- a/repository.h +++ b/repository.h @@ -67,8 +67,6 @@ struct repo_path_cache { char *merge_rr; char *merge_mode; char *merge_head; - char *merge_autostash; - char *auto_merge; char *fetch_head; char *shallow; }; diff --git a/sequencer.c b/sequencer.c index 3cc88d8a80..91de546b32 100644 --- a/sequencer.c +++ b/sequencer.c @@ -474,7 +474,7 @@ static void print_advice(struct repository *r, int show_hint, * of the commit itself so remove CHERRY_PICK_HEAD */ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", - NULL, 0); + NULL, REF_NO_DEREF); return; } @@ -1667,7 +1667,7 @@ static int do_commit(struct repository *r, strbuf_release(&sb); if (!res) { refs_delete_ref(get_main_ref_store(r), "", - "CHERRY_PICK_HEAD", NULL, 0); + "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF); unlink(git_path_merge_msg(r)); if (!is_rebase_i(opts)) print_commit_summary(r, NULL, &oid, @@ -2406,9 +2406,10 @@ static int do_pick_commit(struct repository *r, } else if (allow == 2) { drop_commit = 1; refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", - NULL, 0); + NULL, REF_NO_DEREF); unlink(git_path_merge_msg(r)); - unlink(git_path_auto_merge(r)); + refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE", + NULL, REF_NO_DEREF); fprintf(stderr, _("dropping %s %s -- patch contents already upstream\n"), oid_to_hex(&commit->object.oid), msg.subject); @@ -2802,7 +2803,7 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose) if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) { if (!refs_delete_ref(get_main_ref_store(r), "", - "CHERRY_PICK_HEAD", NULL, 0) && + "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF) && verbose) warning(_("cancelling a cherry picking in progress")); opts.action = REPLAY_PICK; @@ -2811,14 +2812,15 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose) if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) { if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD", - NULL, 0) && + NULL, REF_NO_DEREF) && verbose) warning(_("cancelling a revert in progress")); opts.action = REPLAY_REVERT; need_cleanup = 1; } - unlink(git_path_auto_merge(r)); + refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE", + NULL, REF_NO_DEREF); if (!need_cleanup) return; @@ -4116,7 +4118,7 @@ static int do_merge(struct repository *r, strbuf_release(&ref_name); refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD", - NULL, 0); + NULL, REF_NO_DEREF); rollback_lock_file(&lock); ret = run_command(&cmd); @@ -4461,12 +4463,17 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } -void create_autostash(struct repository *r, const char *path) +static void create_autostash_internal(struct repository *r, + const char *path, + const char *refname) { struct strbuf buf = STRBUF_INIT; struct lock_file lock_file = LOCK_INIT; int fd; + if (path && refname) + BUG("can only pass path or refname"); + fd = repo_hold_locked_index(r, &lock_file, 0); refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); if (0 <= fd) @@ -4493,10 +4500,16 @@ void create_autostash(struct repository *r, const char *path) strbuf_reset(&buf); strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - if (safe_create_leading_directories_const(path)) - die(_("Could not create directory for '%s'"), - path); - write_file(path, "%s", oid_to_hex(&oid)); + if (path) { + if (safe_create_leading_directories_const(path)) + die(_("Could not create directory for '%s'"), + path); + write_file(path, "%s", oid_to_hex(&oid)); + } else { + refs_update_ref(get_main_ref_store(r), "", refname, + &oid, null_oid(), 0, UPDATE_REFS_DIE_ON_ERR); + } + printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(r, &ropts) < 0) die(_("could not reset --hard")); @@ -4507,6 +4520,16 @@ void create_autostash(struct repository *r, const char *path) strbuf_release(&buf); } +void create_autostash(struct repository *r, const char *path) +{ + create_autostash_internal(r, path, NULL); +} + +void create_autostash_ref(struct repository *r, const char *refname) +{ + create_autostash_internal(r, NULL, refname); +} + static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) { struct child_process child = CHILD_PROCESS_INIT; @@ -4584,6 +4607,41 @@ int apply_autostash_oid(const char *stash_oid) return apply_save_autostash_oid(stash_oid, 1); } +static int apply_save_autostash_ref(struct repository *r, const char *refname, + int attempt_apply) +{ + struct object_id stash_oid; + char stash_oid_hex[GIT_MAX_HEXSZ + 1]; + int flag, ret; + + if (!refs_ref_exists(get_main_ref_store(r), refname)) + return 0; + + if (!refs_resolve_ref_unsafe(get_main_ref_store(r), refname, + RESOLVE_REF_READING, &stash_oid, &flag)) + return -1; + if (flag & REF_ISSYMREF) + return error(_("autostash reference is a symref")); + + oid_to_hex_r(stash_oid_hex, &stash_oid); + ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply); + + refs_delete_ref(get_main_ref_store(r), "", refname, + &stash_oid, REF_NO_DEREF); + + return ret; +} + +int save_autostash_ref(struct repository *r, const char *refname) +{ + return apply_save_autostash_ref(r, refname, 0); +} + +int apply_autostash_ref(struct repository *r, const char *refname) +{ + return apply_save_autostash_ref(r, refname, 1); +} + static int checkout_onto(struct repository *r, struct replay_opts *opts, const char *onto_name, const struct object_id *onto, const struct object_id *orig_head) @@ -4766,8 +4824,10 @@ static int pick_commits(struct repository *r, } unlink(rebase_path_author_script()); unlink(git_path_merge_head(r)); - unlink(git_path_auto_merge(r)); - delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); + refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE", + NULL, REF_NO_DEREF); + refs_delete_ref(get_main_ref_store(r), "", "REBASE_HEAD", + NULL, REF_NO_DEREF); if (item->command == TODO_BREAK) { if (!opts->verbose) @@ -5108,7 +5168,7 @@ static int commit_staged_changes(struct repository *r, if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") && refs_delete_ref(get_main_ref_store(r), "", - "CHERRY_PICK_HEAD", NULL, 0)) + "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF)) return error(_("could not remove CHERRY_PICK_HEAD")); if (unlink(git_path_merge_msg(r)) && errno != ENOENT) return error_errno(_("could not remove '%s'"), @@ -5122,7 +5182,8 @@ static int commit_staged_changes(struct repository *r, return error(_("could not commit staged changes.")); unlink(rebase_path_amend()); unlink(git_path_merge_head(r)); - unlink(git_path_auto_merge(r)); + refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE", + NULL, REF_NO_DEREF); if (final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); diff --git a/sequencer.h b/sequencer.h index 913a0f652d..dcef7bb99c 100644 --- a/sequencer.h +++ b/sequencer.h @@ -225,9 +225,12 @@ void commit_post_rewrite(struct repository *r, const struct object_id *new_head); void create_autostash(struct repository *r, const char *path); +void create_autostash_ref(struct repository *r, const char *refname); int save_autostash(const char *path); +int save_autostash_ref(struct repository *r, const char *refname); int apply_autostash(const char *path); int apply_autostash_oid(const char *stash_oid); +int apply_autostash_ref(struct repository *r, const char *refname); #define SUMMARY_INITIAL_COMMIT (1 << 0) #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1) @@ -1371,7 +1371,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (is_git_directory(dir->buf)) { trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf); - if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT) + if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT && + !ends_with_path_components(dir->buf, ".git")) return GIT_DIR_DISALLOWED_BARE; if (!ensure_valid_ownership(NULL, NULL, dir->buf, report)) return GIT_DIR_INVALID_OWNERSHIP; diff --git a/shared.mak b/shared.mak index aeb80fc4d5..29bebd30d8 100644 --- a/shared.mak +++ b/shared.mak @@ -108,3 +108,11 @@ endif define mkdir_p_parent_template $(if $(wildcard $(@D)),,$(QUIET_MKDIR_P_PARENT)$(shell mkdir -p $(@D))) endef + +## Getting sick of writing -L$(SOMELIBDIR) $(CC_LD_DYNPATH)$(SOMELIBDIR)? +## Write $(call libpath_template,$(SOMELIBDIR)) instead, perhaps? +## With CC_LD_DYNPATH set to either an empty string or to "-L", the +## the directory is not shown the second time. +define libpath_template +-L$(1) $(if $(filter-out -L,$(CC_LD_DYNPATH)),$(CC_LD_DYNPATH)$(1)) +endef diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c deleted file mode 100644 index f0bf255f5f..0000000000 --- a/t/helper/test-prio-queue.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "test-tool.h" -#include "prio-queue.h" - -static int intcmp(const void *va, const void *vb, void *data UNUSED) -{ - const int *a = va, *b = vb; - return *a - *b; -} - -static void show(int *v) -{ - if (!v) - printf("NULL\n"); - else - printf("%d\n", *v); - free(v); -} - -int cmd__prio_queue(int argc UNUSED, const char **argv) -{ - struct prio_queue pq = { intcmp }; - - while (*++argv) { - if (!strcmp(*argv, "get")) { - void *peek = prio_queue_peek(&pq); - void *get = prio_queue_get(&pq); - if (peek != get) - BUG("peek and get results do not match"); - show(get); - } else if (!strcmp(*argv, "dump")) { - void *peek; - void *get; - while ((peek = prio_queue_peek(&pq))) { - get = prio_queue_get(&pq); - if (peek != get) - BUG("peek and get results do not match"); - show(get); - } - } else if (!strcmp(*argv, "stack")) { - pq.compare = NULL; - } else { - int *v = xmalloc(sizeof(*v)); - *v = atoi(*argv); - prio_queue_put(&pq, v); - } - } - - clear_prio_queue(&pq); - - return 0; -} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 33b9501c21..482a1e58a4 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -55,7 +55,6 @@ static struct test_cmd cmds[] = { { "path-utils", cmd__path_utils }, { "pcre2-config", cmd__pcre2_config }, { "pkt-line", cmd__pkt_line }, - { "prio-queue", cmd__prio_queue }, { "proc-receive", cmd__proc_receive }, { "progress", cmd__progress }, { "reach", cmd__reach }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index b72f07ded9..b1be7cfcf5 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -48,7 +48,6 @@ int cmd__partial_clone(int argc, const char **argv); int cmd__path_utils(int argc, const char **argv); int cmd__pcre2_config(int argc, const char **argv); int cmd__pkt_line(int argc, const char **argv); -int cmd__prio_queue(int argc, const char **argv); int cmd__proc_receive(int argc, const char **argv); int cmd__progress(int argc, const char **argv); int cmd__reach(int argc, const char **argv); diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh deleted file mode 100755 index eea99107a4..0000000000 --- a/t/t0009-prio-queue.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh - -test_description='basic tests for priority queue implementation' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -cat >expect <<'EOF' -1 -2 -3 -4 -5 -5 -6 -7 -8 -9 -10 -EOF -test_expect_success 'basic ordering' ' - test-tool prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -2 -3 -4 -1 -5 -6 -EOF -test_expect_success 'mixed put and get' ' - test-tool prio-queue 6 2 4 get 5 3 get get 1 dump >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -1 -2 -NULL -1 -2 -NULL -EOF -test_expect_success 'notice empty queue' ' - test-tool prio-queue 1 2 get get get 1 2 get get get >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -3 -2 -6 -4 -5 -1 -8 -EOF -test_expect_success 'stack order' ' - test-tool prio-queue stack 8 1 5 4 6 2 3 dump >actual && - test_cmp expect actual -' - -test_done diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh index c13057a4ca..0dcfb760a2 100755 --- a/t/t0018-advice.sh +++ b/t/t0018-advice.sh @@ -17,7 +17,6 @@ test_expect_success 'advice should be printed when config variable is unset' ' test_expect_success 'advice should be printed when config variable is set to true' ' cat >expect <<-\EOF && hint: This is a piece of advice - hint: Disable this message with "git config advice.nestedTag false" EOF test_config advice.nestedTag true && test-tool advise "This is a piece of advice" 2>actual && diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh index a34de56420..a7f4de4a43 100755 --- a/t/t0024-crlf-archive.sh +++ b/t/t0024-crlf-archive.sh @@ -9,7 +9,7 @@ test_expect_success setup ' git config core.autocrlf true && - printf "CRLF line ending\r\nAnd another\r\n" > sample && + printf "CRLF line ending\r\nAnd another\r\n" >sample && git add sample && test_tick && @@ -19,8 +19,9 @@ test_expect_success setup ' test_expect_success 'tar archive' ' - git archive --format=tar HEAD | - ( mkdir untarred && cd untarred && "$TAR" -xf - ) && + git archive --format=tar HEAD >test.tar && + mkdir untarred && + "$TAR" xf test.tar -C untarred && test_cmp sample untarred/sample @@ -30,7 +31,11 @@ test_expect_success UNZIP 'zip archive' ' git archive --format=zip HEAD >test.zip && - ( mkdir unzipped && cd unzipped && "$GIT_UNZIP" ../test.zip ) && + mkdir unzipped && + ( + cd unzipped && + "$GIT_UNZIP" ../test.zip + ) && test_cmp sample unzipped/sample diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh index 038b8b788d..8048856379 100755 --- a/t/t0035-safe-bare-repository.sh +++ b/t/t0035-safe-bare-repository.sh @@ -78,4 +78,12 @@ test_expect_success 'no trace when GIT_DIR is explicitly provided' ' expect_accepted_explicit "$pwd/outer-repo/bare-repo" ' +test_expect_success 'no trace when "bare repository" is .git' ' + expect_accepted_implicit -C outer-repo/.git +' + +test_expect_success 'no trace when "bare repository" is a subdir of .git' ' + expect_accepted_implicit -C outer-repo/.git/objects +' + test_done diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh index 961b54b06c..6657c114a3 100755 --- a/t/t0080-unit-test-output.sh +++ b/t/t0080-unit-test-output.sh @@ -2,6 +2,7 @@ test_description='Test the output of the unit test framework' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'TAP output from unit tests' ' diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh index 8798feefe3..fca39048fe 100755 --- a/t/t0091-bugreport.sh +++ b/t/t0091-bugreport.sh @@ -39,9 +39,9 @@ test_expect_success 'sanity check "System Info" section' ' sed -ne "/^\[System Info\]$/,/^$/p" <git-bugreport-format.txt >system && - # The beginning should match "git version --build-info" verbatim, + # The beginning should match "git version --build-options" verbatim, # but rather than checking bit-for-bit equality, just test some basics. - grep "git version [0-9]." system && + grep "git version " system && grep "shell-path: ." system && # After the version, there should be some more info. diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh new file mode 100755 index 0000000000..e6a5f1868f --- /dev/null +++ b/t/t0600-reffiles-backend.sh @@ -0,0 +1,384 @@ +#!/bin/sh + +test_description='Test reffiles backend' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +if ! test_have_prereq REFFILES +then + skip_all='skipping reffiles specific tests' + test_done +fi + +test_expect_success 'setup' ' + git commit --allow-empty -m Initial && + C=$(git rev-parse HEAD) && + git commit --allow-empty -m Second && + D=$(git rev-parse HEAD) && + git commit --allow-empty -m Third && + E=$(git rev-parse HEAD) +' + +test_expect_success 'empty directory should not fool rev-parse' ' + prefix=refs/e-rev-parse && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + echo "$C" >expected && + git rev-parse $prefix/foo >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool for-each-ref' ' + prefix=refs/e-for-each-ref && + git update-ref $prefix/foo $C && + git for-each-ref $prefix >expected && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + git for-each-ref $prefix >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool create' ' + prefix=refs/e-create && + mkdir -p .git/$prefix/foo/bar/baz && + printf "create %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool verify' ' + prefix=refs/e-verify && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "verify %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg update' ' + prefix=refs/e-update-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 2-arg update' ' + prefix=refs/e-update-2 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 0-arg delete' ' + prefix=refs/e-delete-0 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg delete' ' + prefix=refs/e-delete-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'non-empty directory blocks create' ' + prefix=refs/ne-create && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks create' ' + prefix=refs/broken-create && + mkdir -p .git/$prefix && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks indirect create' ' + prefix=refs/ne-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks indirect create' ' + prefix=refs/broken-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'no bogus intermediate values during delete' ' + prefix=refs/slow-transaction && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + # Now try to update the reference, but hold the `packed-refs` lock + # for a while to see what happens while the process is blocked: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + { + # Note: the following command is intentionally run in the + # background. We increase the timeout so that `update-ref` + # attempts to acquire the `packed-refs` lock for much longer + # than it takes for us to do the check then delete it: + git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo & + } && + pid2=$! && + # Give update-ref plenty of time to get to the point where it tries + # to lock packed-refs: + sleep 1 && + # Make sure that update-ref did not complete despite the lock: + kill -0 $pid2 && + # Verify that the reference still has its old value: + sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && + case "$sha1" in + $D) + # This is what we hope for; it means that nothing + # user-visible has changed yet. + : ;; + undefined) + # This is not correct; it means the deletion has happened + # already even though update-ref should not have been + # able to acquire the lock yet. + echo "$prefix/foo deleted prematurely" && + break + ;; + $C) + # This value should never be seen. Probably the loose + # reference has been deleted but the packed reference + # is still there: + echo "$prefix/foo incorrectly observed to be C" && + break + ;; + *) + # WTF? + echo "unexpected value observed for $prefix/foo: $sha1" && + break + ;; + esac >out && + rm -f .git/packed-refs.lock && + wait $pid2 && + test_must_be_empty out && + test_must_fail git rev-parse --verify --quiet $prefix/foo +' + +test_expect_success 'delete fails cleanly if packed-refs file is locked' ' + prefix=refs/locked-packed-refs && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to delete it while the `packed-refs` lock is held: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + test_must_fail git update-ref -d $prefix/foo >out 2>err && + git for-each-ref $prefix >actual && + test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err && + test_cmp unchanged actual +' + +test_expect_success 'delete fails cleanly if packed-refs.new write fails' ' + # Setup and expectations are similar to the test above. + prefix=refs/failed-packed-refs && + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # This should not happen in practice, but it is an easy way to get a + # reliable error (we open with create_tempfile(), which uses O_EXCL). + : >.git/packed-refs.new && + test_when_finished "rm -f .git/packed-refs.new" && + test_must_fail git update-ref -d $prefix/foo && + git for-each-ref $prefix >actual && + test_cmp unchanged actual +' + +RWT="test-tool ref-store worktree:wt" +RMAIN="test-tool ref-store worktree:main" + +test_expect_success 'setup worktree' ' + test_commit first && + git worktree add -b wt-main wt && + ( + cd wt && + test_commit second + ) +' + +# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should +# only appear in the for-each-reflog output if it is called from the correct +# worktree, which is exercised in this test. This test is poorly written for +# mulitple reasons: 1) it creates invalidly formatted log entres. 2) it uses +# direct FS access for creating the reflogs. 3) PSEUDO-WT and refs/bisect/random +# do not create reflogs by default, so it is not testing a realistic scenario. +test_expect_success 'for_each_reflog()' ' + echo $ZERO_OID > .git/logs/PSEUDO-MAIN && + mkdir -p .git/logs/refs/bisect && + echo $ZERO_OID > .git/logs/refs/bisect/random && + + echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT && + mkdir -p .git/worktrees/wt/logs/refs/bisect && + echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random && + + $RWT for-each-reflog | cut -d" " -f 2- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-WT 0x0 + refs/bisect/wt-random 0x0 + refs/heads/main 0x0 + refs/heads/wt-main 0x0 + EOF + test_cmp expected actual && + + $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-MAIN 0x0 + refs/bisect/random 0x0 + refs/heads/main 0x0 + refs/heads/wt-main 0x0 + EOF + test_cmp expected actual +' + +# Triggering the bug detected by this test requires a newline to fall +# exactly BUFSIZ-1 bytes from the end of the file. We don't know +# what that value is, since it's platform dependent. However, if +# we choose some value N, we also catch any D which divides N evenly +# (since we will read backwards in chunks of D). So we choose 8K, +# which catches glibc (with an 8K BUFSIZ) and *BSD (1K). +# +# Each line is 114 characters, so we need 75 to still have a few before the +# last 8K. The 89-character padding on the final entry lines up our +# newline exactly. +test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' + git checkout -b reflogskip && + zf=$(test_oid zero_2) && + ident="abc <xyz> 0000000001 +0000" && + for i in $(test_seq 1 75); do + printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" && + if test $i = 75; then + for j in $(test_seq 1 89); do + printf X || return 1 + done + else + printf X + fi && + printf "\n" || return 1 + done >.git/logs/refs/heads/reflogskip && + git rev-parse reflogskip@{73} >actual && + echo ${zf}03 >expect && + test_cmp expect actual +' + +# This test takes a lock on an individual ref; this is not supported in +# reftable. +test_expect_success 'reflog expire operates on symref not referrent' ' + git branch --create-reflog the_symref && + git branch --create-reflog referrent && + git update-ref referrent HEAD && + git symbolic-ref refs/heads/the_symref refs/heads/referrent && + test_when_finished "rm -f .git/refs/heads/referrent.lock" && + touch .git/refs/heads/referrent.lock && + git reflog expire --expire=all the_symref +' + +test_expect_success 'empty reflog' ' + test_when_finished "rm -rf empty" && + git init empty && + test_commit -C empty A && + >empty/.git/logs/refs/heads/foo && + git -C empty reflog expire --all 2>err && + test_must_be_empty err +' + +test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' ' + ln -s does-not-exist .git/refs/heads/broken && + test_must_fail git rev-parse --verify broken +' + +test_expect_success 'log diagnoses bogus HEAD hash' ' + git init empty && + test_when_finished "rm -rf empty" && + echo 1234abcd >empty/.git/refs/heads/main && + test_must_fail git -C empty log 2>stderr && + test_grep broken stderr +' + +test_expect_success 'log diagnoses bogus HEAD symref' ' + git init empty && + test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock && + test_must_fail git -C empty log 2>stderr && + test_grep broken stderr && + test_must_fail git -C empty log --default totally-bogus 2>stderr && + test_grep broken stderr +' + +test_done diff --git a/t/t3210-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh index 7f4e98db7d..c309d2bae8 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t0601-reffiles-pack-refs.sh @@ -15,6 +15,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if ! test_have_prereq REFFILES +then + skip_all='skipping reffiles specific tests' + test_done +fi + test_expect_success 'enable reflogs' ' git config core.logallrefupdates true ' @@ -26,6 +32,14 @@ test_expect_success 'prepare a trivial repository' ' HEAD=$(git rev-parse --verify HEAD) ' +test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' ' + N=`find .git/refs -type f | wc -l` && + test "$N" != 0 && + test-tool ref-store main pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL && + N=`find .git/refs -type f` && + test -z "$N" +' + SHA1= test_expect_success 'see if git show-ref works as expected' ' @@ -294,4 +308,54 @@ test_expect_success SYMLINKS 'pack symlinked packed-refs' ' test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs" ' +# The 'packed-refs' file is stored directly in .git/. This means it is global +# to the repository, and can only contain refs that are shared across all +# worktrees. +test_expect_success 'refs/worktree must not be packed' ' + test_commit initial && + test_commit wt1 && + test_commit wt2 && + git worktree add wt1 wt1 && + git worktree add wt2 wt2 && + git checkout initial && + git update-ref refs/worktree/foo HEAD && + git -C wt1 update-ref refs/worktree/foo HEAD && + git -C wt2 update-ref refs/worktree/foo HEAD && + git pack-refs --all && + test_path_is_missing .git/refs/tags/wt1 && + test_path_is_file .git/refs/worktree/foo && + test_path_is_file .git/worktrees/wt1/refs/worktree/foo && + test_path_is_file .git/worktrees/wt2/refs/worktree/foo +' + +# we do not want to count on running pack-refs to +# actually pack it, as it is perfectly reasonable to +# skip processing a broken ref +test_expect_success 'create packed-refs file with broken ref' ' + test_tick && git commit --allow-empty -m one && + recoverable=$(git rev-parse HEAD) && + test_tick && git commit --allow-empty -m two && + missing=$(git rev-parse HEAD) && + rm -f .git/refs/heads/main && + cat >.git/packed-refs <<-EOF && + $missing refs/heads/main + $recoverable refs/heads/other + EOF + echo $missing >expect && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'pack-refs does not silently delete broken packed ref' ' + git pack-refs --all --prune && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'pack-refs does not drop broken refs during deletion' ' + git update-ref -d refs/heads/other && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1300-config.sh b/t/t1300-config.sh index f4e2752134..31c3878687 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -1098,15 +1098,20 @@ test_expect_success SYMLINKS 'symlink to nonexistent configuration' ' test_must_fail git config --file=linktolinktonada --list ' -test_expect_success 'check split_cmdline return' " - git config alias.split-cmdline-fix 'echo \"' && - test_must_fail git split-cmdline-fix && - echo foo > foo && - git add foo && - git commit -m 'initial commit' && - git config branch.main.mergeoptions 'echo \"' && - test_must_fail git merge main -" +test_expect_success 'check split_cmdline return' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config alias.split-cmdline-fix "echo \"" && + test_must_fail git split-cmdline-fix && + echo foo >foo && + git add foo && + git commit -m "initial commit" && + git config branch.main.mergeoptions "echo \"" && + test_must_fail git merge main + ) +' test_expect_success 'git -c "key=value" support' ' cat >expect <<-\EOF && @@ -1157,10 +1162,16 @@ test_expect_success 'git -c works with aliases of builtins' ' ' test_expect_success 'aliases can be CamelCased' ' - test_config alias.CamelCased "rev-parse HEAD" && - git CamelCased >out && - git rev-parse HEAD >expect && - test_cmp expect out + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + git config alias.CamelCased "rev-parse HEAD" && + git CamelCased >out && + git rev-parse HEAD >expect && + test_cmp expect out + ) ' test_expect_success 'git -c does not split values on equals' ' @@ -2009,11 +2020,11 @@ test_expect_success '--show-origin getting a single key' ' ' test_expect_success 'set up custom config file' ' - CUSTOM_CONFIG_FILE="custom.conf" && - cat >"$CUSTOM_CONFIG_FILE" <<-\EOF + cat >"custom.conf" <<-\EOF && [user] custom = true EOF + CUSTOM_CONFIG_FILE="$(test-tool path-utils real_path custom.conf)" ' test_expect_success !MINGW 'set up custom config file with special name characters' ' @@ -2052,22 +2063,33 @@ test_expect_success '--show-origin stdin with file include' ' ' test_expect_success '--show-origin blob' ' - blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && - cat >expect <<-EOF && - blob:$blob user.custom=true - EOF - git config --blob=$blob --show-origin --list >output && - test_cmp expect output + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && + cat >expect <<-EOF && + blob:$blob user.custom=true + EOF + git config --blob=$blob --show-origin --list >output && + test_cmp expect output + ) ' test_expect_success '--show-origin blob ref' ' - cat >expect <<-\EOF && - blob:main:custom.conf user.custom=true - EOF - git add "$CUSTOM_CONFIG_FILE" && - git commit -m "new config file" && - git config --blob=main:"$CUSTOM_CONFIG_FILE" --show-origin --list >output && - test_cmp expect output + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + cat >expect <<-\EOF && + blob:main:custom.conf user.custom=true + EOF + cp "$CUSTOM_CONFIG_FILE" custom.conf && + git add custom.conf && + git commit -m "new config file" && + git config --blob=main:custom.conf --show-origin --list >output && + test_cmp expect output + ) ' test_expect_success '--show-origin with --default' ' diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index e5a0d65caa..8e2c01e760 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -137,7 +137,7 @@ test_expect_success POSIXPERM 'info/refs respects umask in unshared repo' ' test_cmp expect actual ' -test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' ' +test_expect_success REFFILES,POSIXPERM 'git reflog expire honors core.sharedRepository' ' umask 077 && git config core.sharedRepository group && git reflog expire --all && diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 179474fa65..42caa0d297 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -9,10 +9,6 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' - test_oid_cache <<-\EOF && - version sha1:0 - version sha256:1 - EOF cat >test.patch <<-\EOF && diff --git a/test.txt b/test.txt new file mode 100644 @@ -28,7 +24,12 @@ test_expect_success 'setup' ' ' test_expect_success 'gitdir selection on normal repos' ' - test_oid version >expect && + if test_have_prereq DEFAULT_REPO_FORMAT + then + echo 0 + else + echo 1 + fi >expect && git config core.repositoryformatversion >actual && git -C test config core.repositoryformatversion >actual2 && test_cmp expect actual && @@ -79,8 +80,13 @@ mkconfig () { while read outcome version extensions; do test_expect_success "$outcome version=$version $extensions" " - mkconfig $version $extensions >.git/config && - check_${outcome} + test_when_finished 'rm -rf extensions' && + git init extensions && + ( + cd extensions && + mkconfig $version $extensions >.git/config && + check_${outcome} + ) " done <<\EOF allow 0 @@ -94,7 +100,8 @@ allow 1 noop-v1 EOF test_expect_success 'precious-objects allowed' ' - mkconfig 1 preciousObjects >.git/config && + git config core.repositoryFormatVersion 1 && + git config extensions.preciousObjects 1 && check_allow ' diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index ec1957b709..d0a8f7b121 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -262,9 +262,9 @@ test_expect_success '--exists with non-commit object' ' test_expect_success '--exists with directory fails with generic error' ' cat >expect <<-EOF && - error: failed to look up reference: Is a directory + error: reference does not exist EOF - test_expect_code 1 git show-ref --exists refs/heads 2>err && + test_expect_code 2 git show-ref --exists refs/heads 2>err && test_cmp expect err ' diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 0369beea33..00b7013705 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -191,78 +191,6 @@ test_expect_success 'one new ref is a simple prefix of another' ' ' -test_expect_success REFFILES 'empty directory should not fool rev-parse' ' - prefix=refs/e-rev-parse && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - echo "$C" >expected && - git rev-parse $prefix/foo >actual && - test_cmp expected actual -' - -test_expect_success REFFILES 'empty directory should not fool for-each-ref' ' - prefix=refs/e-for-each-ref && - git update-ref $prefix/foo $C && - git for-each-ref $prefix >expected && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - git for-each-ref $prefix >actual && - test_cmp expected actual -' - -test_expect_success REFFILES 'empty directory should not fool create' ' - prefix=refs/e-create && - mkdir -p .git/$prefix/foo/bar/baz && - printf "create %s $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool verify' ' - prefix=refs/e-verify && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "verify %s $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 1-arg update' ' - prefix=refs/e-update-1 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "update %s $D\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 2-arg update' ' - prefix=refs/e-update-2 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "update %s $D $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 0-arg delete' ' - prefix=refs/e-delete-0 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "delete %s\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 1-arg delete' ' - prefix=refs/e-delete-1 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "delete %s $C\n" $prefix/foo | - git update-ref --stdin -' - test_expect_success REFFILES 'D/F conflict prevents add long + delete short' ' df_test refs/df-al-ds --add-del foo/bar foo ' @@ -468,169 +396,4 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' ' test_cmp expected output.err ' -test_expect_success REFFILES 'non-empty directory blocks create' ' - prefix=refs/ne-create && - mkdir -p .git/$prefix/foo/bar && - : >.git/$prefix/foo/bar/baz.lock && - test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/foo $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/foo $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'broken reference blocks create' ' - prefix=refs/broken-create && - mkdir -p .git/$prefix && - echo "gobbledigook" >.git/$prefix/foo && - test_when_finished "rm -f .git/$prefix/foo" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/foo $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/foo $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'non-empty directory blocks indirect create' ' - prefix=refs/ne-indirect-create && - git symbolic-ref $prefix/symref $prefix/foo && - mkdir -p .git/$prefix/foo/bar && - : >.git/$prefix/foo/bar/baz.lock && - test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/symref $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/symref $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'broken reference blocks indirect create' ' - prefix=refs/broken-indirect-create && - git symbolic-ref $prefix/symref $prefix/foo && - echo "gobbledigook" >.git/$prefix/foo && - test_when_finished "rm -f .git/$prefix/foo" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/symref $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/symref $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'no bogus intermediate values during delete' ' - prefix=refs/slow-transaction && - # Set up a reference with differing loose and packed versions: - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - # Now try to update the reference, but hold the `packed-refs` lock - # for a while to see what happens while the process is blocked: - : >.git/packed-refs.lock && - test_when_finished "rm -f .git/packed-refs.lock" && - { - # Note: the following command is intentionally run in the - # background. We increase the timeout so that `update-ref` - # attempts to acquire the `packed-refs` lock for much longer - # than it takes for us to do the check then delete it: - git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo & - } && - pid2=$! && - # Give update-ref plenty of time to get to the point where it tries - # to lock packed-refs: - sleep 1 && - # Make sure that update-ref did not complete despite the lock: - kill -0 $pid2 && - # Verify that the reference still has its old value: - sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && - case "$sha1" in - $D) - # This is what we hope for; it means that nothing - # user-visible has changed yet. - : ;; - undefined) - # This is not correct; it means the deletion has happened - # already even though update-ref should not have been - # able to acquire the lock yet. - echo "$prefix/foo deleted prematurely" && - break - ;; - $C) - # This value should never be seen. Probably the loose - # reference has been deleted but the packed reference - # is still there: - echo "$prefix/foo incorrectly observed to be C" && - break - ;; - *) - # WTF? - echo "unexpected value observed for $prefix/foo: $sha1" && - break - ;; - esac >out && - rm -f .git/packed-refs.lock && - wait $pid2 && - test_must_be_empty out && - test_must_fail git rev-parse --verify --quiet $prefix/foo -' - -test_expect_success REFFILES 'delete fails cleanly if packed-refs file is locked' ' - prefix=refs/locked-packed-refs && - # Set up a reference with differing loose and packed versions: - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - git for-each-ref $prefix >unchanged && - # Now try to delete it while the `packed-refs` lock is held: - : >.git/packed-refs.lock && - test_when_finished "rm -f .git/packed-refs.lock" && - test_must_fail git update-ref -d $prefix/foo >out 2>err && - git for-each-ref $prefix >actual && - test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err && - test_cmp unchanged actual -' - -test_expect_success REFFILES 'delete fails cleanly if packed-refs.new write fails' ' - # Setup and expectations are similar to the test above. - prefix=refs/failed-packed-refs && - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - git for-each-ref $prefix >unchanged && - # This should not happen in practice, but it is an easy way to get a - # reliable error (we open with create_tempfile(), which uses O_EXCL). - : >.git/packed-refs.new && - test_when_finished "rm -f .git/packed-refs.new" && - test_must_fail git update-ref -d $prefix/foo && - git for-each-ref $prefix >actual && - test_cmp unchanged actual -' - test_done diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index e4627cf1b6..976bd71efb 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -15,14 +15,6 @@ test_expect_success 'setup' ' test_commit one ' -test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' ' - N=`find .git/refs -type f | wc -l` && - test "$N" != 0 && - $RUN pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL && - N=`find .git/refs -type f` && - test -z "$N" -' - test_expect_success 'create_symref(FOO, refs/heads/main)' ' $RUN create-symref FOO refs/heads/main nothing && echo refs/heads/main >expected && @@ -112,7 +104,7 @@ test_expect_success 'delete_reflog(HEAD)' ' test_must_fail git reflog exists HEAD ' -test_expect_success REFFILES 'create-reflog(HEAD)' ' +test_expect_success 'create-reflog(HEAD)' ' $RUN create-reflog HEAD && git reflog exists HEAD ' diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index 05b1881c59..48b1c92a41 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -53,41 +53,4 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' ' test_cmp expected actual ' -# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should -# only appear in the for-each-reflog output if it is called from the correct -# worktree, which is exercised in this test. This test is poorly written (and -# therefore marked REFFILES) for mulitple reasons: 1) it creates invalidly -# formatted log entres. 2) it uses direct FS access for creating the reflogs. 3) -# PSEUDO-WT and refs/bisect/random do not create reflogs by default, so it is -# not testing a realistic scenario. -test_expect_success REFFILES 'for_each_reflog()' ' - echo $ZERO_OID > .git/logs/PSEUDO-MAIN && - mkdir -p .git/logs/refs/bisect && - echo $ZERO_OID > .git/logs/refs/bisect/random && - - echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT && - mkdir -p .git/worktrees/wt/logs/refs/bisect && - echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random && - - $RWT for-each-reflog | cut -d" " -f 2- | sort >actual && - cat >expected <<-\EOF && - HEAD 0x1 - PSEUDO-WT 0x0 - refs/bisect/wt-random 0x0 - refs/heads/main 0x0 - refs/heads/wt-main 0x0 - EOF - test_cmp expected actual && - - $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual && - cat >expected <<-\EOF && - HEAD 0x1 - PSEUDO-MAIN 0x0 - refs/bisect/random 0x0 - refs/heads/main 0x0 - refs/heads/wt-main 0x0 - EOF - test_cmp expected actual -' - test_done diff --git a/t/t1409-avoid-packing-refs.sh b/t/t1409-avoid-packing-refs.sh index f23c0152a8..7748973733 100755 --- a/t/t1409-avoid-packing-refs.sh +++ b/t/t1409-avoid-packing-refs.sh @@ -5,6 +5,12 @@ test_description='avoid rewriting packed-refs unnecessarily' TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if test_have_prereq !REFFILES +then + skip_all='skipping files-backend specific pack-refs tests' + test_done +fi + # 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. diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index a0ff8d51f0..d2f5f42e67 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -354,36 +354,6 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' ' test_must_be_empty actual ' -# Triggering the bug detected by this test requires a newline to fall -# exactly BUFSIZ-1 bytes from the end of the file. We don't know -# what that value is, since it's platform dependent. However, if -# we choose some value N, we also catch any D which divides N evenly -# (since we will read backwards in chunks of D). So we choose 8K, -# which catches glibc (with an 8K BUFSIZ) and *BSD (1K). -# -# Each line is 114 characters, so we need 75 to still have a few before the -# last 8K. The 89-character padding on the final entry lines up our -# newline exactly. -test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' - git checkout -b reflogskip && - zf=$(test_oid zero_2) && - ident="abc <xyz> 0000000001 +0000" && - for i in $(test_seq 1 75); do - printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" && - if test $i = 75; then - for j in $(test_seq 1 89); do - printf X || return 1 - done - else - printf X - fi && - printf "\n" || return 1 - done >.git/logs/refs/heads/reflogskip && - git rev-parse reflogskip@{73} >actual && - echo ${zf}03 >expect && - test_cmp expect actual -' - test_expect_success 'no segfaults for reflog containing non-commit sha1s' ' git update-ref --create-reflog -m "Creating ref" \ refs/tests/tree-in-reflog HEAD && @@ -397,18 +367,6 @@ test_expect_failure 'reflog with non-commit entries displays all entries' ' test_line_count = 3 actual ' -# This test takes a lock on an individual ref; this is not supported in -# reftable. -test_expect_success REFFILES 'reflog expire operates on symref not referrent' ' - git branch --create-reflog the_symref && - git branch --create-reflog referrent && - git update-ref referrent HEAD && - git symbolic-ref refs/heads/the_symref refs/heads/referrent && - test_when_finished "rm -f .git/refs/heads/referrent.lock" && - touch .git/refs/heads/referrent.lock && - git reflog expire --expire=all the_symref -' - test_expect_success 'continue walking past root commits' ' git init orphanage && ( diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh index ea64cecf47..be6c3f472c 100755 --- a/t/t1414-reflog-walk.sh +++ b/t/t1414-reflog-walk.sh @@ -121,13 +121,12 @@ test_expect_success 'min/max age uses entry date to limit' ' # Create a situation where the reflog and ref database disagree about the latest # state of HEAD. -test_expect_success REFFILES 'walk prefers reflog to ref tip' ' +test_expect_success 'walk prefers reflog to ref tip' ' + test_commit A && + test_commit B && + git reflog delete HEAD@{0} && head=$(git rev-parse HEAD) && - one=$(git rev-parse one) && - ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && - echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD && - - echo $one >expect && + git rev-parse A >expect && git log -g --format=%H -1 >actual && test_cmp expect actual ' diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh index 3b531842dd..eb4eec8bec 100755 --- a/t/t1415-worktree-refs.sh +++ b/t/t1415-worktree-refs.sh @@ -17,17 +17,6 @@ test_expect_success 'setup' ' git -C wt2 update-ref refs/worktree/foo HEAD ' -# The 'packed-refs' file is stored directly in .git/. This means it is global -# to the repository, and can only contain refs that are shared across all -# worktrees. -test_expect_success REFFILES 'refs/worktree must not be packed' ' - git pack-refs --all && - test_path_is_missing .git/refs/tags/wt1 && - test_path_is_file .git/refs/worktree/foo && - test_path_is_file .git/worktrees/wt1/refs/worktree/foo && - test_path_is_file .git/worktrees/wt2/refs/worktree/foo -' - test_expect_success 'refs/worktree are per-worktree' ' test_cmp_rev worktree/foo initial && ( cd wt1 && test_cmp_rev worktree/foo wt1 ) && diff --git a/t/t1419-exclude-refs.sh b/t/t1419-exclude-refs.sh index 5d8c86b657..1359574419 100755 --- a/t/t1419-exclude-refs.sh +++ b/t/t1419-exclude-refs.sh @@ -8,6 +8,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if test_have_prereq !REFFILES +then + skip_all='skipping `git for-each-ref --exclude` tests; need files backend' + test_done +fi + for_each_ref__exclude () { GIT_TRACE2_PERF=1 test-tool ref-store main \ for-each-ref--exclude "$@" >actual.raw diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index bc136833c1..79df65ec7f 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -144,11 +144,6 @@ test_expect_success 'main@{n} for various n' ' test_must_fail git rev-parse --verify main@{$Np1} ' -test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' ' - ln -s does-not-exist .git/refs/heads/broken && - test_must_fail git rev-parse --verify broken -' - test_expect_success 'options can appear after --verify' ' git rev-parse --verify HEAD >expect && git rev-parse --verify -q HEAD >actual && diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh index 947d1587ac..a5c7358eea 100755 --- a/t/t2017-checkout-orphan.sh +++ b/t/t2017-checkout-orphan.sh @@ -86,7 +86,7 @@ test_expect_success '--orphan makes reflog by default' ' git rev-parse --verify delta@{0} ' -test_expect_success REFFILES '--orphan does not make reflog when core.logAllRefUpdates = false' ' +test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' ' git checkout main && git config core.logAllRefUpdates false && git checkout --orphan epsilon && diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 3742971105..c28c04133c 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -490,7 +490,8 @@ test_expect_success 'put a worktree under rebase' ' cd under-rebase && set_fake_editor && FAKE_LINES="edit 1" git rebase -i HEAD^ && - git worktree list | grep "under-rebase.*detached HEAD" + git worktree list >actual && + grep "under-rebase.*detached HEAD" actual ) ' @@ -531,7 +532,8 @@ test_expect_success 'checkout a branch under bisect' ' git bisect start && git bisect bad && git bisect good HEAD~2 && - git worktree list | grep "under-bisect.*detached HEAD" && + git worktree list >actual && + grep "under-bisect.*detached HEAD" actual && test_must_fail git worktree add new-bisect under-bisect && ! test -d new-bisect ) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 34faeac3f1..3319240515 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,7 +200,7 @@ test_expect_success 'drop stash reflog updates refs/stash' ' test_cmp expect actual ' -test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' ' +test_expect_success 'drop stash reflog updates refs/stash with rewrite' ' git init repo && ( cd repo && @@ -213,16 +213,16 @@ test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' new_oid="$(git -C repo rev-parse stash@{0})" && cat >expect <<-EOF && - $(test_oid zero) $old_oid - $old_oid $new_oid + $new_oid + $old_oid EOF - cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + git -C repo reflog show refs/stash --format=%H >actual && test_cmp expect actual && git -C repo stash drop stash@{1} && - cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + git -C repo reflog show refs/stash --format=%H >actual && cat >expect <<-EOF && - $(test_oid zero) $new_oid + $new_oid EOF test_cmp expect actual ' diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 85be1367de..49c042a38a 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -286,4 +286,28 @@ test_expect_success 'basename similarity vs best similarity' ' test_cmp expected actual ' +test_expect_success 'last line matters too' ' + { + test_write_lines a 0 1 2 3 4 5 6 7 8 9 && + printf "git ignores final up to 63 characters if not newline terminated" + } >no-final-lf && + git add no-final-lf && + git commit -m "original version of file with no final newline" && + + # Change ONLY the first character of the whole file + { + test_write_lines b 0 1 2 3 4 5 6 7 8 9 && + printf "git ignores final up to 63 characters if not newline terminated" + } >no-final-lf && + git add no-final-lf && + git mv no-final-lf still-absent-final-lf && + git commit -a -m "rename no-final-lf -> still-absent-final-lf" && + git diff-tree -r -M --name-status HEAD^ HEAD >actual && + sed -e "s/^R[0-9]* /R /" actual >actual.munged && + cat >expected <<-\EOF && + R no-final-lf still-absent-final-lf + EOF + test_cmp expected actual.munged +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index cb094241ec..1e3b2dbea4 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -663,4 +663,10 @@ test_expect_success 'diff --default-prefix overrides diff.mnemonicprefix' ' check_prefix actual a/file0 b/file0 ' +test_expect_success 'diff --no-renames cannot be abbreviated' ' + test_expect_code 129 git diff --no-rename >actual 2>error && + test_must_be_empty actual && + grep "invalid option: --no-rename" error +' + test_done diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 5ce345d309..651ec77660 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -205,6 +205,18 @@ test_expect_success POSIXPERM,SYMLINKS 'diff --no-index normalizes: mode not lik test_cmp expected actual ' +test_expect_success POSIXPERM 'external diff with mode-only change' ' + echo content >not-executable && + echo content >executable && + chmod +x executable && + echo executable executable $(test_oid zero) 100755 \ + not-executable $(test_oid zero) 100644 not-executable \ + >expect && + test_expect_code 1 git -c diff.external=echo diff \ + --no-index executable not-executable >actual && + test_cmp expect actual +' + test_expect_success "diff --no-index treats '-' as stdin" ' cat >expect <<-EOF && diff --git a/- b/a/1 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index ddd205f98a..60fe60d761 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -2255,23 +2255,6 @@ test_expect_success 'log on empty repo fails' ' test_grep does.not.have.any.commits stderr ' -test_expect_success REFFILES 'log diagnoses bogus HEAD hash' ' - git init empty && - test_when_finished "rm -rf empty" && - echo 1234abcd >empty/.git/refs/heads/main && - test_must_fail git -C empty log 2>stderr && - test_grep broken stderr -' - -test_expect_success REFFILES 'log diagnoses bogus HEAD symref' ' - git init empty && - test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock && - test_must_fail git -C empty log 2>stderr && - test_grep broken stderr && - test_must_fail git -C empty log --default totally-bogus 2>stderr && - test_grep broken stderr -' - test_expect_success 'log does not default to HEAD when rev input is given' ' git log --branches=does-not-exist >actual && test_must_be_empty actual diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index fc499cdff0..961c6aac25 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -239,4 +239,38 @@ check_zip with_untracked2 check_added with_untracked2 untracked one/untracked check_added with_untracked2 untracked two/untracked +# Test remote archive over HTTP protocol. +# +# Note: this should be the last part of this test suite, because +# by including lib-httpd.sh, the test may end early if httpd tests +# should not be run. +# +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success "setup for HTTP protocol" ' + cp -R bare.git "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" \ + config http.uploadpack true && + set_askpass user@host pass@host +' + +setup_askpass_helper + +test_expect_success 'remote archive does not work with protocol v1' ' + test_must_fail git -c protocol.version=1 archive \ + --remote="$HTTPD_URL/auth/smart/bare.git" \ + --output=remote-http.zip HEAD >actual 2>&1 && + cat >expect <<-EOF && + fatal: can${SQ}t connect to subservice git-upload-archive + EOF + test_cmp expect actual +' + +test_expect_success 'archive remote http repository' ' + git archive --remote="$HTTPD_URL/auth/smart/bare.git" \ + --output=remote-http.zip HEAD && + test_cmp_bin d.zip remote-http.zip +' + test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index d402ec18b7..a58f91035d 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -441,6 +441,47 @@ test_expect_success 'index-pack with --strict' ' ) ' +test_expect_success 'setup for --strict and --fsck-objects downgrading fsck msgs' ' + git init strict && + ( + cd strict && + test_commit first hello && + cat >commit <<-EOF && + tree $(git rev-parse HEAD^{tree}) + parent $(git rev-parse HEAD) + author A U Thor + committer A U Thor + + commit: this is a commit with bad emails + + EOF + git hash-object --literally -t commit -w --stdin <commit >commit_list && + git pack-objects test <commit_list >pack-name + ) +' + +test_with_bad_commit () { + must_fail_arg="$1" && + must_pass_arg="$2" && + ( + cd strict && + test_expect_fail git index-pack "$must_fail_arg" "test-$(cat pack-name).pack" + git index-pack "$must_pass_arg" "test-$(cat pack-name).pack" + ) +} + +test_expect_success 'index-pack with --strict downgrading fsck msgs' ' + test_with_bad_commit --strict --strict="missingEmail=ignore" +' + +test_expect_success 'index-pack with --fsck-objects downgrading fsck msgs' ' + test_with_bad_commit --fsck-objects --fsck-objects="missingEmail=ignore" +' + +test_expect_success 'cleanup for --strict and --fsck-objects downgrading fsck msgs' ' + rm -rf strict +' + test_expect_success 'honor pack.packSizeLimit' ' git config pack.packSizeLimit 3m && packname_10=$(git pack-objects test-10 <obj-list) && diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index 230cb38712..d8d2e30468 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -111,30 +111,4 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' ' test_cmp expect actual ' -# we do not want to count on running pack-refs to -# actually pack it, as it is perfectly reasonable to -# skip processing a broken ref -test_expect_success REFFILES 'create packed-refs file with broken ref' ' - rm -f .git/refs/heads/main && - cat >.git/packed-refs <<-EOF && - $missing refs/heads/main - $recoverable refs/heads/other - EOF - echo $missing >expect && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - -test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' ' - git pack-refs --all --prune && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - -test_expect_success REFFILES 'pack-refs does not drop broken refs during deletion' ' - git update-ref -d refs/heads/other && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - test_done diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh index 2ba788b042..99145327a6 100755 --- a/t/t5332-multi-pack-reuse.sh +++ b/t/t5332-multi-pack-reuse.sh @@ -2,6 +2,7 @@ test_description='pack-objects multi-pack reuse' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-bitmap.sh diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 7ab220fa31..5e566205ba 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -771,7 +771,7 @@ test_expect_success 'fetching submodule into a broken repository' ' git -C dst fetch --recurse-submodules && # Break the receiving submodule - test-tool -C dst/sub ref-store main delete-refs REF_NO_DEREF msg HEAD && + rm -r dst/sub/.git/objects && # NOTE: without the fix the following tests will recurse forever! # They should terminate with an error. diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index e069737b80..a623a1058c 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -733,4 +733,22 @@ test_expect_success 'no empty path components' ' ! grep "//" log ' +test_expect_success 'tag following always works over v0 http' ' + upstream=$HTTPD_DOCUMENT_ROOT_PATH/tags && + git init "$upstream" && + ( + cd "$upstream" && + git commit --allow-empty -m base && + git tag not-annotated && + git tag -m foo annotated + ) && + git init tags && + git -C tags -c protocol.version=0 \ + fetch --depth 1 $HTTPD_URL/smart/tags \ + refs/tags/annotated:refs/tags/annotated && + git -C "$upstream" for-each-ref refs/tags >expect && + git -C tags for-each-ref >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6113-rev-list-bitmap-filters.sh b/t/t6113-rev-list-bitmap-filters.sh index 459f0d7412..a9656a1ec8 100755 --- a/t/t6113-rev-list-bitmap-filters.sh +++ b/t/t6113-rev-list-bitmap-filters.sh @@ -1,10 +1,11 @@ #!/bin/sh test_description='rev-list combining bitmaps and filters' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-bitmap.sh -TEST_PASSES_SANITIZE_LEAK=true test_expect_success 'set up bitmapped repo' ' # one commit will have bitmaps, the other will not diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 72f8c1722f..156a1efacf 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -42,11 +42,15 @@ test_expect_success setup ' #!/bin/sh orig="$1" ours="$2" theirs="$3" exit="$4" path=$5 + orig_name="$6" our_name="$7" their_name="$8" ( echo "orig is $orig" echo "ours is $ours" echo "theirs is $theirs" echo "path is $path" + echo "orig_name is $orig_name" + echo "our_name is $our_name" + echo "their_name is $their_name" echo "=== orig ===" cat "$orig" echo "=== ours ===" @@ -121,7 +125,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0 %P" && + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -132,7 +136,8 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file main^:text) && a=$(git unpack-file side^:text) && b=$(git unpack-file main:text) && - sh -c "./custom-merge $o $a $b 0 text" && + base_revid=$(git rev-parse --short main^) && + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && rm -f $o $a $b @@ -142,7 +147,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 1 %P" && + merge.custom.driver "./custom-merge %O %A %B 1 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -159,7 +164,8 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file main^:text) && a=$(git unpack-file anchor:text) && b=$(git unpack-file main:text) && - sh -c "./custom-merge $o $a $b 0 text" && + base_revid=$(git rev-parse --short main^) && + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && sed -e 1,3d -e 4q $a >check-3 && @@ -173,7 +179,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0 %P" && + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && diff --git a/t/test-lib-github-workflow-markup.sh b/t/test-lib-github-workflow-markup.sh index 970c6538cb..33405c90d7 100644 --- a/t/test-lib-github-workflow-markup.sh +++ b/t/test-lib-github-workflow-markup.sh @@ -42,8 +42,8 @@ finalize_test_case_output () { fixed) echo >>$github_markup_output "::notice::fixed: $this_test.$test_count $1" ;; - ok) - # Exit without printing the "ok" tests + ok|broken) + # Exit without printing the "ok" or ""broken" tests return ;; esac diff --git a/t/test-lib.sh b/t/test-lib.sh index fc93aa57e6..042f557a6f 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1297,6 +1297,11 @@ test_done () { EOF fi + if test -z "$passes_sanitize_leak" && test_bool_env TEST_PASSES_SANITIZE_LEAK false + then + BAIL_OUT "Please, set TEST_PASSES_SANITIZE_LEAK before sourcing test-lib.sh" + fi + if test "$test_fixed" != 0 then say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" diff --git a/t/unit-tests/t-prio-queue.c b/t/unit-tests/t-prio-queue.c new file mode 100644 index 0000000000..d78b002f9e --- /dev/null +++ b/t/unit-tests/t-prio-queue.c @@ -0,0 +1,98 @@ +#include "test-lib.h" +#include "prio-queue.h" + +static int intcmp(const void *va, const void *vb, void *data UNUSED) +{ + const int *a = va, *b = vb; + return *a - *b; +} + + +#define MISSING -1 +#define DUMP -2 +#define STACK -3 +#define GET -4 +#define REVERSE -5 + +static int show(int *v) +{ + return v ? *v : MISSING; +} + +static void test_prio_queue(int *input, int *result, size_t input_size) +{ + struct prio_queue pq = { intcmp }; + + for (int i = 0, j = 0; i < input_size; i++) { + void *peek, *get; + switch(input[i]) { + case GET: + peek = prio_queue_peek(&pq); + get = prio_queue_get(&pq); + if (!check(peek == get)) + return; + if(!check_int(result[j++], ==, show(get))) + test_msg("failed at result[] index %d", j-1); + break; + case DUMP: + while ((peek = prio_queue_peek(&pq))) { + get = prio_queue_get(&pq); + if (!check(peek == get)) + return; + if(!check_int(result[j++], ==, show(get))) + test_msg("failed at result[] index %d", j-1); + } + break; + case STACK: + pq.compare = NULL; + break; + case REVERSE: + prio_queue_reverse(&pq); + break; + default: + prio_queue_put(&pq, &input[i]); + break; + } + } + clear_prio_queue(&pq); +} + +#define BASIC_INPUT 2, 6, 3, 10, 9, 5, 7, 4, 5, 8, 1, DUMP +#define BASIC_RESULT 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10 + +#define MIXED_PUT_GET_INPUT 6, 2, 4, GET, 5, 3, GET, GET, 1, DUMP +#define MIXED_PUT_GET_RESULT 2, 3, 4, 1, 5, 6 + +#define EMPTY_QUEUE_INPUT 1, 2, GET, GET, GET, 1, 2, GET, GET, GET +#define EMPTY_QUEUE_RESULT 1, 2, MISSING, 1, 2, MISSING + +#define STACK_INPUT STACK, 8, 1, 5, 4, 6, 2, 3, DUMP +#define STACK_RESULT 3, 2, 6, 4, 5, 1, 8 + +#define REVERSE_STACK_INPUT STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP +#define REVERSE_STACK_RESULT 1, 2, 3, 4, 5, 6 + +#define TEST_INPUT(INPUT, RESULT, name) \ + static void test_##name(void) \ +{ \ + int input[] = {INPUT}; \ + int result[] = {RESULT}; \ + test_prio_queue(input, result, ARRAY_SIZE(input)); \ +} + +TEST_INPUT(BASIC_INPUT, BASIC_RESULT, basic) +TEST_INPUT(MIXED_PUT_GET_INPUT, MIXED_PUT_GET_RESULT, mixed) +TEST_INPUT(EMPTY_QUEUE_INPUT, EMPTY_QUEUE_RESULT, empty) +TEST_INPUT(STACK_INPUT, STACK_RESULT, stack) +TEST_INPUT(REVERSE_STACK_INPUT, REVERSE_STACK_RESULT, reverse) + +int cmd_main(int argc, const char **argv) +{ + TEST(test_basic(), "prio-queue works for basic input"); + TEST(test_mixed(), "prio-queue works for mixed put & get commands"); + TEST(test_empty(), "prio-queue works when queue is empty"); + TEST(test_stack(), "prio-queue works when used as a LIFO stack"); + TEST(test_reverse(), "prio-queue works when LIFO stack is reversed"); + + return test_done(); +} diff --git a/transport-helper.c b/transport-helper.c index e34a8f47cf..dd6002b393 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -17,6 +17,7 @@ #include "refspec.h" #include "transport-internal.h" #include "protocol.h" +#include "packfile.h" static int debug; @@ -432,6 +433,8 @@ static int fetch_with_fetch(struct transport *transport, warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf); } strbuf_release(&buf); + + reprepare_packed_git(the_repository); return 0; } @@ -626,7 +629,8 @@ static int process_connect_service(struct transport *transport, ret = run_connect(transport, &cmdbuf); } else if (data->stateless_connect && (get_protocol_version_config() == protocol_v2) && - !strcmp("git-upload-pack", name)) { + (!strcmp("git-upload-pack", name) || + !strcmp("git-upload-archive", name))) { strbuf_addf(&cmdbuf, "stateless-connect %s\n", name); ret = run_connect(transport, &cmdbuf); if (ret) @@ -643,6 +647,7 @@ static int process_connect(struct transport *transport, struct helper_data *data = transport->data; const char *name; const char *exec; + int ret; name = for_push ? "git-receive-pack" : "git-upload-pack"; if (for_push) @@ -650,7 +655,10 @@ static int process_connect(struct transport *transport, else exec = data->transport_options.uploadpack; - return process_connect_service(transport, name, exec); + ret = process_connect_service(transport, name, exec); + if (ret) + do_take_over(transport); + return ret; } static int connect_helper(struct transport *transport, const char *name, @@ -660,14 +668,14 @@ static int connect_helper(struct transport *transport, const char *name, /* Get_helper so connect is inited. */ get_helper(transport); - if (!data->connect) - die(_("operation not supported by protocol")); if (!process_connect_service(transport, name, exec)) die(_("can't connect to subservice %s"), name); fd[0] = data->helper->out; fd[1] = data->helper->in; + + do_take_over(transport); return 0; } @@ -682,10 +690,8 @@ static int fetch_refs(struct transport *transport, get_helper(transport); - if (process_connect(transport, 0)) { - do_take_over(transport); + if (process_connect(transport, 0)) return transport->vtable->fetch_refs(transport, nr_heads, to_fetch); - } /* * If we reach here, then the server, the client, and/or the transport @@ -1142,10 +1148,8 @@ static int push_refs(struct transport *transport, { struct helper_data *data = transport->data; - if (process_connect(transport, 1)) { - do_take_over(transport); + if (process_connect(transport, 1)) return transport->vtable->push_refs(transport, remote_refs, flags); - } if (!remote_refs) { fprintf(stderr, @@ -1186,11 +1190,9 @@ static struct ref *get_refs_list(struct transport *transport, int for_push, { get_helper(transport); - if (process_connect(transport, for_push)) { - do_take_over(transport); + if (process_connect(transport, for_push)) return transport->vtable->get_refs_list(transport, for_push, transport_options); - } return get_refs_list_using_list(transport, for_push); } @@ -1274,10 +1276,8 @@ static int get_bundle_uri(struct transport *transport) { get_helper(transport); - if (process_connect(transport, 0)) { - do_take_over(transport); + if (process_connect(transport, 0)) return transport->vtable->get_bundle_uri(transport); - } return -1; } |