diff options
68 files changed, 1030 insertions, 284 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd4df939b5..84a5dcff7a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,13 +6,57 @@ env: DEVELOPER: 1 jobs: + ci-config: + runs-on: ubuntu-latest + outputs: + enabled: ${{ steps.check-ref.outputs.enabled }} + steps: + - name: try to clone ci-config branch + continue-on-error: true + run: | + git -c protocol.version=2 clone \ + --no-tags \ + --single-branch \ + -b ci-config \ + --depth 1 \ + --no-checkout \ + --filter=blob:none \ + https://github.com/${{ github.repository }} \ + config-repo && + cd config-repo && + git checkout HEAD -- ci/config + - id: check-ref + name: check whether CI is enabled for ref + run: | + enabled=yes + if test -x config-repo/ci/config/allow-ref && + ! config-repo/ci/config/allow-ref '${{ github.ref }}' + then + enabled=no + fi + echo "::set-output name=enabled::$enabled" + windows-build: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' runs-on: windows-latest steps: - uses: actions/checkout@v1 - name: download git-sdk-64-minimal shell: bash - run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf - + run: | + ## Get artifact + urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds + id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" | + jq -r ".value[] | .id") + download_url="$(curl "$urlbase/$id/artifacts" | + jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')" + curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \ + -o artifacts.zip "$download_url" + + ## Unzip and remove the artifact + unzip artifacts.zip + rm artifacts.zip - name: build shell: powershell env: @@ -30,6 +74,11 @@ jobs: with: name: windows-artifacts path: artifacts + - name: upload git-sdk-64-minimal + uses: actions/upload-artifact@v1 + with: + name: git-sdk-64-minimal + path: git-sdk-64-minimal windows-test: runs-on: windows-latest needs: [windows-build] @@ -38,9 +87,6 @@ jobs: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: - uses: actions/checkout@v1 - - name: download git-sdk-64-minimal - shell: bash - run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf - - name: download build artifacts uses: actions/download-artifact@v1 with: @@ -49,6 +95,11 @@ jobs: - name: extract build artifacts shell: bash run: tar xf artifacts.tar.gz + - name: download git-sdk-64-minimal + uses: actions/download-artifact@v1 + with: + name: git-sdk-64-minimal + path: ${{github.workspace}}/git-sdk-64-minimal/ - name: test shell: powershell run: | @@ -70,6 +121,8 @@ jobs: name: failed-tests-windows path: ${{env.FAILED_TEST_ARTIFACTS}} vs-build: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' env: MSYSTEM: MINGW64 NO_PERL: 1 @@ -79,7 +132,19 @@ jobs: - uses: actions/checkout@v1 - name: download git-sdk-64-minimal shell: bash - run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf - + run: | + ## Get artifact + urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds + id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" | + jq -r ".value[] | .id") + download_url="$(curl "$urlbase/$id/artifacts" | + jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')" + curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \ + -o artifacts.zip "$download_url" + + ## Unzip and remove the artifact + unzip artifacts.zip + rm artifacts.zip - name: generate Visual Studio solution shell: powershell run: | @@ -119,15 +184,17 @@ jobs: path: artifacts vs-test: runs-on: windows-latest - needs: [vs-build] + needs: [vs-build, windows-build] strategy: matrix: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: - uses: actions/checkout@v1 - - name: download git-64-portable - shell: bash - run: a=git-64-portable && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf - + - name: download git-sdk-64-minimal + uses: actions/download-artifact@v1 + with: + name: git-sdk-64-minimal + path: ${{github.workspace}}/git-sdk-64-minimal/ - name: download build artifacts uses: actions/download-artifact@v1 with: @@ -143,9 +210,9 @@ jobs: NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 run: | - & git-64-portable\git-cmd.exe --command=usr\bin\bash.exe -lc @" + & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @" # Let Git ignore the SDK and the test-cache - printf '%s\n' /git-64-portable/ /test-cache/ >>.git/info/exclude + printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude cd t && PATH=\"`$PWD/helper:`$PATH\" && @@ -154,6 +221,8 @@ jobs: ${{matrix.nr}} 10 t[0-9]*.sh) "@ regular: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' strategy: matrix: vector: @@ -189,6 +258,8 @@ jobs: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} dockerized: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' strategy: matrix: vector: @@ -213,6 +284,8 @@ jobs: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} static-analysis: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' env: jobname: StaticAnalysis runs-on: ubuntu-latest @@ -221,6 +294,8 @@ jobs: - run: ci/install-dependencies.sh - run: ci/run-static-analysis.sh documentation: + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' env: jobname: Documentation runs-on: ubuntu-latest diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index a89e8dcfbc..227f46ae40 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -232,6 +232,18 @@ For C programs: while( condition ) func (bar+1); + - Do not explicitly compare an integral value with constant 0 or '\0', + or a pointer value with constant NULL. For instance, to validate that + counted array <ptr, cnt> is initialized but has no elements, write: + + if (!ptr || cnt) + BUG("empty array expected"); + + and not: + + if (ptr == NULL || cnt != 0); + BUG("empty array expected"); + - We avoid using braces unnecessarily. I.e. if (bla) { diff --git a/Documentation/RelNotes/2.27.0.txt b/Documentation/RelNotes/2.27.0.txt index 2a8c6e8f28..e3121d0164 100644 --- a/Documentation/RelNotes/2.27.0.txt +++ b/Documentation/RelNotes/2.27.0.txt @@ -108,6 +108,13 @@ UI, Workflows & Features * "git restore --staged --worktree" now defaults to take the contents out of "HEAD", instead of erring out. + * "git p4" learned to recover from a (broken) state where a directory + and a file are recorded at the same path in the Perforce repository + the same way as their clients do. + + * "git multi-pack-index repack" has been taught to honor some + repack.* configuration variables. + Performance, Internal Implementation, Development Support etc. @@ -161,6 +168,16 @@ Performance, Internal Implementation, Development Support etc. * The "bugreport" tool has been added. + * The object walk with object filter "--filter=tree:0" can now take + advantage of the pack bitmap when available. + + * Instead of always building all branches at GitHub via Actions, + users can specify which branches to build. + + * Codepaths that show progress meter have been taught to also use the + start_progress() and the stop_progress() calls as a "region" to be + traced. + Fixes since v2.26 ----------------- @@ -420,6 +437,39 @@ Fixes since v2.26 been corrected. (merge 0555e4af58 cb/t0000-use-the-configured-shell later to maint). + * Minor in-code comments and documentation updates around credential + API. + (merge 1aed817f99 cb/credential-doc-fixes later to maint). + + * Teach "am", "commit", "merge" and "rebase", when they are run with + the "--quiet" option, to pass "--quiet" down to "gc --auto". + (merge 7c3e9e8cfb jc/auto-gc-quiet later to maint). + + * The code to skip unmerged paths in the index when sparse checkout + is in use would have made out-of-bound access of the in-core index + when the last path was unmerged, which has been corrected. + + * Serving a "git fetch" client over "git://" and "ssh://" protocols + using the on-wire protocol version 2 was buggy on the server end + when the client needs to make a follow-up request to + e.g. auto-follow tags. + (merge 08450ef791 cc/upload-pack-v2-fetch-fix later to maint). + + * "git bisect replay" had trouble with input files when they used + CRLF line ending, which has been corrected. + (merge 6c722cbe5a cw/bisect-replay-with-dos later to maint). + + * "rebase -i" segfaulted when rearranging a sequence that has a + fix-up that applies another fix-up (which may or may not be a + fix-up of yet another step). + (merge 02471e7e20 js/rebase-autosquash-double-fixup-fix later to maint). + + * "git fsck" ensures that the paths recorded in tree objects are + sorted and without duplicates, but it failed to notice a case where + a blob is followed by entries that sort before a tree with the same + name. This has been corrected. + (merge 9068cfb20f rs/fsck-duplicate-names-in-trees later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 564956f358 jc/maintain-doc later to maint). (merge 7422b2a0a1 sg/commit-slab-clarify-peek later to maint). @@ -448,3 +498,4 @@ Fixes since v2.26 (merge bdccbf7047 mt/doc-worktree-ref later to maint). (merge ce9baf234f dl/push-recurse-submodules-fix later to maint). (merge 4153274052 bc/doc-credential-helper-value later to maint). + (merge 5c7bb0146e jc/codingstyle-compare-with-null later to maint). diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt index 643d1b2884..7fe9aef34e 100644 --- a/Documentation/git-bugreport.txt +++ b/Documentation/git-bugreport.txt @@ -28,6 +28,7 @@ The following information is captured automatically: - 'git version --build-options' - uname sysname, release, version, and machine strings - Compiler-specific info string + - A list of enabled hooks This tool is invoked via the typical Git setup process, which means that in some cases, it might not be able to launch - for example, if a relevant config file diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 6f0c7ca80f..8d990e92fd 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -103,17 +103,20 @@ INPUT/OUTPUT FORMAT `git credential` reads and/or writes (depending on the action used) credential information in its standard input/output. This information can correspond either to keys for which `git credential` will obtain -the login/password information (e.g. host, protocol, path), or to the -actual credential data to be obtained (login/password). +the login information (e.g. host, protocol, path), or to the actual +credential data to be obtained (username/password). The credential is split into a set of named attributes, with one -attribute per line. Each attribute is -specified by a key-value pair, separated by an `=` (equals) sign, -followed by a newline. The key may contain any bytes except `=`, -newline, or NUL. The value may contain any bytes except newline or NUL. +attribute per line. Each attribute is specified by a key-value pair, +separated by an `=` (equals) sign, followed by a newline. + +The key may contain any bytes except `=`, newline, or NUL. The value may +contain any bytes except newline or NUL. + In both cases, all bytes are treated as-is (i.e., there is no quoting, and one cannot transmit a value with newline or NUL in it). The list of attributes is terminated by a blank line or end-of-file. + Git understands the following attributes: `protocol`:: @@ -123,7 +126,8 @@ Git understands the following attributes: `host`:: - The remote hostname for a network credential. + The remote hostname for a network credential. This includes + the port number if one was specified (e.g., "example.com:8088"). `path`:: @@ -134,7 +138,7 @@ Git understands the following attributes: `username`:: The credential's username, if we already have one (e.g., from a - URL, from the user, or from a previously run helper). + URL, the configuration, the user, or from a previously run helper). `password`:: @@ -146,8 +150,12 @@ Git understands the following attributes: value is parsed as a URL and treated as if its constituent parts were read (e.g., `url=https://example.com` would behave as if `protocol=https` and `host=example.com` had been provided). This - can help callers avoid parsing URLs themselves. Note that any - components which are missing from the URL (e.g., there is no - username in the example above) will be set to empty; if you want - to provide a URL and override some attributes, provide the URL - attribute first, followed by any overrides. + can help callers avoid parsing URLs themselves. + + Note that specifying a protocol is mandatory and if the URL + doesn't specify a hostname (e.g., "cert:///path/to/file") the + credential will contain a hostname attribute whose value is an + empty string. + + Components which are missing from the URL (e.g., there is no + username in the example above) will be left unset. diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index 642d9ac5b7..0c6619493c 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -56,6 +56,9 @@ repack:: file is created, rewrite the multi-pack-index to reference the new pack-file. A later run of 'git multi-pack-index expire' will delete the pack-files that were part of this batch. ++ +If `repack.packKeptObjects` is `false`, then any pack-files with an +associated `.keep` file will not be selected for the batch to repack. EXAMPLES diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt index 0d0f7149bd..9e481aec85 100644 --- a/Documentation/gitcredentials.txt +++ b/Documentation/gitcredentials.txt @@ -268,16 +268,26 @@ For a `get` operation, the helper should produce a list of attributes on stdout in the same format (see linkgit:git-credential[1] for common attributes). A helper is free to produce a subset, or even no values at all if it has nothing useful to provide. Any provided attributes will -overwrite those already known about by Git. If a helper outputs a -`quit` attribute with a value of `true` or `1`, no further helpers will -be consulted, nor will the user be prompted (if no credential has been -provided, the operation will then fail). +overwrite those already known about by Git's credential subsystem. + +While it is possible to override all attributes, well behaving helpers +should refrain from doing so for any attribute other than username and +password. + +If a helper outputs a `quit` attribute with a value of `true` or `1`, +no further helpers will be consulted, nor will the user be prompted +(if no credential has been provided, the operation will then fail). + +Similarly, no more helpers will be consulted once both username and +password had been provided. For a `store` or `erase` operation, the helper's output is ignored. -If it fails to perform the requested operation, it may complain to -stderr to inform the user. If it does not support the requested -operation (e.g., a read-only store), it should silently ignore the -request. + +If a helper fails to perform the requested operation or needs to notify +the user of a potential issue, it may write to stderr. + +If it does not support the requested operation (e.g., a read-only store), +it should silently ignore the request. If a helper receives any other operation, it should silently ignore the request. This leaves room for future operations to be added (older diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt index 1cf83df118..370d62dae4 100644 --- a/Documentation/gitfaq.txt +++ b/Documentation/gitfaq.txt @@ -223,6 +223,24 @@ a file checked into the repository which is a template or set of defaults which can then be copied alongside and modified as appropriate. This second, modified file is usually ignored to prevent accidentally committing it. +[[files-in-.gitignore-are-tracked]] +I asked Git to ignore various files, yet they are still tracked:: + A `gitignore` file ensures that certain file(s) which are not + tracked by Git remain untracked. However, sometimes particular + file(s) may have been tracked before adding them into the + `.gitignore`, hence they still remain tracked. To untrack and + ignore files/patterns, use `git rm --cached <file/pattern>` + and add a pattern to `.gitignore` that matches the <file>. + See linkgit:gitignore[5] for details. + +[[fetching-and-pulling]] +How do I know if I want to do a fetch or a pull?:: + A fetch stores a copy of the latest changes from the remote + repository, without modifying the working tree or current branch. + You can then at your leisure inspect, merge, rebase on top of, or + ignore the upstream changes. A pull consists of a fetch followed + immediately by either a merge or rebase. See linkgit:git-pull[1]. + Hooks ----- diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index de56f9f1ef..1beef17182 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -97,10 +97,10 @@ CHUNK DATA: bit on. The other bits correspond to the position of the last parent. Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional] - * The ith entry, BIDX[i], stores the number of 8-byte word blocks in all - Bloom filters from commit 0 to commit i (inclusive) in lexicographic - order. The Bloom filter for the i-th commit spans from BIDX[i-1] to - BIDX[i] (plus header length), where BIDX[-1] is 0. + * The ith entry, BIDX[i], stores the number of bytes in all Bloom filters + from commit 0 to commit i (inclusive) in lexicographic order. The Bloom + filter for the i-th commit spans from BIDX[i-1] to BIDX[i] (plus header + length), where BIDX[-1] is 0. * The BIDX chunk is ignored if the BDAT chunk is not present. Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 21f929e7ee..03d2243c0d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.26.GIT +DEF_VER=v2.27.0-rc0 LF=' ' @@ -399,9 +399,6 @@ all:: # with a different indexfile format version. If it isn't set the index # file format used is index-v[23]. # -# Define GMTIME_UNRELIABLE_ERRORS if your gmtime() function does not -# return NULL when it receives a bogus time_t. -# # Define HAVE_CLOCK_GETTIME if your platform has clock_gettime. # # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC. @@ -1809,11 +1806,6 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS MSGFMT += --check --statistics endif -ifdef GMTIME_UNRELIABLE_ERRORS - COMPAT_OBJS += compat/gmtime.o - BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS -endif - ifdef HAVE_CLOCK_GETTIME BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME endif @@ -9,7 +9,7 @@ define_commit_slab(bloom_filter_slab, struct bloom_filter); -struct bloom_filter_slab bloom_filters; +static struct bloom_filter_slab bloom_filters; struct pathmap_hash_entry { struct hashmap_entry entry; @@ -29,8 +29,8 @@ static inline unsigned char get_bitmask(uint32_t pos) } static int load_bloom_filter_from_graph(struct commit_graph *g, - struct bloom_filter *filter, - struct commit *c) + struct bloom_filter *filter, + struct commit *c) { uint32_t lex_pos, start_index, end_index; @@ -123,9 +123,9 @@ uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len) } void fill_bloom_key(const char *data, - size_t len, - struct bloom_key *key, - const struct bloom_filter_settings *settings) + size_t len, + struct bloom_key *key, + const struct bloom_filter_settings *settings) { int i; const uint32_t seed0 = 0x293ae76f; @@ -139,8 +139,8 @@ void fill_bloom_key(const char *data, } void add_key_to_filter(const struct bloom_key *key, - struct bloom_filter *filter, - const struct bloom_filter_settings *settings) + struct bloom_filter *filter, + const struct bloom_filter_settings *settings) { int i; uint64_t mod = filter->len * BITS_PER_WORD; @@ -158,9 +158,22 @@ void init_bloom_filters(void) init_bloom_filter_slab(&bloom_filters); } +static int pathmap_cmp(const void *hashmap_cmp_fn_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct pathmap_hash_entry *e1, *e2; + + e1 = container_of(eptr, const struct pathmap_hash_entry, entry); + e2 = container_of(entry_or_key, const struct pathmap_hash_entry, entry); + + return strcmp(e1->path, e2->path); +} + struct bloom_filter *get_bloom_filter(struct repository *r, struct commit *c, - int compute_if_not_present) + int compute_if_not_present) { struct bloom_filter *filter; struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS; @@ -193,35 +206,42 @@ struct bloom_filter *get_bloom_filter(struct repository *r, diffopt.max_changes = max_changes; diff_setup_done(&diffopt); + /* ensure commit is parsed so we have parent information */ + repo_parse_commit(r, c); + if (c->parents) diff_tree_oid(&c->parents->item->object.oid, &c->object.oid, "", &diffopt); else diff_tree_oid(NULL, &c->object.oid, "", &diffopt); diffcore_std(&diffopt); - if (diff_queued_diff.nr <= max_changes) { + if (diffopt.num_changes <= max_changes) { struct hashmap pathmap; struct pathmap_hash_entry *e; struct hashmap_iter iter; - hashmap_init(&pathmap, NULL, NULL, 0); + hashmap_init(&pathmap, pathmap_cmp, NULL, 0); for (i = 0; i < diff_queued_diff.nr; i++) { const char *path = diff_queued_diff.queue[i]->two->path; /* - * Add each leading directory of the changed file, i.e. for - * 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so - * the Bloom filter could be used to speed up commands like - * 'git log dir/subdir', too. - * - * Note that directories are added without the trailing '/'. - */ + * Add each leading directory of the changed file, i.e. for + * 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so + * the Bloom filter could be used to speed up commands like + * 'git log dir/subdir', too. + * + * Note that directories are added without the trailing '/'. + */ do { char *last_slash = strrchr(path, '/'); FLEX_ALLOC_STR(e, path, path); hashmap_entry_init(&e->entry, strhash(path)); - hashmap_add(&pathmap, &e->entry); + + if (!hashmap_get(&pathmap, &e->entry, NULL)) + hashmap_add(&pathmap, &e->entry); + else + free(e); if (!last_slash) last_slash = (char*)path; @@ -273,4 +293,4 @@ int bloom_filter_contains(const struct bloom_filter *filter, } return 1; -}
\ No newline at end of file +} @@ -74,8 +74,8 @@ void fill_bloom_key(const char *data, const struct bloom_filter_settings *settings); void add_key_to_filter(const struct bloom_key *key, - struct bloom_filter *filter, - const struct bloom_filter_settings *settings); + struct bloom_filter *filter, + const struct bloom_filter_settings *settings); void init_bloom_filters(void); @@ -87,4 +87,4 @@ int bloom_filter_contains(const struct bloom_filter *filter, const struct bloom_key *key, const struct bloom_filter_settings *settings); -#endif
\ No newline at end of file +#endif diff --git a/bugreport.c b/bugreport.c index acacca8fef..aa8a489c35 100644 --- a/bugreport.c +++ b/bugreport.c @@ -3,6 +3,8 @@ #include "strbuf.h" #include "help.h" #include "compat/compiler.h" +#include "run-command.h" + static void get_system_info(struct strbuf *sys_info) { @@ -31,6 +33,53 @@ static void get_system_info(struct strbuf *sys_info) get_libc_info(sys_info); } +static void get_populated_hooks(struct strbuf *hook_info, int nongit) +{ + /* + * NEEDSWORK: Doesn't look like there is a list of all possible hooks; + * so below is a transcription of `git help hooks`. Later, this should + * be replaced with some programmatically generated list (generated from + * doc or else taken from some library which tells us about all the + * hooks) + */ + static const char *hook[] = { + "applypatch-msg", + "pre-applypatch", + "post-applypatch", + "pre-commit", + "pre-merge-commit", + "prepare-commit-msg", + "commit-msg", + "post-commit", + "pre-rebase", + "post-checkout", + "post-merge", + "pre-push", + "pre-receive", + "update", + "post-receive", + "post-update", + "push-to-checkout", + "pre-auto-gc", + "post-rewrite", + "sendemail-validate", + "fsmonitor-watchman", + "p4-pre-submit", + "post-index-change", + }; + int i; + + if (nongit) { + strbuf_addstr(hook_info, + _("not run from a git repository - no hooks to show\n")); + return; + } + + for (i = 0; i < ARRAY_SIZE(hook); i++) + if (find_hook(hook[i])) + strbuf_addf(hook_info, "%s\n", hook[i]); +} + static const char * const bugreport_usage[] = { N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"), NULL @@ -114,6 +163,9 @@ int cmd_main(int argc, const char **argv) get_header(&buffer, _("System Info")); get_system_info(&buffer); + get_header(&buffer, _("Enabled Hooks")); + get_populated_hooks(&buffer, nongit_ok); + /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */ report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); diff --git a/builtin/am.c b/builtin/am.c index e3dfd93c25..69e50de018 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1691,7 +1691,6 @@ static int do_interactive(struct am_state *state) */ static void am_run(struct am_state *state, int resume) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; struct strbuf sb = STRBUF_INIT; unlink(am_path(state, "dirtyindex")); @@ -1796,7 +1795,7 @@ next: if (!state->rebasing) { am_destroy(state); close_object_store(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_gc(state->quiet); } } diff --git a/builtin/commit.c b/builtin/commit.c index a73de0a4c5..d1b7396052 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1494,7 +1494,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) int cmd_commit(int argc, const char **argv, const char *prefix) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; static struct wt_status s; static struct option builtin_commit_options[] = { OPT__QUIET(&quiet, N_("suppress summary after successful commit")), @@ -1703,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_gc(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); diff --git a/builtin/fetch.c b/builtin/fetch.c index 3ae52c015d..b5788c16bf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -27,6 +27,7 @@ #include "branch.h" #include "promisor-remote.h" #include "commit-graph.h" +#include "shallow.h" #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) @@ -1752,7 +1753,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct remote *remote = NULL; int result = 0; int prune_tags_ok = 1; - struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; packet_trace_identity("fetch"); @@ -1879,13 +1879,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_object_store(the_repository->objects); - if (enable_auto_gc) { - argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (verbosity < 0) - argv_array_push(&argv_gc_auto, "--quiet"); - run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); - argv_array_clear(&argv_gc_auto); - } + if (enable_auto_gc) + run_auto_gc(verbosity < 0); return result; } diff --git a/builtin/merge.c b/builtin/merge.c index 923e32acf1..ca6a5dc4bf 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -450,7 +450,6 @@ static void finish(struct commit *head_commit, if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, UPDATE_REFS_DIE_ON_ERR); /* @@ -458,7 +457,7 @@ static void finish(struct commit *head_commit, * user should see them. */ close_object_store(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_gc(verbosity < 0); } } if (new_head && show_diffstat) { diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 03b85f5166..c5b433a23f 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -34,6 +34,7 @@ #include "dir.h" #include "midx.h" #include "trace2.h" +#include "shallow.h" #define IN_PACK(obj) oe_in_pack(&to_pack, obj) #define SIZE(obj) oe_size(&to_pack, obj) diff --git a/builtin/prune.c b/builtin/prune.c index fd9acc7222..02c6ab7cba 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -8,6 +8,7 @@ #include "progress.h" #include "prune-packed.h" #include "object-store.h" +#include "shallow.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), diff --git a/builtin/rebase.c b/builtin/rebase.c index ca6aa0dc7a..37ba76ac3d 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -722,7 +722,6 @@ static int rebase_write_basic_state(struct rebase_options *opts) static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; int ret = 0; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); @@ -732,7 +731,7 @@ static int finish_rebase(struct rebase_options *opts) * We ignore errors in 'gc --auto', since the * user should see them. */ - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d37ab776b3..ea3d0f01af 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -28,6 +28,7 @@ #include "protocol.h" #include "commit-reach.h" #include "worktree.h" +#include "shallow.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -876,7 +877,7 @@ static void refuse_unconfigured_deny_delete_current(void) static int command_singleton_iterator(void *cb_data, struct object_id *oid); static int update_shallow_ref(struct command *cmd, struct shallow_info *si) { - struct lock_file shallow_lock = LOCK_INIT; + struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT; struct oid_array extra = OID_ARRAY_INIT; struct check_connected_options opt = CHECK_CONNECTED_INIT; uint32_t mask = 1 << (cmd->index % 32); diff --git a/builtin/repack.c b/builtin/repack.c index 1b686ee9ce..df287739d9 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -13,6 +13,7 @@ #include "prune-packed.h" #include "object-store.h" #include "promisor-remote.h" +#include "shallow.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 06056434ed..669dd2fd6f 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -16,6 +16,7 @@ #include "split-index.h" #include "submodule.h" #include "commit-reach.h" +#include "shallow.h" #define DO_REVS 1 #define DO_NOREV 2 diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 1a4b391c88..46c03d2a12 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -2246,6 +2246,37 @@ static int module_config(int argc, const char **argv, const char *prefix) usage_with_options(git_submodule_helper_usage, module_config_options); } +static int module_set_url(int argc, const char **argv, const char *prefix) +{ + int quiet = 0; + const char *newurl; + const char *path; + char *config_name; + + struct option options[] = { + OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper set-url [--quiet] <path> <newurl>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.url", path); + + config_set_in_gitmodules_file_gently(config_name, newurl); + sync_submodule(path, prefix, quiet ? OPT_QUIET : 0); + + free(config_name); + + return 0; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -2276,6 +2307,7 @@ static struct cmd_struct commands[] = { {"is-active", is_active, 0}, {"check-name", check_name, 0}, {"config", module_config, 0}, + {"set-url", module_set_url, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/ci/config/allow-refs.sample b/ci/config/allow-refs.sample new file mode 100755 index 0000000000..f157f1945a --- /dev/null +++ b/ci/config/allow-refs.sample @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Sample script for enabling/disabling GitHub Actions CI runs on +# particular refs. By default, CI is run for all branches pushed to +# GitHub. You can override this by dropping the ".sample" from the script, +# editing it, committing, and pushing the result to the "ci-config" branch of +# your repository: +# +# git checkout -b ci-config +# cp allow-refs.sample allow-refs +# $EDITOR allow-refs +# git commit -am "implement my ci preferences" +# git push +# +# This script will then be run when any refs are pushed to that repository. It +# gets the fully qualified refname as the first argument, and should exit with +# success only for refs for which you want to run CI. + +case "$1" in +# allow one-off tests by pushing to "for-ci" or "for-ci/mybranch" +refs/heads/for-ci*) true ;; +# always build your integration branch +refs/heads/my-integration-branch) true ;; +# don't build any other branches or tags +*) false ;; +esac diff --git a/commit-graph.c b/commit-graph.c index 5ea0c8e15c..e3420ddcbf 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -18,6 +18,7 @@ #include "progress.h" #include "bloom.h" #include "commit-slab.h" +#include "shallow.h" void git_test_write_commit_graph_or_die(void) { @@ -20,6 +20,7 @@ #include "refs.h" #include "commit-reach.h" #include "run-command.h" +#include "shallow.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -110,7 +111,7 @@ static const unsigned char *commit_graft_sha1_access(size_t index, void *table) return commit_graft_table[index]->oid.hash; } -static int commit_graft_pos(struct repository *r, const unsigned char *sha1) +int commit_graft_pos(struct repository *r, const unsigned char *sha1) { return sha1_pos(sha1, r->parsed_objects->grafts, r->parsed_objects->grafts_nr, @@ -245,19 +246,6 @@ int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data) return ret; } -int unregister_shallow(const struct object_id *oid) -{ - int pos = commit_graft_pos(the_repository, oid->hash); - if (pos < 0) - return -1; - if (pos + 1 < the_repository->parsed_objects->grafts_nr) - MOVE_ARRAY(the_repository->parsed_objects->grafts + pos, - the_repository->parsed_objects->grafts + pos + 1, - the_repository->parsed_objects->grafts_nr - pos - 1); - the_repository->parsed_objects->grafts_nr--; - return 0; -} - struct commit_buffer { void *buffer; unsigned long size; @@ -236,6 +236,8 @@ struct commit_graft { typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *); struct commit_graft *read_graft_line(struct strbuf *line); +/* commit_graft_pos returns an index into r->parsed_objects->grafts. */ +int commit_graft_pos(struct repository *r, const unsigned char *sha1); int register_commit_graft(struct repository *r, struct commit_graft *, int); void prepare_commit_graft(struct repository *r); struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid); @@ -247,55 +249,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit); struct oid_array; struct ref; -int register_shallow(struct repository *r, const struct object_id *oid); -int unregister_shallow(const struct object_id *oid); -int commit_shallow_file(struct repository *r, struct lock_file *lk); -void rollback_shallow_file(struct repository *r, struct lock_file *lk); int for_each_commit_graft(each_commit_graft_fn, void *); -int is_repository_shallow(struct repository *r); -struct commit_list *get_shallow_commits(struct object_array *heads, - int depth, int shallow_flag, int not_shallow_flag); -struct commit_list *get_shallow_commits_by_rev_list( - int ac, const char **av, int shallow_flag, int not_shallow_flag); -void set_alternate_shallow_file(struct repository *r, const char *path, int override); -int write_shallow_commits(struct strbuf *out, int use_pack_protocol, - const struct oid_array *extra); -void setup_alternate_shallow(struct lock_file *shallow_lock, - const char **alternate_shallow_file, - const struct oid_array *extra); -const char *setup_temporary_shallow(const struct oid_array *extra); -void advertise_shallow_grafts(int); - -/* - * Initialize with prepare_shallow_info() or zero-initialize (equivalent to - * prepare_shallow_info with a NULL oid_array). - */ -struct shallow_info { - struct oid_array *shallow; - int *ours, nr_ours; - int *theirs, nr_theirs; - struct oid_array *ref; - - /* for receive-pack */ - uint32_t **used_shallow; - int *need_reachability_test; - int *reachable; - int *shallow_ref; - struct commit **commits; - int nr_commits; -}; - -void prepare_shallow_info(struct shallow_info *, struct oid_array *); -void clear_shallow_info(struct shallow_info *); -void remove_nonexistent_theirs_shallow(struct shallow_info *); -void assign_shallow_commits_to_refs(struct shallow_info *info, - uint32_t **used, - int *ref_status); -int delayed_reachability_test(struct shallow_info *si, int c); -#define PRUNE_SHOW_ONLY 1 -#define PRUNE_QUICK 2 -void prune_shallow(unsigned options); -extern struct trace_key trace_shallow; int interactive_add(int argc, const char **argv, const char *prefix, int patch); int run_add_interactive(const char *revision, const char *patch_mode, diff --git a/compat/gmtime.c b/compat/gmtime.c deleted file mode 100644 index e8362dd2b9..0000000000 --- a/compat/gmtime.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "../git-compat-util.h" -#undef gmtime -#undef gmtime_r - -struct tm *git_gmtime(const time_t *timep) -{ - static struct tm result; - return git_gmtime_r(timep, &result); -} - -struct tm *git_gmtime_r(const time_t *timep, struct tm *result) -{ - struct tm *ret; - - memset(result, 0, sizeof(*result)); - ret = gmtime_r(timep, result); - - /* - * Rather than NULL, FreeBSD gmtime simply leaves the "struct tm" - * untouched when it encounters overflow. Since "mday" cannot otherwise - * be zero, we can test this very quickly. - */ - if (ret && !ret->tm_mday) { - ret = NULL; - errno = EOVERFLOW; - } - - return ret; -} diff --git a/config.mak.uname b/config.mak.uname index 5ad43c80b1..c7eba69e54 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -246,7 +246,6 @@ ifeq ($(uname_S),FreeBSD) PYTHON_PATH = /usr/local/bin/python PERL_PATH = /usr/local/bin/perl HAVE_PATHS_H = YesPlease - GMTIME_UNRELIABLE_ERRORS = UnfortunatelyYes HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease PAGER_ENV = LESS=FRX LV=-c MORE=FRX diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b1d6e5ebed..70ad04e1b2 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1860,6 +1860,7 @@ _git_log () $merge $__git_diff_common_options --pickaxe-all --pickaxe-regex + --patch --no-patch " return ;; diff --git a/credential.h b/credential.h index d99ec42b2a..c0e17e3554 100644 --- a/credential.h +++ b/credential.h @@ -177,8 +177,8 @@ void credential_write(const struct credential *, FILE *); * Parse a url into a credential struct, replacing any existing contents. * * If the url can't be parsed (e.g., a missing "proto://" component), the - * resulting credential will be empty but we'll still return success from the - * "gently" form. + * resulting credential will be empty and the function will return an + * error (even in the "gently" form). * * If we encounter a component which cannot be represented as a credential * value (e.g., because it contains a newline), the "gently" form will return @@ -189,7 +189,7 @@ void credential_write(const struct credential *, FILE *); void credential_from_url(struct credential *, const char *url); int credential_from_url_gently(struct credential *, const char *url, int quiet); -int credential_match(const struct credential *have, - const struct credential *want); +int credential_match(const struct credential *want, + const struct credential *have); #endif /* CREDENTIAL_H */ diff --git a/environment.c b/environment.c index 10c9061c43..aaca0e91ac 100644 --- a/environment.c +++ b/environment.c @@ -17,6 +17,7 @@ #include "argv-array.h" #include "object-store.h" #include "chdir-notify.h" +#include "shallow.h" int trust_executable_bit = 1; int trust_ctime = 1; diff --git a/fetch-pack.c b/fetch-pack.c index f73a2ce6cb..7eaa19d7c1 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -22,6 +22,7 @@ #include "connected.h" #include "fetch-negotiator.h" #include "fsck.h" +#include "shallow.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -34,7 +35,7 @@ static int fetch_fsck_objects = -1; static int transfer_fsck_objects = -1; static int agent_supported; static int server_supports_filtering; -static struct lock_file shallow_lock; +static struct shallow_lock shallow_lock; static const char *alternate_shallow_file; static struct strbuf fsck_msg_types = STRBUF_INIT; @@ -523,6 +523,28 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) } } +struct name_stack { + const char **names; + size_t nr, alloc; +}; + +static void name_stack_push(struct name_stack *stack, const char *name) +{ + ALLOC_GROW(stack->names, stack->nr + 1, stack->alloc); + stack->names[stack->nr++] = name; +} + +static const char *name_stack_pop(struct name_stack *stack) +{ + return stack->nr ? stack->names[--stack->nr] : NULL; +} + +static void name_stack_clear(struct name_stack *stack) +{ + FREE_AND_NULL(stack->names); + stack->nr = stack->alloc = 0; +} + /* * The entries in a tree are ordered in the _path_ order, * which means that a directory entry is ordered by adding @@ -534,7 +556,14 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) #define TREE_UNORDERED (-1) #define TREE_HAS_DUPS (-2) -static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2) +static int is_less_than_slash(unsigned char c) +{ + return '\0' < c && c < '/'; +} + +static int verify_ordered(unsigned mode1, const char *name1, + unsigned mode2, const char *name2, + struct name_stack *candidates) { int len1 = strlen(name1); int len2 = strlen(name2); @@ -566,6 +595,41 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con c1 = '/'; if (!c2 && S_ISDIR(mode2)) c2 = '/'; + + /* + * There can be non-consecutive duplicates due to the implicitly + * add slash, e.g.: + * + * foo + * foo.bar + * foo.bar.baz + * foo.bar/ + * foo/ + * + * Record non-directory candidates (like "foo" and "foo.bar" in + * the example) on a stack and check directory candidates (like + * foo/" and "foo.bar/") against that stack. + */ + if (!c1 && is_less_than_slash(c2)) { + name_stack_push(candidates, name1); + } else if (c2 == '/' && is_less_than_slash(c1)) { + for (;;) { + const char *p; + const char *f_name = name_stack_pop(candidates); + + if (!f_name) + break; + if (!skip_prefix(name2, f_name, &p)) + break; + if (!*p) + return TREE_HAS_DUPS; + if (is_less_than_slash(*p)) { + name_stack_push(candidates, f_name); + break; + } + } + } + return c1 < c2 ? 0 : TREE_UNORDERED; } @@ -587,6 +651,7 @@ static int fsck_tree(const struct object_id *oid, struct tree_desc desc; unsigned o_mode; const char *o_name; + struct name_stack df_dup_candidates = { NULL }; if (init_tree_desc_gently(&desc, buffer, size)) { retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); @@ -666,7 +731,8 @@ static int fsck_tree(const struct object_id *oid, } if (o_name) { - switch (verify_ordered(o_mode, o_name, mode, name)) { + switch (verify_ordered(o_mode, o_name, mode, name, + &df_dup_candidates)) { case TREE_UNORDERED: not_properly_sorted = 1; break; @@ -682,6 +748,8 @@ static int fsck_tree(const struct object_id *oid, o_name = name; } + name_stack_clear(&df_dup_candidates); + if (has_null_sha1) retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); if (has_full_path) diff --git a/git-bisect.sh b/git-bisect.sh index efee12b8b1..71b367a944 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -209,6 +209,7 @@ bisect_replay () { test "$#" -eq 1 || die "$(gettext "No logfile given")" test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")" git bisect--helper --bisect-reset || exit + oIFS="$IFS" IFS="$IFS$(printf '\015')" while read git bisect command rev do test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue @@ -232,6 +233,7 @@ bisect_replay () { die "$(gettext "?? what are you talking about?")" ;; esac done <"$file" + IFS="$oIFS" bisect_auto_next } diff --git a/git-compat-util.h b/git-compat-util.h index 8ba576e81e..a73632e8e4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1216,13 +1216,6 @@ int access_or_die(const char *path, int mode, unsigned flag); /* Warn on an inaccessible file if errno indicates this is an error */ int warn_on_fopen_errors(const char *path); -#ifdef GMTIME_UNRELIABLE_ERRORS -struct tm *git_gmtime(const time_t *); -struct tm *git_gmtime_r(const time_t *, struct tm *); -#define gmtime git_gmtime -#define gmtime_r git_gmtime_r -#endif - #if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__) #define USE_PARENS_AROUND_GETTEXT_N 1 #endif @@ -3214,6 +3214,42 @@ class P4Sync(Command, P4UserMap): print('Ignoring file outside of prefix: {0}'.format(path)) return hasPrefix + def findShadowedFiles(self, files, change): + # Perforce allows you commit files and directories with the same name, + # so you could have files //depot/foo and //depot/foo/bar both checked + # in. A p4 sync of a repository in this state fails. Deleting one of + # the files recovers the repository. + # + # Git will not allow the broken state to exist and only the most recent + # of the conflicting names is left in the repository. When one of the + # conflicting files is deleted we need to re-add the other one to make + # sure the git repository recovers in the same way as perforce. + deleted = [f for f in files if f['action'] in self.delete_actions] + to_check = set() + for f in deleted: + path = decode_path(f['path']) + to_check.add(path + '/...') + while True: + path = path.rsplit("/", 1)[0] + if path == "/" or path in to_check: + break + to_check.add(path) + to_check = ['%s@%s' % (wildcard_encode(p), change) for p in to_check + if self.hasBranchPrefix(p)] + if to_check: + stat_result = p4CmdList(["-x", "-", "fstat", "-T", + "depotFile,headAction,headRev,headType"], stdin=to_check) + for record in stat_result: + if record['code'] != 'stat': + continue + if record['headAction'] in self.delete_actions: + continue + files.append({ + 'action': 'add', + 'path': record['depotFile'], + 'rev': record['headRev'], + 'type': record['headType']}) + def commit(self, details, files, branch, parent = "", allow_empty=False): epoch = details["time"] author = details["user"] @@ -3222,11 +3258,14 @@ class P4Sync(Command, P4UserMap): if self.verbose: print('commit into {0}'.format(branch)) + files = [f for f in files + if self.hasBranchPrefix(decode_path(f['path']))] + self.findShadowedFiles(files, details['change']) + if self.clientSpecDirs: self.clientSpecDirs.update_client_spec_path_cache(files) - files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files) - if self.inClientSpec(path) and self.hasBranchPrefix(path)] + files = [f for f in files if self.inClientSpec(decode_path(f['path']))] if gitConfigBool('git-p4.keepEmptyCommits'): allow_empty = True diff --git a/git-submodule.sh b/git-submodule.sh index 08e0439df0..39ebdf25b5 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -805,27 +805,7 @@ cmd_set_url() { shift done - if test $# -ne 2 - then - usage - fi - - # we can't use `git submodule--helper name` here because internally, it - # hashes the path so a trailing slash could lead to an unintentional no match - name="$(git submodule--helper list "$1" | cut -f2)" - if test -z "$name" - then - exit 1 - fi - - url="$2" - if test -z "$url" - then - exit 1 - fi - - git submodule--helper config submodule."$name".url "$url" - git submodule--helper sync ${GIT_QUIET:+--quiet} "$name" + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@" } # @@ -4,6 +4,7 @@ #include "help.h" #include "run-command.h" #include "alias.h" +#include "shallow.h" #define RUN_SETUP (1<<0) #define RUN_SETUP_GENTLY (1<<1) diff --git a/list-objects-filter.c b/list-objects-filter.c index 1e8d4e763d..0a3ef3cab3 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -663,6 +663,9 @@ struct filter *list_objects_filter__init( assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT); + if (!filter_options) + return NULL; + if (filter_options->choice >= LOFC__COUNT) BUG("invalid list-objects filter choice: %d", filter_options->choice); diff --git a/merge-recursive.c b/merge-recursive.c index d92e2acf1e..36948eafb7 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1750,6 +1750,18 @@ static int handle_rename_rename_1to2(struct merge_options *opt, return -1; } + if (!mfi.clean && mfi.blob.mode == a->mode && + oideq(&mfi.blob.oid, &a->oid)) { + /* + * Getting here means we were attempting to merge a binary + * blob. Since we can't merge binaries, the merge algorithm + * just takes one side. But we don't want to copy the + * contents of one side to both paths; we'd rather use the + * original content at the given path for each path. + */ + oidcpy(&mfi.blob.oid, &b->oid); + mfi.blob.mode = b->mode; + } add = &ci->ren2->dst_entry->stages[flip_stage(3)]; if (is_valid(add)) { add->path = mfi.blob.path = b->path; @@ -1293,15 +1293,26 @@ static int compare_by_mtime(const void *a_, const void *b_) return 0; } -static int fill_included_packs_all(struct multi_pack_index *m, +static int fill_included_packs_all(struct repository *r, + struct multi_pack_index *m, unsigned char *include_pack) { - uint32_t i; + uint32_t i, count = 0; + int pack_kept_objects = 0; + + repo_config_get_bool(r, "repack.packkeptobjects", &pack_kept_objects); + + for (i = 0; i < m->num_packs; i++) { + if (prepare_midx_pack(r, m, i)) + continue; + if (!pack_kept_objects && m->packs[i]->pack_keep) + continue; - for (i = 0; i < m->num_packs; i++) include_pack[i] = 1; + count++; + } - return m->num_packs < 2; + return count < 2; } static int fill_included_packs_batch(struct repository *r, @@ -1312,6 +1323,9 @@ static int fill_included_packs_batch(struct repository *r, uint32_t i, packs_to_repack; size_t total_size; struct repack_info *pack_info = xcalloc(m->num_packs, sizeof(struct repack_info)); + int pack_kept_objects = 0; + + repo_config_get_bool(r, "repack.packkeptobjects", &pack_kept_objects); for (i = 0; i < m->num_packs; i++) { pack_info[i].pack_int_id = i; @@ -1338,6 +1352,8 @@ static int fill_included_packs_batch(struct repository *r, if (!p) continue; + if (!pack_kept_objects && p->pack_keep) + continue; if (open_pack_index(p) || !p->num_objects) continue; @@ -1370,6 +1386,14 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, struct strbuf base_name = STRBUF_INIT; struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + /* + * When updating the default for these configuration + * variables in builtin/repack.c, these must be adjusted + * to match. + */ + int delta_base_offset = 1; + int use_delta_islands = 0; + if (!m) return 0; @@ -1378,15 +1402,23 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, if (batch_size) { if (fill_included_packs_batch(r, m, include_pack, batch_size)) goto cleanup; - } else if (fill_included_packs_all(m, include_pack)) + } else if (fill_included_packs_all(r, m, include_pack)) goto cleanup; + repo_config_get_bool(r, "repack.usedeltabaseoffset", &delta_base_offset); + repo_config_get_bool(r, "repack.usedeltaislands", &use_delta_islands); + argv_array_push(&cmd.args, "pack-objects"); strbuf_addstr(&base_name, object_dir); strbuf_addstr(&base_name, "/pack/pack"); argv_array_push(&cmd.args, base_name.buf); + if (delta_base_offset) + argv_array_push(&cmd.args, "--delta-base-offset"); + if (use_delta_islands) + argv_array_push(&cmd.args, "--delta-islands"); + if (flags & MIDX_PROGRESS) argv_array_push(&cmd.args, "--progress"); else diff --git a/pack-bitmap.c b/pack-bitmap.c index 49a8d10d0c..4077e731e8 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -506,7 +506,8 @@ static int should_include(struct commit *commit, void *_data) static struct bitmap *find_objects(struct bitmap_index *bitmap_git, struct rev_info *revs, struct object_list *roots, - struct bitmap *seen) + struct bitmap *seen, + struct list_objects_filter_options *filter) { struct bitmap *base = NULL; int needs_walk = 0; @@ -599,8 +600,9 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git, show_data.bitmap_git = bitmap_git; show_data.base = base; - traverse_commit_list(revs, show_commit, show_object, - &show_data); + traverse_commit_list_filtered(filter, revs, + show_commit, show_object, + &show_data, NULL); } return base; @@ -715,8 +717,9 @@ static int in_bitmapped_pack(struct bitmap_index *bitmap_git, return 0; } -static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git, - struct object_list *tip_objects) +static struct bitmap *find_tip_objects(struct bitmap_index *bitmap_git, + struct object_list *tip_objects, + enum object_type type) { struct bitmap *result = bitmap_new(); struct object_list *p; @@ -724,7 +727,7 @@ static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git, for (p = tip_objects; p; p = p->next) { int pos; - if (p->item->type != OBJ_BLOB) + if (p->item->type != type) continue; pos = bitmap_position(bitmap_git, &p->item->oid); @@ -737,9 +740,10 @@ static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git, return result; } -static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, - struct object_list *tip_objects, - struct bitmap *to_filter) +static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, + struct object_list *tip_objects, + struct bitmap *to_filter, + enum object_type type) { struct eindex *eindex = &bitmap_git->ext_index; struct bitmap *tips; @@ -747,18 +751,21 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, eword_t mask; uint32_t i; + if (type != OBJ_BLOB && type != OBJ_TREE) + BUG("filter_bitmap_exclude_type: unsupported type '%d'", type); + /* * The non-bitmap version of this filter never removes - * blobs which the other side specifically asked for, + * objects which the other side specifically asked for, * so we must match that behavior. */ - tips = find_tip_blobs(bitmap_git, tip_objects); + tips = find_tip_objects(bitmap_git, tip_objects, type); /* * We can use the blob type-bitmap to work in whole words * for the objects that are actually in the bitmapped packfile. */ - for (i = 0, init_type_iterator(&it, bitmap_git, OBJ_BLOB); + for (i = 0, init_type_iterator(&it, bitmap_git, type); i < to_filter->word_alloc && ewah_iterator_next(&mask, &it); i++) { if (i < tips->word_alloc) @@ -773,7 +780,7 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, */ for (i = 0; i < eindex->count; i++) { uint32_t pos = i + bitmap_git->pack->num_objects; - if (eindex->objects[i]->type == OBJ_BLOB && + if (eindex->objects[i]->type == type && bitmap_get(to_filter, pos) && !bitmap_get(tips, pos)) bitmap_unset(to_filter, pos); @@ -782,6 +789,14 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, bitmap_free(tips); } +static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git, + struct object_list *tip_objects, + struct bitmap *to_filter) +{ + filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, + OBJ_BLOB); +} + static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, uint32_t pos) { @@ -820,7 +835,7 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git, eword_t mask; uint32_t i; - tips = find_tip_blobs(bitmap_git, tip_objects); + tips = find_tip_objects(bitmap_git, tip_objects, OBJ_BLOB); for (i = 0, init_type_iterator(&it, bitmap_git, OBJ_BLOB); i < to_filter->word_alloc && ewah_iterator_next(&mask, &it); @@ -854,6 +869,20 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git, bitmap_free(tips); } +static void filter_bitmap_tree_depth(struct bitmap_index *bitmap_git, + struct object_list *tip_objects, + struct bitmap *to_filter, + unsigned long limit) +{ + if (limit) + BUG("filter_bitmap_tree_depth given non-zero limit"); + + filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, + OBJ_TREE); + filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter, + OBJ_BLOB); +} + static int filter_bitmap(struct bitmap_index *bitmap_git, struct object_list *tip_objects, struct bitmap *to_filter, @@ -877,6 +906,15 @@ static int filter_bitmap(struct bitmap_index *bitmap_git, return 0; } + if (filter->choice == LOFC_TREE_DEPTH && + filter->tree_exclude_depth == 0) { + if (bitmap_git) + filter_bitmap_tree_depth(bitmap_git, tip_objects, + to_filter, + filter->tree_exclude_depth); + return 0; + } + /* filter choice not handled */ return -1; } @@ -963,7 +1001,8 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, if (haves) { revs->ignore_missing_links = 1; - haves_bitmap = find_objects(bitmap_git, revs, haves, NULL); + haves_bitmap = find_objects(bitmap_git, revs, haves, NULL, + filter); reset_revision_walk(); revs->ignore_missing_links = 0; @@ -971,7 +1010,8 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, BUG("failed to perform bitmap walk"); } - wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap); + wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap, + filter); if (!wants_bitmap) BUG("failed to perform bitmap walk"); diff --git a/run-command.c b/run-command.c index 0f41af3b55..9b3a57d1e3 100644 --- a/run-command.c +++ b/run-command.c @@ -1864,3 +1864,16 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, return result; } + +int run_auto_gc(int quiet) +{ + struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; + int status; + + argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); + if (quiet) + argv_array_push(&argv_gc_auto, "--quiet"); + status = run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); + argv_array_clear(&argv_gc_auto); + return status; +} diff --git a/run-command.h b/run-command.h index 0f3cc73ab6..191dfcdafe 100644 --- a/run-command.h +++ b/run-command.h @@ -218,6 +218,11 @@ LAST_ARG_MUST_BE_NULL int run_hook_le(const char *const *env, const char *name, ...); int run_hook_ve(const char *const *env, const char *name, va_list args); +/* + * Trigger an auto-gc + */ +int run_auto_gc(int quiet); + #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 diff --git a/send-pack.c b/send-pack.c index d1b7edc995..0abee22283 100644 --- a/send-pack.c +++ b/send-pack.c @@ -15,6 +15,7 @@ #include "oid-array.h" #include "gpg-interface.h" #include "cache.h" +#include "shallow.h" int option_parse_push_signed(const struct option *opt, const char *arg, int unset) diff --git a/sequencer.c b/sequencer.c index 9d1b3e7d4f..fd7701c88a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5385,10 +5385,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) todo_list->items[i].command = starts_with(subject, "fixup!") ? TODO_FIXUP : TODO_SQUASH; - if (next[i2] < 0) + if (tail[i2] < 0) { + next[i] = next[i2]; next[i2] = i; - else + } else { + next[i] = next[tail[i2]]; next[tail[i2]] = i; + } tail[i2] = i; } else if (!hashmap_get_from_hash(&subject2item, strhash(subject), subject)) { @@ -14,6 +14,7 @@ #include "commit-slab.h" #include "list-objects.h" #include "commit-reach.h" +#include "shallow.h" void set_alternate_shallow_file(struct repository *r, const char *path, int override) { @@ -38,6 +39,19 @@ int register_shallow(struct repository *r, const struct object_id *oid) return register_commit_graft(r, graft, 0); } +int unregister_shallow(const struct object_id *oid) +{ + int pos = commit_graft_pos(the_repository, oid->hash); + if (pos < 0) + return -1; + if (pos + 1 < the_repository->parsed_objects->grafts_nr) + MOVE_ARRAY(the_repository->parsed_objects->grafts + pos, + the_repository->parsed_objects->grafts + pos + 1, + the_repository->parsed_objects->grafts_nr - pos - 1); + the_repository->parsed_objects->grafts_nr--; + return 0; +} + int is_repository_shallow(struct repository *r) { FILE *fp; @@ -78,16 +92,16 @@ static void reset_repository_shallow(struct repository *r) stat_validity_clear(r->parsed_objects->shallow_stat); } -int commit_shallow_file(struct repository *r, struct lock_file *lk) +int commit_shallow_file(struct repository *r, struct shallow_lock *lk) { - int res = commit_lock_file(lk); + int res = commit_lock_file(&lk->lock); reset_repository_shallow(r); return res; } -void rollback_shallow_file(struct repository *r, struct lock_file *lk) +void rollback_shallow_file(struct repository *r, struct shallow_lock *lk) { - rollback_lock_file(lk); + rollback_lock_file(&lk->lock); reset_repository_shallow(r); } @@ -352,22 +366,22 @@ const char *setup_temporary_shallow(const struct oid_array *extra) return ""; } -void setup_alternate_shallow(struct lock_file *shallow_lock, +void setup_alternate_shallow(struct shallow_lock *shallow_lock, const char **alternate_shallow_file, const struct oid_array *extra) { struct strbuf sb = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(shallow_lock, + fd = hold_lock_file_for_update(&shallow_lock->lock, git_path_shallow(the_repository), LOCK_DIE_ON_ERROR); check_shallow_file_for_update(the_repository); if (write_shallow_commits(&sb, 0, extra)) { if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", - get_lock_file_path(shallow_lock)); - *alternate_shallow_file = get_lock_file_path(shallow_lock); + get_lock_file_path(&shallow_lock->lock)); + *alternate_shallow_file = get_lock_file_path(&shallow_lock->lock); } else /* * is_repository_shallow() sees empty string as "no @@ -400,7 +414,7 @@ void advertise_shallow_grafts(int fd) */ void prune_shallow(unsigned options) { - struct lock_file shallow_lock = LOCK_INIT; + struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = SEEN_ONLY; int fd; @@ -414,14 +428,14 @@ void prune_shallow(unsigned options) strbuf_release(&sb); return; } - fd = hold_lock_file_for_update(&shallow_lock, + fd = hold_lock_file_for_update(&shallow_lock.lock, git_path_shallow(the_repository), LOCK_DIE_ON_ERROR); check_shallow_file_for_update(the_repository); if (write_shallow_commits_1(&sb, 0, NULL, flags)) { if (write_in_full(fd, sb.buf, sb.len) < 0) die_errno("failed to write to %s", - get_lock_file_path(&shallow_lock)); + get_lock_file_path(&shallow_lock.lock)); commit_shallow_file(the_repository, &shallow_lock); } else { unlink(git_path_shallow(the_repository)); diff --git a/shallow.h b/shallow.h new file mode 100644 index 0000000000..5b4a96dcd6 --- /dev/null +++ b/shallow.h @@ -0,0 +1,81 @@ +#ifndef SHALLOW_H +#define SHALLOW_H + +#include "lockfile.h" +#include "object.h" +#include "repository.h" +#include "strbuf.h" + +void set_alternate_shallow_file(struct repository *r, const char *path, int override); +int register_shallow(struct repository *r, const struct object_id *oid); +int unregister_shallow(const struct object_id *oid); +int is_repository_shallow(struct repository *r); + +/* + * Lock for updating the $GIT_DIR/shallow file. + * + * Use `commit_shallow_file()` to commit an update, or + * `rollback_shallow_file()` to roll it back. In either case, any + * in-memory cached information about which commits are shallow will be + * appropriately invalidated so that future operations reflect the new + * state. + */ +struct shallow_lock { + struct lock_file lock; +}; +#define SHALLOW_LOCK_INIT { LOCK_INIT } + +/* commit $GIT_DIR/shallow and reset stat-validity checks */ +int commit_shallow_file(struct repository *r, struct shallow_lock *lk); +/* rollback $GIT_DIR/shallow and reset stat-validity checks */ +void rollback_shallow_file(struct repository *r, struct shallow_lock *lk); + +struct commit_list *get_shallow_commits(struct object_array *heads, + int depth, int shallow_flag, int not_shallow_flag); +struct commit_list *get_shallow_commits_by_rev_list( + int ac, const char **av, int shallow_flag, int not_shallow_flag); +int write_shallow_commits(struct strbuf *out, int use_pack_protocol, + const struct oid_array *extra); + +void setup_alternate_shallow(struct shallow_lock *shallow_lock, + const char **alternate_shallow_file, + const struct oid_array *extra); + +const char *setup_temporary_shallow(const struct oid_array *extra); + +void advertise_shallow_grafts(int); + +#define PRUNE_SHOW_ONLY 1 +#define PRUNE_QUICK 2 +void prune_shallow(unsigned options); + +/* + * Initialize with prepare_shallow_info() or zero-initialize (equivalent to + * prepare_shallow_info with a NULL oid_array). + */ +struct shallow_info { + struct oid_array *shallow; + int *ours, nr_ours; + int *theirs, nr_theirs; + struct oid_array *ref; + + /* for receive-pack */ + uint32_t **used_shallow; + int *need_reachability_test; + int *reachable; + int *shallow_ref; + struct commit **commits; + int nr_commits; +}; + +void prepare_shallow_info(struct shallow_info *, struct oid_array *); +void clear_shallow_info(struct shallow_info *); +void remove_nonexistent_theirs_shallow(struct shallow_info *); +void assign_shallow_commits_to_refs(struct shallow_info *info, + uint32_t **used, + int *ref_status); +int delayed_reachability_test(struct shallow_info *si, int c); + +extern struct trace_key trace_shallow; + +#endif /* SHALLOW_H */ diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c index 77eb27adac..f0aa80b98e 100644 --- a/t/helper/test-bloom.c +++ b/t/helper/test-bloom.c @@ -3,7 +3,7 @@ #include "test-tool.h" #include "commit.h" -struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS; +static struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS; static void add_string_to_filter(const char *data, struct bloom_filter *filter) { struct bloom_key key; @@ -44,7 +44,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid) } static const char *bloom_usage = "\n" -" test-tool bloom get_murmer3 <string>\n" +" test-tool bloom get_murmur3 <string>\n" " test-tool bloom generate_filter <string> [<string>...]\n" " test-tool get_filter_for_commit <commit-hex>\n"; diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index 80c53edca7..b3e725f031 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -53,6 +53,11 @@ test_perf 'rev-list count with blob:limit=1k' ' --filter=blob:limit=1k >/dev/null ' +test_perf 'rev-list count with tree:0' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=tree:0 >/dev/null +' + test_perf 'simulated partial clone' ' git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null ' @@ -86,4 +91,9 @@ test_perf 'pack to file (partial bitmap)' ' git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null ' +test_perf 'rev-list with tree filter (partial bitmap)' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=tree:0 >/dev/null +' + test_done diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh index 2e73658a5c..526304ff95 100755 --- a/t/t0091-bugreport.sh +++ b/t/t0091-bugreport.sh @@ -57,5 +57,20 @@ test_expect_success 'can create leading directories outside of a git dir' ' nongit git bugreport -o foo/bar/baz ' +test_expect_success 'indicates populated hooks' ' + test_when_finished rm git-bugreport-hooks.txt && + test_when_finished rm -fr .git/hooks && + rm -fr .git/hooks && + mkdir .git/hooks && + for hook in applypatch-msg prepare-commit-msg.sample + do + write_script ".git/hooks/$hook" <<-EOF || return 1 + echo "hook $hook exists" + EOF + done && + git bugreport -s hooks && + grep applypatch-msg git-bugreport-hooks.txt && + ! grep prepare-commit-msg git-bugreport-hooks.txt +' test_done diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh index 8f9eef116d..232ba2c485 100755 --- a/t/t0095-bloom.sh +++ b/t/t0095-bloom.sh @@ -89,8 +89,8 @@ test_expect_success 'get bloom filter for commit with 10 changes' ' git add smallDir && git commit -m "commit with 10 changes" && cat >expect <<-\EOF && - Filter_Length:25 - Filter_Data:82|a0|65|47|0c|92|90|c0|a1|40|02|a0|e2|40|e0|04|0a|9a|66|cf|80|19|85|42|23| + Filter_Length:14 + Filter_Data:02|b3|c4|a0|34|e7|fe|eb|cb|47|fe|a0|e8|72| EOF test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual && test_cmp expect actual @@ -100,7 +100,7 @@ test_expect_success EXPENSIVE 'get bloom filter for commit with 513 changes' ' rm actual && rm expect && mkdir bigDir && - for i in $(test_seq 0 512) + for i in $(test_seq 0 511) do echo $i >bigDir/$i done && @@ -114,4 +114,4 @@ test_expect_success EXPENSIVE 'get bloom filter for commit with 513 changes' ' test_cmp expect actual ' -test_done
\ No newline at end of file +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 449ebc5657..91a6e34f38 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -257,6 +257,22 @@ test_expect_success 'tree object with duplicate entries' ' test_i18ngrep "error in tree .*contains duplicate file entries" out ' +test_expect_success 'tree object with dublicate names' ' + test_when_finished "remove_object \$blob" && + test_when_finished "remove_object \$tree" && + test_when_finished "remove_object \$badtree" && + blob=$(echo blob | git hash-object -w --stdin) && + printf "100644 blob %s\t%s\n" $blob x.2 >tree && + tree=$(git mktree <tree) && + printf "100644 blob %s\t%s\n" $blob x.1 >badtree && + printf "100644 blob %s\t%s\n" $blob x >>badtree && + printf "040000 tree %s\t%s\n" $tree x >>badtree && + badtree=$(git mktree <badtree) && + test_must_fail git fsck 2>out && + test_i18ngrep "$badtree" out && + test_i18ngrep "error in tree .*contains duplicate file entries" out +' + test_expect_success 'unparseable tree object' ' test_oid_cache <<-\EOF && junk sha1:twenty-bytes-of-junk diff --git a/t/t1509-root-work-tree.sh b/t/t1509-root-work-tree.sh index 553a3f601b..fd2f7abf1c 100755 --- a/t/t1509-root-work-tree.sh +++ b/t/t1509-root-work-tree.sh @@ -221,7 +221,7 @@ test_expect_success 'setup' ' rm -rf /.git && echo "Initialized empty Git repository in /.git/" > expected && git init > result && - test_cmp expected result + test_i18ncmp expected result ' test_vars 'auto gitdir, root' ".git" "/" "" @@ -246,7 +246,7 @@ test_expect_success 'setup' ' cd / && echo "Initialized empty Git repository in /" > expected && git init --bare > result && - test_cmp expected result + test_i18ncmp expected result ' test_vars 'auto gitdir, root' "." "" "" diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 093de9005b..7bab6000dc 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -424,4 +424,20 @@ test_expect_success 'abort last squash' ' ! grep first actual ' +test_expect_success 'fixup a fixup' ' + echo 0to-fixup >file0 && + test_tick && + git commit -m "to-fixup" file0 && + test_tick && + git commit --squash HEAD -m X --allow-empty && + test_tick && + git commit --squash HEAD^ -m Y --allow-empty && + test_tick && + git commit -m "squash! $(git rev-parse HEAD^)" -m Z --allow-empty && + test_tick && + git commit -m "squash! $(git rev-parse HEAD^^)" -m W --allow-empty && + git rebase -ki --autosquash HEAD~5 && + test XZWY = $(git show | tr -cd W-Z) +' + test_done diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh index c7011f33e2..21b68dd6c8 100755 --- a/t/t4216-log-bloom.sh +++ b/t/t4216-log-bloom.sh @@ -152,4 +152,4 @@ test_expect_success 'Use Bloom filters if they exist in the latest but not all c test_bloom_filters_used_when_some_filters_are_missing "-- A/B" ' -test_done
\ No newline at end of file +test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 030a7222b2..7214cab36c 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -538,6 +538,33 @@ test_expect_success 'repack with minimum size does not alter existing packs' ' ) ' +test_expect_success 'repack respects repack.packKeptObjects=false' ' + test_when_finished rm -f dup/.git/objects/pack/*keep && + ( + cd dup && + ls .git/objects/pack/*idx >idx-list && + test_line_count = 5 idx-list && + ls .git/objects/pack/*.pack | sed "s/\.pack/.keep/" >keep-list && + test_line_count = 5 keep-list && + for keep in $(cat keep-list) + do + touch $keep || return 1 + done && + git multi-pack-index repack --batch-size=0 && + ls .git/objects/pack/*idx >idx-list && + test_line_count = 5 idx-list && + test-tool read-midx .git/objects | grep idx >midx-list && + test_line_count = 5 midx-list && + THIRD_SMALLEST_SIZE=$(test-tool path-utils file-size .git/objects/pack/*pack | sort -n | sed -n 3p) && + BATCH_SIZE=$((THIRD_SMALLEST_SIZE + 1)) && + git multi-pack-index repack --batch-size=$BATCH_SIZE && + ls .git/objects/pack/*idx >idx-list && + test_line_count = 5 idx-list && + test-tool read-midx .git/objects | grep idx >midx-list && + test_line_count = 5 midx-list + ) +' + test_expect_success 'repack creates a new pack' ' ( cd dup && diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 52dd1a688c..8c54e34ef1 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -386,7 +386,7 @@ test_expect_success 'clone shallow with packed refs' ' ' test_expect_success 'in_vain not triggered before first ACK' ' - rm -rf myserver myclient trace && + rm -rf myserver myclient && git init myserver && test_commit -C myserver foo && git clone "file://$(pwd)/myserver" myclient && @@ -399,12 +399,12 @@ test_expect_success 'in_vain not triggered before first ACK' ' # The new commit that the client wants to fetch. test_commit -C myserver bar && - GIT_TRACE_PACKET="$(pwd)/trace" git -C myclient fetch --progress origin && - test_i18ngrep "Total 3 " trace + git -C myclient fetch --progress origin 2>log && + test_i18ngrep "remote: Total 3 " log ' test_expect_success 'in_vain resetted upon ACK' ' - rm -rf myserver myclient trace && + rm -rf myserver myclient && git init myserver && # Linked list of commits on master. The first is common; the rest are @@ -429,8 +429,8 @@ test_expect_success 'in_vain resetted upon ACK' ' # first. The 256th commit is common between the client and the server, # and should reset in_vain. This allows negotiation to continue until # the client reports that first_anotherbranch_commit is common. - GIT_TRACE_PACKET="$(pwd)/trace" git -C myclient fetch --progress origin master && - test_i18ngrep "Total 3 " trace + git -C myclient fetch --progress origin master 2>log && + test_i18ngrep "Total 3 " log ' test_expect_success 'fetch in shallow repo unreachable shallow objects' ' diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 88002b24af..8a27452a51 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -384,12 +384,11 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' ' grep "want $(cat hash)" trace ' -# The following two tests must be in this order, or else -# the first will not fail. It is important that the srv.bare -# repository did not have tags during clone, but has tags +# The following two tests must be in this order. It is important that +# the srv.bare repository did not have tags during clone, but has tags # in the fetch. -test_expect_failure 'verify fetch succeeds when asking for new tags' ' +test_expect_success 'verify fetch succeeds when asking for new tags' ' git clone --filter=blob:none "file://$(pwd)/srv.bare" tag-test && for i in I J K do diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 1313142564..ac31faefa1 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -792,6 +792,13 @@ test_expect_success 'bisect replay with old and new' ' git bisect reset ' +test_expect_success 'bisect replay with CRLF log' ' + append_cr <log_to_replay.txt >log_to_replay_crlf.txt && + git bisect replay log_to_replay_crlf.txt >bisect_result_crlf && + grep "$HASH2 is the first new commit" bisect_result_crlf && + git bisect reset +' + test_expect_success 'bisect cannot mix old/new and good/bad' ' git bisect start && git bisect bad $HASH4 && diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index b047cf1c1c..f163893ff9 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -1379,4 +1379,59 @@ test_expect_success 'check nested conflicts from rename/rename(2to1)' ' ) ' +# Testcase rename/rename(1to2) of a binary file +# Commit O: orig +# Commit A: orig-A +# Commit B: orig-B +# Expected: CONFLICT(rename/rename) message, three unstaged entries in the +# index, and contents of orig-[AB] at path orig-[AB] +test_setup_rename_rename_1_to_2_binary () { + test_create_repo rename_rename_1_to_2_binary && + ( + cd rename_rename_1_to_2_binary && + + echo '* binary' >.gitattributes && + git add .gitattributes && + + test_seq 1 10 >orig && + git add orig && + git commit -m orig && + + git branch A && + git branch B && + + git checkout A && + git mv orig orig-A && + test_seq 1 11 >orig-A && + git add orig-A && + git commit -m orig-A && + + git checkout B && + git mv orig orig-B && + test_seq 0 10 >orig-B && + git add orig-B && + git commit -m orig-B + + ) +} + +test_expect_success 'rename/rename(1to2) with a binary file' ' + test_setup_rename_rename_1_to_2_binary && + ( + cd rename_rename_1_to_2_binary && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 && + + # Make sure the index has the right number of entries + git ls-files -s >actual && + test_line_count = 4 actual && + + git rev-parse A:orig-A B:orig-B >expect && + git hash-object orig-A orig-B >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 145603f124..2b551e6fd0 100755 --- a/t/t6113-rev-list-bitmap-filters.sh +++ b/t/t6113-rev-list-bitmap-filters.sh @@ -53,4 +53,25 @@ test_expect_success 'blob:limit filter with specified blob' ' test_bitmap_traversal expect actual ' +test_expect_success 'tree:0 filter' ' + git rev-list --objects --filter=tree:0 HEAD >expect && + git rev-list --use-bitmap-index \ + --objects --filter=tree:0 HEAD >actual && + test_bitmap_traversal expect actual +' + +test_expect_success 'tree:0 filter with specified blob, tree' ' + git rev-list --objects --filter=tree:0 HEAD HEAD:two.t >expect && + git rev-list --use-bitmap-index \ + --objects --filter=tree:0 HEAD HEAD:two.t >actual && + test_bitmap_traversal expect actual +' + +test_expect_success 'tree:1 filter' ' + git rev-list --objects --filter=tree:1 HEAD >expect && + git rev-list --use-bitmap-index \ + --objects --filter=tree:1 HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t9834-git-p4-file-dir-bug.sh b/t/t9834-git-p4-file-dir-bug.sh new file mode 100755 index 0000000000..031e1f8668 --- /dev/null +++ b/t/t9834-git-p4-file-dir-bug.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='git p4 directory/file bug handling + +This test creates files and directories with the same name in perforce and +checks that git-p4 recovers from the error at the same time as the perforce +repository.' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d && + test_might_fail p4 configure set submit.collision.check=0 +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + + touch add_file_add_dir_del_file add_file_add_dir_del_dir && + p4 add add_file_add_dir_del_file add_file_add_dir_del_dir && + mkdir add_dir_add_file_del_file add_dir_add_file_del_dir && + touch add_dir_add_file_del_file/file add_dir_add_file_del_dir/file && + p4 add add_dir_add_file_del_file/file add_dir_add_file_del_dir/file && + p4 submit -d "add initial" && + + rm -f add_file_add_dir_del_file add_file_add_dir_del_dir && + mkdir add_file_add_dir_del_file add_file_add_dir_del_dir && + touch add_file_add_dir_del_file/file add_file_add_dir_del_dir/file && + p4 add add_file_add_dir_del_file/file add_file_add_dir_del_dir/file && + rm -rf add_dir_add_file_del_file add_dir_add_file_del_dir && + touch add_dir_add_file_del_file add_dir_add_file_del_dir && + p4 add add_dir_add_file_del_file add_dir_add_file_del_dir && + p4 submit -d "add conflicting" && + + p4 delete -k add_file_add_dir_del_file && + p4 delete -k add_file_add_dir_del_dir/file && + p4 delete -k add_dir_add_file_del_file && + p4 delete -k add_dir_add_file_del_dir/file && + p4 submit -d "delete conflicting" && + + p4 delete -k "add_file_add_dir_del_file/file" && + p4 delete -k "add_file_add_dir_del_dir" && + p4 delete -k "add_dir_add_file_del_file/file" && + p4 delete -k "add_dir_add_file_del_dir" && + p4 submit -d "delete remaining" + ) +' + +test_expect_success 'clone with git-p4' ' + git p4 clone --dest="$git" //depot/@1,3 +' + +test_expect_success 'check contents' ' + test_path_is_dir "$git/add_file_add_dir_del_file" && + test_path_is_file "$git/add_file_add_dir_del_dir" && + test_path_is_dir "$git/add_dir_add_file_del_file" && + test_path_is_file "$git/add_dir_add_file_del_dir" +' + +test_expect_success 'rebase and check empty' ' + git -C "$git" p4 rebase && + + test_path_is_missing "$git/add_file_add_dir_del_file" && + test_path_is_missing "$git/add_file_add_dir_del_dir" && + test_path_is_missing "$git/add_dir_add_file_del_file" && + test_path_is_missing "$git/add_dir_add_file_del_dir" +' + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 1fe3764f2b..02048dfdac 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -562,11 +562,11 @@ static int warn_conflicted_path(struct index_state *istate, add_rejected_path(o, WARNING_SPARSE_UNMERGED_FILE, conflicting_path); - /* Find out how many higher stage entries at same path */ - while (++count < istate->cache_nr && - !strcmp(conflicting_path, - istate->cache[i+count]->name)) - /* do nothing */; + /* Find out how many higher stage entries are at same path */ + while ((++count) + i < istate->cache_nr && + !strcmp(conflicting_path, istate->cache[count + i]->name)) + ; /* do nothing */ + return count; } diff --git a/upload-pack.c b/upload-pack.c index 902d0ad5e1..0478bff3e7 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -26,6 +26,7 @@ #include "serve.h" #include "commit-graph.h" #include "commit-reach.h" +#include "shallow.h" /* Remember to update object flag allocation in object.h */ #define THEY_HAVE (1u << 11) @@ -68,7 +69,6 @@ static const char *pack_objects_hook; static int filter_capability_requested; static int allow_filter; static int allow_ref_in_want; -static struct list_objects_filter_options filter_options; static int allow_sideband_all; @@ -103,7 +103,8 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data) } static void create_pack_file(const struct object_array *have_obj, - const struct object_array *want_obj) + const struct object_array *want_obj, + struct list_objects_filter_options *filter_options) { struct child_process pack_objects = CHILD_PROCESS_INIT; char data[8193], progress[128]; @@ -140,9 +141,9 @@ static void create_pack_file(const struct object_array *have_obj, argv_array_push(&pack_objects.args, "--delta-base-offset"); if (use_include_tag) argv_array_push(&pack_objects.args, "--include-tag"); - if (filter_options.choice) { + if (filter_options->choice) { const char *spec = - expand_list_objects_filter_spec(&filter_options); + expand_list_objects_filter_spec(filter_options); if (pack_objects.use_shell) { struct strbuf buf = STRBUF_INIT; sq_quote_buf(&buf, spec); @@ -848,7 +849,9 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not, return 0; } -static void receive_needs(struct packet_reader *reader, struct object_array *want_obj) +static void receive_needs(struct packet_reader *reader, + struct object_array *want_obj, + struct list_objects_filter_options *filter_options) { struct object_array shallows = OBJECT_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; @@ -883,8 +886,8 @@ static void receive_needs(struct packet_reader *reader, struct object_array *wan if (skip_prefix(reader->line, "filter ", &arg)) { if (!filter_capability_requested) die("git upload-pack: filtering capability not negotiated"); - list_objects_filter_die_if_populated(&filter_options); - parse_list_objects_filter(&filter_options, arg); + list_objects_filter_die_if_populated(filter_options); + parse_list_objects_filter(filter_options, arg); continue; } @@ -1087,11 +1090,14 @@ void upload_pack(struct upload_pack_options *options) struct string_list symref = STRING_LIST_INIT_DUP; struct object_array want_obj = OBJECT_ARRAY_INIT; struct packet_reader reader; + struct list_objects_filter_options filter_options; stateless_rpc = options->stateless_rpc; timeout = options->timeout; daemon_mode = options->daemon_mode; + memset(&filter_options, 0, sizeof(filter_options)); + git_config(upload_pack_config, NULL); head_ref_namespaced(find_symref, &symref); @@ -1114,12 +1120,14 @@ void upload_pack(struct upload_pack_options *options) PACKET_READ_CHOMP_NEWLINE | PACKET_READ_DIE_ON_ERR_PACKET); - receive_needs(&reader, &want_obj); + receive_needs(&reader, &want_obj, &filter_options); if (want_obj.nr) { struct object_array have_obj = OBJECT_ARRAY_INIT; get_common_commits(&reader, &have_obj, &want_obj); - create_pack_file(&have_obj, &want_obj); + create_pack_file(&have_obj, &want_obj, &filter_options); } + + list_objects_filter_release(&filter_options); } struct upload_pack_data { @@ -1134,6 +1142,8 @@ struct upload_pack_data { int deepen_rev_list; int deepen_relative; + struct list_objects_filter_options filter_options; + struct packet_writer writer; unsigned stateless_rpc : 1; @@ -1169,6 +1179,7 @@ static void upload_pack_data_clear(struct upload_pack_data *data) oid_array_clear(&data->haves); object_array_clear(&data->shallows); string_list_clear(&data->deepen_not, 0); + list_objects_filter_release(&data->filter_options); } static int parse_want(struct packet_writer *writer, const char *line, @@ -1306,8 +1317,8 @@ static void process_args(struct packet_reader *request, } if (allow_filter && skip_prefix(arg, "filter ", &p)) { - list_objects_filter_die_if_populated(&filter_options); - parse_list_objects_filter(&filter_options, p); + list_objects_filter_die_if_populated(&data->filter_options); + parse_list_objects_filter(&data->filter_options, p); continue; } @@ -1514,7 +1525,7 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys, send_shallow_info(&data, &want_obj); packet_writer_write(&data.writer, "packfile\n"); - create_pack_file(&have_obj, &want_obj); + create_pack_file(&have_obj, &want_obj, &data.filter_options); state = FETCH_DONE; break; case FETCH_DONE: |