summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig2
-rw-r--r--.gitignore1
-rw-r--r--.mailmap4
-rw-r--r--.tsan-suppressions6
-rw-r--r--Documentation/Makefile1
-rw-r--r--Documentation/MyFirstContribution.txt36
-rw-r--r--Documentation/RelNotes/2.17.4.txt16
-rw-r--r--Documentation/RelNotes/2.17.5.txt22
-rw-r--r--Documentation/RelNotes/2.18.3.txt5
-rw-r--r--Documentation/RelNotes/2.18.4.txt5
-rw-r--r--Documentation/RelNotes/2.19.4.txt5
-rw-r--r--Documentation/RelNotes/2.19.5.txt5
-rw-r--r--Documentation/RelNotes/2.20.3.txt5
-rw-r--r--Documentation/RelNotes/2.20.4.txt5
-rw-r--r--Documentation/RelNotes/2.21.2.txt5
-rw-r--r--Documentation/RelNotes/2.21.3.txt5
-rw-r--r--Documentation/RelNotes/2.22.3.txt5
-rw-r--r--Documentation/RelNotes/2.22.4.txt5
-rw-r--r--Documentation/RelNotes/2.23.2.txt5
-rw-r--r--Documentation/RelNotes/2.23.3.txt5
-rw-r--r--Documentation/RelNotes/2.24.2.txt5
-rw-r--r--Documentation/RelNotes/2.24.3.txt5
-rw-r--r--Documentation/RelNotes/2.25.1.txt55
-rw-r--r--Documentation/RelNotes/2.25.2.txt60
-rw-r--r--Documentation/RelNotes/2.25.3.txt5
-rw-r--r--Documentation/RelNotes/2.25.4.txt5
-rw-r--r--Documentation/RelNotes/2.26.0.txt341
-rw-r--r--Documentation/RelNotes/2.26.1.txt5
-rw-r--r--Documentation/RelNotes/2.26.2.txt5
-rw-r--r--Documentation/RelNotes/2.27.0.txt94
-rw-r--r--Documentation/config.txt6
-rw-r--r--Documentation/config/advice.txt6
-rw-r--r--Documentation/config/branch.txt7
-rw-r--r--Documentation/config/core.txt11
-rw-r--r--Documentation/config/feature.txt3
-rw-r--r--Documentation/config/gpg.txt15
-rw-r--r--Documentation/config/http.txt33
-rw-r--r--Documentation/config/pack.txt11
-rw-r--r--Documentation/config/protocol.txt9
-rw-r--r--Documentation/config/pull.txt7
-rw-r--r--Documentation/config/push.txt9
-rw-r--r--Documentation/config/rebase.txt6
-rw-r--r--Documentation/config/stash.txt18
-rw-r--r--Documentation/config/tag.txt7
-rw-r--r--Documentation/config/tar.txt6
-rw-r--r--Documentation/config/user.txt7
-rw-r--r--Documentation/diff-options.txt8
-rwxr-xr-xDocumentation/doc-diff2
-rw-r--r--Documentation/fetch-options.txt15
-rw-r--r--Documentation/git-am.txt10
-rw-r--r--Documentation/git-check-ignore.txt12
-rw-r--r--Documentation/git-clone.txt13
-rw-r--r--Documentation/git-commit-graph.txt5
-rw-r--r--Documentation/git-commit-tree.txt22
-rw-r--r--Documentation/git-commit.txt40
-rw-r--r--Documentation/git-config.txt15
-rw-r--r--Documentation/git-fast-import.txt20
-rw-r--r--Documentation/git-filter-branch.txt6
-rw-r--r--Documentation/git-grep.txt18
-rw-r--r--Documentation/git-init.txt7
-rw-r--r--Documentation/git-ls-remote.txt4
-rw-r--r--Documentation/git-p4.txt45
-rw-r--r--Documentation/git-pack-objects.txt10
-rw-r--r--Documentation/git-rebase.txt180
-rw-r--r--Documentation/git-rm.txt61
-rw-r--r--Documentation/git-sparse-checkout.txt30
-rw-r--r--Documentation/git-stash.txt144
-rw-r--r--Documentation/git-submodule.txt8
-rw-r--r--Documentation/git-update-index.txt16
-rw-r--r--Documentation/git.txt53
-rw-r--r--Documentation/gitcli.txt5
-rw-r--r--Documentation/gitcore-tutorial.txt2
-rw-r--r--Documentation/gitcredentials.txt93
-rw-r--r--Documentation/gitfaq.txt337
-rw-r--r--Documentation/githooks.txt64
-rw-r--r--Documentation/howto/maintain-git.txt52
-rw-r--r--Documentation/pretty-formats.txt1
-rw-r--r--Documentation/technical/bundle-format.txt48
-rw-r--r--Documentation/technical/pack-format.txt5
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile7
l---------RelNotes2
-rw-r--r--abspath.c18
-rw-r--r--add-interactive.c30
-rw-r--r--add-interactive.h16
-rw-r--r--add-patch.c413
-rw-r--r--advice.c101
-rw-r--r--advice.h54
-rw-r--r--apply.c8
-rw-r--r--archive-tar.c6
-rw-r--r--archive-zip.c3
-rw-r--r--archive.c13
-rw-r--r--archive.h1
-rw-r--r--azure-pipelines.yml37
-rw-r--r--bisect.c135
-rw-r--r--bisect.h29
-rw-r--r--blame.h4
-rw-r--r--builtin/add.c45
-rw-r--r--builtin/am.c96
-rw-r--r--builtin/bisect--helper.c123
-rw-r--r--builtin/cat-file.c7
-rw-r--r--builtin/check-ignore.c3
-rw-r--r--builtin/checkout.c137
-rw-r--r--builtin/clone.c20
-rw-r--r--builtin/commit-graph.c38
-rw-r--r--builtin/commit.c53
-rw-r--r--builtin/config.c46
-rw-r--r--builtin/describe.c37
-rw-r--r--builtin/fast-export.c6
-rw-r--r--builtin/fetch.c12
-rw-r--r--builtin/fmt-merge-msg.c10
-rw-r--r--builtin/gc.c2
-rw-r--r--builtin/grep.c96
-rw-r--r--builtin/index-pack.c14
-rw-r--r--builtin/init-db.c79
-rw-r--r--builtin/merge-base.c12
-rw-r--r--builtin/merge.c9
-rw-r--r--builtin/mktag.c7
-rw-r--r--builtin/name-rev.c191
-rw-r--r--builtin/notes.c4
-rw-r--r--builtin/pack-objects.c296
-rw-r--r--builtin/pull.c58
-rw-r--r--builtin/rebase.c291
-rw-r--r--builtin/receive-pack.c36
-rw-r--r--builtin/reflog.c9
-rw-r--r--builtin/remote.c189
-rw-r--r--builtin/replace.c3
-rw-r--r--builtin/reset.c16
-rw-r--r--builtin/rev-list.c121
-rw-r--r--builtin/rev-parse.c12
-rw-r--r--builtin/rm.c28
-rw-r--r--builtin/show-branch.c2
-rw-r--r--builtin/sparse-checkout.c191
-rw-r--r--builtin/stash.c154
-rw-r--r--builtin/submodule--helper.c51
-rw-r--r--builtin/tag.c5
-rw-r--r--builtin/unpack-objects.c3
-rw-r--r--builtin/worktree.c18
-rw-r--r--cache-tree.c11
-rw-r--r--cache.h35
-rwxr-xr-xci/install-dependencies.sh4
-rwxr-xr-xci/lib.sh6
-rwxr-xr-xci/run-build-and-tests.sh1
-rwxr-xr-xci/test-documentation.sh1
-rw-r--r--color.c75
-rw-r--r--commit-graph.c123
-rw-r--r--commit-graph.h15
-rw-r--r--commit-slab.h7
-rw-r--r--commit.c59
-rw-r--r--commit.h12
-rw-r--r--compat/mingw.c15
-rw-r--r--compat/mingw.h10
-rw-r--r--compat/obstack.h6
-rw-r--r--compat/poll/poll.c31
-rw-r--r--compat/regex/regex.h5
-rw-r--r--compat/terminal.c249
-rw-r--r--compat/terminal.h3
-rwxr-xr-xcompat/vcbuild/scripts/clink.pl4
-rw-r--r--config.c45
-rw-r--r--config.h21
-rw-r--r--config.mak.dev3
-rw-r--r--connected.c4
-rwxr-xr-xcontrib/buildsystems/engine.pl4
-rw-r--r--contrib/completion/git-completion.bash160
-rw-r--r--contrib/completion/git-prompt.sh6
-rw-r--r--contrib/credential/netrc/.gitignore1
-rw-r--r--contrib/credential/netrc/Makefile26
-rwxr-xr-xcontrib/credential/netrc/git-credential-netrc.perl (renamed from contrib/credential/netrc/git-credential-netrc)2
-rwxr-xr-xcontrib/fast-import/import-tars.perl2
-rw-r--r--convert.c70
-rw-r--r--convert.h29
-rw-r--r--credential.c142
-rw-r--r--credential.h108
-rw-r--r--csum-file.c2
-rw-r--r--csum-file.h9
-rw-r--r--diff.c23
-rw-r--r--diffcore-rename.c4
-rw-r--r--dir.c166
-rw-r--r--editor.c11
-rw-r--r--entry.c7
-rw-r--r--environment.c18
-rw-r--r--ewah/bitmap.c21
-rw-r--r--ewah/ewok.h2
-rw-r--r--fast-import.c246
-rw-r--r--fsck.c147
-rw-r--r--fsmonitor.c120
-rwxr-xr-xgit-gui/git-gui--askpass5
-rwxr-xr-xgit-gui/git-gui.sh14
-rw-r--r--git-gui/lib/chord.tcl56
-rw-r--r--git-gui/lib/index.tcl10
-rw-r--r--git-gui/lib/merge.tcl2
-rw-r--r--git-gui/po/de.po3388
-rw-r--r--git-gui/po/git-gui.pot2506
-rw-r--r--git-gui/po/glossary/de.po315
-rw-r--r--git-gui/po/glossary/git-gui-glossary.pot250
-rw-r--r--git-gui/po/glossary/git-gui-glossary.txt101
-rwxr-xr-xgit-legacy-stash.sh798
-rwxr-xr-xgit-p4.py650
-rwxr-xr-xgit-submodule.sh24
-rw-r--r--git.c7
-rw-r--r--gpg-interface.c91
-rw-r--r--gpg-interface.h10
-rw-r--r--graph.c10
-rw-r--r--grep.c32
-rw-r--r--grep.h13
-rw-r--r--hash.h21
-rw-r--r--hex.c55
-rw-r--r--http.c80
-rw-r--r--log-tree.c27
-rw-r--r--mailinfo.c68
-rw-r--r--merge-recursive.c171
-rw-r--r--merge.c1
-rw-r--r--midx.c2
-rw-r--r--notes-utils.c2
-rw-r--r--notes.c30
-rw-r--r--object-store.h54
-rw-r--r--object.c16
-rw-r--r--object.h2
-rw-r--r--oidset.h1
-rw-r--r--pack-bitmap.c480
-rw-r--r--pack-bitmap.h11
-rw-r--r--pack-check.c32
-rw-r--r--pack-objects.c4
-rw-r--r--pack-objects.h2
-rw-r--r--packfile.c113
-rw-r--r--packfile.h18
-rw-r--r--parse-options-cb.c42
-rw-r--r--parse-options.c27
-rw-r--r--parse-options.h10
-rw-r--r--path.c8
-rw-r--r--pathspec.c9
-rw-r--r--po/TEAMS4
-rw-r--r--po/bg.po5376
-rw-r--r--po/ca.po517
-rw-r--r--po/de.po5634
-rw-r--r--po/es.po5529
-rw-r--r--po/fr.po5470
-rw-r--r--po/git.pot5228
-rw-r--r--po/it.po5607
-rw-r--r--po/sv.po5553
-rw-r--r--po/tr.po24563
-rw-r--r--po/vi.po5560
-rw-r--r--po/zh_CN.po5385
-rw-r--r--po/zh_TW.po5696
-rw-r--r--pretty.c34
-rw-r--r--protocol.c11
-rw-r--r--quote.c2
-rw-r--r--reachable.c4
-rw-r--r--rebase-interactive.c95
-rw-r--r--rebase-interactive.h7
-rw-r--r--rebase.c35
-rw-r--r--rebase.h15
-rw-r--r--ref-filter.c10
-rw-r--r--refs/files-backend.c3
-rw-r--r--remote-curl.c6
-rw-r--r--remote.c14
-rw-r--r--remote.h3
-rw-r--r--replace-object.c11
-rw-r--r--replace-object.h7
-rw-r--r--repo-settings.c3
-rw-r--r--repository.c4
-rw-r--r--run-command.c3
-rw-r--r--run-command.h2
-rw-r--r--sequencer.c228
-rw-r--r--sequencer.h11
-rw-r--r--setup.c73
-rw-r--r--sha1-file.c185
-rw-r--r--sha1-name.c52
-rw-r--r--sha256/gcrypt.h6
-rw-r--r--strbuf.c15
-rw-r--r--strbuf.h18
-rw-r--r--streaming.c28
-rw-r--r--streaming.h4
-rw-r--r--string-list.h6
-rw-r--r--submodule-config.c22
-rw-r--r--submodule-config.h2
-rw-r--r--submodule.c79
-rw-r--r--submodule.h4
-rw-r--r--t/README17
-rwxr-xr-xt/check-non-portable-shell.pl2
-rw-r--r--t/helper/test-advise.c22
-rw-r--r--t/helper/test-config.c18
-rw-r--r--t/helper/test-dump-fsmonitor.c2
-rw-r--r--t/helper/test-dump-split-index.c2
-rw-r--r--t/helper/test-parse-options.c2
-rw-r--r--t/helper/test-parse-pathspec-file.c33
-rw-r--r--t/helper/test-path-utils.c5
-rw-r--r--t/helper/test-read-graph.c8
-rw-r--r--t/helper/test-repository.c14
-rw-r--r--t/helper/test-tool.c2
-rw-r--r--t/helper/test-tool.h2
-rw-r--r--t/helper/test-windows-named-pipe.c2
-rwxr-xr-xt/lib-credential.sh2
-rw-r--r--t/lib-git-p4.sh2
-rwxr-xr-xt/lib-gpg.sh110
-rw-r--r--t/lib-httpd.sh2
-rw-r--r--t/lib-httpd/apache.conf6
-rw-r--r--t/lib-httpd/apply-one-time-perl.sh27
-rw-r--r--t/lib-httpd/apply-one-time-sed.sh24
-rwxr-xr-xt/lib-log-graph.sh28
-rw-r--r--t/lib-pack.sh35
-rwxr-xr-xt/lib-submodule-update.sh68
-rwxr-xr-xt/perf/p5310-pack-bitmaps.sh22
-rwxr-xr-xt/t0000-basic.sh27
-rwxr-xr-xt/t0003-attributes.sh47
-rwxr-xr-xt/t0008-ignores.sh39
-rwxr-xr-xt/t0018-advice.sh32
-rwxr-xr-xt/t0020-crlf.sh18
-rwxr-xr-xt/t0021-conversion.sh199
-rw-r--r--t/t0021/rot13-filter.pl6
-rwxr-xr-xt/t0040-parse-options.sh22
-rwxr-xr-xt/t0067-parse_pathspec_file.sh108
-rwxr-xr-xt/t0300-credentials.sh279
-rwxr-xr-xt/t1050-large.sh10
-rwxr-xr-xt/t1091-sparse-checkout-builtin.sh435
-rwxr-xr-xt/t1300-config.sh246
-rwxr-xr-xt/t1306-xdg-files.sh8
-rwxr-xr-xt/t1307-config-blob.sh2
-rwxr-xr-xt/t1308-config-set.sh18
-rwxr-xr-xt/t1400-update-ref.sh36
-rwxr-xr-xt/t1406-submodule-ref-store.sh2
-rwxr-xr-xt/t1409-avoid-packing-refs.sh16
-rwxr-xr-xt/t1450-fsck.sh25
-rwxr-xr-xt/t1501-work-tree.sh2
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh62
-rwxr-xr-xt/t1507-rev-parse-upstream.sh104
-rwxr-xr-xt/t2018-checkout-branch.sh75
-rwxr-xr-xt/t2024-checkout-dwim.sh28
-rwxr-xr-xt/t2026-checkout-pathspec-file.sh28
-rwxr-xr-xt/t2070-restore.sh17
-rwxr-xr-xt/t2072-restore-pathspec-file.sh29
-rwxr-xr-xt/t2107-update-index-basic.sh1
-rwxr-xr-xt/t2400-worktree-add.sh9
-rwxr-xr-xt/t2402-worktree-list.sh6
-rwxr-xr-xt/t2405-worktree-submodule.sh90
-rwxr-xr-xt/t3007-ls-files-recurse-submodules.sh1
-rwxr-xr-xt/t3030-merge-recursive.sh2
-rwxr-xr-xt/t3035-merge-sparse.sh4
-rwxr-xr-xt/t3206-range-diff.sh14
-rwxr-xr-xt/t3305-notes-fanout.sh107
-rwxr-xr-xt/t3308-notes-merge.sh83
-rwxr-xr-xt/t3309-notes-merge-auto-resolve.sh228
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh106
-rwxr-xr-xt/t3311-notes-merge-fanout.sh60
-rwxr-xr-xt/t3400-rebase.sh60
-rwxr-xr-xt/t3401-rebase-and-am-rename.sh4
-rwxr-xr-xt/t3403-rebase-skip.sh79
-rwxr-xr-xt/t3404-rebase-interactive.sh221
-rwxr-xr-xt/t3406-rebase-message.sh19
-rwxr-xr-xt/t3407-rebase-abort.sh6
-rwxr-xr-xt/t3415-rebase-autosquash.sh153
-rwxr-xr-xt/t3417-rebase-whitespace-fix.sh4
-rwxr-xr-xt/t3419-rebase-patch-id.sh113
-rwxr-xr-xt/t3420-rebase-autostash.sh22
-rwxr-xr-xt/t3421-rebase-topology-linear.sh48
-rwxr-xr-xt/t3424-rebase-empty.sh134
-rwxr-xr-xt/t3425-rebase-topology-merges.sh8
-rwxr-xr-xt/t3427-rebase-subtree.sh12
-rwxr-xr-xt/t3430-rebase-merges.sh5
-rwxr-xr-xt/t3431-rebase-fork-point.sh20
-rwxr-xr-xt/t3432-rebase-fast-forward.sh54
-rwxr-xr-xt/t3433-rebase-across-mode-change.sh48
-rwxr-xr-xt/t3504-cherry-pick-rerere.sh6
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh51
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh3
-rwxr-xr-xt/t3600-rm.sh7
-rwxr-xr-xt/t3601-rm-pathspec-file.sh79
-rwxr-xr-xt/t3700-add.sh6
-rwxr-xr-xt/t3701-add-interactive.sh26
-rwxr-xr-xt/t3704-add-pathspec-file.sh36
-rwxr-xr-xt/t3903-stash.sh19
-rwxr-xr-xt/t3909-stash-pathspec-file.sh100
-rwxr-xr-xt/t4013-diff-various.sh44
-rwxr-xr-xt/t4018-diff-funcname.sh1
-rwxr-xr-xt/t4026-color.sh12
-rwxr-xr-xt/t4054-diff-bogus-tree.sh3
-rwxr-xr-xt/t4057-diff-combined-paths.sh2
-rwxr-xr-xt/t4060-diff-submodule-option-diff-format.sh126
-rwxr-xr-xt/t4066-diff-emit-delay.sh10
-rwxr-xr-xt/t4117-apply-reject.sh6
-rwxr-xr-xt/t4124-apply-ws-rule.sh10
-rwxr-xr-xt/t4134-apply-submodule.sh5
-rwxr-xr-xt/t4150-am.sh22
-rwxr-xr-xt/t4200-rerere.sh3
-rwxr-xr-xt/t4202-log.sh305
-rwxr-xr-xt/t4204-patch-id.sh2
-rwxr-xr-xt/t4211-line-log.sh3
-rw-r--r--t/t4211/sha1/expect.beginning-of-file (renamed from t/t4211/expect.beginning-of-file)0
-rw-r--r--t/t4211/sha1/expect.end-of-file (renamed from t/t4211/expect.end-of-file)0
-rw-r--r--t/t4211/sha1/expect.move-support-f (renamed from t/t4211/expect.move-support-f)0
-rw-r--r--t/t4211/sha1/expect.multiple (renamed from t/t4211/expect.multiple)0
-rw-r--r--t/t4211/sha1/expect.multiple-overlapping (renamed from t/t4211/expect.multiple-overlapping)0
-rw-r--r--t/t4211/sha1/expect.multiple-superset (renamed from t/t4211/expect.multiple-superset)0
-rw-r--r--t/t4211/sha1/expect.parallel-change-f-to-main (renamed from t/t4211/expect.parallel-change-f-to-main)0
-rw-r--r--t/t4211/sha1/expect.simple-f (renamed from t/t4211/expect.simple-f)0
-rw-r--r--t/t4211/sha1/expect.simple-f-to-main (renamed from t/t4211/expect.simple-f-to-main)0
-rw-r--r--t/t4211/sha1/expect.simple-main (renamed from t/t4211/expect.simple-main)0
-rw-r--r--t/t4211/sha1/expect.simple-main-to-end (renamed from t/t4211/expect.simple-main-to-end)0
-rw-r--r--t/t4211/sha1/expect.two-ranges (renamed from t/t4211/expect.two-ranges)0
-rw-r--r--t/t4211/sha1/expect.vanishes-early (renamed from t/t4211/expect.vanishes-early)0
-rw-r--r--t/t4211/sha256/expect.beginning-of-file43
-rw-r--r--t/t4211/sha256/expect.end-of-file62
-rw-r--r--t/t4211/sha256/expect.move-support-f80
-rw-r--r--t/t4211/sha256/expect.multiple104
-rw-r--r--t/t4211/sha256/expect.multiple-overlapping187
-rw-r--r--t/t4211/sha256/expect.multiple-superset187
-rw-r--r--t/t4211/sha256/expect.parallel-change-f-to-main160
-rw-r--r--t/t4211/sha256/expect.simple-f59
-rw-r--r--t/t4211/sha256/expect.simple-f-to-main100
-rw-r--r--t/t4211/sha256/expect.simple-main68
-rw-r--r--t/t4211/sha256/expect.simple-main-to-end70
-rw-r--r--t/t4211/sha256/expect.two-ranges102
-rw-r--r--t/t4211/sha256/expect.vanishes-early39
-rwxr-xr-xt/t4214-log-graph-octopus.sh82
-rwxr-xr-xt/t4215-log-skewed-merges.sh71
-rwxr-xr-xt/t4300-merge-tree.sh188
-rwxr-xr-xt/t5100-mailinfo.sh15
-rwxr-xr-xt/t5150-request-pull.sh2
-rwxr-xr-xt/t5302-pack-index.sh21
-rwxr-xr-xt/t5309-pack-delta-cycles.sh18
-rwxr-xr-xt/t5310-pack-bitmaps.sh36
-rwxr-xr-xt/t5313-pack-bounds-checks.sh19
-rwxr-xr-xt/t5318-commit-graph.sh10
-rwxr-xr-xt/t5319-multi-pack-index.sh26
-rwxr-xr-xt/t5321-pack-large-objects.sh4
-rwxr-xr-xt/t5322-pack-objects-sparse.sh4
-rwxr-xr-xt/t5324-split-commit-graph.sh13
-rwxr-xr-xt/t5400-send-pack.sh2
-rwxr-xr-xt/t5407-post-rewrite-hook.sh16
-rwxr-xr-xt/t5409-colorize-remote-messages.sh3
-rwxr-xr-xt/t5500-fetch-pack.sh23
-rwxr-xr-xt/t5504-fetch-receive-strict.sh17
-rwxr-xr-xt/t5505-remote.sh88
-rwxr-xr-xt/t5509-fetch-push-namespaces.sh13
-rwxr-xr-xt/t5510-fetch.sh34
-rwxr-xr-xt/t5512-ls-remote.sh27
-rwxr-xr-xt/t5515-fetch-merge-logic.sh62
-rwxr-xr-xt/t5516-fetch-push.sh23
-rwxr-xr-xt/t5520-pull.sh27
-rwxr-xr-xt/t5521-pull-options.sh22
-rwxr-xr-xt/t5530-upload-pack-error.sh26
-rwxr-xr-xt/t5537-fetch-shallow.sh20
-rwxr-xr-xt/t5539-fetch-http-shallow.sh2
-rwxr-xr-xt/t5540-http-push-webdav.sh6
-rwxr-xr-xt/t5541-http-push-smart.sh4
-rwxr-xr-xt/t5550-http-fetch-dumb.sh16
-rwxr-xr-xt/t5551-http-fetch-smart.sh12
-rwxr-xr-xt/t5552-skipping-fetch-negotiator.sh18
-rwxr-xr-xt/t5562-http-backend-content-length.sh2
-rwxr-xr-xt/t5573-pull-verify-signatures.sh64
-rwxr-xr-xt/t5580-unc-paths.sh (renamed from t/t5580-clone-push-unc.sh)12
-rwxr-xr-xt/t5601-clone.sh4
-rwxr-xr-xt/t5604-clone-reference.sh7
-rwxr-xr-xt/t5607-clone-bundle.sh2
-rwxr-xr-xt/t5616-partial-clone.sh105
-rwxr-xr-xt/t5617-clone-submodules-remote.sh13
-rwxr-xr-xt/t5700-protocol-v1.sh3
-rwxr-xr-xt/t5702-protocol-v2.sh24
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh13
-rwxr-xr-xt/t6000-rev-list-misc.sh25
-rwxr-xr-xt/t6006-rev-list-format.sh4
-rwxr-xr-xt/t6020-merge-df.sh55
-rwxr-xr-xt/t6021-merge-criss-cross.sh135
-rwxr-xr-xt/t6022-merge-rename.sh393
-rwxr-xr-xt/t6023-merge-file.sh568
-rwxr-xr-xt/t6024-recursive-merge.sh15
-rwxr-xr-xt/t6025-merge-symlinks.sh95
-rwxr-xr-xt/t6026-merge-attr.sh46
-rwxr-xr-xt/t6034-merge-rename-nocruft.sh122
-rwxr-xr-xt/t6035-merge-dir-to-symlink.sh28
-rwxr-xr-xt/t6036-recursive-corner-cases.sh39
-rwxr-xr-xt/t6046-merge-skip-unneeded-updates.sh82
-rwxr-xr-xt/t6047-diff3-conflict-markers.sh13
-rwxr-xr-xt/t6113-rev-list-bitmap-filters.sh56
-rwxr-xr-xt/t6120-describe.sh71
-rwxr-xr-xt/t6136-pathspec-in-bare.sh38
-rwxr-xr-xt/t6200-fmt-merge-msg.sh23
-rwxr-xr-xt/t6300-for-each-ref.sh27
-rwxr-xr-xt/t7004-tag.sh1
-rwxr-xr-xt/t7030-verify-tag.sh24
-rwxr-xr-xt/t7107-reset-pathspec-file.sh42
-rwxr-xr-xt/t7112-reset-submodule.sh1
-rwxr-xr-xt/t7300-clean.sh9
-rwxr-xr-xt/t7400-submodule-basic.sh44
-rwxr-xr-xt/t7406-submodule-update.sh2
-rwxr-xr-xt/t7410-submodule-checkout-to.sh77
-rwxr-xr-xt/t7416-submodule-dash-url.sh143
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh9
-rwxr-xr-xt/t7510-signed-commit.sh55
-rwxr-xr-xt/t7512-status-help.sh12
-rwxr-xr-xt/t7519-status-fsmonitor.sh7
-rwxr-xr-xt/t7519/fsmonitor-all1
-rwxr-xr-xt/t7519/fsmonitor-all-v221
-rwxr-xr-xt/t7519/fsmonitor-watchman3
-rwxr-xr-xt/t7519/fsmonitor-watchman-v2173
-rwxr-xr-xt/t7526-commit-pathspec-file.sh38
-rwxr-xr-xt/t7601-merge-pull-config.sh38
-rwxr-xr-xt/t7609-merge-co-error-msgs.sh2
-rwxr-xr-xt/t7612-merge-verify-signatures.sh22
-rwxr-xr-xt/t7800-difftool.sh5
-rwxr-xr-xt/t7814-grep-recurse-submodules.sh11
-rwxr-xr-xt/t8003-blame-corner-cases.sh1
-rwxr-xr-xt/t9001-send-email.sh4
-rwxr-xr-xt/t9010-svn-fe.sh2
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh3
-rwxr-xr-xt/t9116-git-svn-log.sh12
-rwxr-xr-xt/t9300-fast-import.sh110
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh70
-rwxr-xr-xt/t9800-git-p4-basic.sh1
-rwxr-xr-xt/t9810-git-p4-rcs.sh1
-rwxr-xr-xt/t9831-git-p4-triggers.sh2
-rwxr-xr-xt/t9902-completion.sh57
-rwxr-xr-xt/t9903-bash-prompt.sh8
-rw-r--r--t/test-lib-functions.sh27
-rw-r--r--t/test-lib.sh100
-rwxr-xr-xtemplates/hooks--fsmonitor-watchman.sample168
-rwxr-xr-xtemplates/hooks--pre-commit.sample2
-rwxr-xr-xtemplates/hooks--update.sample10
-rw-r--r--transport-helper.c5
-rw-r--r--transport.c2
-rw-r--r--tree-walk.c14
-rw-r--r--tree-walk.h2
-rw-r--r--unicode-width.h43
-rw-r--r--unpack-trees.c52
-rw-r--r--unpack-trees.h3
-rw-r--r--upload-pack.c3
-rw-r--r--urlmatch.c4
-rw-r--r--urlmatch.h9
-rw-r--r--walker.c17
-rw-r--r--walker.h1
-rw-r--r--worktree.c43
-rw-r--r--worktree.h20
-rw-r--r--wt-status.h15
-rw-r--r--xdiff-interface.c12
534 files changed, 74019 insertions, 35256 deletions
diff --git a/.editorconfig b/.editorconfig
index 42cdc4b..f9d8196 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,7 +4,7 @@ insert_final_newline = true
# The settings for C (*.c and *.h) files are mirrored in .clang-format. Keep
# them in sync.
-[*.{c,h,sh,perl,pl,pm}]
+[*.{c,h,sh,perl,pl,pm,txt}]
indent_style = tab
tab_width = 8
diff --git a/.gitignore b/.gitignore
index aebe7c0..188bd1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,7 +83,6 @@
/git-init-db
/git-interpret-trailers
/git-instaweb
-/git-legacy-stash
/git-log
/git-ls-files
/git-ls-remote
diff --git a/.mailmap b/.mailmap
index 7c94418..bde7aba 100644
--- a/.mailmap
+++ b/.mailmap
@@ -31,6 +31,7 @@ Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil>
Brandon Williams <bwilliams.eng@gmail.com> <bmwill@google.com>
brian m. carlson <sandals@crustytoothpaste.net>
brian m. carlson <sandals@crustytoothpaste.net> <sandals@crustytoothpaste.ath.cx>
+brian m. carlson <sandals@crustytoothpaste.net> <bk2204@github.com>
Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com>
Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com>
Cheng Renquan <crquan@gmail.com>
@@ -59,6 +60,7 @@ David S. Miller <davem@davemloft.net>
David Turner <novalis@novalis.org> <dturner@twopensource.com>
David Turner <novalis@novalis.org> <dturner@twosigma.com>
Derrick Stolee <dstolee@microsoft.com> <stolee@gmail.com>
+Derrick Stolee <dstolee@microsoft.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Deskin Miller <deskinm@umich.edu>
Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh
Dirk Süsserott <newsletter@dirk.my1.cc>
@@ -109,6 +111,7 @@ Jim Meyering <jim@meyering.net> <meyering@redhat.com>
Joachim Berdal Haga <cjhaga@fys.uio.no>
Joachim Jablon <joachim.jablon@people-doc.com> <ewjoachim@gmail.com>
Johannes Schindelin <Johannes.Schindelin@gmx.de> <johannes.schindelin@gmx.de>
+Johannes Schindelin <Johannes.Schindelin@gmx.de> Johannes Schindelin via GitGitGadget <gitgitgadget@gmail.com>
Johannes Sixt <j6t@kdbg.org> <J.Sixt@eudaptics.com>
Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
@@ -287,6 +290,7 @@ William Pursell <bill.pursell@gmail.com>
YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx>
YONETANI Tomokazu <y0n3t4n1@gmail.com> <y0netan1@dragonflybsd.org>
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+Yi-Jyun Pan <pan93412@gmail.com>
# the two anonymous contributors are different persons:
anonymous <linux@horizon.com>
anonymous <linux@horizon.net>
diff --git a/.tsan-suppressions b/.tsan-suppressions
index 8c85014..5ba86d6 100644
--- a/.tsan-suppressions
+++ b/.tsan-suppressions
@@ -8,3 +8,9 @@
# in practice it (hopefully!) doesn't matter.
race:^want_color$
race:^transfer_debug$
+
+# A boolean value, which tells whether the replace_map has been initialized or
+# not, is read racily with an update. As this variable is written to only once,
+# and it's OK if the value change right after reading it, this shouldn't be a
+# problem.
+race:^lookup_replace_object$
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 8fe829c..59e6ce3 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -30,6 +30,7 @@ MAN7_TXT += gitcredentials.txt
MAN7_TXT += gitcvs-migration.txt
MAN7_TXT += gitdiffcore.txt
MAN7_TXT += giteveryday.txt
+MAN7_TXT += gitfaq.txt
MAN7_TXT += gitglossary.txt
MAN7_TXT += gitnamespaces.txt
MAN7_TXT += gitremote-helpers.txt
diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 35b9130..427274d 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -23,6 +23,42 @@ useful additional context:
- `Documentation/SubmittingPatches`
- `Documentation/howto/new-command.txt`
+[[getting-help]]
+=== Getting Help
+
+If you get stuck, you can seek help in the following places.
+
+==== git@vger.kernel.org
+
+This is the main Git project mailing list where code reviews, version
+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
+available to view in a browser.
+
+==== https://groups.google.com/forum/#!forum/git-mentoring[git-mentoring@googlegroups.com]
+
+This mailing list is targeted to new contributors and was created as a place to
+post questions and receive answers outside of the public eye of the main list.
+Veteran contributors who are especially interested in helping mentor newcomers
+are present on the list. In order to avoid search indexers, group membership is
+required to view messages; anyone can join and no approval is required.
+
+==== https://webchat.freenode.net/#git-devel[#git-devel] on Freenode
+
+This IRC channel is for conversations between Git contributors. If someone is
+currently online and knows the answer to your question, you can receive help
+in real time. Otherwise, you can read the
+https://colabti.org/irclogger/irclogger_logs/git-devel[scrollback] to see
+whether someone answered you. IRC does not allow offline private messaging, so
+if you try to private message someone and then log out of IRC, they cannot
+respond to you. It's better to ask your questions in the channel so that you
+can be answered if you disconnect and so that others can learn from the
+conversation.
+
[[getting-started]]
== Getting Started
diff --git a/Documentation/RelNotes/2.17.4.txt b/Documentation/RelNotes/2.17.4.txt
new file mode 100644
index 0000000..7d794ca
--- /dev/null
+++ b/Documentation/RelNotes/2.17.4.txt
@@ -0,0 +1,16 @@
+Git v2.17.4 Release Notes
+=========================
+
+This release is to address the security issue: CVE-2020-5260
+
+Fixes since v2.17.3
+-------------------
+
+ * With a crafted URL that contains a newline in it, the credential
+ helper machinery can be fooled to give credential information for
+ a wrong host. The attack has been made impossible by forbidding
+ a newline character in any value passed via the credential
+ protocol.
+
+Credit for finding the vulnerability goes to Felix Wilhelm of Google
+Project Zero.
diff --git a/Documentation/RelNotes/2.17.5.txt b/Documentation/RelNotes/2.17.5.txt
new file mode 100644
index 0000000..2abb821
--- /dev/null
+++ b/Documentation/RelNotes/2.17.5.txt
@@ -0,0 +1,22 @@
+Git v2.17.5 Release Notes
+=========================
+
+This release is to address a security issue: CVE-2020-11008
+
+Fixes since v2.17.4
+-------------------
+
+ * With a crafted URL that contains a newline or empty host, or lacks
+ a scheme, the credential helper machinery can be fooled into
+ providing credential information that is not appropriate for the
+ protocol in use and host being contacted.
+
+ Unlike the vulnerability CVE-2020-5260 fixed in v2.17.4, the
+ credentials are not for a host of the attacker's choosing; instead,
+ they are for some unspecified host (based on how the configured
+ credential helper handles an absent "host" parameter).
+
+ The attack has been made impossible by refusing to work with
+ under-specified credential patterns.
+
+Credit for finding the vulnerability goes to Carlo Arenas.
diff --git a/Documentation/RelNotes/2.18.3.txt b/Documentation/RelNotes/2.18.3.txt
new file mode 100644
index 0000000..25143f0
--- /dev/null
+++ b/Documentation/RelNotes/2.18.3.txt
@@ -0,0 +1,5 @@
+Git v2.18.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.18.4.txt b/Documentation/RelNotes/2.18.4.txt
new file mode 100644
index 0000000..e8ef858
--- /dev/null
+++ b/Documentation/RelNotes/2.18.4.txt
@@ -0,0 +1,5 @@
+Git v2.18.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.19.4.txt b/Documentation/RelNotes/2.19.4.txt
new file mode 100644
index 0000000..35d0ae5
--- /dev/null
+++ b/Documentation/RelNotes/2.19.4.txt
@@ -0,0 +1,5 @@
+Git v2.19.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.19.5.txt b/Documentation/RelNotes/2.19.5.txt
new file mode 100644
index 0000000..18a4dcb
--- /dev/null
+++ b/Documentation/RelNotes/2.19.5.txt
@@ -0,0 +1,5 @@
+Git v2.19.5 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.20.3.txt b/Documentation/RelNotes/2.20.3.txt
new file mode 100644
index 0000000..f6eccd1
--- /dev/null
+++ b/Documentation/RelNotes/2.20.3.txt
@@ -0,0 +1,5 @@
+Git v2.20.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.20.4.txt b/Documentation/RelNotes/2.20.4.txt
new file mode 100644
index 0000000..5a9e24e
--- /dev/null
+++ b/Documentation/RelNotes/2.20.4.txt
@@ -0,0 +1,5 @@
+Git v2.20.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.21.2.txt b/Documentation/RelNotes/2.21.2.txt
new file mode 100644
index 0000000..a0fb83b
--- /dev/null
+++ b/Documentation/RelNotes/2.21.2.txt
@@ -0,0 +1,5 @@
+Git v2.21.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.21.3.txt b/Documentation/RelNotes/2.21.3.txt
new file mode 100644
index 0000000..2ca0aa5
--- /dev/null
+++ b/Documentation/RelNotes/2.21.3.txt
@@ -0,0 +1,5 @@
+Git v2.21.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.22.3.txt b/Documentation/RelNotes/2.22.3.txt
new file mode 100644
index 0000000..57296f6
--- /dev/null
+++ b/Documentation/RelNotes/2.22.3.txt
@@ -0,0 +1,5 @@
+Git v2.22.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.22.4.txt b/Documentation/RelNotes/2.22.4.txt
new file mode 100644
index 0000000..8b5f3e3
--- /dev/null
+++ b/Documentation/RelNotes/2.22.4.txt
@@ -0,0 +1,5 @@
+Git v2.22.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.23.2.txt b/Documentation/RelNotes/2.23.2.txt
new file mode 100644
index 0000000..b697cbe
--- /dev/null
+++ b/Documentation/RelNotes/2.23.2.txt
@@ -0,0 +1,5 @@
+Git v2.23.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.23.3.txt b/Documentation/RelNotes/2.23.3.txt
new file mode 100644
index 0000000..2e35490
--- /dev/null
+++ b/Documentation/RelNotes/2.23.3.txt
@@ -0,0 +1,5 @@
+Git v2.23.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.24.2.txt b/Documentation/RelNotes/2.24.2.txt
new file mode 100644
index 0000000..0049f65
--- /dev/null
+++ b/Documentation/RelNotes/2.24.2.txt
@@ -0,0 +1,5 @@
+Git v2.24.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.24.3.txt b/Documentation/RelNotes/2.24.3.txt
new file mode 100644
index 0000000..5302e0f
--- /dev/null
+++ b/Documentation/RelNotes/2.24.3.txt
@@ -0,0 +1,5 @@
+Git v2.24.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.25.1.txt b/Documentation/RelNotes/2.25.1.txt
new file mode 100644
index 0000000..cd869b0
--- /dev/null
+++ b/Documentation/RelNotes/2.25.1.txt
@@ -0,0 +1,55 @@
+Git 2.25.1 Release Notes
+========================
+
+Fixes since v2.25
+-----------------
+
+ * "git commit" gives output similar to "git status" when there is
+ nothing to commit, but without honoring the advise.statusHints
+ configuration variable, which has been corrected.
+
+ * has_object_file() said "no" given an object registered to the
+ system via pretend_object_file(), making it inconsistent with
+ read_object_file(), causing lazy fetch to attempt fetching an
+ empty tree from promisor remotes.
+
+ * The code that tries to skip over the entries for the paths in a
+ single directory using the cache-tree was not careful enough
+ against corrupt index file.
+
+ * Complete an update to tutorial that encourages "git switch" over
+ "git checkout" that was done only half-way.
+
+ * Reduce unnecessary round-trip when running "ls-remote" over the
+ stateless RPC mechanism.
+
+ * "git restore --staged" did not correctly update the cache-tree
+ structure, resulting in bogus trees to be written afterwards, which
+ has been corrected.
+
+ * The code recently added to move to the entry beyond the ones in the
+ same directory in the index in the sparse-cone mode did not count
+ the number of entries to skip over incorrectly, which has been
+ corrected.
+
+ * Work around test breakages caused by custom regex engine used in
+ libasan, when address sanitizer is used with more recent versions
+ of gcc and clang.
+
+ * "git fetch --refmap=" option has got a better documentation.
+
+ * Corner case bugs in "git clean" that stems from a (necessarily for
+ performance reasons) awkward calling convention in the directory
+ enumeration API has been corrected.
+
+ * "git grep --no-index" should not get affected by the contents of
+ the .gitmodules file but when "--recurse-submodules" is given or
+ the "submodule.recurse" variable is set, it did. Now these
+ settings are ignored in the "--no-index" mode.
+
+ * Technical details of the bundle format has been documented.
+
+ * Unhelpful warning messages during documentation build have been
+ squelched.
+
+Also contains various documentation updates, code clean-ups and minor fixups.
diff --git a/Documentation/RelNotes/2.25.2.txt b/Documentation/RelNotes/2.25.2.txt
new file mode 100644
index 0000000..303c53a
--- /dev/null
+++ b/Documentation/RelNotes/2.25.2.txt
@@ -0,0 +1,60 @@
+Git 2.25.2 Release Notes
+========================
+
+Fixes since v2.25.1
+-------------------
+
+ * Minor bugfixes to "git add -i" that has recently been rewritten in C.
+
+ * An earlier update to show the location of working tree in the error
+ message did not consider the possibility that a git command may be
+ run in a bare repository, which has been corrected.
+
+ * The "--recurse-submodules" option of various subcommands did not
+ work well when run in an alternate worktree, which has been
+ corrected.
+
+ * Running "git rm" on a submodule failed unnecessarily when
+ .gitmodules is only cache-dirty, which has been corrected.
+
+ * "git rebase -i" identifies existing commits in its todo file with
+ their abbreviated object name, which could become ambigous as it
+ goes to create new commits, and has a mechanism to avoid ambiguity
+ in the main part of its execution. A few other cases however were
+ not covered by the protection against ambiguity, which has been
+ corrected.
+
+ * The index-pack code now diagnoses a bad input packstream that
+ records the same object twice when it is used as delta base; the
+ code used to declare a software bug when encountering such an
+ input, but it is an input error.
+
+ * The code to automatically shrink the fan-out in the notes tree had
+ an off-by-one bug, which has been killed.
+
+ * "git check-ignore" did not work when the given path is explicitly
+ marked as not ignored with a negative entry in the .gitignore file.
+
+ * The merge-recursive machinery failed to refresh the cache entry for
+ a merge result in a couple of places, resulting in an unnecessary
+ merge failure, which has been fixed.
+
+ * Fix for a bug revealed by a recent change to make the protocol v2
+ the default.
+
+ * "git merge signed-tag" while lacking the public key started to say
+ "No signature", which was utterly wrong. This regression has been
+ reverted.
+
+ * MinGW's poll() emulation has been improved.
+
+ * "git show" and others gave an object name in raw format in its
+ error output, which has been corrected to give it in hex.
+
+ * Both "git ls-remote -h" and "git grep -h" give short usage help,
+ like any other Git subcommand, but it is not unreasonable to expect
+ that the former would behave the same as "git ls-remote --head"
+ (there is no other sensible behaviour for the latter). The
+ documentation has been updated in an attempt to clarify this.
+
+Also contains various documentation updates, code clean-ups and minor fixups.
diff --git a/Documentation/RelNotes/2.25.3.txt b/Documentation/RelNotes/2.25.3.txt
new file mode 100644
index 0000000..15f7f21
--- /dev/null
+++ b/Documentation/RelNotes/2.25.3.txt
@@ -0,0 +1,5 @@
+Git v2.25.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.25.4.txt b/Documentation/RelNotes/2.25.4.txt
new file mode 100644
index 0000000..0dbb5da
--- /dev/null
+++ b/Documentation/RelNotes/2.25.4.txt
@@ -0,0 +1,5 @@
+Git v2.25.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.26.0.txt b/Documentation/RelNotes/2.26.0.txt
new file mode 100644
index 0000000..3a7a734
--- /dev/null
+++ b/Documentation/RelNotes/2.26.0.txt
@@ -0,0 +1,341 @@
+Git 2.26 Release Notes
+======================
+
+Updates since v2.25
+-------------------
+
+Backward compatibility notes
+
+ * "git rebase" uses a different backend that is based on the 'merge'
+ machinery by default. There are a few known differences in the
+ behaviour from the traditional machinery based on patch+apply.
+
+ If your workflow is negatively affected by this change, please
+ report it to git@vger.kernel.org so that we can take a look into
+ it. After doing so, you can set the 'rebase.backend' configuration
+ variable to 'apply', in order to use the old default behaviour in
+ the meantime.
+
+
+UI, Workflows & Features
+
+ * Sample credential helper for using .netrc has been updated to work
+ out of the box.
+
+ * gpg.minTrustLevel configuration variable has been introduced to
+ tell various signature verification codepaths the required minimum
+ trust level.
+
+ * The command line completion (in contrib/) learned to complete
+ subcommands and arguments to "git worktree".
+
+ * Disambiguation logic to tell revisions and pathspec apart has been
+ tweaked so that backslash-escaped glob special characters do not
+ count in the "wildcards are pathspec" rule.
+
+ * One effect of specifying where the GIT_DIR is (either with the
+ environment variable, or with the "git --git-dir=<where> cmd"
+ option) is to disable the repository discovery. This has been
+ placed a bit more stress in the documentation, as new users often
+ get confused.
+
+ * Two help messages given when "git add" notices the user gave it
+ nothing to add have been updated to use advise() API.
+
+ * A new version of fsmonitor-watchman hook has been introduced, to
+ avoid races.
+
+ * "git config" learned to show in which "scope", in addition to in
+ which file, each config setting comes from.
+
+ * The basic 7 colors learned the brighter counterparts
+ (e.g. "brightred").
+
+ * "git sparse-checkout" learned a new "add" subcommand.
+
+ * A configuration element used for credential subsystem can now use
+ wildcard pattern to specify for which set of URLs the entry
+ applies.
+
+ * "git clone --recurse-submodules --single-branch" now uses the same
+ single-branch option when cloning the submodules.
+
+ * "git rm" and "git stash" learns the new "--pathspec-from-file"
+ option.
+
+ * "git am --show-current-patch" is a way to show the piece of e-mail
+ for the stopped step, which is not suitable to directly feed "git
+ apply" (it is designed to be a good "git am" input). It learned a
+ new option to show only the patch part.
+
+ * Handling of conflicting renames in merge-recursive have further
+ been made consistent with how existing codepaths try to mimic what
+ is done to add/add conflicts.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * Tell .editorconfig that in this project, *.txt files are indented
+ with tabs.
+
+ * The test-lint machinery knew to check "VAR=VAL shell_function"
+ construct, but did not check "VAR= shell_function", which has been
+ corrected.
+
+ * Replace "git config --bool" calls with "git config --type=bool" in
+ sample templates.
+
+ * The effort to move "git-add--interactive" to C continues.
+
+ * Improve error message generation for "git submodule add".
+
+ * Preparation of test scripts for the day when the object names will
+ use SHA-256 continues.
+
+ * Warn programmers about pretend_object_file() that allows the code
+ to tentatively use in-core objects.
+
+ * The way "git pack-objects" reuses objects stored in existing pack
+ to generate its result has been improved.
+
+ * The transport protocol version 2 becomes the default one.
+
+ * Traditionally, we avoided threaded grep while searching in objects
+ (as opposed to files in the working tree) as accesses to the object
+ layer is not thread-safe. This limitation is getting lifted.
+
+ * "git rebase -i" (and friends) used to unnecessarily check out the
+ tip of the branch to be rebased, which has been corrected.
+
+ * A low-level API function get_oid(), that accepts various ways to
+ name an object, used to issue end-user facing error messages
+ without l10n, which has been updated to be translatable.
+
+ * Unneeded connectivity check is now disabled in a partial clone when
+ fetching into it.
+
+ * Some rough edges in the sparse-checkout feature, especially around
+ the cone mode, have been cleaned up.
+
+ * The diff-* plumbing family of subcommands now pay attention to the
+ diff.wsErrorHighlight configuration, which has been ignored before;
+ this allows "git add -p" to also show the whitespace problems to
+ the end user.
+
+ * Some codepaths were given a repository instance as a parameter to
+ work in the repository, but passed the_repository instance to its
+ callees, which has been cleaned up (somewhat).
+
+ * Memory footprint and performance of "git name-rev" has been
+ improved.
+
+ * The object reachability bitmap machinery and the partial cloning
+ machinery were not prepared to work well together, because some
+ object-filtering criteria that partial clones use inherently rely
+ on object traversal, but the bitmap machinery is an optimization
+ to bypass that object traversal. There however are some cases
+ where they can work together, and they were taught about them.
+
+ * "git rebase" has learned to use the merge backend (i.e. the
+ machinery that drives "rebase -i") by default, while allowing
+ "--apply" option to use the "apply" backend (e.g. the moral
+ equivalent of "format-patch piped to am"). The rebase.backend
+ configuration variable can be set to customize.
+
+ * Underlying machinery of "git bisect--helper" is being refactored
+ into pieces that are more easily reused.
+
+
+Fixes since v2.25
+-----------------
+
+ * "git commit" gives output similar to "git status" when there is
+ nothing to commit, but without honoring the advise.statusHints
+ configuration variable, which has been corrected.
+
+ * has_object_file() said "no" given an object registered to the
+ system via pretend_object_file(), making it inconsistent with
+ read_object_file(), causing lazy fetch to attempt fetching an
+ empty tree from promisor remotes.
+
+ * Complete an update to tutorial that encourages "git switch" over
+ "git checkout" that was done only half-way.
+
+ * C pedantry ;-) fix.
+
+ * The code that tries to skip over the entries for the paths in a
+ single directory using the cache-tree was not careful enough
+ against corrupt index file.
+
+ * Reduce unnecessary round-trip when running "ls-remote" over the
+ stateless RPC mechanism.
+
+ * "git restore --staged" did not correctly update the cache-tree
+ structure, resulting in bogus trees to be written afterwards, which
+ has been corrected.
+
+ * The code recently added to move to the entry beyond the ones in the
+ same directory in the index in the sparse-cone mode did not count
+ the number of entries to skip over incorrectly, which has been
+ corrected.
+
+ * Rendering by "git log --graph" of ancestry lines leading to a merge
+ commit were made suboptimal to waste vertical space a bit with a
+ recent update, which has been corrected.
+
+ * Work around test breakages caused by custom regex engine used in
+ libasan, when address sanitizer is used with more recent versions
+ of gcc and clang.
+
+ * Minor bugfixes to "git add -i" that has recently been rewritten in C.
+
+ * "git fetch --refmap=" option has got a better documentation.
+
+ * "git checkout X" did not correctly fail when X is not a local
+ branch but could name more than one remote-tracking branches
+ (i.e. to be dwimmed as the starting point to create a corresponding
+ local branch), which has been corrected.
+ (merge fa74180d08 am/checkout-file-and-ref-ref-ambiguity later to maint).
+
+ * Corner case bugs in "git clean" that stems from a (necessarily for
+ performance reasons) awkward calling convention in the directory
+ enumeration API has been corrected.
+
+ * A fetch that is told to recursively fetch updates in submodules
+ inevitably produces reams of output, and it becomes hard to spot
+ error messages. The command has been taught to enumerate
+ submodules that had errors at the end of the operation.
+ (merge 0222540827 es/fetch-show-failed-submodules-atend later to maint).
+
+ * The "--recurse-submodules" option of various subcommands did not
+ work well when run in an alternate worktree, which has been
+ corrected.
+
+ * Futureproofing a test not to depend on the current implementation
+ detail.
+
+ * Running "git rm" on a submodule failed unnecessarily when
+ .gitmodules is only cache-dirty, which has been corrected.
+
+ * C pedantry ;-) fix.
+
+ * "git grep --no-index" should not get affected by the contents of
+ the .gitmodules file but when "--recurse-submodules" is given or
+ the "submodule.recurse" variable is set, it did. Now these
+ settings are ignored in the "--no-index" mode.
+
+ * Technical details of the bundle format has been documented.
+
+ * Unhelpful warning messages during documentation build have been squelched.
+
+ * "git rebase -i" identifies existing commits in its todo file with
+ their abbreviated object name, which could become ambiguous as it
+ goes to create new commits, and has a mechanism to avoid ambiguity
+ in the main part of its execution. A few other cases however were
+ not covered by the protection against ambiguity, which has been
+ corrected.
+
+ * Allow the rebase.missingCommitsCheck configuration to kick in when
+ "rebase --edit-todo" and "rebase --continue" restarts the procedure.
+ (merge 5a5445d878 ag/edit-todo-drop-check later to maint).
+
+ * The way "git submodule status" reports an initialized but not yet
+ populated submodule has not been reimplemented correctly when a
+ part of the "git submodule" command was rewritten in C, which has
+ been corrected.
+ (merge f38c92452d pk/status-of-uncloned-submodule later to maint).
+
+ * The code to automatically shrink the fan-out in the notes tree had
+ an off-by-one bug, which has been killed.
+
+ * The index-pack code now diagnoses a bad input packstream that
+ records the same object twice when it is used as delta base; the
+ code used to declare a software bug when encountering such an
+ input, but it is an input error.
+
+
+ * The code to compute the commit-graph has been taught to use a more
+ robust way to tell if two object directories refer to the same
+ thing.
+ (merge a7df60cac8 tb/commit-graph-object-dir later to maint).
+
+ * "git remote rename X Y" needs to adjust configuration variables
+ (e.g. branch.<name>.remote) whose value used to be X to Y.
+ branch.<name>.pushRemote is now also updated.
+
+ * Update to doc-diff.
+
+ * Doc markup fix.
+
+ * "git check-ignore" did not work when the given path is explicitly
+ marked as not ignored with a negative entry in the .gitignore file.
+
+ * The merge-recursive machinery failed to refresh the cache entry for
+ a merge result in a couple of places, resulting in an unnecessary
+ merge failure, which has been fixed.
+
+ * Fix for a bug revealed by a recent change to make the protocol v2
+ the default.
+
+ * In rare cases "git worktree add <path>" could think that <path>
+ was already a registered worktree even when it wasn't and refuse
+ to add the new worktree. This has been corrected.
+ (merge bb69b3b009 es/worktree-avoid-duplication-fix later to maint).
+
+ * "git push" should stop from updating a branch that is checked out
+ when receive.denyCurrentBranch configuration is set, but it failed
+ to pay attention to checkouts in secondary worktrees. This has
+ been corrected.
+ (merge 4d864895a2 hv/receive-denycurrent-everywhere later to maint).
+
+ * "git rebase BASE BRANCH" rebased/updated the tip of BRANCH and
+ checked it out, even when the BRANCH is checked out in a different
+ worktree. This has been corrected.
+ (merge b5cabb4a96 es/do-not-let-rebase-switch-to-protected-branch later to maint).
+
+ * "git describe" in a repository with multiple root commits sometimes
+ gave up looking for the best tag to describe a given commit with
+ too early, which has been adjusted.
+
+ * "git merge signed-tag" while lacking the public key started to say
+ "No signature", which was utterly wrong. This regression has been
+ reverted.
+
+ * MinGW's poll() emulation has been improved.
+
+ * "git show" and others gave an object name in raw format in its
+ error output, which has been corrected to give it in hex.
+
+ * "git fetch" over HTTP walker protocol did not show any progress
+ output. We inherently do not know how much work remains, but still
+ we can show something not to bore users.
+ (merge 7655b4119d rs/show-progress-in-dumb-http-fetch later to maint).
+
+ * Both "git ls-remote -h" and "git grep -h" give short usage help,
+ like any other Git subcommand, but it is not unreasonable to expect
+ that the former would behave the same as "git ls-remote --head"
+ (there is no other sensible behaviour for the latter). The
+ documentation has been updated in an attempt to clarify this.
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge d0d0a357a1 am/update-pathspec-f-f-tests later to maint).
+ (merge f94f7bd00d am/test-pathspec-f-f-error-cases later to maint).
+ (merge c513a958b6 ss/t6025-modernize later to maint).
+ (merge b441717256 dl/test-must-fail-fixes later to maint).
+ (merge d031049da3 mt/sparse-checkout-doc-update later to maint).
+ (merge 145136a95a jc/skip-prefix later to maint).
+ (merge 5290d45134 jk/alloc-cleanups later to maint).
+ (merge 7a9f8ca805 rs/parse-options-concat-dup later to maint).
+ (merge 517b60564e rs/strbuf-insertstr later to maint).
+ (merge f696a2b1c8 jk/mailinfo-cleanup later to maint).
+ (merge de26f02db1 js/test-avoid-pipe later to maint).
+ (merge a2dc43414c es/doc-mentoring later to maint).
+ (merge 02bbbe9df9 es/worktree-cleanup later to maint).
+ (merge 2ce6d075fa rs/micro-cleanups later to maint).
+ (merge 27f182b3fc rs/blame-typefix-for-fingerprint later to maint).
+ (merge 3c29e21eb0 ma/test-cleanup later to maint).
+ (merge 240fc04f81 ag/rebase-remove-redundant-code later to maint).
+ (merge d68ce906c7 rs/commit-graph-code-simplification later to maint).
+ (merge a51d9e8f07 rj/t1050-use-test-path-is-file later to maint).
+ (merge fd0bc17557 kk/complete-diff-color-moved later to maint).
+ (merge 65bf820d0e en/test-cleanup later to maint).
diff --git a/Documentation/RelNotes/2.26.1.txt b/Documentation/RelNotes/2.26.1.txt
new file mode 100644
index 0000000..1b4ecb3
--- /dev/null
+++ b/Documentation/RelNotes/2.26.1.txt
@@ -0,0 +1,5 @@
+Git v2.26.1 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.4; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.26.2.txt b/Documentation/RelNotes/2.26.2.txt
new file mode 100644
index 0000000..d434d0c
--- /dev/null
+++ b/Documentation/RelNotes/2.26.2.txt
@@ -0,0 +1,5 @@
+Git v2.26.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.27.0.txt b/Documentation/RelNotes/2.27.0.txt
new file mode 100644
index 0000000..7a5c7ff
--- /dev/null
+++ b/Documentation/RelNotes/2.27.0.txt
@@ -0,0 +1,94 @@
+Git 2.27 Release Notes
+======================
+
+Updates since v2.26
+-------------------
+
+Backward compatibility notes
+
+ * When "git describe C" finds that commit C is pointed by a signed or
+ annotated tag, which records T as its tagname in the object, the
+ command gives T as its answer. Even if the user renames or moves
+ such a tag from its natural location in the "refs/tags/" hierarchy,
+ "git describe C" would still give T as the answer, but in such a
+ case "git show T^0" would no longer work as expected. There may be
+ nothing at "refs/tags/T" or even worse there may be a different tag
+ instead.
+
+ Starting from this version, "git describe" will always use the
+ "long" version, as if the "--long" option were given, when giving
+ its output based on such a misplaced tag to work around the problem.
+
+ * "git pull" issues a warning message until the pull.rebase
+ configuration variable is explicitly given, which some existing
+ users may find annoying---those who prefer not to rebase need to
+ set the variable to false to squelch the warning.
+
+
+UI, Workflows & Features
+
+ * A handful of options to configure SSL when talking to proxies have
+ been added.
+
+ * Smudge/clean conversion filters are now given more information
+ (e.g. the object of the tree-ish in which the blob being converted
+ appears, in addition to its path, which has already been given).
+
+ * When "git describe C" finds an annotated tag with tagname A to be
+ the best name to explain commit C, and the tag is stored in a
+ "wrong" place in the refs/tags hierarchy, e.g. refs/tags/B, the
+ command gave a warning message but used A (not B) to describe C.
+ If C is exactly at the tag, the describe output would be "A", but
+ "git rev-parse A^0" would not be equal as "git rev-parse C^0". The
+ behavior of the command has been changed to use the "long" form
+ i.e. A-0-gOBJECTNAME, which is correctly interpreted by rev-parse.
+
+ * "git pull" learned to warn when no pull.rebase configuration
+ exists, and neither --[no-]rebase nor --ff-only is given (which
+ would result a merge).
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The advise API has been revamped to allow more systematic enumeration of
+ advice knobs in the future.
+
+ * SHA-256 transition continues.
+
+ * The code to interface with GnuPG has been refactored.
+
+ * "git stash" has kept an escape hatch to use the scripted version
+ for a few releases, which got stale. It has been removed.
+
+
+Fixes since v2.26
+-----------------
+
+ * The real_path() convenience function can easily be misused; with a
+ bit of code refactoring in the callers' side, its use has been
+ eliminated.
+ (merge 49d3c4b481 am/real-path-fix later to maint).
+
+ * Update "git p4" to work with Python 3.
+ (merge 6bb40ed20a yz/p4-py3 later to maint).
+
+ * The mechanism to prevent "git commit" from making an empty commit
+ or amending during an interrupted cherry-pick was broken during the
+ rewrite of "git rebase" in C, which has been corrected.
+ (merge 430b75f720 pw/advise-rebase-skip later to maint).
+
+ * Fix "git checkout --recurse-submodules" of a nested submodule
+ hierarchy.
+ (merge 846f34d351 pb/recurse-submodules-fix later to maint).
+
+ * The "--fork-point" mode of "git rebase" regressed when the command
+ was rewritten in C back in 2.20 era, which has been corrected.
+ (merge f08132f889 at/rebase-fork-point-regression-fix 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).
+ (merge 9c688735f6 rs/doc-passthru-fetch-options later to maint).
+ (merge 757c2ba3e2 en/oidset-uninclude-hashmap later to maint).
+ (merge 8312aa7d74 jc/config-tar later to maint).
+ (merge d00a5bdd50 ss/submodule-foreach-cb later to maint).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 83e7bba..2450589 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -263,7 +263,9 @@ color::
+
The basic colors accepted are `normal`, `black`, `red`, `green`, `yellow`,
`blue`, `magenta`, `cyan` and `white`. The first color given is the
-foreground; the second is the background.
+foreground; the second is the background. All the basic colors except
+`normal` have a bright variant that can be speficied by prefixing the
+color with `bright`, like `brightred`.
+
Colors may also be given as numbers between 0 and 255; these use ANSI
256-color mode (but note that not all terminals may support this). If
@@ -445,6 +447,8 @@ include::config/submodule.txt[]
include::config/tag.txt[]
+include::config/tar.txt[]
+
include::config/trace2.txt[]
include::config/transfer.txt[]
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 4be93f8..bdd37c3 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -110,4 +110,10 @@ advice.*::
submoduleAlternateErrorStrategyDie::
Advice shown when a submodule.alternateErrorStrategy option
configured to "die" causes a fatal error.
+ addIgnoredFile::
+ Advice shown if a user attempts to add an ignored file to
+ the index.
+ addEmptyPathspec::
+ Advice shown if a user runs the add command without providing
+ the pathspec parameter.
--
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index a592d52..cc5f324 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -81,15 +81,16 @@ branch.<name>.rebase::
"git pull" is run. See "pull.rebase" for doing this in a non
branch-specific manner.
+
-When `merges`, pass the `--rebase-merges` option to 'git rebase'
+When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
so that the local merge commits are included in the rebase (see
linkgit:git-rebase[1] for details).
+
-When `preserve` (deprecated in favor of `merges`), also pass
+When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
`--preserve-merges` along to 'git rebase' so that locally committed merge
commits will not be flattened by running 'git pull'.
+
-When the value is `interactive`, the rebase is run in interactive mode.
+When the value is `interactive` (or just 'i'), the rebase is run in interactive
+mode.
+
*NOTE*: this is a possibly dangerous operation; do *not* use
it unless you understand the implications (see linkgit:git-rebase[1]
diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index 9e440b1..74619a9 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -68,6 +68,17 @@ core.fsmonitor::
avoiding unnecessary processing of files that have not changed.
See the "fsmonitor-watchman" section of linkgit:githooks[5].
+core.fsmonitorHookVersion::
+ Sets the version of hook that is to be used when calling fsmonitor.
+ There are currently versions 1 and 2. When this is not set,
+ version 2 will be tried first and if it fails then version 1
+ will be tried. Version 1 uses a timestamp as input to determine
+ which files have changes since that time but some monitors
+ like watchman have race conditions when used with a timestamp.
+ Version 2 uses an opaque string so that the monitor can return
+ something that can be used to determine what files have changed
+ without race conditions.
+
core.trustctime::
If false, the ctime differences between the index and the
working tree are ignored; useful when the inode change time
diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt
index 875f8c8..4e3a5c0 100644
--- a/Documentation/config/feature.txt
+++ b/Documentation/config/feature.txt
@@ -12,9 +12,6 @@ feature.experimental::
setting if you are interested in providing feedback on experimental
features. The new default values are:
+
-* `pack.useSparse=true` uses a new algorithm when constructing a pack-file
-which can improve `git push` performance in repos with many files.
-+
* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
skipping more commits at a time, reducing the number of round trips.
+
diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt
index cce2c89..d94025c 100644
--- a/Documentation/config/gpg.txt
+++ b/Documentation/config/gpg.txt
@@ -18,3 +18,18 @@ gpg.<format>.program::
chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
be used as a legacy synonym for `gpg.openpgp.program`. The default
value for `gpg.x509.program` is "gpgsm".
+
+gpg.minTrustLevel::
+ Specifies a minimum trust level for signature verification. If
+ this option is unset, then signature verification for merge
+ operations require a key with at least `marginal` trust. Other
+ operations that perform signature verification require a key
+ with at least `undefined` trust. Setting this option overrides
+ the required trust-level for all operations. Supported values,
+ in increasing order of significance:
++
+* `undefined`
+* `never`
+* `marginal`
+* `fully`
+* `ultimate`
diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt
index 5a32f5b..3968fbb 100644
--- a/Documentation/config/http.txt
+++ b/Documentation/config/http.txt
@@ -29,6 +29,27 @@ http.proxyAuthMethod::
* `ntlm` - NTLM authentication (compare the --ntlm option of `curl(1)`)
--
+http.proxySSLCert::
+ The pathname of a file that stores a client certificate to use to authenticate
+ with an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_CERT` environment
+ variable.
+
+http.proxySSLKey::
+ The pathname of a file that stores a private key to use to authenticate with
+ an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_KEY` environment
+ variable.
+
+http.proxySSLCertPasswordProtected::
+ Enable Git's password prompt for the proxy SSL certificate. Otherwise OpenSSL
+ will prompt the user, possibly many times, if the certificate or private key
+ is encrypted. Can be overriden by the `GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED`
+ environment variable.
+
+http.proxySSLCAInfo::
+ Pathname to the file containing the certificate bundle that should be used to
+ verify the proxy with when using an HTTPS proxy. Can be overriden by the
+ `GIT_PROXY_SSL_CAINFO` environment variable.
+
http.emptyAuth::
Attempt authentication without seeking a username or password. This
can be used to attempt GSS-Negotiate authentication without specifying
@@ -71,7 +92,7 @@ http.saveCookies::
http.version::
Use the specified HTTP protocol version when communicating with a server.
If you want to force the default. The available and default version depend
- on libcurl. Actually the possible values of
+ on libcurl. Currently the possible values of
this option are:
- HTTP/2
@@ -84,7 +105,7 @@ http.sslVersion::
particular configuration of the crypto library in use. Internally
this sets the 'CURLOPT_SSL_VERSION' option; see the libcurl
documentation for more details on the format of this option and
- for the ssl version supported. Actually the possible values of
+ for the ssl version supported. Currently the possible values of
this option are:
- sslv2
@@ -199,6 +220,14 @@ http.postBuffer::
Transfer-Encoding: chunked is used to avoid creating a
massive pack file locally. Default is 1 MiB, which is
sufficient for most requests.
++
+Note that raising this limit is only effective for disabling chunked
+transfer encoding and therefore should be used only where the remote
+server or a proxy only supports HTTP/1.0 or is noncompliant with the
+HTTP standard. Raising this is not, in general, an effective solution
+for most push problems, but can increase memory consumption
+significantly since the entire buffer is allocated even for small
+pushes.
http.lowSpeedLimit, http.lowSpeedTime::
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index 1d66f0c..837f1b1 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -27,6 +27,13 @@ Note that changing the compression level will not automatically recompress
all existing objects. You can force recompression by passing the -F option
to linkgit:git-repack[1].
+pack.allowPackReuse::
+ When true, and when reachability bitmaps are enabled,
+ pack-objects will try to send parts of the bitmapped packfile
+ verbatim. This can reduce memory and CPU usage to serve fetches,
+ but might result in sending a slightly larger pack. Defaults to
+ true.
+
pack.island::
An extended regular expression configuring a set of delta
islands. See "DELTA ISLANDS" in linkgit:git-pack-objects[1]
@@ -112,8 +119,8 @@ pack.useSparse::
objects. This can have significant performance benefits when
computing a pack to send a small change. However, it is possible
that extra objects are added to the pack-file if the included
- commits contain certain types of direct renames. Default is `false`
- unless `feature.experimental` is enabled.
+ commits contain certain types of direct renames. Default is
+ `true`.
pack.writeBitmaps (deprecated)::
This is a deprecated synonym for `repack.writeBitmaps`.
diff --git a/Documentation/config/protocol.txt b/Documentation/config/protocol.txt
index bfccc07..756591d 100644
--- a/Documentation/config/protocol.txt
+++ b/Documentation/config/protocol.txt
@@ -45,11 +45,10 @@ The protocol names currently used by git are:
--
protocol.version::
- Experimental. If set, clients will attempt to communicate with a
- server using the specified protocol version. If unset, no
- attempt will be made by the client to communicate using a
- particular protocol version, this results in protocol version 0
- being used.
+ If set, clients will attempt to communicate with a server
+ using the specified protocol version. If the server does
+ not support it, communication falls back to version 0.
+ If unset, the default is `2`.
Supported versions:
+
--
diff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt
index b87cab3..5404830 100644
--- a/Documentation/config/pull.txt
+++ b/Documentation/config/pull.txt
@@ -14,15 +14,16 @@ pull.rebase::
pull" is run. See "branch.<name>.rebase" for setting this on a
per-branch basis.
+
-When `merges`, pass the `--rebase-merges` option to 'git rebase'
+When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
so that the local merge commits are included in the rebase (see
linkgit:git-rebase[1] for details).
+
-When `preserve` (deprecated in favor of `merges`), also pass
+When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
`--preserve-merges` along to 'git rebase' so that locally committed merge
commits will not be flattened by running 'git pull'.
+
-When the value is `interactive`, the rebase is run in interactive mode.
+When the value is `interactive` (or just 'i'), the rebase is run in interactive
+mode.
+
*NOTE*: this is a possibly dangerous operation; do *not* use
it unless you understand the implications (see linkgit:git-rebase[1]
diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt
index 0a0e000..0a7aa32 100644
--- a/Documentation/config/push.txt
+++ b/Documentation/config/push.txt
@@ -1,6 +1,7 @@
push.default::
Defines the action `git push` should take if no refspec is
- explicitly given. Different values are well-suited for
+ given (whether from the command-line, config, or elsewhere).
+ Different values are well-suited for
specific workflows; for instance, in a purely central workflow
(i.e. the fetch source is equal to the push destination),
`upstream` is probably what you want. Possible values are:
@@ -8,7 +9,7 @@ push.default::
--
* `nothing` - do not push anything (error out) unless a refspec is
- explicitly given. This is primarily meant for people who want to
+ given. This is primarily meant for people who want to
avoid mistakes by always being explicit.
* `current` - push the current branch to update a branch with the same
@@ -79,7 +80,7 @@ higher priority configuration file (e.g. `.git/config` in a
repository) to clear the values inherited from a lower priority
configuration files (e.g. `$HOME/.gitconfig`).
+
---
+----
Example:
@@ -96,7 +97,7 @@ repo/.git/config
This will result in only b (a and c are cleared).
---
+----
push.recurseSubmodules::
Make sure all submodule commits used by the revisions to be pushed
diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d98e32d..7f7a07d 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -5,6 +5,12 @@ rebase.useBuiltin::
is always used. Setting this will emit a warning, to alert any
remaining users that setting this now does nothing.
+rebase.backend::
+ Default backend to use for rebasing. Possible choices are
+ 'apply' or 'merge'. In the future, if the merge backend gains
+ all remaining capabilities of the apply backend, this setting
+ may become unused.
+
rebase.stat::
Whether to show a diffstat of what changed upstream since the last
rebase. False by default.
diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt
index abc7ef4..00eb354 100644
--- a/Documentation/config/stash.txt
+++ b/Documentation/config/stash.txt
@@ -1,17 +1,9 @@
stash.useBuiltin::
- Set to `false` to use the legacy shell script implementation of
- linkgit:git-stash[1]. Is `true` by default, which means use
- the built-in rewrite of it in C.
-+
-The C rewrite is first included with Git version 2.22 (and Git for Windows
-version 2.19). This option serves as an escape hatch to re-enable the
-legacy version in case any bugs are found in the rewrite. This option and
-the shell script version of linkgit:git-stash[1] will be removed in some
-future release.
-+
-If you find some reason to set this option to `false`, other than
-one-off testing, you should report the behavior difference as a bug in
-Git (see https://git-scm.com/community for details).
+ Unused configuration variable. Used in Git versions 2.22 to
+ 2.26 as an escape hatch to enable the legacy shellscript
+ implementation of stash. Now the built-in rewrite of it in C
+ is always used. Setting this will emit a warning, to alert any
+ remaining users that setting this now does nothing.
stash.showPatch::
If this is set to true, the `git stash show` command without an
diff --git a/Documentation/config/tag.txt b/Documentation/config/tag.txt
index 6d9110d..5062a05 100644
--- a/Documentation/config/tag.txt
+++ b/Documentation/config/tag.txt
@@ -15,10 +15,3 @@ tag.gpgSign::
convenient to use an agent to avoid typing your gpg passphrase
several times. Note that this option doesn't affect tag signing
behavior enabled by "-u <keyid>" or "--local-user=<keyid>" options.
-
-tar.umask::
- This variable can be used to restrict the permission bits of
- tar archive entries. The default is 0002, which turns off the
- world write bit. The special value "user" indicates that the
- archiving user's umask will be used instead. See umask(2) and
- linkgit:git-archive[1].
diff --git a/Documentation/config/tar.txt b/Documentation/config/tar.txt
new file mode 100644
index 0000000..de8ff48
--- /dev/null
+++ b/Documentation/config/tar.txt
@@ -0,0 +1,6 @@
+tar.umask::
+ This variable can be used to restrict the permission bits of
+ tar archive entries. The default is 0002, which turns off the
+ world write bit. The special value "user" indicates that the
+ archiving user's umask will be used instead. See umask(2) and
+ linkgit:git-archive[1].
diff --git a/Documentation/config/user.txt b/Documentation/config/user.txt
index 0557cbb..59aec7c 100644
--- a/Documentation/config/user.txt
+++ b/Documentation/config/user.txt
@@ -13,7 +13,12 @@ committer.email::
Also, all of these can be overridden by the `GIT_AUTHOR_NAME`,
`GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`,
`GIT_COMMITTER_EMAIL` and `EMAIL` environment variables.
- See linkgit:git-commit-tree[1] for more information.
++
+Note that the `name` forms of these variables conventionally refer to
+some form of a personal name. See linkgit:git-commit[1] and the
+environment variables section of linkgit:git[1] for more information on
+these settings and the `credential.username` option if you're looking
+for authentication credentials instead.
user.useConfigOnly::
Instruct Git to avoid trying to guess defaults for `user.email`
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 09faee3..bb31f0c 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -567,13 +567,13 @@ To illustrate the difference between `-S<regex> --pickaxe-regex` and
file:
+
----
-+ return !regexec(regexp, two->ptr, 1, &regmatch, 0);
++ return frotz(nitfol, two->ptr, 1, 0);
...
-- hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);
+- hit = frotz(nitfol, mf2.ptr, 1, 0);
----
+
-While `git log -G"regexec\(regexp"` will show this commit, `git log
--S"regexec\(regexp" --pickaxe-regex` will not (because the number of
+While `git log -G"frotz\(nitfol"` will show this commit, `git log
+-S"frotz\(nitfol" --pickaxe-regex` will not (because the number of
occurrences of that string did not change).
+
Unless `--text` is supplied patches of binary files without a textconv
diff --git a/Documentation/doc-diff b/Documentation/doc-diff
index 88a9b20..1694300 100755
--- a/Documentation/doc-diff
+++ b/Documentation/doc-diff
@@ -127,7 +127,7 @@ generate_render_makefile () {
while read src
do
dst=$2/${src#$1/}
- printf 'all:: %s\n' "$dst"
+ printf 'all: %s\n' "$dst"
printf '%s: %s\n' "$dst" "$src"
printf '\t@echo >&2 " RENDER $(notdir $@)" && \\\n'
printf '\tmkdir -p $(dir $@) && \\\n'
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index a2f7862..00d03ec 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -61,10 +61,8 @@ this option multiple times, one for each matching ref name.
See also the `fetch.negotiationAlgorithm` configuration variable
documented in linkgit:git-config[1].
-ifndef::git-pull[]
--dry-run::
Show what would be done, without making any changes.
-endif::git-pull[]
-f::
--force::
@@ -95,6 +93,7 @@ ifndef::git-pull[]
--[no-]write-commit-graph::
Write a commit-graph after fetching. This overrides the config
setting `fetch.writeCommitGraph`.
+endif::git-pull[]
-p::
--prune::
@@ -107,6 +106,7 @@ ifndef::git-pull[]
was cloned with the --mirror option), then they are also
subject to pruning. Supplying `--prune-tags` is a shorthand for
providing the tag refspec.
+ifndef::git-pull[]
+
See the PRUNING section below for more details.
@@ -133,13 +133,15 @@ endif::git-pull[]
behavior for a remote may be specified with the remote.<name>.tagOpt
setting. See linkgit:git-config[1].
-ifndef::git-pull[]
--refmap=<refspec>::
When fetching refs listed on the command line, use the
specified refspec (can be given more than once) to map the
refs to remote-tracking branches, instead of the values of
`remote.*.fetch` configuration variables for the remote
- repository. See section on "Configured Remote-tracking
+ repository. Providing an empty `<refspec>` to the
+ `--refmap` option causes Git to ignore the configured
+ refspecs and rely entirely on the refspecs supplied as
+ command-line arguments. See section on "Configured Remote-tracking
Branches" for details.
-t::
@@ -151,6 +153,7 @@ ifndef::git-pull[]
is used (though tags may be pruned anyway if they are also the
destination of an explicit refspec; see `--prune`).
+ifndef::git-pull[]
--recurse-submodules[=yes|on-demand|no]::
This option controls if and under what conditions new commits of
populated submodules should be fetched too. It can be used as a
@@ -161,6 +164,7 @@ ifndef::git-pull[]
when the superproject retrieves a commit that updates the submodule's
reference to a commit that isn't already in the local submodule
clone.
+endif::git-pull[]
-j::
--jobs=<n>::
@@ -174,9 +178,11 @@ parallel. To control them independently, use the config settings
Typically, parallel recursive and multi-remote fetches will be faster. By
default fetches are performed sequentially, not in parallel.
+ifndef::git-pull[]
--no-recurse-submodules::
Disable recursive fetching of submodules (this has the same effect as
using the `--recurse-submodules=no` option).
+endif::git-pull[]
--set-upstream::
If the remote is fetched successfully, pull and add upstream
@@ -185,6 +191,7 @@ default fetches are performed sequentially, not in parallel.
see `branch.<name>.merge` and `branch.<name>.remote` in
linkgit:git-config[1].
+ifndef::git-pull[]
--submodule-prefix=<path>::
Prepend <path> to paths printed in informative messages
such as "Fetching submodule foo". This option is used
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 11ca61b..ab5754e 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,7 +16,7 @@ SYNOPSIS
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
[--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
[(<mbox> | <Maildir>)...]
-'git am' (--continue | --skip | --abort | --quit | --show-current-patch)
+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
DESCRIPTION
-----------
@@ -176,9 +176,11 @@ default. You can use `--no-utf8` to override this.
Abort the patching operation but keep HEAD and the index
untouched.
---show-current-patch::
- Show the entire e-mail message "git am" has stopped at, because
- of conflicts.
+--show-current-patch[=(diff|raw)]::
+ Show the message at which `git am` has stopped due to
+ conflicts. If `raw` is specified, show the raw contents of
+ the e-mail message; if `diff`, show the diff portion only.
+ Defaults to `raw`.
DISCUSSION
----------
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
index 8b2d49c..0c3924a 100644
--- a/Documentation/git-check-ignore.txt
+++ b/Documentation/git-check-ignore.txt
@@ -30,9 +30,15 @@ OPTIONS
valid with a single pathname.
-v, --verbose::
- Also output details about the matching pattern (if any)
- for each given pathname. For precedence rules within and
- between exclude sources, see linkgit:gitignore[5].
+ Instead of printing the paths that are excluded, for each path
+ that matches an exclude pattern, print the exclude pattern
+ together with the path. (Matching an exclude pattern usually
+ means the path is excluded, but if the pattern begins with '!'
+ then it is a negated pattern and matching it means the path is
+ NOT excluded.)
++
+For precedence rules within and between exclude sources, see
+linkgit:gitignore[5].
--stdin::
Read pathnames from the standard input, one per line,
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index bf24f18..08d6045 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -15,7 +15,8 @@ SYNOPSIS
[--dissociate] [--separate-git-dir <git dir>]
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
- [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--] <repository>
+ [--[no-]remote-submodules] [--jobs <n>] [--sparse]
+ [--filter=<filter>] [--] <repository>
[<directory>]
DESCRIPTION
@@ -162,6 +163,16 @@ objects from the source repository into a pack in the cloned repository.
of the repository. The sparse-checkout file can be
modified to grow the working directory as needed.
+--filter=<filter-spec>::
+ Use the partial clone feature and request that the server sends
+ a subset of reachable objects according to a given object filter.
+ When using `--filter`, the supplied `<filter-spec>` is used for
+ the partial clone filter. For example, `--filter=blob:none` will
+ filter out all blobs (file contents) until needed by Git. Also,
+ `--filter=blob:limit=<size>` will filter out all blobs of size
+ at least `<size>`. For more details on filter specifications, see
+ the `--filter` option in linkgit:git-rev-list[1].
+
--mirror::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the
diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt
index bcd85c1..28d1fee 100644
--- a/Documentation/git-commit-graph.txt
+++ b/Documentation/git-commit-graph.txt
@@ -26,7 +26,10 @@ OPTIONS
file. This parameter exists to specify the location of an alternate
that only has the objects directory, not a full `.git` directory. The
commit-graph file is expected to be in the `<dir>/info` directory and
- the packfiles are expected to be in `<dir>/pack`.
+ the packfiles are expected to be in `<dir>/pack`. If the directory
+ could not be made into an absolute path, or does not match any known
+ object directory, `git commit-graph ...` will exit with non-zero
+ status.
--[no-]progress::
Turn progress on/off explicitly. If neither is specified, progress is
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
index 4b90b9c..ec15ee8 100644
--- a/Documentation/git-commit-tree.txt
+++ b/Documentation/git-commit-tree.txt
@@ -69,7 +69,6 @@ OPTIONS
Do not GPG-sign commit, to countermand a `--gpg-sign` option
given earlier on the command line.
-
Commit Information
------------------
@@ -79,26 +78,6 @@ A commit encapsulates:
- author name, email and date
- committer name and email and the commit time.
-While parent object ids are provided on the command line, author and
-committer information is taken from the following environment variables,
-if set:
-
- GIT_AUTHOR_NAME
- GIT_AUTHOR_EMAIL
- GIT_AUTHOR_DATE
- GIT_COMMITTER_NAME
- GIT_COMMITTER_EMAIL
- GIT_COMMITTER_DATE
-
-(nb "<", ">" and "\n"s are stripped)
-
-In case (some of) these environment variables are not set, the information
-is taken from the configuration items user.name and user.email, or, if not
-present, the environment variable EMAIL, or, if that is not set,
-system user name and the hostname used for outgoing mail (taken
-from `/etc/mailname` and falling back to the fully qualified hostname when
-that file does not exist).
-
A commit comment is read from stdin. If a changelog
entry is not provided via "<" redirection, 'git commit-tree' will just wait
for one to be entered and terminated with ^D.
@@ -117,6 +96,7 @@ FILES
SEE ALSO
--------
linkgit:git-write-tree[1]
+linkgit:git-commit[1]
GIT
---
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index ced5a9b..13f6539 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -367,9 +367,6 @@ changes to tracked files.
+
For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
-:git-commit: 1
-include::date-formats.txt[]
-
EXAMPLES
--------
When recording your own work, the contents of modified files in
@@ -463,6 +460,43 @@ alter the order the changes are committed, because the merge
should be recorded as a single commit. In fact, the command
refuses to run when given pathnames (but see `-i` option).
+COMMIT INFORMATION
+------------------
+
+Author and committer information is taken from the following environment
+variables, if set:
+
+ GIT_AUTHOR_NAME
+ GIT_AUTHOR_EMAIL
+ GIT_AUTHOR_DATE
+ GIT_COMMITTER_NAME
+ GIT_COMMITTER_EMAIL
+ GIT_COMMITTER_DATE
+
+(nb "<", ">" and "\n"s are stripped)
+
+The author and committer names are by convention some form of a personal name
+(that is, the name by which other humans refer to you), although Git does not
+enforce or require any particular form. Arbitrary Unicode may be used, subject
+to the constraints listed above. This name has no effect on authentication; for
+that, see the `credential.username` variable in linkgit:git-config[1].
+
+In case (some of) these environment variables are not set, the information
+is taken from the configuration items `user.name` and `user.email`, or, if not
+present, the environment variable EMAIL, or, if that is not set,
+system user name and the hostname used for outgoing mail (taken
+from `/etc/mailname` and falling back to the fully qualified hostname when
+that file does not exist).
+
+The `author.name` and `committer.name` and their corresponding email options
+override `user.name` and `user.email` if set and are overridden themselves by
+the environment variables.
+
+The typical usage is to set just the `user.name` and `user.email` variables;
+the other options are provided for more complex use cases.
+
+:git-commit: 1
+include::date-formats.txt[]
DISCUSSION
----------
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 899e92a..7573160 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -9,18 +9,18 @@ git-config - Get and set repository or global options
SYNOPSIS
--------
[verse]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] name [value [value_regex]]
+'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] name [value [value_regex]]
'git config' [<file-option>] [--type=<type>] --add name value
'git config' [<file-option>] [--type=<type>] --replace-all name value [value_regex]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get name [value_regex]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get-all name [value_regex]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
+'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get name [value_regex]
+'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get-all name [value_regex]
+'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch name URL
'git config' [<file-option>] --unset name [value_regex]
'git config' [<file-option>] --unset-all name [value_regex]
'git config' [<file-option>] --rename-section old_name new_name
'git config' [<file-option>] --remove-section name
-'git config' [<file-option>] [--show-origin] [-z|--null] [--name-only] -l | --list
+'git config' [<file-option>] [--show-origin] [--show-scope] [-z|--null] [--name-only] -l | --list
'git config' [<file-option>] --get-color name [default]
'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
'git config' [<file-option>] -e | --edit
@@ -222,6 +222,11 @@ Valid `<type>`'s include:
the actual origin (config file path, ref, or blob id if
applicable).
+--show-scope::
+ Similar to `--show-origin` in that it augments the output of
+ all queried config options with the scope of that value
+ (local, global, system, command).
+
--get-colorbool name [stdout-is-tty]::
Find the color setting for `name` (e.g. `color.diff`) and output
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 7889f95..77c6b3d 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -122,6 +122,26 @@ Locations of Marks Files
Relative and non-relative marks may be combined by interweaving
--(no-)-relative-marks with the --(import|export)-marks= options.
+Submodule Rewriting
+~~~~~~~~~~~~~~~~~~~
+
+--rewrite-submodules-from=<name>:<file>::
+--rewrite-submodules-to=<name>:<file>::
+ Rewrite the object IDs for the submodule specified by <name> from the values
+ used in the from <file> to those used in the to <file>. The from marks should
+ have been created by `git fast-export`, and the to marks should have been
+ created by `git fast-import` when importing that same submodule.
++
+<name> may be any arbitrary string not containing a colon character, but the
+same value must be used with both options when specifying corresponding marks.
+Multiple submodules may be specified with different values for <name>. It is an
+error not to use these options in corresponding pairs.
++
+These options are primarily useful when converting a repository from one hash
+algorithm to another; without them, fast-import will fail if it encounters a
+submodule because it has no way of writing the object ID into the new hash
+algorithm.
+
Performance and Compression Tuning
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index a530fef..40ba4aa 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -467,9 +467,9 @@ impossible for a backward-compatible implementation to ever be fast:
* In editing files, git-filter-branch by design checks out each and
every commit as it existed in the original repo. If your repo has
- 10\^5 files and 10\^5 commits, but each commit only modifies 5
- files, then git-filter-branch will make you do 10\^10 modifications,
- despite only having (at most) 5*10^5 unique blobs.
+ `10^5` files and `10^5` commits, but each commit only modifies five
+ files, then git-filter-branch will make you do `10^10` modifications,
+ despite only having (at most) `5*10^5` unique blobs.
* If you try and cheat and try to make git-filter-branch only work on
files modified in a commit, then two things happen
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index c89fb56..ddb6acc 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -59,8 +59,8 @@ grep.extendedRegexp::
other than 'default'.
grep.threads::
- Number of grep worker threads to use. If unset (or set to 0),
- 8 threads are used by default (for now).
+ Number of grep worker threads to use. If unset (or set to 0), Git will
+ use as many threads as the number of logical cores available.
grep.fullName::
If set to true, enable `--full-name` option by default.
@@ -96,7 +96,8 @@ OPTIONS
Recursively search in each submodule that has been initialized and
checked out in the repository. When used in combination with the
<tree> option the prefix of all submodule output will be the name of
- the parent project's <tree> object.
+ the parent project's <tree> object. This option has no effect
+ if `--no-index` is given.
-a::
--text::
@@ -347,6 +348,17 @@ EXAMPLES
`git grep solution -- :^Documentation`::
Looks for `solution`, excluding files in `Documentation`.
+NOTES ON THREADS
+----------------
+
+The `--threads` option (and the grep.threads configuration) will be ignored when
+`--open-files-in-pager` is used, forcing a single-threaded execution.
+
+When grepping the object store (with `--cached` or giving tree objects), running
+with multiple threads might perform slower than single threaded if `--textconv`
+is given and there're too many text conversions. So if you experience low
+performance in this case, it might be desirable to use `--threads=1`.
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
index 32880aa..adc6adf 100644
--- a/Documentation/git-init.txt
+++ b/Documentation/git-init.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
- [--separate-git-dir <git dir>]
+ [--separate-git-dir <git dir>] [--object-format=<format]
[--shared[=<permissions>]] [directory]
@@ -48,6 +48,11 @@ Only print error and warning messages; all other output will be suppressed.
Create a bare repository. If `GIT_DIR` environment is not set, it is set to the
current working directory.
+--object-format=<format>::
+
+Specify the given object format (hash algorithm) for the repository. The valid
+values are 'sha1' and (if enabled) 'sha256'. 'sha1' is the default.
+
--template=<template_directory>::
Specify the directory from which templates will be used. (See the "TEMPLATE
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index a2ea1fd..0a5c8b7 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -28,7 +28,9 @@ OPTIONS
Limit to only refs/heads and refs/tags, respectively.
These options are _not_ mutually exclusive; when given
both, references stored in refs/heads and refs/tags are
- displayed.
+ displayed. Note that `git ls-remote -h` used without
+ anything else on the command line gives help, consistent
+ with other git subcommands.
--refs::
Do not show peeled tags or pseudorefs like `HEAD` in the output.
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index 3494a1d..dab9609 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -374,14 +374,55 @@ These options can be used to modify 'git p4 submit' behavior.
been submitted. Implies --disable-rebase. Can also be set with
git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
-Hook for submit
-~~~~~~~~~~~~~~~
+Hooks for submit
+----------------
+
+p4-pre-submit
+~~~~~~~~~~~~~
+
The `p4-pre-submit` hook is executed if it exists and is executable.
The hook takes no parameters and nothing from standard input. Exiting with
non-zero status from this script prevents `git-p4 submit` from launching.
+It can be bypassed with the `--no-verify` command line option.
One usage scenario is to run unit tests in the hook.
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+p4-changelist
+~~~~~~~~~~~~~
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+
+
Rebase options
~~~~~~~~~~~~~~
These options can be used to modify 'git p4 rebase' behavior.
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index fecdf26..eaa2f2a 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -14,7 +14,7 @@ SYNOPSIS
[--local] [--incremental] [--window=<n>] [--depth=<n>]
[--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
[--stdout [--filter=<filter-spec>] | base-name]
- [--shallow] [--keep-true-parents] [--sparse] < object-list
+ [--shallow] [--keep-true-parents] [--[no-]sparse] < object-list
DESCRIPTION
@@ -196,14 +196,16 @@ depth is 4095.
Add --no-reuse-object if you want to force a uniform compression
level on all data no matter the source.
---sparse::
- Use the "sparse" algorithm to determine which objects to include in
+--[no-]sparse::
+ Toggle the "sparse" algorithm to determine which objects to include in
the pack, when combined with the "--revs" option. This algorithm
only walks trees that appear in paths that introduce new objects.
This can have significant performance benefits when computing
a pack to send a small change. However, it is possible that extra
objects are added to the pack-file if the included commits contain
- certain types of direct renames.
+ certain types of direct renames. If this option is not included,
+ it defaults to the value of `pack.useSparse`, which is true unless
+ otherwise specified.
--thin::
Create a "thin" pack by omitting the common objects between a
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 0c4f038..f7a6033 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -258,16 +258,45 @@ See also INCOMPATIBLE OPTIONS below.
original branch. The index and working tree are also left
unchanged as a result.
---keep-empty::
- Keep the commits that do not change anything from its
- parents in the result.
+--apply:
+ Use applying strategies to rebase (calling `git-am`
+ internally). This option may become a no-op in the future
+ once the merge backend handles everything the apply one does.
++
+See also INCOMPATIBLE OPTIONS below.
+
+--empty={drop,keep,ask}::
+ How to handle commits that are not empty to start and are not
+ clean cherry-picks of any upstream commit, but which become
+ empty after rebasing (because they contain a subset of already
+ upstream changes). With drop (the default), commits that
+ become empty are dropped. With keep, such commits are kept.
+ With ask (implied by --interactive), the rebase will halt when
+ an empty commit is applied allowing you to choose whether to
+ drop it, edit files more, or just commit the empty changes.
+ Other options, like --exec, will use the default of drop unless
+ -i/--interactive is explicitly specified.
++
+Note that commits which start empty are kept, and commits which are
+clean cherry-picks (as determined by `git log --cherry-mark ...`) are
+always dropped.
+
See also INCOMPATIBLE OPTIONS below.
+--keep-empty::
+ No-op. Rebasing commits that started empty (had no change
+ relative to their parent) used to fail and this option would
+ override that behavior, allowing commits with empty changes to
+ be rebased. Now commits with no changes do not cause rebasing
+ to halt.
++
+See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
+
--allow-empty-message::
- By default, rebasing commits with an empty message will fail.
- This option overrides that behavior, allowing commits with empty
- messages to be rebased.
+ No-op. Rebasing commits with an empty message used to fail
+ and this option would override that behavior, allowing commits
+ with empty messages to be rebased. Now commits with an empty
+ message do not cause rebasing to halt.
+
See also INCOMPATIBLE OPTIONS below.
@@ -286,7 +315,7 @@ See also INCOMPATIBLE OPTIONS below.
--merge::
Use merging strategies to rebase. When the recursive (default) merge
strategy is used, this allows rebase to be aware of renames on the
- upstream side.
+ upstream side. This is the default.
+
Note that a rebase merge works by replaying each commit from the working
branch on top of the <upstream> branch. Because of this, when a merge
@@ -356,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below.
Ensure at least <n> lines of surrounding context match before
and after each change. When fewer lines of surrounding
context exist they all must match. By default no context is
- ever ignored.
+ ever ignored. Implies --apply.
+
See also INCOMPATIBLE OPTIONS below.
@@ -394,8 +423,9 @@ with `--keep-base` in order to drop those commits from your branch.
--ignore-whitespace::
--whitespace=<option>::
- These flag are passed to the 'git apply' program
+ These flags are passed to the 'git apply' program
(see linkgit:git-apply[1]) that applies the patch.
+ Implies --apply.
+
See also INCOMPATIBLE OPTIONS below.
@@ -539,10 +569,11 @@ INCOMPATIBLE OPTIONS
The following options:
+ * --apply
* --committer-date-is-author-date
* --ignore-date
- * --whitespace
* --ignore-whitespace
+ * --whitespace
* -C
are incompatible with the following options:
@@ -557,6 +588,7 @@ are incompatible with the following options:
* --interactive
* --exec
* --keep-empty
+ * --empty=
* --edit-todo
* --root when used in combination with --onto
@@ -565,33 +597,137 @@ In addition, the following pairs of options are incompatible:
* --preserve-merges and --interactive
* --preserve-merges and --signoff
* --preserve-merges and --rebase-merges
+ * --preserve-merges and --empty=
* --keep-base and --onto
* --keep-base and --root
BEHAVIORAL DIFFERENCES
-----------------------
-There are some subtle differences how the backends behave.
+git rebase has two primary backends: apply and merge. (The apply
+backend used to known as the 'am' backend, but the name led to
+confusion as it looks like a verb instead of a noun. Also, the merge
+backend used to be known as the interactive backend, but it is now
+used for non-interactive cases as well. Both were renamed based on
+lower-level functionality that underpinned each.) There are some
+subtle differences in how these two backends behave:
Empty commits
~~~~~~~~~~~~~
-The am backend drops any "empty" commits, regardless of whether the
-commit started empty (had no changes relative to its parent to
-start with) or ended empty (all changes were already applied
-upstream in other commits).
+The apply backend unfortunately drops intentionally empty commits, i.e.
+commits that started empty, though these are rare in practice. It
+also drops commits that become empty and has no option for controlling
+this behavior.
-The interactive backend drops commits by default that
-started empty and halts if it hits a commit that ended up empty.
-The `--keep-empty` option exists for the interactive backend to allow
-it to keep commits that started empty.
+The merge backend keeps intentionally empty commits. Similar to the
+apply backend, by default the merge backend drops commits that become
+empty unless -i/--interactive is specified (in which case it stops and
+asks the user what to do). The merge backend also has an
+--empty={drop,keep,ask} option for changing the behavior of handling
+commits that become empty.
Directory rename detection
~~~~~~~~~~~~~~~~~~~~~~~~~~
-Directory rename heuristics are enabled in the merge and interactive
-backends. Due to the lack of accurate tree information, directory
-rename detection is disabled in the am backend.
+Due to the lack of accurate tree information (arising from
+constructing fake ancestors with the limited information available in
+patches), directory rename detection is disabled in the apply backend.
+Disabled directory rename detection means that if one side of history
+renames a directory and the other adds new files to the old directory,
+then the new files will be left behind in the old directory without
+any warning at the time of rebasing that you may want to move these
+files into the new directory.
+
+Directory rename detection works with the merge backend to provide you
+warnings in such cases.
+
+Context
+~~~~~~~
+
+The apply backend works by creating a sequence of patches (by calling
+`format-patch` internally), and then applying the patches in sequence
+(calling `am` internally). Patches are composed of multiple hunks,
+each with line numbers, a context region, and the actual changes. The
+line numbers have to be taken with some fuzz, since the other side
+will likely have inserted or deleted lines earlier in the file. The
+context region is meant to help find how to adjust the line numbers in
+order to apply the changes to the right lines. However, if multiple
+areas of the code have the same surrounding lines of context, the
+wrong one can be picked. There are real-world cases where this has
+caused commits to be reapplied incorrectly with no conflicts reported.
+Setting diff.context to a larger value may prevent such types of
+problems, but increases the chance of spurious conflicts (since it
+will require more lines of matching context to apply).
+
+The merge backend works with a full copy of each relevant file,
+insulating it from these types of problems.
+
+Labelling of conflicts markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When there are content conflicts, the merge machinery tries to
+annotate each side's conflict markers with the commits where the
+content came from. Since the apply backend drops the original
+information about the rebased commits and their parents (and instead
+generates new fake commits based off limited information in the
+generated patches), those commits cannot be identified; instead it has
+to fall back to a commit summary. Also, when merge.conflictStyle is
+set to diff3, the apply backend will use "constructed merge base" to
+label the content from the merge base, and thus provide no information
+about the merge base commit whatsoever.
+
+The merge backend works with the full commits on both sides of history
+and thus has no such limitations.
+
+Hooks
+~~~~~
+
+The apply backend has not traditionally called the post-commit hook,
+while the merge backend has. However, this was by accident of
+implementation rather than by design. Both backends should have the
+same behavior, though it is not clear which one is correct.
+
+Interruptability
+~~~~~~~~~~~~~~~~
+
+The apply backend has safety problems with an ill-timed interrupt; if
+the user presses Ctrl-C at the wrong time to try to abort the rebase,
+the rebase can enter a state where it cannot be aborted with a
+subsequent `git rebase --abort`. The merge backend does not appear to
+suffer from the same shortcoming. (See
+https://lore.kernel.org/git/20200207132152.GC2868@szeder.dev/ for
+details.)
+
+Commit Rewording
+~~~~~~~~~~~~~~~~
+
+When a conflict occurs while rebasing, rebase stops and asks the user
+to resolve. Since the user may need to make notable changes while
+resolving conflicts, after conflicts are resolved and the user has run
+`git rebase --continue`, the rebase should open an editor and ask the
+user to update the commit message. The merge backend does this, while
+the apply backend blindly applies the original commit message.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are a few more behavioral differences that most folks would
+probably consider inconsequential but which are mentioned for
+completeness:
+
+* Reflog: The two backends will use different wording when describing
+ the changes made in the reflog, though both will make use of the
+ word "rebase".
+
+* Progress, informational, and error messages: The two backends
+ provide slightly different progress and informational messages.
+ Also, the apply backend writes error messages (such as "Your files
+ would be overwritten...") to stdout, while the merge backend writes
+ them to stderr.
+
+* State directories: The two backends keep their state in different
+ directories under .git/
include::merge-strategies.txt[]
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index b5c4622..ab75036 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -8,16 +8,18 @@ git-rm - Remove files from the working tree and from the index
SYNOPSIS
--------
[verse]
-'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...
+'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch]
+ [--quiet] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+ [--] [<pathspec>...]
DESCRIPTION
-----------
-Remove files from the index, or from the working tree and the index.
-`git rm` will not remove a file from just your working directory.
-(There is no option to remove a file only from the working tree
-and yet keep it in the index; use `/bin/rm` if you want to do that.)
-The files being removed have to be identical to the tip of the branch,
-and no updates to their contents can be staged in the index,
+Remove files matching pathspec from the index, or from the working tree
+and the index. `git rm` will not remove a file from just your working
+directory. (There is no option to remove a file only from the working
+tree and yet keep it in the index; use `/bin/rm` if you want to do
+that.) The files being removed have to be identical to the tip of the
+branch, and no updates to their contents can be staged in the index,
though that default behavior can be overridden with the `-f` option.
When `--cached` is given, the staged content has to
match either the tip of the branch or the file on disk,
@@ -26,15 +28,20 @@ allowing the file to be removed from just the index.
OPTIONS
-------
-<file>...::
- Files to remove. Fileglobs (e.g. `*.c`) can be given to
- remove all matching files. If you want Git to expand
- file glob characters, you may need to shell-escape them.
- A leading directory name
- (e.g. `dir` to remove `dir/file1` and `dir/file2`) can be
- given to remove all files in the directory, and recursively
- all sub-directories,
- but this requires the `-r` option to be explicitly given.
+<pathspec>...::
+ Files to remove. A leading directory name (e.g. `dir` to remove
+ `dir/file1` and `dir/file2`) can be given to remove all files in
+ the directory, and recursively all sub-directories, but this
+ requires the `-r` option to be explicitly given.
++
+The command removes only the paths that are known to Git.
++
+File globbing matches across directory boundaries. Thus, given two
+directories `d` and `d2`, there is a difference between using
+`git rm 'd*'` and `git rm 'd/*'`, as the former will also remove all
+of directory `d2`.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
-f::
--force::
@@ -68,19 +75,19 @@ OPTIONS
`git rm` normally outputs one line (in the form of an `rm` command)
for each file removed. This option suppresses that output.
+--pathspec-from-file=<file>::
+ Pathspec is passed in `<file>` instead of commandline args. If
+ `<file>` is exactly `-` then standard input is used. Pathspec
+ elements are separated by LF or CR/LF. Pathspec elements can be
+ quoted as explained for the configuration variable `core.quotePath`
+ (see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+ global `--literal-pathspecs`.
-DISCUSSION
-----------
-
-The <file> list given to the command can be exact pathnames,
-file glob patterns, or leading directory names. The command
-removes only the paths that are known to Git. Giving the name of
-a file that you have not told Git about does not remove that file.
+--pathspec-file-nul::
+ Only meaningful with `--pathspec-from-file`. Pathspec elements are
+ separated with NUL character and all other characters are taken
+ literally (including newlines and quotes).
-File globbing matches across directory boundaries. Thus, given
-two directories `d` and `d2`, there is a difference between
-using `git rm 'd*'` and `git rm 'd/*'`, as the former will
-also remove all of directory `d2`.
REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
--------------------------------------------------------
diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt
index 974ade2..c0342e5 100644
--- a/Documentation/git-sparse-checkout.txt
+++ b/Documentation/git-sparse-checkout.txt
@@ -41,6 +41,10 @@ COMMANDS
To avoid interfering with other worktrees, it first enables the
`extensions.worktreeConfig` setting and makes sure to set the
`core.sparseCheckout` setting in the worktree-specific config file.
++
+When `--cone` is provided, the `core.sparseCheckoutCone` setting is
+also set, allowing for better performance with a limited set of
+patterns (see 'CONE PATTERN SET' below).
'set'::
Write a set of patterns to the sparse-checkout file, as given as
@@ -50,6 +54,21 @@ To avoid interfering with other worktrees, it first enables the
+
When the `--stdin` option is provided, the patterns are read from
standard in as a newline-delimited list instead of from the arguments.
++
+When `core.sparseCheckoutCone` is enabled, the input list is considered a
+list of directories instead of sparse-checkout patterns. The command writes
+patterns to the sparse-checkout file to include all files contained in those
+directories (recursively) as well as files that are siblings of ancestor
+directories. The input format matches the output of `git ls-tree --name-only`.
+This includes interpreting pathnames that begin with a double quote (") as
+C-style quoted strings.
+
+'add'::
+ Update the sparse-checkout file to include additional patterns.
+ By default, these patterns are read from the command-line arguments,
+ but they can be read from stdin using the `--stdin` option. When
+ `core.sparseCheckoutCone` is enabled, the given patterns are interpreted
+ as directory names as in the 'set' subcommand.
'disable'::
Disable the `core.sparseCheckout` config setting, and restore the
@@ -106,7 +125,7 @@ The full pattern set allows for arbitrary pattern matches and complicated
inclusion/exclusion rules. These can result in O(N*M) pattern matches when
updating the index, where N is the number of patterns and M is the number
of paths in the index. To combat this performance issue, a more restricted
-pattern set is allowed when `core.spareCheckoutCone` is enabled.
+pattern set is allowed when `core.sparseCheckoutCone` is enabled.
The accepted patterns in the cone pattern set are:
@@ -128,9 +147,12 @@ the following patterns:
----------------
This says "include everything in root, but nothing two levels below root."
-If we then add the folder `A/B/C` as a recursive pattern, the folders `A` and
-`A/B` are added as parent patterns. The resulting sparse-checkout file is
-now
+
+When in cone mode, the `git sparse-checkout set` subcommand takes a list of
+directories instead of a list of sparse-checkout patterns. In this mode,
+the command `git sparse-checkout set A/B/C` sets the directory `A/B/C` as
+a recursive pattern, the directories `A` and `A/B` are added as parent
+patterns. The resulting sparse-checkout file is now
----------------
/*
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 53e1a12..31f1beb 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -15,6 +15,7 @@ SYNOPSIS
'git stash' branch <branchname> [<stash>]
'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
+ [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]]
'git stash' clear
'git stash' create [<message>]
@@ -43,10 +44,10 @@ created stash, `stash@{1}` is the one before it, `stash@{2.hours.ago}`
is also possible). Stashes may also be referenced by specifying just the
stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
-OPTIONS
--------
+COMMANDS
+--------
-push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]::
+push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
Save your local modifications to a new 'stash entry' and roll them
back to HEAD (in the working tree and in the index).
@@ -56,38 +57,13 @@ push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q
For quickly making a snapshot, you can omit "push". In this mode,
non-option arguments are not allowed to prevent a misspelled
subcommand from making an unwanted stash entry. The two exceptions to this
-are `stash -p` which acts as alias for `stash push -p` and pathspecs,
+are `stash -p` which acts as alias for `stash push -p` and pathspec elements,
which are allowed after a double hyphen `--` for disambiguation.
-+
-When pathspec is given to 'git stash push', the new stash entry records the
-modified states only for the files that match the pathspec. The index
-entries and working tree files are then rolled back to the state in
-HEAD only for these files, too, leaving files that do not match the
-pathspec intact.
-+
-If the `--keep-index` option is used, all changes already added to the
-index are left intact.
-+
-If the `--include-untracked` option is used, all untracked files are also
-stashed and then cleaned up with `git clean`, leaving the working directory
-in a very clean state. If the `--all` option is used instead then the
-ignored files are stashed and cleaned in addition to the untracked files.
-+
-With `--patch`, you can interactively select hunks from the diff
-between HEAD and the working tree to be stashed. The stash entry is
-constructed such that its index state is the same as the index state
-of your repository, and its worktree contains only the changes you
-selected interactively. The selected changes are then rolled back
-from your worktree. See the ``Interactive Mode'' section of
-linkgit:git-add[1] to learn how to operate the `--patch` mode.
-+
-The `--patch` option implies `--keep-index`. You can use
-`--no-keep-index` to override this.
save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
This option is deprecated in favour of 'git stash push'. It
- differs from "stash push" in that it cannot take pathspecs.
+ differs from "stash push" in that it cannot take pathspec.
Instead, all non-option arguments are concatenated to form the stash
message.
@@ -111,7 +87,7 @@ show [<options>] [<stash>]::
Show the changes recorded in the stash entry as a diff between the
stashed contents and the commit back when the stash entry was first
- created. When no `<stash>` is given, it shows the latest one.
+ created.
By default, the command shows the diffstat, but it will accept any
format known to 'git diff' (e.g., `git stash show -p stash@{1}`
to view the second most recent entry in patch form).
@@ -128,14 +104,6 @@ pop [--index] [-q|--quiet] [<stash>]::
Applying the state can fail with conflicts; in this case, it is not
removed from the stash list. You need to resolve the conflicts by hand
and call `git stash drop` manually afterwards.
-+
-If the `--index` option is used, then tries to reinstate not only the working
-tree's changes, but also the index's ones. However, this can fail, when you
-have conflicts (which are stored in the index, where you therefore can no
-longer apply the changes as they were originally).
-+
-When no `<stash>` is given, `stash@{0}` is assumed, otherwise `<stash>` must
-be a reference of the form `stash@{<revision>}`.
apply [--index] [-q|--quiet] [<stash>]::
@@ -149,8 +117,7 @@ branch <branchname> [<stash>]::
the commit at which the `<stash>` was originally created, applies the
changes recorded in `<stash>` to the new working tree and index.
If that succeeds, and `<stash>` is a reference of the form
- `stash@{<revision>}`, it then drops the `<stash>`. When no `<stash>`
- is given, applies the latest one.
+ `stash@{<revision>}`, it then drops the `<stash>`.
+
This is useful if the branch on which you ran `git stash push` has
changed enough that `git stash apply` fails due to conflicts. Since
@@ -166,9 +133,6 @@ clear::
drop [-q|--quiet] [<stash>]::
Remove a single stash entry from the list of stash entries.
- When no `<stash>` is given, it removes the latest one.
- i.e. `stash@{0}`, otherwise `<stash>` must be a valid stash
- log reference of the form `stash@{<revision>}`.
create::
@@ -185,6 +149,98 @@ store::
reflog. This is intended to be useful for scripts. It is
probably not the command you want to use; see "push" above.
+OPTIONS
+-------
+-a::
+--all::
+ This option is only valid for `push` and `save` commands.
++
+All ignored and untracked files are also stashed and then cleaned
+up with `git clean`.
+
+-u::
+--include-untracked::
+ This option is only valid for `push` and `save` commands.
++
+All untracked files are also stashed and then cleaned up with
+`git clean`.
+
+--index::
+ This option is only valid for `pop` and `apply` commands.
++
+Tries to reinstate not only the working tree's changes, but also
+the index's ones. However, this can fail, when you have conflicts
+(which are stored in the index, where you therefore can no longer
+apply the changes as they were originally).
+
+-k::
+--keep-index::
+--no-keep-index::
+ This option is only valid for `push` and `save` commands.
++
+All changes already added to the index are left intact.
+
+-p::
+--patch::
+ This option is only valid for `push` and `save` commands.
++
+Interactively select hunks from the diff between HEAD and the
+working tree to be stashed. The stash entry is constructed such
+that its index state is the same as the index state of your
+repository, and its worktree contains only the changes you selected
+interactively. The selected changes are then rolled back from your
+worktree. See the ``Interactive Mode'' section of linkgit:git-add[1]
+to learn how to operate the `--patch` mode.
++
+The `--patch` option implies `--keep-index`. You can use
+`--no-keep-index` to override this.
+
+--pathspec-from-file=<file>::
+ This option is only valid for `push` command.
++
+Pathspec is passed in `<file>` instead of commandline args. If
+`<file>` is exactly `-` then standard input is used. Pathspec
+elements are separated by LF or CR/LF. Pathspec elements can be
+quoted as explained for the configuration variable `core.quotePath`
+(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
+global `--literal-pathspecs`.
+
+--pathspec-file-nul::
+ This option is only valid for `push` command.
++
+Only meaningful with `--pathspec-from-file`. Pathspec elements are
+separated with NUL character and all other characters are taken
+literally (including newlines and quotes).
+
+-q::
+--quiet::
+ This option is only valid for `apply`, `drop`, `pop`, `push`,
+ `save`, `store` commands.
++
+Quiet, suppress feedback messages.
+
+\--::
+ This option is only valid for `push` command.
++
+Separates pathspec from options for disambiguation purposes.
+
+<pathspec>...::
+ This option is only valid for `push` command.
++
+The new stash entry records the modified states only for the files
+that match the pathspec. The index entries and working tree files
+are then rolled back to the state in HEAD only for these files,
+too, leaving files that do not match the pathspec intact.
++
+For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
+
+<stash>::
+ This option is only valid for `apply`, `branch`, `drop`, `pop`,
+ `show` commands.
++
+A reference of the form `stash@{<revision>}`. When no `<stash>` is
+given, the latest stash is assumed (that is, `stash@{0}`).
+
DISCUSSION
----------
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 5232407..c9ed2bf 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -133,7 +133,7 @@ If you really want to remove a submodule from the repository and commit
that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
options.
-update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...]::
+update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--] [<path>...]::
+
--
Update the registered submodules to match what the superproject
@@ -229,7 +229,7 @@ As an example, the command below will show the path and currently
checked out commit for each submodule:
+
--------------
-git submodule foreach 'echo $path `git rev-parse HEAD`'
+git submodule foreach 'echo $sm_path `git rev-parse HEAD`'
--------------
sync [--recursive] [--] [<path>...]::
@@ -430,6 +430,10 @@ options carefully.
Clone new submodules in parallel with as many jobs.
Defaults to the `submodule.fetchJobs` option.
+--[no-]single-branch::
+ This option is only valid for the update command.
+ Clone only one branch during update: HEAD or one specified by --branch.
+
<path>...::
Paths to submodule(s). When specified this will restrict the command
to only operate on the submodules found at the specified paths.
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index c7a6271..1489cb0 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -549,6 +549,22 @@ The untracked cache extension can be enabled by the
`core.untrackedCache` configuration variable (see
linkgit:git-config[1]).
+NOTES
+-----
+
+Users often try to use the assume-unchanged and skip-worktree bits
+to tell Git to ignore changes to files that are tracked. This does not
+work as expected, since Git may still check working tree files against
+the index when performing certain operations. In general, Git does not
+provide a way to ignore changes to tracked files, so alternate solutions
+are recommended.
+
+For example, if the file you want to change is some sort of config file,
+the repository can include a sample config file that can then be copied
+into the ignored name and modified. The repository can even include a
+script to treat the sample file as a template, modifying and copying it
+automatically.
+
SEE ALSO
--------
linkgit:git-config[1],
diff --git a/Documentation/git.txt b/Documentation/git.txt
index b1597ac..9d6769e 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -110,9 +110,23 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config
Do not pipe Git output into a pager.
--git-dir=<path>::
- Set the path to the repository. This can also be controlled by
- setting the `GIT_DIR` environment variable. It can be an absolute
- path or relative path to current working directory.
+ Set the path to the repository (".git" directory). This can also be
+ controlled by setting the `GIT_DIR` environment variable. It can be
+ an absolute path or relative path to current working directory.
++
+Specifying the location of the ".git" directory using this
+option (or `GIT_DIR` environment variable) turns off the
+repository discovery that tries to find a directory with
+".git" subdirectory (which is how the repository and the
+top-level of the working tree are discovered), and tells Git
+that you are at the top level of the working tree. If you
+are not at the top-level directory of the working tree, you
+should tell Git where the top-level of the working tree is,
+with the `--work-tree=<path>` option (or `GIT_WORK_TREE`
+environment variable)
++
+If you just want to run git as if it was started in `<path>` then use
+`git -C <path>`.
--work-tree=<path>::
Set the path to the working tree. It can be an absolute path
@@ -479,16 +493,45 @@ double-quotes and respecting backslash escapes. E.g., the value
details. This variable has lower precedence than other path
variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
+`GIT_DEFAULT_HASH_ALGORITHM`::
+ If this variable is set, the default hash algorithm for new
+ repositories will be set to this value. This value is currently
+ ignored when cloning; the setting of the remote repository
+ is used instead. The default is "sha1".
+
Git Commits
~~~~~~~~~~~
`GIT_AUTHOR_NAME`::
+ The human-readable name used in the author identity when creating commit or
+ tag objects, or when writing reflogs. Overrides the `user.name` and
+ `author.name` configuration settings.
+
`GIT_AUTHOR_EMAIL`::
+ The email address used in the author identity when creating commit or
+ tag objects, or when writing reflogs. Overrides the `user.email` and
+ `author.email` configuration settings.
+
`GIT_AUTHOR_DATE`::
+ The date used for the author identity when creating commit or tag objects, or
+ when writing reflogs. See linkgit:git-commit[1] for valid formats.
+
`GIT_COMMITTER_NAME`::
+ The human-readable name used in the committer identity when creating commit or
+ tag objects, or when writing reflogs. Overrides the `user.name` and
+ `committer.name` configuration settings.
+
`GIT_COMMITTER_EMAIL`::
+ The email address used in the author identity when creating commit or
+ tag objects, or when writing reflogs. Overrides the `user.email` and
+ `committer.email` configuration settings.
+
`GIT_COMMITTER_DATE`::
-'EMAIL'::
- see linkgit:git-commit-tree[1]
+ The date used for the committer identity when creating commit or tag objects, or
+ when writing reflogs. See linkgit:git-commit[1] for valid formats.
+
+`EMAIL`::
+ The email address used in the author and committer identities if no other
+ relevant environment variable or configuration setting has been set.
Git Diffs
~~~~~~~~~
diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt
index 373cfa2..92e4ba6 100644
--- a/Documentation/gitcli.txt
+++ b/Documentation/gitcli.txt
@@ -126,6 +126,11 @@ usage: git describe [<options>] <commit-ish>*
--long always use long format
--abbrev[=<n>] use <n> digits to display SHA-1s
---------------------------------------------
++
+Note that some subcommand (e.g. `git grep`) may behave differently
+when there are things on the command line other than `-h`, but `git
+subcmd -h` without anything else on the command line is meant to
+consistently give the usage.
--help-all::
Some Git commands take options that are only used for plumbing or that
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index f880d21..c0b9525 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -751,7 +751,7 @@ to it.
================================================
If you make the decision to start your new branch at some
other point in the history than the current `HEAD`, you can do so by
-just telling 'git checkout' what the base of the checkout would be.
+just telling 'git switch' what the base of the checkout would be.
In other words, if you have an earlier tag or branch, you'd just do
------------
diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt
index ea759fd..1814d2d 100644
--- a/Documentation/gitcredentials.txt
+++ b/Documentation/gitcredentials.txt
@@ -131,7 +131,9 @@ context would not match:
because the hostnames differ. Nor would it match `foo.example.com`; Git
compares hostnames exactly, without considering whether two hosts are part of
the same domain. Likewise, a config entry for `http://example.com` would not
-match: Git compares the protocols exactly.
+match: Git compares the protocols exactly. However, you may use wildcards in
+the domain name and other pattern matching techniques as with the `http.<url>.*`
+options.
If the "pattern" URL does include a path component, then this too must match
exactly: the context `https://example.com/bar/baz.git` will match a config
@@ -186,7 +188,94 @@ CUSTOM HELPERS
--------------
You can write your own custom helpers to interface with any system in
-which you keep credentials. See credential.h for details.
+which you keep credentials.
+
+Credential helpers are programs executed by Git to fetch or save
+credentials from and to long-term storage (where "long-term" is simply
+longer than a single Git process; e.g., credentials may be stored
+in-memory for a few minutes, or indefinitely on disk).
+
+Each helper is specified by a single string in the configuration
+variable `credential.helper` (and others, see linkgit:git-config[1]).
+The string is transformed by Git into a command to be executed using
+these rules:
+
+ 1. If the helper string begins with "!", it is considered a shell
+ snippet, and everything after the "!" becomes the command.
+
+ 2. Otherwise, if the helper string begins with an absolute path, the
+ verbatim helper string becomes the command.
+
+ 3. Otherwise, the string "git credential-" is prepended to the helper
+ string, and the result becomes the command.
+
+The resulting command then has an "operation" argument appended to it
+(see below for details), and the result is executed by the shell.
+
+Here are some example specifications:
+
+----------------------------------------------------
+# run "git credential-foo"
+foo
+
+# same as above, but pass an argument to the helper
+foo --bar=baz
+
+# the arguments are parsed by the shell, so use shell
+# quoting if necessary
+foo --bar="whitespace arg"
+
+# you can also use an absolute path, which will not use the git wrapper
+/path/to/my/helper --with-arguments
+
+# or you can specify your own shell snippet
+!f() { echo "password=`cat $HOME/.secret`"; }; f
+----------------------------------------------------
+
+Generally speaking, rule (3) above is the simplest for users to specify.
+Authors of credential helpers should make an effort to assist their
+users by naming their program "git-credential-$NAME", and putting it in
+the `$PATH` or `$GIT_EXEC_PATH` during installation, which will allow a
+user to enable it with `git config credential.helper $NAME`.
+
+When a helper is executed, it will have one "operation" argument
+appended to its command line, which is one of:
+
+`get`::
+
+ Return a matching credential, if any exists.
+
+`store`::
+
+ Store the credential, if applicable to the helper.
+
+`erase`::
+
+ Remove a matching credential, if any, from the helper's storage.
+
+The details of the credential will be provided on the helper's stdin
+stream. The exact format is the same as the input/output format of the
+`git credential` plumbing command (see the section `INPUT/OUTPUT
+FORMAT` in linkgit:git-credential[1] for a detailed specification).
+
+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).
+
+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 receives any other operation, it should silently ignore the
+request. This leaves room for future operations to be added (older
+helpers will just ignore the new requests).
GIT
---
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
new file mode 100644
index 0000000..1cf83df
--- /dev/null
+++ b/Documentation/gitfaq.txt
@@ -0,0 +1,337 @@
+gitfaq(7)
+=========
+
+NAME
+----
+gitfaq - Frequently asked questions about using Git
+
+SYNOPSIS
+--------
+gitfaq
+
+DESCRIPTION
+-----------
+
+The examples in this FAQ assume a standard POSIX shell, like `bash` or `dash`,
+and a user, A U Thor, who has the account `author` on the hosting provider
+`git.example.org`.
+
+Configuration
+-------------
+
+[[user-name]]
+What should I put in `user.name`?::
+ You should put your personal name, generally a form using a given name
+ and family name. For example, the current maintainer of Git uses "Junio
+ C Hamano". This will be the name portion that is stored in every commit
+ you make.
++
+This configuration doesn't have any effect on authenticating to remote services;
+for that, see `credential.username` in linkgit:git-config[1].
+
+[[http-postbuffer]]
+What does `http.postBuffer` really do?::
+ This option changes the size of the buffer that Git uses when pushing
+ data to a remote over HTTP or HTTPS. If the data is larger than this
+ size, libcurl, which handles the HTTP support for Git, will use chunked
+ transfer encoding since it isn't known ahead of time what the size of
+ the pushed data will be.
++
+Leaving this value at the default size is fine unless you know that either the
+remote server or a proxy in the middle doesn't support HTTP/1.1 (which
+introduced the chunked transfer encoding) or is known to be broken with chunked
+data. This is often (erroneously) suggested as a solution for generic push
+problems, but since almost every server and proxy supports at least HTTP/1.1,
+raising this value usually doesn't solve most push problems. A server or proxy
+that didn't correctly support HTTP/1.1 and chunked transfer encoding wouldn't be
+that useful on the Internet today, since it would break lots of traffic.
++
+Note that increasing this value will increase the memory used on every relevant
+push that Git does over HTTP or HTTPS, since the entire buffer is allocated
+regardless of whether or not it is all used. Thus, it's best to leave it at the
+default unless you are sure you need a different value.
+
+[[configure-editor]]
+How do I configure a different editor?::
+ If you haven't specified an editor specifically for Git, it will by default
+ use the editor you've configured using the `VISUAL` or `EDITOR` environment
+ variables, or if neither is specified, the system default (which is usually
+ `vi`). Since some people find `vi` difficult to use or prefer a different
+ editor, it may be desirable to change the editor used.
++
+If you want to configure a general editor for most programs which need one, you
+can edit your shell configuration (e.g., `~/.bashrc` or `~/.zshenv`) to contain
+a line setting the `EDITOR` or `VISUAL` environment variable to an appropriate
+value. For example, if you prefer the editor `nano`, then you could write the
+following:
++
+----
+export VISUAL=nano
+----
++
+If you want to configure an editor specifically for Git, you can either set the
+`core.editor` configuration value or the `GIT_EDITOR` environment variable. You
+can see linkgit:git-var[1] for details on the order in which these options are
+consulted.
++
+Note that in all cases, the editor value will be passed to the shell, so any
+arguments containing spaces should be appropriately quoted. Additionally, if
+your editor normally detaches from the terminal when invoked, you should specify
+it with an argument that makes it not do that, or else Git will not see any
+changes. An example of a configuration addressing both of these issues on
+Windows would be the configuration `"C:\Program Files\Vim\gvim.exe" --nofork`,
+which quotes the filename with spaces and specifies the `--nofork` option to
+avoid backgrounding the process.
+
+Credentials
+-----------
+
+[[http-credentials]]
+How do I specify my credentials when pushing over HTTP?::
+ The easiest way to do this is to use a credential helper via the
+ `credential.helper` configuration. Most systems provide a standard
+ choice to integrate with the system credential manager. For example,
+ Git for Windows provides the `wincred` credential manager, macOS has the
+ `osxkeychain` credential manager, and Unix systems with a standard
+ desktop environment can use the `libsecret` credential manager. All of
+ these store credentials in an encrypted store to keep your passwords or
+ tokens secure.
++
+In addition, you can use the `store` credential manager which stores in a file
+in your home directory, or the `cache` credential manager, which does not
+permanently store your credentials, but does prevent you from being prompted for
+them for a certain period of time.
++
+You can also just enter your password when prompted. While it is possible to
+place the password (which must be percent-encoded) in the URL, this is not
+particularly secure and can lead to accidental exposure of credentials, so it is
+not recommended.
+
+[[http-credentials-environment]]
+How do I read a password or token from an environment variable?::
+ The `credential.helper` configuration option can also take an arbitrary
+ shell command that produces the credential protocol on standard output.
+ This is useful when passing credentials into a container, for example.
++
+Such a shell command can be specified by starting the option value with an
+exclamation point. If your password or token were stored in the `GIT_TOKEN`,
+you could run the following command to set your credential helper:
++
+----
+$ git config credential.helper \
+ '!f() { echo username=author; echo "password=$GIT_TOKEN"; };f'
+----
+
+[[http-reset-credentials]]
+How do I change the password or token I've saved in my credential manager?::
+ Usually, if the password or token is invalid, Git will erase it and
+ prompt for a new one. However, there are times when this doesn't always
+ happen. To change the password or token, you can erase the existing
+ credentials and then Git will prompt for new ones. To erase
+ credentials, use a syntax like the following (substituting your username
+ and the hostname):
++
+----
+$ echo url=https://author@git.example.org | git credential reject
+----
+
+[[multiple-accounts-http]]
+How do I use multiple accounts with the same hosting provider using HTTP?::
+ Usually the easiest way to distinguish between these accounts is to use
+ the username in the URL. For example, if you have the accounts `author`
+ and `committer` on `git.example.org`, you can use the URLs
+ https://author@git.example.org/org1/project1.git and
+ https://committer@git.example.org/org2/project2.git. This way, when you
+ use a credential helper, it will automatically try to look up the
+ correct credentials for your account. If you already have a remote set
+ up, you can change the URL with something like `git remote set-url
+ origin https://author@git.example.org/org1/project1.git` (see
+ linkgit:git-remote[1] for details).
+
+[[multiple-accounts-ssh]]
+How do I use multiple accounts with the same hosting provider using SSH?::
+ With most hosting providers that support SSH, a single key pair uniquely
+ identifies a user. Therefore, to use multiple accounts, it's necessary
+ to create a key pair for each account. If you're using a reasonably
+ modern OpenSSH version, you can create a new key pair with something
+ like `ssh-keygen -t ed25519 -f ~/.ssh/id_committer`. You can then
+ register the public key (in this case, `~/.ssh/id_committer.pub`; note
+ the `.pub`) with the hosting provider.
++
+Most hosting providers use a single SSH account for pushing; that is, all users
+push to the `git` account (e.g., `git@git.example.org`). If that's the case for
+your provider, you can set up multiple aliases in SSH to make it clear which key
+pair to use. For example, you could write something like the following in
+`~/.ssh/config`, substituting the proper private key file:
++
+----
+# This is the account for author on git.example.org.
+Host example_author
+ HostName git.example.org
+ User git
+ # This is the key pair registered for author with git.example.org.
+ IdentityFile ~/.ssh/id_author
+ IdentitiesOnly yes
+# This is the account for committer on git.example.org.
+Host example_committer
+ HostName git.example.org
+ User git
+ # This is the key pair registered for committer with git.example.org.
+ IdentityFile ~/.ssh/id_committer
+ IdentitiesOnly yes
+----
++
+Then, you can adjust your push URL to use `git@example_author` or
+`git@example_committer` instead of `git@example.org` (e.g., `git remote set-url
+git@example_author:org1/project1.git`).
+
+Common Issues
+-------------
+
+[[last-commit-amend]]
+I've made a mistake in the last commit. How do I change it?::
+ You can make the appropriate change to your working tree, run `git add
+ <file>` or `git rm <file>`, as appropriate, to stage it, and then `git
+ commit --amend`. Your change will be included in the commit, and you'll
+ be prompted to edit the commit message again; if you wish to use the
+ original message verbatim, you can use the `--no-edit` option to `git
+ commit` in addition, or just save and quit when your editor opens.
+
+[[undo-previous-change]]
+I've made a change with a bug and it's been included in the main branch. How should I undo it?::
+ The usual way to deal with this is to use `git revert`. This preserves
+ the history that the original change was made and was a valuable
+ contribution, but also introduces a new commit that undoes those changes
+ because the original had a problem. The commit message of the revert
+ indicates the commit which was reverted and is usually edited to include
+ an explanation as to why the revert was made.
+
+[[ignore-tracked-files]]
+How do I ignore changes to a tracked file?::
+ Git doesn't provide a way to do this. The reason is that if Git needs
+ to overwrite this file, such as during a checkout, it doesn't know
+ whether the changes to the file are precious and should be kept, or
+ whether they are irrelevant and can safely be destroyed. Therefore, it
+ has to take the safe route and always preserve them.
++
+It's tempting to try to use certain features of `git update-index`, namely the
+assume-unchanged and skip-worktree bits, but these don't work properly for this
+purpose and shouldn't be used this way.
++
+If your goal is to modify a configuration file, it can often be helpful to have
+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.
+
+Hooks
+-----
+
+[[restrict-with-hooks]]
+How do I use hooks to prevent users from making certain changes?::
+ The only safe place to make these changes is on the remote repository
+ (i.e., the Git server), usually in the `pre-receive` hook or in a
+ continuous integration (CI) system. These are the locations in which
+ policy can be enforced effectively.
++
+It's common to try to use `pre-commit` hooks (or, for commit messages,
+`commit-msg` hooks) to check these things, which is great if you're working as a
+solo developer and want the tooling to help you. However, using hooks on a
+developer machine is not effective as a policy control because a user can bypass
+these hooks with `--no-verify` without being noticed (among various other ways).
+Git assumes that the user is in control of their local repositories and doesn't
+try to prevent this or tattle on the user.
++
+In addition, some advanced users find `pre-commit` hooks to be an impediment to
+workflows that use temporary commits to stage work in progress or that create
+fixup commits, so it's better to push these kinds of checks to the server
+anyway.
+
+Cross-Platform Issues
+---------------------
+
+[[windows-text-binary]]
+I'm on Windows and my text files are detected as binary.::
+ Git works best when you store text files as UTF-8. Many programs on
+ Windows support UTF-8, but some do not and only use the little-endian
+ UTF-16 format, which Git detects as binary. If you can't use UTF-8 with
+ your programs, you can specify a working tree encoding that indicates
+ which encoding your files should be checked out with, while still
+ storing these files as UTF-8 in the repository. This allows tools like
+ linkgit:git-diff[1] to work as expected, while still allowing your tools
+ to work.
++
+To do so, you can specify a linkgit:gitattributes[5] pattern with the
+`working-tree-encoding` attribute. For example, the following pattern sets all
+C files to use UTF-16LE-BOM, which is a common encoding on Windows:
++
+----
+*.c working-tree-encoding=UTF-16LE-BOM
+----
++
+You will need to run `git add --renormalize` to have this take effect. Note
+that if you are making these changes on a project that is used across platforms,
+you'll probably want to make it in a per-user configuration file or in the one
+in `$GIT_DIR/info/attributes`, since making it in a `.gitattributes` file in the
+repository will apply to all users of the repository.
++
+See the following entry for information about normalizing line endings as well,
+and see linkgit:gitattributes[5] for more information about attribute files.
+
+[[windows-diff-control-m]]
+I'm on Windows and git diff shows my files as having a `^M` at the end.::
+ By default, Git expects files to be stored with Unix line endings. As such,
+ the carriage return (`^M`) that is part of a Windows line ending is shown
+ because it is considered to be trailing whitespace. Git defaults to showing
+ trailing whitespace only on new lines, not existing ones.
++
+You can store the files in the repository with Unix line endings and convert
+them automatically to your platform's line endings. To do that, set the
+configuration option `core.eol` to `native` and see the following entry for
+information about how to configure files as text or binary.
++
+You can also control this behavior with the `core.whitespace` setting if you
+don't wish to remove the carriage returns from your line endings.
+
+[[recommended-storage-settings]]
+What's the recommended way to store files in Git?::
+ While Git can store and handle any file of any type, there are some
+ settings that work better than others. In general, we recommend that
+ text files be stored in UTF-8 without a byte-order mark (BOM) with LF
+ (Unix-style) endings. We also recommend the use of UTF-8 (again,
+ without BOM) in commit messages. These are the settings that work best
+ across platforms and with tools such as `git diff` and `git merge`.
++
+Additionally, if you have a choice between storage formats that are text based
+or non-text based, we recommend storing files in the text format and, if
+necessary, transforming them into the other format. For example, a text-based
+SQL dump with one record per line will work much better for diffing and merging
+than an actual database file. Similarly, text-based formats such as Markdown
+and AsciiDoc will work better than binary formats such as Microsoft Word and
+PDF.
++
+Similarly, storing binary dependencies (e.g., shared libraries or JAR files) or
+build products in the repository is generally not recommended. Dependencies and
+build products are best stored on an artifact or package server with only
+references, URLs, and hashes stored in the repository.
++
+We also recommend setting a linkgit:gitattributes[5] file to explicitly mark
+which files are text and which are binary. If you want Git to guess, you can
+set the attribute `text=auto`. For example, the following might be appropriate
+in some projects:
++
+----
+# By default, guess.
+* text=auto
+# Mark all C files as text.
+*.c text
+# Mark all JPEG files as binary.
+*.jpg binary
+----
++
+These settings help tools pick the right format for output such as patches and
+result in files being checked out in the appropriate line ending for the
+platform.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 50365f2..81f2a87 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -490,9 +490,16 @@ fsmonitor-watchman
~~~~~~~~~~~~~~~~~~
This hook is invoked when the configuration option `core.fsmonitor` is
-set to `.git/hooks/fsmonitor-watchman`. It takes two arguments, a version
-(currently 1) and the time in elapsed nanoseconds since midnight,
-January 1, 1970.
+set to `.git/hooks/fsmonitor-watchman` or `.git/hooks/fsmonitor-watchmanv2`
+depending on the version of the hook to use.
+
+Version 1 takes two arguments, a version (1) and the time in elapsed
+nanoseconds since midnight, January 1, 1970.
+
+Version 2 takes two arguments, a version (2) and a token that is used
+for identifying changes since the token. For watchman this would be
+a clock id. This version must output to stdout the new token followed
+by a NUL before the list of files.
The hook should output to stdout the list of all files in the working
directory that may have changed since the requested time. The logic
@@ -515,12 +522,61 @@ The exit status determines whether git will use the data from the
hook to limit its search. On error, it will fall back to verifying
all files and folders.
+p4-changelist
+~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+Run `git-p4 submit --help` for details.
+
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+Run `git-p4 submit --help` for details.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+Run `git-p4 submit --help` for details.
+
p4-pre-submit
~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`. It takes no parameters and nothing
from standard input. Exiting with non-zero status from this script prevent
-`git-p4 submit` from launching. Run `git-p4 submit --help` for details.
+`git-p4 submit` from launching. It can be bypassed with the `--no-verify`
+command line option. Run `git-p4 submit --help` for details.
+
+
post-index-change
~~~~~~~~~~~~~~~~~
diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt
index ca43787..73be8b4 100644
--- a/Documentation/howto/maintain-git.txt
+++ b/Documentation/howto/maintain-git.txt
@@ -154,15 +154,17 @@ by doing the following:
- Anything unobvious that is applicable to 'master' (in other
words, does not depend on anything that is still in 'next'
and not in 'master') is applied to a new topic branch that
- is forked from the tip of 'master'. This includes both
+ is forked from the tip of 'master' (or the last feature release,
+ which is a bit older than 'master'). This includes both
enhancements and unobvious fixes to 'master'. A topic
branch is named as ai/topic where "ai" is two-letter string
named after author's initial and "topic" is a descriptive name
of the topic (in other words, "what's the series is about").
- An unobvious fix meant for 'maint' is applied to a new
- topic branch that is forked from the tip of 'maint'. The
- topic is named as ai/maint-topic.
+ topic branch that is forked from the tip of 'maint' (or the
+ oldest and still relevant maintenance branch). The
+ topic may be named as ai/maint-topic.
- Changes that pertain to an existing topic are applied to
the branch, but:
@@ -174,24 +176,40 @@ by doing the following:
- Replacement patches to an existing topic are accepted only
for commits not in 'next'.
- The above except the "replacement" are all done with:
+ The initial round is done with:
$ git checkout ai/topic ;# or "git checkout -b ai/topic master"
$ git am -sc3 mailbox
- while patch replacement is often done by:
+ and replacing an existing topic with subsequent round is done with:
- $ git format-patch ai/topic~$n..ai/topic ;# export existing
+ $ git checkout master...ai/topic ;# try to reapply to the same base
+ $ git am -sc3 mailbox
+
+ to prepare the new round on a detached HEAD, and then
+
+ $ git range-diff @{-1}...
+ $ git diff @{-1}
- then replace some parts with the new patch, and reapplying:
+ to double check what changed since the last round, and finally
- $ git checkout ai/topic
- $ git reset --hard ai/topic~$n
- $ git am -sc3 -s 000*.txt
+ $ git checkout -B @{-1}
+
+ to conclude (the last step is why a topic already in 'next' is
+ not replaced but updated incrementally).
+
+ Whether it is the initial round or a subsequent round, the topic
+ may not build even in isolation, or may break the build when
+ merged to integration branches due to bugs. There may already
+ be obvious and trivial improvements suggested on the list. The
+ maintainer often adds an extra commit, with "SQUASH???" in its
+ title, to fix things up, before publishing the integration
+ branches to make it usable by other developers for testing.
+ These changes are what the maintainer is not 100% committed to
+ (trivial typofixes etc. are often squashed directly into the
+ patches that need fixing, without being applied as a separate
+ "SQUASH???" commit), so that they can be removed easily as needed.
- The full test suite is always run for 'maint' and 'master'
- after patch application; for topic branches the tests are run
- as time permits.
- Merge maint to master as needed:
@@ -371,6 +389,14 @@ Some observations to be made.
be included in the next feature release. Being in the
'master' branch typically is.
+ * Due to the nature of "SQUASH???" fix-ups, if the original author
+ agrees with the suggested changes, it is OK to squash them to
+ appropriate patches in the next round (when the suggested change
+ is small enough, the author should not even bother with
+ "Helped-by"). It is also OK to drop them from the next round
+ when the original author does not agree with the suggestion, but
+ the author is expected to say why somewhere in the discussion.
+
Appendix
--------
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 1a7212c..a4b6f49 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -226,6 +226,7 @@ endif::git-rev-list[]
'%GF':: show the fingerprint of the key used to sign a signed commit
'%GP':: show the fingerprint of the primary key whose subkey was used
to sign a signed commit
+'%GT':: show the trust level for the key used to sign a signed commit
'%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2
minutes ago}`; the format follows the rules described for the
`-g` option. The portion before the `@` is the refname as
diff --git a/Documentation/technical/bundle-format.txt b/Documentation/technical/bundle-format.txt
new file mode 100644
index 0000000..0e82815
--- /dev/null
+++ b/Documentation/technical/bundle-format.txt
@@ -0,0 +1,48 @@
+= Git bundle v2 format
+
+The Git bundle format is a format that represents both refs and Git objects.
+
+== Format
+
+We will use ABNF notation to define the Git bundle format. See
+protocol-common.txt for the details.
+
+----
+bundle = signature *prerequisite *reference LF pack
+signature = "# v2 git bundle" LF
+
+prerequisite = "-" obj-id SP comment LF
+comment = *CHAR
+reference = obj-id SP refname LF
+
+pack = ... ; packfile
+----
+
+== Semantics
+
+A Git bundle consists of three parts.
+
+* "Prerequisites" lists the objects that are NOT included in the bundle and the
+ reader of the bundle MUST already have, in order to use the data in the
+ bundle. The objects stored in the bundle may refer to prerequisite objects and
+ anything reachable from them (e.g. a tree object in the bundle can reference
+ a blob that is reachable from a prerequisite) and/or expressed as a delta
+ against prerequisite objects.
+
+* "References" record the tips of the history graph, iow, what the reader of the
+ bundle CAN "git fetch" from it.
+
+* "Pack" is the pack data stream "git fetch" would send, if you fetch from a
+ repository that has the references recorded in the "References" above into a
+ repository that has references pointing at the objects listed in
+ "Prerequisites" above.
+
+In the bundle format, there can be a comment following a prerequisite obj-id.
+This is a comment and it has no specific meaning. The writer of the bundle MAY
+put any string here. The reader of the bundle MUST ignore the comment.
+
+=== Note on the shallow clone and a Git bundle
+
+Note that the prerequisites does not represent a shallow-clone boundary. The
+semantics of the prerequisites and the shallow-clone boundaries are different,
+and the Git bundle v2 format cannot represent a shallow clone repository.
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
index cab5bdd..d3a142c 100644
--- a/Documentation/technical/pack-format.txt
+++ b/Documentation/technical/pack-format.txt
@@ -315,10 +315,11 @@ CHUNK DATA:
Stores two 4-byte values for every object.
1: The pack-int-id for the pack storing this object.
2: The offset within the pack.
- If all offsets are less than 2^31, then the large offset chunk
+ If all offsets are less than 2^32, then the large offset chunk
will not exist and offsets are stored as in IDX v1.
If there is at least one offset value larger than 2^32-1, then
- the large offset chunk must exist. If the large offset chunk
+ the large offset chunk must exist, and offsets larger than
+ 2^31-1 must be stored in it instead. If the large offset chunk
exists and the 31st bit is on, then removing that bit reveals
the row in the large offsets containing the 8-byte offset of
this object.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 2b7f62b..21f929e 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.25.0
+DEF_VER=v2.26.GIT
LF='
'
diff --git a/Makefile b/Makefile
index 09f98b7..ef1ff22 100644
--- a/Makefile
+++ b/Makefile
@@ -609,7 +609,6 @@ SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-quiltimport.sh
-SCRIPT_SH += git-legacy-stash.sh
SCRIPT_SH += git-request-pull.sh
SCRIPT_SH += git-submodule.sh
SCRIPT_SH += git-web--browse.sh
@@ -695,6 +694,7 @@ X =
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
+TEST_BUILTINS_OBJS += test-advise.o
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
TEST_BUILTINS_OBJS += test-ctype.o
@@ -721,6 +721,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o
TEST_BUILTINS_OBJS += test-oidmap.o
TEST_BUILTINS_OBJS += test-online-cpus.o
TEST_BUILTINS_OBJS += test-parse-options.o
+TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
TEST_BUILTINS_OBJS += test-path-utils.o
TEST_BUILTINS_OBJS += test-pkt-line.o
TEST_BUILTINS_OBJS += test-prio-queue.o
@@ -954,6 +955,7 @@ LIB_OBJS += quote.o
LIB_OBJS += range-diff.o
LIB_OBJS += reachable.o
LIB_OBJS += read-cache.o
+LIB_OBJS += rebase.o
LIB_OBJS += rebase-interactive.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
@@ -1220,6 +1222,9 @@ endif
ifneq ($(filter leak,$(SANITIZERS)),)
BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
endif
+ifneq ($(filter address,$(SANITIZERS)),)
+NO_REGEX = NeededForASAN
+endif
endif
ifndef sysconfdir
diff --git a/RelNotes b/RelNotes
index 091dd02..f3d8527 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.25.0.txt \ No newline at end of file
+Documentation/RelNotes/2.27.0.txt \ No newline at end of file
diff --git a/abspath.c b/abspath.c
index 9857985..6f15a41 100644
--- a/abspath.c
+++ b/abspath.c
@@ -202,22 +202,6 @@ error_out:
return retval;
}
-/*
- * Resolve `path` into an absolute, cleaned-up path. The return value
- * comes from a shared buffer.
- */
-const char *real_path(const char *path)
-{
- static struct strbuf realpath = STRBUF_INIT;
- return strbuf_realpath(&realpath, path, 1);
-}
-
-const char *real_path_if_valid(const char *path)
-{
- static struct strbuf realpath = STRBUF_INIT;
- return strbuf_realpath(&realpath, path, 0);
-}
-
char *real_pathdup(const char *path, int die_on_error)
{
struct strbuf realpath = STRBUF_INIT;
@@ -233,7 +217,7 @@ char *real_pathdup(const char *path, int die_on_error)
/*
* Use this to get an absolute path from a relative one. If you want
- * to resolve links, you should use real_path.
+ * to resolve links, you should use strbuf_realpath.
*/
const char *absolute_path(const char *path)
{
diff --git a/add-interactive.c b/add-interactive.c
index 6a5048c..4a9bf85 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -52,6 +52,24 @@ void init_add_i_state(struct add_i_state *s, struct repository *r)
diff_get_color(s->use_color, DIFF_FILE_OLD));
init_color(r, s, "new", s->file_new_color,
diff_get_color(s->use_color, DIFF_FILE_NEW));
+
+ FREE_AND_NULL(s->interactive_diff_filter);
+ git_config_get_string("interactive.difffilter",
+ &s->interactive_diff_filter);
+
+ FREE_AND_NULL(s->interactive_diff_algorithm);
+ git_config_get_string("diff.algorithm",
+ &s->interactive_diff_algorithm);
+
+ git_config_get_bool("interactive.singlekey", &s->use_single_key);
+}
+
+void clear_add_i_state(struct add_i_state *s)
+{
+ FREE_AND_NULL(s->interactive_diff_filter);
+ FREE_AND_NULL(s->interactive_diff_algorithm);
+ memset(s, 0, sizeof(*s));
+ s->use_color = -1;
}
/*
@@ -326,7 +344,10 @@ static ssize_t list_and_choose(struct add_i_state *s,
if (endp == p + sep)
to = from + 1;
else if (*endp == '-') {
- to = strtoul(++endp, &endp, 10);
+ if (isdigit(*(++endp)))
+ to = strtoul(endp, &endp, 10);
+ else
+ to = items->items.nr;
/* extra characters after the range? */
if (endp != p + sep)
from = -1;
@@ -913,7 +934,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
opts->prompt = N_("Patch update");
count = list_and_choose(s, files, opts);
- if (count >= 0) {
+ if (count > 0) {
struct argv_array args = ARGV_ARRAY_INIT;
struct pathspec ps_selected = { 0 };
@@ -924,7 +945,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
parse_pathspec(&ps_selected,
PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
PATHSPEC_LITERAL_PATH, "", args.argv);
- res = run_add_p(s->r, &ps_selected);
+ res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
argv_array_clear(&args);
clear_pathspec(&ps_selected);
}
@@ -954,7 +975,7 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
opts->flags = IMMEDIATE;
count = list_and_choose(s, files, opts);
opts->flags = 0;
- if (count >= 0) {
+ if (count > 0) {
struct argv_array args = ARGV_ARRAY_INIT;
argv_array_pushl(&args, "git", "diff", "-p", "--cached",
@@ -1149,6 +1170,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps)
strbuf_release(&print_file_item_data.worktree);
strbuf_release(&header);
prefix_item_list_clear(&commands);
+ clear_add_i_state(&s);
return res;
}
diff --git a/add-interactive.h b/add-interactive.h
index 062dc36..693f125 100644
--- a/add-interactive.h
+++ b/add-interactive.h
@@ -15,13 +15,27 @@ struct add_i_state {
char context_color[COLOR_MAXLEN];
char file_old_color[COLOR_MAXLEN];
char file_new_color[COLOR_MAXLEN];
+
+ int use_single_key;
+ char *interactive_diff_filter, *interactive_diff_algorithm;
};
void init_add_i_state(struct add_i_state *s, struct repository *r);
+void clear_add_i_state(struct add_i_state *s);
struct repository;
struct pathspec;
int run_add_i(struct repository *r, const struct pathspec *ps);
-int run_add_p(struct repository *r, const struct pathspec *ps);
+
+enum add_p_mode {
+ ADD_P_ADD,
+ ADD_P_STASH,
+ ADD_P_RESET,
+ ADD_P_CHECKOUT,
+ ADD_P_WORKTREE,
+};
+
+int run_add_p(struct repository *r, enum add_p_mode mode,
+ const char *revision, const struct pathspec *ps);
#endif
diff --git a/add-patch.c b/add-patch.c
index 2c46fe5..d8dafa8 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -6,15 +6,218 @@
#include "pathspec.h"
#include "color.h"
#include "diff.h"
+#include "compat/terminal.h"
enum prompt_mode_type {
- PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
+ PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
+ PROMPT_MODE_MAX, /* must be last */
};
-static const char *prompt_mode[] = {
- N_("Stage mode change [y,n,a,q,d%s,?]? "),
- N_("Stage deletion [y,n,a,q,d%s,?]? "),
- N_("Stage this hunk [y,n,a,q,d%s,?]? ")
+struct patch_mode {
+ /*
+ * The magic constant 4 is chosen such that all patch modes
+ * provide enough space for three command-line arguments followed by a
+ * trailing `NULL`.
+ */
+ const char *diff_cmd[4], *apply_args[4], *apply_check_args[4];
+ unsigned is_reverse:1, index_only:1, apply_for_checkout:1;
+ const char *prompt_mode[PROMPT_MODE_MAX];
+ const char *edit_hunk_hint, *help_patch_text;
+};
+
+static struct patch_mode patch_mode_add = {
+ .diff_cmd = { "diff-files", NULL },
+ .apply_args = { "--cached", NULL },
+ .apply_check_args = { "--cached", NULL },
+ .prompt_mode = {
+ N_("Stage mode change [y,n,q,a,d%s,?]? "),
+ N_("Stage deletion [y,n,q,a,d%s,?]? "),
+ N_("Stage this hunk [y,n,q,a,d%s,?]? ")
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for staging."),
+ .help_patch_text =
+ N_("y - stage this hunk\n"
+ "n - do not stage this hunk\n"
+ "q - quit; do not stage this hunk or any of the remaining "
+ "ones\n"
+ "a - stage this hunk and all later hunks in the file\n"
+ "d - do not stage this hunk or any of the later hunks in "
+ "the file\n")
+};
+
+static struct patch_mode patch_mode_stash = {
+ .diff_cmd = { "diff-index", "HEAD", NULL },
+ .apply_args = { "--cached", NULL },
+ .apply_check_args = { "--cached", NULL },
+ .prompt_mode = {
+ N_("Stash mode change [y,n,q,a,d%s,?]? "),
+ N_("Stash deletion [y,n,q,a,d%s,?]? "),
+ N_("Stash this hunk [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for stashing."),
+ .help_patch_text =
+ N_("y - stash this hunk\n"
+ "n - do not stash this hunk\n"
+ "q - quit; do not stash this hunk or any of the remaining "
+ "ones\n"
+ "a - stash this hunk and all later hunks in the file\n"
+ "d - do not stash this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_head = {
+ .diff_cmd = { "diff-index", "--cached", NULL },
+ .apply_args = { "-R", "--cached", NULL },
+ .apply_check_args = { "-R", "--cached", NULL },
+ .is_reverse = 1,
+ .index_only = 1,
+ .prompt_mode = {
+ N_("Unstage mode change [y,n,q,a,d%s,?]? "),
+ N_("Unstage deletion [y,n,q,a,d%s,?]? "),
+ N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for unstaging."),
+ .help_patch_text =
+ N_("y - unstage this hunk\n"
+ "n - do not unstage this hunk\n"
+ "q - quit; do not unstage this hunk or any of the remaining "
+ "ones\n"
+ "a - unstage this hunk and all later hunks in the file\n"
+ "d - do not unstage this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_nothead = {
+ .diff_cmd = { "diff-index", "-R", "--cached", NULL },
+ .apply_args = { "--cached", NULL },
+ .apply_check_args = { "--cached", NULL },
+ .index_only = 1,
+ .prompt_mode = {
+ N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
+ N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
+ N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for applying."),
+ .help_patch_text =
+ N_("y - apply this hunk to index\n"
+ "n - do not apply this hunk to index\n"
+ "q - quit; do not apply this hunk or any of the remaining "
+ "ones\n"
+ "a - apply this hunk and all later hunks in the file\n"
+ "d - do not apply this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_index = {
+ .diff_cmd = { "diff-files", NULL },
+ .apply_args = { "-R", NULL },
+ .apply_check_args = { "-R", NULL },
+ .is_reverse = 1,
+ .prompt_mode = {
+ N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for discarding."),
+ .help_patch_text =
+ N_("y - discard this hunk from worktree\n"
+ "n - do not discard this hunk from worktree\n"
+ "q - quit; do not discard this hunk or any of the remaining "
+ "ones\n"
+ "a - discard this hunk and all later hunks in the file\n"
+ "d - do not discard this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_head = {
+ .diff_cmd = { "diff-index", NULL },
+ .apply_for_checkout = 1,
+ .apply_check_args = { "-R", NULL },
+ .is_reverse = 1,
+ .prompt_mode = {
+ N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for discarding."),
+ .help_patch_text =
+ N_("y - discard this hunk from index and worktree\n"
+ "n - do not discard this hunk from index and worktree\n"
+ "q - quit; do not discard this hunk or any of the remaining "
+ "ones\n"
+ "a - discard this hunk and all later hunks in the file\n"
+ "d - do not discard this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_nothead = {
+ .diff_cmd = { "diff-index", "-R", NULL },
+ .apply_for_checkout = 1,
+ .apply_check_args = { NULL },
+ .prompt_mode = {
+ N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for applying."),
+ .help_patch_text =
+ N_("y - apply this hunk to index and worktree\n"
+ "n - do not apply this hunk to index and worktree\n"
+ "q - quit; do not apply this hunk or any of the remaining "
+ "ones\n"
+ "a - apply this hunk and all later hunks in the file\n"
+ "d - do not apply this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_head = {
+ .diff_cmd = { "diff-index", NULL },
+ .apply_args = { "-R", NULL },
+ .apply_check_args = { "-R", NULL },
+ .is_reverse = 1,
+ .prompt_mode = {
+ N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for discarding."),
+ .help_patch_text =
+ N_("y - discard this hunk from worktree\n"
+ "n - do not discard this hunk from worktree\n"
+ "q - quit; do not discard this hunk or any of the remaining "
+ "ones\n"
+ "a - discard this hunk and all later hunks in the file\n"
+ "d - do not discard this hunk or any of the later hunks in "
+ "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_nothead = {
+ .diff_cmd = { "diff-index", "-R", NULL },
+ .apply_args = { NULL },
+ .apply_check_args = { NULL },
+ .prompt_mode = {
+ N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+ N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+ },
+ .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+ "will immediately be marked for applying."),
+ .help_patch_text =
+ N_("y - apply this hunk to worktree\n"
+ "n - do not apply this hunk to worktree\n"
+ "q - quit; do not apply this hunk or any of the remaining "
+ "ones\n"
+ "a - apply this hunk and all later hunks in the file\n"
+ "d - do not apply this hunk or any of the later hunks in "
+ "the file\n"),
};
struct hunk_header {
@@ -47,6 +250,10 @@ struct add_p_state {
unsigned deleted:1, mode_change:1,binary:1;
} *file_diff;
size_t file_diff_nr;
+
+ /* patch mode */
+ struct patch_mode *mode;
+ const char *revision;
};
static void err(struct add_p_state *s, const char *fmt, ...)
@@ -154,6 +361,7 @@ static int is_octal(const char *p, size_t len)
static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
{
struct argv_array args = ARGV_ARRAY_INIT;
+ const char *diff_algorithm = s->s.interactive_diff_algorithm;
struct strbuf *plain = &s->plain, *colored = NULL;
struct child_process cp = CHILD_PROCESS_INIT;
char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -162,9 +370,20 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
struct hunk *hunk = NULL;
int res;
+ argv_array_pushv(&args, s->mode->diff_cmd);
+ if (diff_algorithm)
+ argv_array_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+ if (s->revision) {
+ struct object_id oid;
+ argv_array_push(&args,
+ /* could be on an unborn branch */
+ !strcmp("HEAD", s->revision) &&
+ get_oid("HEAD", &oid) ?
+ empty_tree_oid_hex() : s->revision);
+ }
+ color_arg_index = args.argc;
/* Use `--no-color` explicitly, just in case `diff.color = always`. */
- argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
- color_arg_index = args.argc - 2;
+ argv_array_pushl(&args, "--no-color", "-p", "--", NULL);
for (i = 0; i < ps->nr; i++)
argv_array_push(&args, ps->items[i].original);
@@ -183,6 +402,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
if (want_color_fd(1, -1)) {
struct child_process colored_cp = CHILD_PROCESS_INIT;
+ const char *diff_filter = s->s.interactive_diff_filter;
setup_child_process(s, &colored_cp, NULL);
xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
@@ -192,6 +412,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
argv_array_clear(&args);
if (res)
return error(_("could not parse colored diff"));
+
+ if (diff_filter) {
+ struct child_process filter_cp = CHILD_PROCESS_INIT;
+
+ setup_child_process(s, &filter_cp,
+ diff_filter, NULL);
+ filter_cp.git_cmd = 0;
+ filter_cp.use_shell = 1;
+ strbuf_reset(&s->buf);
+ if (pipe_command(&filter_cp,
+ colored->buf, colored->len,
+ &s->buf, colored->len,
+ NULL, 0) < 0)
+ return error(_("failed to run '%s'"),
+ diff_filter);
+ strbuf_swap(colored, &s->buf);
+ }
+
strbuf_complete_line(colored);
colored_p = colored->buf;
colored_pend = colored_p + colored->len;
@@ -316,6 +554,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
colored_pend - colored_p);
if (colored_eol)
colored_p = colored_eol + 1;
+ else if (p != pend)
+ /* colored shorter than non-colored? */
+ goto mismatched_output;
else
colored_p = colored_pend;
@@ -340,6 +581,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
*/
hunk->splittable_into++;
+ /* non-colored shorter than colored? */
+ if (colored_p != colored_pend) {
+mismatched_output:
+ error(_("mismatched output from interactive.diffFilter"));
+ advise(_("Your filter must maintain a one-to-one correspondence\n"
+ "between its input and output lines."));
+ return -1;
+ }
+
return 0;
}
@@ -382,7 +632,10 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
- header->colored_extra_start;
}
- new_offset += delta;
+ if (s->mode->is_reverse)
+ old_offset -= delta;
+ else
+ new_offset += delta;
strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
old_offset, header->old_count,
@@ -805,11 +1058,10 @@ static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
"(context).\n"
"To remove '%c' lines, delete them.\n"
"Lines starting with %c will be removed.\n"),
- '-', '+', comment_line_char);
- strbuf_commented_addf(&s->buf,
- _("If the patch applies cleanly, the edited hunk "
- "will immediately be\n"
- "marked for staging.\n"));
+ s->mode->is_reverse ? '+' : '-',
+ s->mode->is_reverse ? '-' : '+',
+ comment_line_char);
+ strbuf_commented_addf(&s->buf, "%s", _(s->mode->edit_hunk_hint));
/*
* TRANSLATORS: 'it' refers to the patch mentioned in the previous
* messages.
@@ -890,21 +1142,35 @@ static int run_apply_check(struct add_p_state *s,
reassemble_patch(s, file_diff, 1, &s->buf);
setup_child_process(s, &cp,
- "apply", "--cached", "--check", NULL);
+ "apply", "--check", NULL);
+ argv_array_pushv(&cp.args, s->mode->apply_check_args);
if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
return error(_("'git apply --cached' failed"));
return 0;
}
+static int read_single_character(struct add_p_state *s)
+{
+ if (s->s.use_single_key) {
+ int res = read_key_without_echo(&s->answer);
+ printf("%s\n", res == EOF ? "" : s->answer.buf);
+ return res;
+ }
+
+ if (strbuf_getline(&s->answer, stdin) == EOF)
+ return EOF;
+ strbuf_trim_trailing_newline(&s->answer);
+ return 0;
+}
+
static int prompt_yesno(struct add_p_state *s, const char *prompt)
{
for (;;) {
color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
fflush(stdout);
- if (strbuf_getline(&s->answer, stdin) == EOF)
+ if (read_single_character(s) == EOF)
return -1;
- strbuf_trim_trailing_newline(&s->answer);
switch (tolower(s->answer.buf[0])) {
case 'n': return 0;
case 'y': return 1;
@@ -957,6 +1223,57 @@ static int edit_hunk_loop(struct add_p_state *s,
}
}
+static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff,
+ int is_reverse)
+{
+ const char *reverse = is_reverse ? "-R" : NULL;
+ struct child_process check_index = CHILD_PROCESS_INIT;
+ struct child_process check_worktree = CHILD_PROCESS_INIT;
+ struct child_process apply_index = CHILD_PROCESS_INIT;
+ struct child_process apply_worktree = CHILD_PROCESS_INIT;
+ int applies_index, applies_worktree;
+
+ setup_child_process(s, &check_index,
+ "apply", "--cached", "--check", reverse, NULL);
+ applies_index = !pipe_command(&check_index, diff->buf, diff->len,
+ NULL, 0, NULL, 0);
+
+ setup_child_process(s, &check_worktree,
+ "apply", "--check", reverse, NULL);
+ applies_worktree = !pipe_command(&check_worktree, diff->buf, diff->len,
+ NULL, 0, NULL, 0);
+
+ if (applies_worktree && applies_index) {
+ setup_child_process(s, &apply_index,
+ "apply", "--cached", reverse, NULL);
+ pipe_command(&apply_index, diff->buf, diff->len,
+ NULL, 0, NULL, 0);
+
+ setup_child_process(s, &apply_worktree,
+ "apply", reverse, NULL);
+ pipe_command(&apply_worktree, diff->buf, diff->len,
+ NULL, 0, NULL, 0);
+
+ return 1;
+ }
+
+ if (!applies_index) {
+ err(s, _("The selected hunks do not apply to the index!"));
+ if (prompt_yesno(s, _("Apply them to the worktree "
+ "anyway? ")) > 0) {
+ setup_child_process(s, &apply_worktree,
+ "apply", reverse, NULL);
+ return pipe_command(&apply_worktree, diff->buf,
+ diff->len, NULL, 0, NULL, 0);
+ }
+ err(s, _("Nothing was applied.\n"));
+ } else
+ /* As a last resort, show the diff to the user */
+ fwrite(diff->buf, diff->len, 1, stderr);
+
+ return 0;
+}
+
#define SUMMARY_HEADER_WIDTH 20
#define SUMMARY_LINE_WIDTH 80
static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
@@ -1005,13 +1322,6 @@ static size_t display_hunks(struct add_p_state *s,
return end_index;
}
-static const char help_patch_text[] =
-N_("y - stage this hunk\n"
- "n - do not stage this hunk\n"
- "q - quit; do not stage this hunk or any of the remaining ones\n"
- "a - stage this and all the remaining hunks\n"
- "d - do not stage this hunk nor any of the remaining hunks\n");
-
static const char help_patch_remainder[] =
N_("j - leave this hunk undecided, see next undecided hunk\n"
"J - leave this hunk undecided, see next hunk\n"
@@ -1097,11 +1407,11 @@ static int patch_update_file(struct add_p_state *s,
(uintmax_t)hunk_index + 1,
(uintmax_t)file_diff->hunk_nr);
color_fprintf(stdout, s->s.prompt_color,
- _(prompt_mode[prompt_mode_type]), s->buf.buf);
+ _(s->mode->prompt_mode[prompt_mode_type]),
+ s->buf.buf);
fflush(stdout);
- if (strbuf_getline(&s->answer, stdin) == EOF)
+ if (read_single_character(s) == EOF)
break;
- strbuf_trim_trailing_newline(&s->answer);
if (!s->answer.len)
continue;
@@ -1254,7 +1564,7 @@ soft_increment:
const char *p = _(help_patch_remainder), *eol = p;
color_fprintf(stdout, s->s.help_color, "%s",
- _(help_patch_text));
+ _(s->mode->help_patch_text));
/*
* Show only those lines of the remainder that are
@@ -1288,10 +1598,16 @@ soft_increment:
reassemble_patch(s, file_diff, 0, &s->buf);
discard_index(s->s.r->index);
- setup_child_process(s, &cp, "apply", "--cached", NULL);
- if (pipe_command(&cp, s->buf.buf, s->buf.len,
- NULL, 0, NULL, 0))
- error(_("'git apply --cached' failed"));
+ if (s->mode->apply_for_checkout)
+ apply_for_checkout(s, &s->buf,
+ s->mode->is_reverse);
+ else {
+ setup_child_process(s, &cp, "apply", NULL);
+ argv_array_pushv(&cp.args, s->mode->apply_args);
+ if (pipe_command(&cp, s->buf.buf, s->buf.len,
+ NULL, 0, NULL, 0))
+ error(_("'git apply' failed"));
+ }
if (!repo_read_index(s->s.r))
repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
1, NULL, NULL, NULL);
@@ -1301,7 +1617,8 @@ soft_increment:
return quit;
}
-int run_add_p(struct repository *r, const struct pathspec *ps)
+int run_add_p(struct repository *r, enum add_p_mode mode,
+ const char *revision, const struct pathspec *ps)
{
struct add_p_state s = {
{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -1310,12 +1627,39 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
init_add_i_state(&s.s, r);
+ if (mode == ADD_P_STASH)
+ s.mode = &patch_mode_stash;
+ else if (mode == ADD_P_RESET) {
+ if (!revision || !strcmp(revision, "HEAD"))
+ s.mode = &patch_mode_reset_head;
+ else
+ s.mode = &patch_mode_reset_nothead;
+ } else if (mode == ADD_P_CHECKOUT) {
+ if (!revision)
+ s.mode = &patch_mode_checkout_index;
+ else if (!strcmp(revision, "HEAD"))
+ s.mode = &patch_mode_checkout_head;
+ else
+ s.mode = &patch_mode_checkout_nothead;
+ } else if (mode == ADD_P_WORKTREE) {
+ if (!revision)
+ s.mode = &patch_mode_checkout_index;
+ else if (!strcmp(revision, "HEAD"))
+ s.mode = &patch_mode_worktree_head;
+ else
+ s.mode = &patch_mode_worktree_nothead;
+ } else
+ s.mode = &patch_mode_add;
+ s.revision = revision;
+
if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
- repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
- NULL, NULL, NULL) < 0 ||
+ (!s.mode->index_only &&
+ repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+ NULL, NULL, NULL) < 0) ||
parse_diff(&s, ps) < 0) {
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
return -1;
}
@@ -1334,5 +1678,6 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
strbuf_release(&s.buf);
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
return 0;
}
diff --git a/advice.c b/advice.c
index 249c60d..f0a3d32 100644
--- a/advice.c
+++ b/advice.c
@@ -29,8 +29,9 @@ int advice_ignored_hook = 1;
int advice_waiting_for_editor = 1;
int advice_graft_file_deprecated = 1;
int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_nested_tag = 1;
int advice_submodule_alternate_error_strategy_die = 1;
+int advice_add_ignored_file = 1;
+int advice_add_empty_pathspec = 1;
static int advice_use_color = -1;
static char advice_colors[][COLOR_MAXLEN] = {
@@ -80,7 +81,7 @@ static struct {
{ "sequencerInUse", &advice_sequencer_in_use },
{ "implicitIdentity", &advice_implicit_identity },
{ "detachedHead", &advice_detached_head },
- { "setupStreamFailure", &advice_set_upstream_failure },
+ { "setUpstreamFailure", &advice_set_upstream_failure },
{ "objectNameWarning", &advice_object_name_warning },
{ "amWorkDir", &advice_amworkdir },
{ "rmHints", &advice_rm_hints },
@@ -89,22 +90,66 @@ static struct {
{ "waitingForEditor", &advice_waiting_for_editor },
{ "graftFileDeprecated", &advice_graft_file_deprecated },
{ "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
- { "nestedTag", &advice_nested_tag },
{ "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
+ { "addIgnoredFile", &advice_add_ignored_file },
+ { "addEmptyPathspec", &advice_add_empty_pathspec },
/* make this an alias for backward compatibility */
{ "pushNonFastForward", &advice_push_update_rejected }
};
-void advise(const char *advice, ...)
+static struct {
+ const char *key;
+ int enabled;
+} advice_setting[] = {
+ [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 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_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 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 },
+
+ /* make this an alias for backward compatibility */
+ [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 },
+
+ [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent", 1 },
+ [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching", 1 },
+ [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName", 1 },
+ [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected", 1 },
+ [ADVICE_RESET_QUIET_WARNING] = { "resetQuiet", 1 },
+ [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict", 1 },
+ [ADVICE_RM_HINTS] = { "rmHints", 1 },
+ [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 },
+ [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 },
+ [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 },
+ [ADVICE_STATUS_HINTS] = { "statusHints", 1 },
+ [ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
+ [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+ [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
+};
+
+static const char turn_off_instructions[] =
+N_("\n"
+ "Disable this message with \"git config advice.%s false\"");
+
+static void vadvise(const char *advice, int display_instructions,
+ const char *key, va_list params)
{
struct strbuf buf = STRBUF_INIT;
- va_list params;
const char *cp, *np;
- va_start(params, advice);
strbuf_vaddf(&buf, advice, params);
- va_end(params);
+
+ if (display_instructions)
+ strbuf_addf(&buf, turn_off_instructions, key);
for (cp = buf.buf; *cp; cp = np) {
np = strchrnul(cp, '\n');
@@ -118,6 +163,37 @@ void advise(const char *advice, ...)
strbuf_release(&buf);
}
+void advise(const char *advice, ...)
+{
+ va_list params;
+ va_start(params, advice);
+ vadvise(advice, 0, "", params);
+ va_end(params);
+}
+
+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;
+ }
+}
+
+void advise_if_enabled(enum advice_type type, const char *advice, ...)
+{
+ va_list params;
+
+ if (!advice_enabled(type))
+ return;
+
+ va_start(params, advice);
+ vadvise(advice, 1, advice_setting[type].key, params);
+ va_end(params);
+}
+
int git_default_advice_config(const char *var, const char *value)
{
const char *k, *slot_name;
@@ -144,6 +220,13 @@ int git_default_advice_config(const char *var, const char *value)
if (strcasecmp(k, advice_config[i].name))
continue;
*advice_config[i].preference = git_config_bool(var, value);
+ break;
+ }
+
+ 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);
return 0;
}
@@ -154,8 +237,8 @@ void list_config_advices(struct string_list *list, const char *prefix)
{
int i;
- for (i = 0; i < ARRAY_SIZE(advice_config); i++)
- list_config_item(list, prefix, advice_config[i].name);
+ for (i = 0; i < ARRAY_SIZE(advice_setting); i++)
+ list_config_item(list, prefix, advice_setting[i].key);
}
int error_resolve_conflict(const char *me)
diff --git a/advice.h b/advice.h
index b706780..16f2c11 100644
--- a/advice.h
+++ b/advice.h
@@ -29,12 +29,64 @@ extern int advice_ignored_hook;
extern int advice_waiting_for_editor;
extern int advice_graft_file_deprecated;
extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_nested_tag;
extern int advice_submodule_alternate_error_strategy_die;
+extern int advice_add_ignored_file;
+extern int advice_add_empty_pathspec;
+
+/*
+ * To add a new advice, you need to:
+ * Define a new advice_type.
+ * Add a new entry to advice_setting array.
+ * Add the new config variable to Documentation/config/advice.txt.
+ * Call advise_if_enabled to print your advice.
+ */
+ enum advice_type {
+ ADVICE_ADD_EMBEDDED_REPO,
+ ADVICE_AM_WORK_DIR,
+ ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
+ ADVICE_COMMIT_BEFORE_MERGE,
+ ADVICE_DETACHED_HEAD,
+ ADVICE_FETCH_SHOW_FORCED_UPDATES,
+ ADVICE_GRAFT_FILE_DEPRECATED,
+ ADVICE_IGNORED_HOOK,
+ ADVICE_IMPLICIT_IDENTITY,
+ ADVICE_NESTED_TAG,
+ ADVICE_OBJECT_NAME_WARNING,
+ ADVICE_PUSH_ALREADY_EXISTS,
+ ADVICE_PUSH_FETCH_FIRST,
+ ADVICE_PUSH_NEEDS_FORCE,
+ ADVICE_PUSH_NON_FF_CURRENT,
+ ADVICE_PUSH_NON_FF_MATCHING,
+ ADVICE_PUSH_UNQUALIFIED_REF_NAME,
+ ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
+ ADVICE_PUSH_UPDATE_REJECTED,
+ ADVICE_RESET_QUIET_WARNING,
+ ADVICE_RESOLVE_CONFLICT,
+ ADVICE_RM_HINTS,
+ ADVICE_SEQUENCER_IN_USE,
+ ADVICE_SET_UPSTREAM_FAILURE,
+ ADVICE_STATUS_AHEAD_BEHIND_WARNING,
+ ADVICE_STATUS_HINTS,
+ ADVICE_STATUS_U_OPTION,
+ ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+ ADVICE_WAITING_FOR_EDITOR,
+};
int git_default_advice_config(const char *var, const char *value);
__attribute__((format (printf, 1, 2)))
void advise(const char *advice, ...);
+
+/**
+ * Checks if advice type is enabled (can be printed to the user).
+ * Should be called before advise().
+ */
+int advice_enabled(enum advice_type type);
+
+/**
+ * Checks the visibility of the advice before printing.
+ */
+void advise_if_enabled(enum advice_type type, const char *advice, ...);
+
int error_resolve_conflict(const char *me);
void NORETURN die_resolve_conflict(const char *me);
void NORETURN die_conclude_merge(void);
diff --git a/apply.c b/apply.c
index fab4432..144c19a 100644
--- a/apply.c
+++ b/apply.c
@@ -3157,7 +3157,8 @@ static int apply_binary(struct apply_state *state,
* See if the old one matches what the patch
* applies to.
*/
- hash_object_file(img->buf, img->len, blob_type, &oid);
+ hash_object_file(the_hash_algo, img->buf, img->len, blob_type,
+ &oid);
if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix))
return error(_("the patch applies to '%s' (%s), "
"which does not match the "
@@ -3202,7 +3203,8 @@ static int apply_binary(struct apply_state *state,
name);
/* verify that the result matches */
- hash_object_file(img->buf, img->len, blob_type, &oid);
+ hash_object_file(the_hash_algo, img->buf, img->len, blob_type,
+ &oid);
if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix))
return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
name, patch->new_oid_prefix, oid_to_hex(&oid));
@@ -4347,7 +4349,7 @@ static int try_create_file(struct apply_state *state, const char *path,
if (fd < 0)
return 1;
- if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) {
+ if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) {
size = nbuf.len;
buf = nbuf.buf;
}
diff --git a/archive-tar.c b/archive-tar.c
index e16d3f7..5a77701 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -112,7 +112,7 @@ static void write_trailer(void)
* queues up writes, so that all our write(2) calls write exactly one
* full block; pads writes to RECORDSIZE
*/
-static int stream_blocked(const struct object_id *oid)
+static int stream_blocked(struct repository *r, const struct object_id *oid)
{
struct git_istream *st;
enum object_type type;
@@ -120,7 +120,7 @@ static int stream_blocked(const struct object_id *oid)
char buf[BLOCKSIZE];
ssize_t readlen;
- st = open_istream(oid, &type, &sz, NULL);
+ st = open_istream(r, oid, &type, &sz, NULL);
if (!st)
return error(_("cannot stream blob %s"), oid_to_hex(oid));
for (;;) {
@@ -324,7 +324,7 @@ static int write_tar_entry(struct archiver_args *args,
if (buffer)
write_blocked(buffer, size);
else
- err = stream_blocked(oid);
+ err = stream_blocked(args->repo, oid);
}
free(buffer);
return err;
diff --git a/archive-zip.c b/archive-zip.c
index 11f5b19..e9f4262 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -345,7 +345,8 @@ static int write_zip_entry(struct archiver_args *args,
if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert &&
size > big_file_threshold) {
- stream = open_istream(oid, &type, &size, NULL);
+ stream = open_istream(args->repo, oid, &type, &size,
+ NULL);
if (!stream)
return error(_("cannot stream blob %s"),
oid_to_hex(oid));
diff --git a/archive.c b/archive.c
index a8da0fc..fb39706 100644
--- a/archive.c
+++ b/archive.c
@@ -77,6 +77,11 @@ void *object_file_to_archive(const struct archiver_args *args,
{
void *buffer;
const struct commit *commit = args->convert ? args->commit : NULL;
+ struct checkout_metadata meta;
+
+ init_checkout_metadata(&meta, args->refname,
+ args->commit_oid ? args->commit_oid :
+ (args->tree ? &args->tree->object.oid : NULL), oid);
path += args->baselen;
buffer = read_object_file(oid, type, sizep);
@@ -85,7 +90,7 @@ void *object_file_to_archive(const struct archiver_args *args,
size_t size = 0;
strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
- convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf);
+ convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
if (commit)
format_subst(commit, buf.buf, buf.len, &buf);
buffer = strbuf_detach(&buf, &size);
@@ -385,16 +390,17 @@ static void parse_treeish_arg(const char **argv,
struct tree *tree;
const struct commit *commit;
struct object_id oid;
+ char *ref = NULL;
/* Remotes are only allowed to fetch actual refs */
if (remote && !remote_allow_unreachable) {
- char *ref = NULL;
const char *colon = strchrnul(name, ':');
int refnamelen = colon - name;
if (!dwim_ref(name, refnamelen, &oid, &ref))
die(_("no such ref: %.*s"), refnamelen, name);
- free(ref);
+ } else {
+ dwim_ref(name, strlen(name), &oid, &ref);
}
if (get_oid(name, &oid))
@@ -427,6 +433,7 @@ static void parse_treeish_arg(const char **argv,
tree = parse_tree_indirect(&tree_oid);
}
+ ar_args->refname = ref;
ar_args->tree = tree;
ar_args->commit_oid = commit_oid;
ar_args->commit = commit;
diff --git a/archive.h b/archive.h
index e60e3dd..3bd96bf 100644
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct repository;
struct archiver_args {
struct repository *repo;
+ const char *refname;
const char *base;
size_t baselen;
struct tree *tree;
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index af2a5ea..675c3a4 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -5,7 +5,8 @@ jobs:
- job: windows_build
displayName: Windows Build
condition: succeeded()
- pool: Hosted
+ pool:
+ vmImage: windows-latest
timeoutInMinutes: 240
steps:
- powershell: |
@@ -61,7 +62,8 @@ jobs:
displayName: Windows Test
dependsOn: windows_build
condition: succeeded()
- pool: Hosted
+ pool:
+ vmImage: windows-latest
timeoutInMinutes: 240
strategy:
parallel: 10
@@ -133,7 +135,8 @@ jobs:
- job: vs_build
displayName: Visual Studio Build
condition: succeeded()
- pool: Hosted VS2017
+ pool:
+ vmImage: windows-latest
timeoutInMinutes: 240
steps:
- powershell: |
@@ -181,6 +184,7 @@ jobs:
platform: x64
configuration: Release
maximumCpuCount: 4
+ msbuildArguments: /p:PlatformToolset=v142
- powershell: |
& compat\vcbuild\vcpkg_copy_dlls.bat release
if (!$?) { exit(1) }
@@ -224,7 +228,8 @@ jobs:
displayName: Visual Studio Test
dependsOn: vs_build
condition: succeeded()
- pool: Hosted
+ pool:
+ vmImage: windows-latest
timeoutInMinutes: 240
strategy:
parallel: 10
@@ -292,7 +297,8 @@ jobs:
- job: linux_clang
displayName: linux-clang
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -330,7 +336,8 @@ jobs:
- job: linux_gcc
displayName: linux-gcc
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -367,7 +374,8 @@ jobs:
- job: osx_clang
displayName: osx-clang
condition: succeeded()
- pool: Hosted macOS
+ pool:
+ vmImage: macOS-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -402,7 +410,8 @@ jobs:
- job: osx_gcc
displayName: osx-gcc
condition: succeeded()
- pool: Hosted macOS
+ pool:
+ vmImage: macOS-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -435,7 +444,8 @@ jobs:
- job: gettext_poison
displayName: GETTEXT_POISON
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -472,7 +482,8 @@ jobs:
- job: linux32
displayName: Linux32
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -506,7 +517,8 @@ jobs:
- job: static_analysis
displayName: StaticAnalysis
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
@@ -526,7 +538,8 @@ jobs:
- job: documentation
displayName: Documentation
condition: succeeded()
- pool: Hosted Ubuntu 1604
+ pool:
+ vmImage: ubuntu-latest
steps:
- bash: |
test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
diff --git a/bisect.c b/bisect.c
index e81c91d..9154f81 100644
--- a/bisect.c
+++ b/bisect.c
@@ -572,7 +572,7 @@ static int sqrti(int val)
{
float d, x = val;
- if (val == 0)
+ if (!val)
return 0;
do {
@@ -661,11 +661,11 @@ static void bisect_common(struct rev_info *revs)
mark_edges_uninteresting(revs, NULL, 0);
}
-static void exit_if_skipped_commits(struct commit_list *tried,
+static enum bisect_error error_if_skipped_commits(struct commit_list *tried,
const struct object_id *bad)
{
if (!tried)
- return;
+ return BISECT_OK;
printf("There are only 'skip'ped commits left to test.\n"
"The first %s commit could be any of:\n", term_bad);
@@ -676,7 +676,8 @@ static void exit_if_skipped_commits(struct commit_list *tried,
if (bad)
printf("%s\n", oid_to_hex(bad));
printf(_("We cannot bisect more!\n"));
- exit(2);
+
+ return BISECT_ONLY_SKIPPED_LEFT;
}
static int is_expected_rev(const struct object_id *oid)
@@ -703,9 +704,10 @@ static int is_expected_rev(const struct object_id *oid)
return res;
}
-static int bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
+static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
{
char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
+ enum bisect_error res = BISECT_OK;
memcpy(bisect_rev_hex, oid_to_hex(bisect_rev), the_hash_algo->hexsz + 1);
update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
@@ -715,14 +717,24 @@ static int bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
} else {
- int res;
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
if (res)
- exit(res);
+ /*
+ * Errors in `run_command()` itself, signaled by res < 0,
+ * and errors in the child process, signaled by res > 0
+ * can both be treated as regular BISECT_FAILURE (-1).
+ */
+ return -abs(res);
}
argv_show_branch[1] = bisect_rev_hex;
- return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
+ res = run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
+ /*
+ * Errors in `run_command()` itself, signaled by res < 0,
+ * and errors in the child process, signaled by res > 0
+ * can both be treated as regular BISECT_FAILURE (-1).
+ */
+ return -abs(res);
}
static struct commit *get_commit_reference(struct repository *r,
@@ -749,7 +761,7 @@ static struct commit **get_bad_and_good_commits(struct repository *r,
return rev;
}
-static void handle_bad_merge_base(void)
+static enum bisect_error handle_bad_merge_base(void)
{
if (is_expected_rev(current_bad_oid)) {
char *bad_hex = oid_to_hex(current_bad_oid);
@@ -770,14 +782,14 @@ static void handle_bad_merge_base(void)
"between %s and [%s].\n"),
bad_hex, term_bad, term_good, bad_hex, good_hex);
}
- exit(3);
+ return BISECT_MERGE_BASE_CHECK;
}
fprintf(stderr, _("Some %s revs are not ancestors of the %s rev.\n"
"git bisect cannot work properly in this case.\n"
"Maybe you mistook %s and %s revs?\n"),
term_good, term_bad, term_good, term_bad);
- exit(1);
+ return BISECT_FAILED;
}
static void handle_skipped_merge_base(const struct object_id *mb)
@@ -799,13 +811,18 @@ static void handle_skipped_merge_base(const struct object_id *mb)
* "check_merge_bases" checks that merge bases are not "bad" (or "new").
*
* - If one is "bad" (or "new"), it means the user assumed something wrong
- * and we must exit with a non 0 error code.
+ * and we must return error with a non 0 error code.
* - If one is "good" (or "old"), that's good, we have nothing to do.
* - If one is "skipped", we can't know but we should warn.
* - If we don't know, we should check it out and ask the user to test.
+ * - If a merge base must be tested, on success return
+ * BISECT_INTERNAL_SUCCESS_MERGE_BASE (-11) a special condition
+ * for early success, this will be converted back to 0 in
+ * check_good_are_ancestors_of_bad().
*/
-static void check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)
+static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)
{
+ enum bisect_error res = BISECT_OK;
struct commit_list *result;
result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1);
@@ -813,18 +830,24 @@ static void check_merge_bases(int rev_nr, struct commit **rev, int no_checkout)
for (; result; result = result->next) {
const struct object_id *mb = &result->item->object.oid;
if (oideq(mb, current_bad_oid)) {
- handle_bad_merge_base();
+ res = handle_bad_merge_base();
+ break;
} else if (0 <= oid_array_lookup(&good_revs, mb)) {
continue;
} else if (0 <= oid_array_lookup(&skipped_revs, mb)) {
handle_skipped_merge_base(mb);
} else {
printf(_("Bisecting: a merge base must be tested\n"));
- exit(bisect_checkout(mb, no_checkout));
+ res = bisect_checkout(mb, no_checkout);
+ if (!res)
+ /* indicate early success */
+ res = BISECT_INTERNAL_SUCCESS_MERGE_BASE;
+ break;
}
}
free_commit_list(result);
+ return res;
}
static int check_ancestors(struct repository *r, int rev_nr,
@@ -850,43 +873,58 @@ static int check_ancestors(struct repository *r, int rev_nr,
*
* If that's not the case, we need to check the merge bases.
* If a merge base must be tested by the user, its source code will be
- * checked out to be tested by the user and we will exit.
+ * checked out to be tested by the user and we will return.
*/
-static void check_good_are_ancestors_of_bad(struct repository *r,
+
+static enum bisect_error check_good_are_ancestors_of_bad(struct repository *r,
const char *prefix,
int no_checkout)
{
- char *filename = git_pathdup("BISECT_ANCESTORS_OK");
+ char *filename;
struct stat st;
int fd, rev_nr;
+ enum bisect_error res = BISECT_OK;
struct commit **rev;
if (!current_bad_oid)
- die(_("a %s revision is needed"), term_bad);
+ return error(_("a %s revision is needed"), term_bad);
+
+ filename = git_pathdup("BISECT_ANCESTORS_OK");
/* Check if file BISECT_ANCESTORS_OK exists. */
if (!stat(filename, &st) && S_ISREG(st.st_mode))
goto done;
/* Bisecting with no good rev is ok. */
- if (good_revs.nr == 0)
+ if (!good_revs.nr)
goto done;
/* Check if all good revs are ancestor of the bad rev. */
+
rev = get_bad_and_good_commits(r, &rev_nr);
if (check_ancestors(r, rev_nr, rev, prefix))
- check_merge_bases(rev_nr, rev, no_checkout);
+ res = check_merge_bases(rev_nr, rev, no_checkout);
free(rev);
- /* Create file BISECT_ANCESTORS_OK. */
- fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- warning_errno(_("could not create file '%s'"),
- filename);
- else
- close(fd);
+ if (!res) {
+ /* Create file BISECT_ANCESTORS_OK. */
+ fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+ if (fd < 0)
+ /*
+ * BISECT_ANCESTORS_OK file is not absolutely necessary,
+ * the bisection process will continue at the next
+ * bisection step.
+ * So, just signal with a warning that something
+ * might be wrong.
+ */
+ warning_errno(_("could not create file '%s'"),
+ filename);
+ else
+ close(fd);
+ }
done:
free(filename);
+ return res;
}
/*
@@ -938,18 +976,19 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
}
/*
- * We use the convention that exiting with an exit code 10 means that
+ * We use the convention that return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND (-10) means
* the bisection process finished successfully.
- * In this case the calling shell script should exit 0.
- *
+ * In this case the calling function or command should not turn a
+ * BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND return code into an error or a non zero exit code.
* If no_checkout is non-zero, the bisection process does not
* checkout the trial commit but instead simply updates BISECT_HEAD.
*/
-int bisect_next_all(struct repository *r, const char *prefix, int no_checkout)
+enum bisect_error bisect_next_all(struct repository *r, const char *prefix, int no_checkout)
{
struct rev_info revs;
struct commit_list *tried;
int reaches = 0, all = 0, nr, steps;
+ enum bisect_error res = BISECT_OK;
struct object_id *bisect_rev;
char *steps_msg;
@@ -957,7 +996,9 @@ int bisect_next_all(struct repository *r, const char *prefix, int no_checkout)
if (read_bisect_refs())
die(_("reading bisect refs failed"));
- check_good_are_ancestors_of_bad(r, prefix, no_checkout);
+ res = check_good_are_ancestors_of_bad(r, prefix, no_checkout);
+ if (res)
+ return res;
bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1);
revs.limited = 1;
@@ -969,33 +1010,45 @@ int bisect_next_all(struct repository *r, const char *prefix, int no_checkout)
if (!revs.commits) {
/*
- * We should exit here only if the "bad"
+ * We should return error here only if the "bad"
* commit is also a "skip" commit.
*/
- exit_if_skipped_commits(tried, NULL);
-
+ res = error_if_skipped_commits(tried, NULL);
+ if (res < 0)
+ return res;
printf(_("%s was both %s and %s\n"),
oid_to_hex(current_bad_oid),
term_good,
term_bad);
- exit(1);
+
+ return BISECT_FAILED;
}
if (!all) {
fprintf(stderr, _("No testable commit found.\n"
"Maybe you started with bad path parameters?\n"));
- exit(4);
+
+ return BISECT_NO_TESTABLE_COMMIT;
}
bisect_rev = &revs.commits->item->object.oid;
if (oideq(bisect_rev, current_bad_oid)) {
- exit_if_skipped_commits(tried, current_bad_oid);
+ res = error_if_skipped_commits(tried, current_bad_oid);
+ if (res)
+ return res;
printf("%s is the first %s commit\n", oid_to_hex(bisect_rev),
term_bad);
+
show_diff_tree(r, prefix, revs.commits->item);
- /* This means the bisection process succeeded. */
- exit(10);
+ /*
+ * This means the bisection process succeeded.
+ * Using BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND (-10)
+ * so that the call chain can simply check
+ * for negative return values for early returns up
+ * until the cmd_bisect__helper() caller.
+ */
+ return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
}
nr = all - reaches - 1;
diff --git a/bisect.h b/bisect.h
index 4e69a11..8bad8d8 100644
--- a/bisect.h
+++ b/bisect.h
@@ -31,7 +31,34 @@ struct rev_list_info {
const char *header_prefix;
};
-int bisect_next_all(struct repository *r,
+/*
+ * enum bisect_error represents the following return codes:
+ * BISECT_OK: success code. Internally, it means that next
+ * commit has been found (and possibly checked out) and it
+ * should be tested.
+ * BISECT_FAILED error code: default error code.
+ * BISECT_ONLY_SKIPPED_LEFT error code: only skipped
+ * commits left to be tested.
+ * BISECT_MERGE_BASE_CHECK error code: merge base check failed.
+ * BISECT_NO_TESTABLE_COMMIT error code: no testable commit found.
+ * BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND early success code:
+ * first term_bad commit found.
+ * BISECT_INTERNAL_SUCCESS_MERGE_BASE early success
+ * code: found merge base that should be tested.
+ * Early success codes BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND and
+ * BISECT_INTERNAL_SUCCESS_MERGE_BASE should be only internal codes.
+ */
+enum bisect_error {
+ BISECT_OK = 0,
+ BISECT_FAILED = -1,
+ BISECT_ONLY_SKIPPED_LEFT = -2,
+ BISECT_MERGE_BASE_CHECK = -3,
+ BISECT_NO_TESTABLE_COMMIT = -4,
+ BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND = -10,
+ BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11
+};
+
+enum bisect_error bisect_next_all(struct repository *r,
const char *prefix,
int no_checkout);
diff --git a/blame.h b/blame.h
index 4a9e127..089b181 100644
--- a/blame.h
+++ b/blame.h
@@ -16,6 +16,8 @@
#define BLAME_DEFAULT_MOVE_SCORE 20
#define BLAME_DEFAULT_COPY_SCORE 40
+struct fingerprint;
+
/*
* One blob in a commit that is being suspected
*/
@@ -52,7 +54,7 @@ struct blame_origin {
struct blame_entry *suspects;
mmfile_t file;
int num_lines;
- void *fingerprints;
+ struct fingerprint *fingerprints;
struct object_id blob_oid;
unsigned short mode;
/* guilty gets set when shipping any suspects to the final
diff --git a/builtin/add.c b/builtin/add.c
index 4c38aff..18a0881 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -31,6 +31,7 @@ static int take_worktree_changes;
static int add_renormalize;
static int pathspec_file_nul;
static const char *pathspec_from_file;
+static int legacy_stash_p; /* support for the scripted `git stash` */
struct update_callback_data {
int flags;
@@ -196,12 +197,25 @@ int run_add_interactive(const char *revision, const char *patch_mode,
&use_builtin_add_i);
if (use_builtin_add_i == 1) {
+ enum add_p_mode mode;
+
if (!patch_mode)
return !!run_add_i(the_repository, pathspec);
- if (strcmp(patch_mode, "--patch"))
- die("'%s' not yet supported in the built-in add -p",
- patch_mode);
- return !!run_add_p(the_repository, pathspec);
+
+ if (!strcmp(patch_mode, "--patch"))
+ mode = ADD_P_ADD;
+ else if (!strcmp(patch_mode, "--patch=stash"))
+ mode = ADD_P_STASH;
+ else if (!strcmp(patch_mode, "--patch=reset"))
+ mode = ADD_P_RESET;
+ else if (!strcmp(patch_mode, "--patch=checkout"))
+ mode = ADD_P_CHECKOUT;
+ else if (!strcmp(patch_mode, "--patch=worktree"))
+ mode = ADD_P_WORKTREE;
+ else
+ die("'%s' not supported", patch_mode);
+
+ return !!run_add_p(the_repository, mode, revision, pathspec);
}
argv_array_push(&argv, "add--interactive");
@@ -327,6 +341,8 @@ static struct option builtin_add_options[] = {
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")),
+ OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p,
+ N_("backend for `git stash -p`")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
@@ -390,7 +406,10 @@ static int add_files(struct dir_struct *dir, int flags)
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- fprintf(stderr, _("Use -f if you really want to add them.\n"));
+ if (advice_add_ignored_file)
+ advise(_("Use -f if you really want to add them.\n"
+ "Turn this message off by running\n"
+ "\"git config advice.addIgnoredFile false\""));
exit_status = 1;
}
@@ -428,6 +447,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
}
+ if (legacy_stash_p) {
+ struct pathspec pathspec;
+
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_PREFIX_ORIGIN,
+ prefix, argv);
+
+ return run_add_interactive(NULL, "--patch=stash", &pathspec);
+ }
if (edit_interactive) {
if (pathspec_from_file)
@@ -480,7 +510,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (require_pathspec && pathspec.nr == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
- fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
+ if (advice_add_empty_pathspec)
+ advise( _("Maybe you wanted to say 'git add .'?\n"
+ "Turn this message off by running\n"
+ "\"git config advice.addEmptyPathspec false\""));
return 0;
}
diff --git a/builtin/am.c b/builtin/am.c
index 8181c2a..e3dfd93 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -81,6 +81,11 @@ enum signoff_type {
SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
};
+enum show_patch_type {
+ SHOW_PATCH_RAW = 0,
+ SHOW_PATCH_DIFF = 1,
+};
+
struct am_state {
/* state directory path */
char *dir;
@@ -1763,7 +1768,7 @@ static void am_run(struct am_state *state, int resume)
linelen(state->msg), state->msg);
if (advice_amworkdir)
- advise(_("Use 'git am --show-current-patch' to see the failed patch"));
+ advise(_("Use 'git am --show-current-patch=diff' to see the failed patch"));
die_user_resolve(state);
}
@@ -2061,7 +2066,7 @@ static void am_abort(struct am_state *state)
am_destroy(state);
}
-static int show_patch(struct am_state *state)
+static int show_patch(struct am_state *state, enum show_patch_type sub_mode)
{
struct strbuf sb = STRBUF_INIT;
const char *patch_path;
@@ -2078,7 +2083,17 @@ static int show_patch(struct am_state *state)
return ret;
}
- patch_path = am_path(state, msgnum(state));
+ switch (sub_mode) {
+ case SHOW_PATCH_RAW:
+ patch_path = am_path(state, msgnum(state));
+ break;
+ case SHOW_PATCH_DIFF:
+ patch_path = am_path(state, "patch");
+ break;
+ default:
+ BUG("invalid mode for --show-current-patch");
+ }
+
len = strbuf_read_file(&sb, patch_path, 0);
if (len < 0)
die_errno(_("failed to read '%s'"), patch_path);
@@ -2118,7 +2133,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
return 0;
}
-enum resume_mode {
+enum resume_type {
RESUME_FALSE = 0,
RESUME_APPLY,
RESUME_RESOLVED,
@@ -2128,6 +2143,45 @@ enum resume_mode {
RESUME_SHOW_PATCH
};
+struct resume_mode {
+ enum resume_type mode;
+ enum show_patch_type sub_mode;
+};
+
+static int parse_opt_show_current_patch(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+ struct resume_mode *resume = container_of(opt_value, struct resume_mode, mode);
+
+ /*
+ * Please update $__git_showcurrentpatch in git-completion.bash
+ * when you add new options
+ */
+ const char *valid_modes[] = {
+ [SHOW_PATCH_DIFF] = "diff",
+ [SHOW_PATCH_RAW] = "raw"
+ };
+ int new_value = SHOW_PATCH_RAW;
+
+ if (arg) {
+ for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) {
+ if (!strcmp(arg, valid_modes[new_value]))
+ break;
+ }
+ if (new_value >= ARRAY_SIZE(valid_modes))
+ return error(_("Invalid value for --show-current-patch: %s"), arg);
+ }
+
+ if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode)
+ return error(_("--show-current-patch=%s is incompatible with "
+ "--show-current-patch=%s"),
+ arg, valid_modes[resume->sub_mode]);
+
+ resume->mode = RESUME_SHOW_PATCH;
+ resume->sub_mode = new_value;
+ return 0;
+}
+
static int git_am_config(const char *k, const char *v, void *cb)
{
int status;
@@ -2145,7 +2199,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
int binary = -1;
int keep_cr = -1;
int patch_format = PATCH_FORMAT_UNKNOWN;
- enum resume_mode resume = RESUME_FALSE;
+ struct resume_mode resume = { .mode = RESUME_FALSE };
int in_progress;
int ret = 0;
@@ -2214,24 +2268,26 @@ int cmd_am(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG),
OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
N_("override error message when patch failure occurs")),
- OPT_CMDMODE(0, "continue", &resume,
+ OPT_CMDMODE(0, "continue", &resume.mode,
N_("continue applying patches after resolving a conflict"),
RESUME_RESOLVED),
- OPT_CMDMODE('r', "resolved", &resume,
+ OPT_CMDMODE('r', "resolved", &resume.mode,
N_("synonyms for --continue"),
RESUME_RESOLVED),
- OPT_CMDMODE(0, "skip", &resume,
+ OPT_CMDMODE(0, "skip", &resume.mode,
N_("skip the current patch"),
RESUME_SKIP),
- OPT_CMDMODE(0, "abort", &resume,
+ OPT_CMDMODE(0, "abort", &resume.mode,
N_("restore the original branch and abort the patching operation."),
RESUME_ABORT),
- OPT_CMDMODE(0, "quit", &resume,
+ OPT_CMDMODE(0, "quit", &resume.mode,
N_("abort the patching operation but keep HEAD where it is."),
RESUME_QUIT),
- OPT_CMDMODE(0, "show-current-patch", &resume,
- N_("show the patch being applied."),
- RESUME_SHOW_PATCH),
+ { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode,
+ "(diff|raw)",
+ N_("show the patch being applied"),
+ PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ parse_opt_show_current_patch, RESUME_SHOW_PATCH },
OPT_BOOL(0, "committer-date-is-author-date",
&state.committer_date_is_author_date,
N_("lie about committer date")),
@@ -2281,12 +2337,12 @@ int cmd_am(int argc, const char **argv, const char *prefix)
* intend to feed us a patch but wanted to continue
* unattended.
*/
- if (argc || (resume == RESUME_FALSE && !isatty(0)))
+ if (argc || (resume.mode == RESUME_FALSE && !isatty(0)))
die(_("previous rebase directory %s still exists but mbox given."),
state.dir);
- if (resume == RESUME_FALSE)
- resume = RESUME_APPLY;
+ if (resume.mode == RESUME_FALSE)
+ resume.mode = RESUME_APPLY;
if (state.signoff == SIGNOFF_EXPLICIT)
am_append_signoff(&state);
@@ -2300,7 +2356,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
* stray directories.
*/
if (file_exists(state.dir) && !state.rebasing) {
- if (resume == RESUME_ABORT || resume == RESUME_QUIT) {
+ if (resume.mode == RESUME_ABORT || resume.mode == RESUME_QUIT) {
am_destroy(&state);
am_state_release(&state);
return 0;
@@ -2311,7 +2367,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
state.dir);
}
- if (resume)
+ if (resume.mode)
die(_("Resolve operation not in progress, we are not resuming."));
for (i = 0; i < argc; i++) {
@@ -2329,7 +2385,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
argv_array_clear(&paths);
}
- switch (resume) {
+ switch (resume.mode) {
case RESUME_FALSE:
am_run(&state, 0);
break;
@@ -2350,7 +2406,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
am_destroy(&state);
break;
case RESUME_SHOW_PATCH:
- ret = show_patch(&state);
+ ret = show_patch(&state, resume.sub_mode);
break;
default:
BUG("invalid resume value");
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 1718df7..c1c40b5 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -52,8 +52,8 @@ static void set_terms(struct bisect_terms *terms, const char *bad,
terms->term_bad = xstrdup(bad);
}
-static const char *vocab_bad = "bad|new";
-static const char *vocab_good = "good|old";
+static const char vocab_bad[] = "bad|new";
+static const char vocab_good[] = "good|old";
/*
* Check whether the string `term` belongs to the set of strings
@@ -206,31 +206,31 @@ static int bisect_write(const char *state, const char *rev,
struct object_id oid;
struct commit *commit;
FILE *fp = NULL;
- int retval = 0;
+ int res = 0;
if (!strcmp(state, terms->term_bad)) {
strbuf_addf(&tag, "refs/bisect/%s", state);
} else if (one_of(state, terms->term_good, "skip", NULL)) {
strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
} else {
- retval = error(_("Bad bisect_write argument: %s"), state);
+ res = error(_("Bad bisect_write argument: %s"), state);
goto finish;
}
if (get_oid(rev, &oid)) {
- retval = error(_("couldn't get the oid of the rev '%s'"), rev);
+ res = error(_("couldn't get the oid of the rev '%s'"), rev);
goto finish;
}
if (update_ref(NULL, tag.buf, &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
- retval = -1;
+ res = -1;
goto finish;
}
fp = fopen(git_path_bisect_log(), "a");
if (!fp) {
- retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
+ res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
goto finish;
}
@@ -244,7 +244,7 @@ finish:
if (fp)
fclose(fp);
strbuf_release(&tag);
- return retval;
+ return res;
}
static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
@@ -291,26 +291,14 @@ static const char need_bisect_start_warning[] =
"You then need to give me at least one %s and %s revision.\n"
"You can use \"git bisect %s\" and \"git bisect %s\" for that.");
-static int bisect_next_check(const struct bisect_terms *terms,
- const char *current_term)
+static int decide_next(const struct bisect_terms *terms,
+ const char *current_term, int missing_good,
+ int missing_bad)
{
- int missing_good = 1, missing_bad = 1, retval = 0;
- const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
- const char *good_glob = xstrfmt("%s-*", terms->term_good);
-
- if (ref_exists(bad_ref))
- missing_bad = 0;
-
- for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
- (void *) &missing_good);
-
if (!missing_good && !missing_bad)
- goto finish;
-
- if (!current_term) {
- retval = -1;
- goto finish;
- }
+ return 0;
+ if (!current_term)
+ return -1;
if (missing_good && !missing_bad &&
!strcmp(current_term, terms->term_good)) {
@@ -321,7 +309,7 @@ static int bisect_next_check(const struct bisect_terms *terms,
*/
warning(_("bisecting only with a %s commit"), terms->term_bad);
if (!isatty(0))
- goto finish;
+ return 0;
/*
* TRANSLATORS: Make sure to include [Y] and [n] in your
* translation. The program will only accept English input
@@ -329,21 +317,35 @@ static int bisect_next_check(const struct bisect_terms *terms,
*/
yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO);
if (starts_with(yesno, "N") || starts_with(yesno, "n"))
- retval = -1;
- goto finish;
- }
- if (!is_empty_or_missing_file(git_path_bisect_start())) {
- retval = error(_(need_bad_and_good_revision_warning),
- vocab_bad, vocab_good, vocab_bad, vocab_good);
- } else {
- retval = error(_(need_bisect_start_warning),
- vocab_good, vocab_bad, vocab_good, vocab_bad);
+ return -1;
+ return 0;
}
-finish:
- free((void *) good_glob);
- free((void *) bad_ref);
- return retval;
+ if (!is_empty_or_missing_file(git_path_bisect_start()))
+ return error(_(need_bad_and_good_revision_warning),
+ vocab_bad, vocab_good, vocab_bad, vocab_good);
+ else
+ return error(_(need_bisect_start_warning),
+ vocab_good, vocab_bad, vocab_good, vocab_bad);
+}
+
+static int bisect_next_check(const struct bisect_terms *terms,
+ const char *current_term)
+{
+ int missing_good = 1, missing_bad = 1;
+ char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
+ char *good_glob = xstrfmt("%s-*", terms->term_good);
+
+ if (ref_exists(bad_ref))
+ missing_bad = 0;
+
+ for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
+ (void *) &missing_good);
+
+ free(good_glob);
+ free(bad_ref);
+
+ return decide_next(terms, current_term, missing_good, missing_bad);
}
static int get_terms(struct bisect_terms *terms)
@@ -397,7 +399,7 @@ static int bisect_terms(struct bisect_terms *terms, const char *option)
static int bisect_append_log_quoted(const char **argv)
{
- int retval = 0;
+ int res = 0;
FILE *fp = fopen(git_path_bisect_log(), "a");
struct strbuf orig_args = STRBUF_INIT;
@@ -405,25 +407,25 @@ static int bisect_append_log_quoted(const char **argv)
return -1;
if (fprintf(fp, "git bisect start") < 1) {
- retval = -1;
+ res = -1;
goto finish;
}
sq_quote_argv(&orig_args, argv);
if (fprintf(fp, "%s\n", orig_args.buf) < 1)
- retval = -1;
+ res = -1;
finish:
fclose(fp);
strbuf_release(&orig_args);
- return retval;
+ return res;
}
static int bisect_start(struct bisect_terms *terms, int no_checkout,
const char **argv, int argc)
{
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
- int flags, pathspec_pos, retval = 0;
+ int flags, pathspec_pos, res = 0;
struct string_list revs = STRING_LIST_INIT_DUP;
struct string_list states = STRING_LIST_INIT_DUP;
struct strbuf start_head = STRBUF_INIT;
@@ -524,7 +526,7 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
argv_array_pushl(&argv, "checkout", start_head.buf,
"--", NULL);
if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
- retval = error(_("checking out '%s' failed."
+ res = error(_("checking out '%s' failed."
" Try 'git bisect start "
"<valid-branch>'."),
start_head.buf);
@@ -572,12 +574,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
if (no_checkout) {
if (get_oid(start_head.buf, &oid) < 0) {
- retval = error(_("invalid ref: '%s'"), start_head.buf);
+ res = error(_("invalid ref: '%s'"), start_head.buf);
goto finish;
}
if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
- retval = -1;
+ res = -1;
goto finish;
}
}
@@ -589,26 +591,26 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
for (i = 0; i < states.nr; i++)
if (bisect_write(states.items[i].string,
revs.items[i].string, terms, 1)) {
- retval = -1;
+ res = -1;
goto finish;
}
if (must_write_terms && write_terms(terms->term_bad,
terms->term_good)) {
- retval = -1;
+ res = -1;
goto finish;
}
- retval = bisect_append_log_quoted(argv);
- if (retval)
- retval = -1;
+ res = bisect_append_log_quoted(argv);
+ if (res)
+ res = -1;
finish:
string_list_clear(&revs, 0);
string_list_clear(&states, 0);
strbuf_release(&start_head);
strbuf_release(&bisect_names);
- return retval;
+ return res;
}
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
@@ -664,7 +666,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
switch (cmdmode) {
case NEXT_ALL:
- return bisect_next_all(the_repository, prefix, no_checkout);
+ res = bisect_next_all(the_repository, prefix, no_checkout);
+ break;
case WRITE_TERMS:
if (argc != 2)
return error(_("--write-terms requires two arguments"));
@@ -711,5 +714,13 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
return error("BUG: unknown subcommand '%d'", cmdmode);
}
free_terms(&terms);
- return !!res;
+
+ /*
+ * Handle early success
+ * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all
+ */
+ if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
+ res = BISECT_OK;
+
+ return abs(res);
}
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index d6a1aa7..6ecc8ee 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode,
oid_to_hex(oid), path);
if ((type == OBJ_BLOB) && S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
- if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
+ struct checkout_metadata meta;
+
+ init_checkout_metadata(&meta, NULL, NULL, oid);
+ if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) {
free(*buf);
*size = strbuf.len;
*buf = strbuf_detach(&strbuf, NULL);
@@ -262,7 +265,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
strbuf_addstr(sb, data->rest);
} else if (is_atom("deltabase", atom, len)) {
if (data->mark_query)
- data->info.delta_base_sha1 = data->delta_base_oid.hash;
+ data->info.delta_base_oid = &data->delta_base_oid;
else
strbuf_addstr(sb,
oid_to_hex(&data->delta_base_oid));
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 5a4f923..ea5d0ae 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -108,6 +108,9 @@ static int check_ignore(struct dir_struct *dir,
int dtype = DT_UNKNOWN;
pattern = last_matching_pattern(dir, &the_index,
full_path, &dtype);
+ if (!verbose && pattern &&
+ pattern->flags & PATTERN_FLAG_NEGATIVE)
+ pattern = NULL;
}
if (!quiet && (pattern || show_non_matching))
output_pattern(pathspec.items[i].original, pattern);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b52c490..8bc94d3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -88,6 +88,19 @@ struct checkout_opts {
struct tree *source_tree;
};
+struct branch_info {
+ const char *name; /* The short name used */
+ const char *path; /* The full name of a real branch */
+ struct commit *commit; /* The named commit */
+ char *refname; /* The full name of the ref being checked out. */
+ struct object_id oid; /* The object ID of the commit being checked out. */
+ /*
+ * if not null the branch is detached because it's already
+ * checked out in this checkout
+ */
+ char *checkout;
+};
+
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
int changed)
{
@@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
}
}
-static int checkout_worktree(const struct checkout_opts *opts)
+static int checkout_worktree(const struct checkout_opts *opts,
+ const struct branch_info *info)
{
struct checkout state = CHECKOUT_INIT;
int nr_checkouts = 0, nr_unmerged = 0;
@@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts)
state.refresh_cache = 1;
state.istate = &the_index;
+ init_checkout_metadata(&state.meta, info->refname,
+ info->commit ? &info->commit->object.oid : &info->oid,
+ NULL);
+
enable_delayed_checkout(&state);
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
@@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts)
}
static int checkout_paths(const struct checkout_opts *opts,
- const char *revision)
+ const struct branch_info *new_branch_info)
{
int pos;
static char *ps_matched;
@@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts,
else
BUG("either flag must have been set, worktree=%d, index=%d",
opts->checkout_worktree, opts->checkout_index);
- return run_add_interactive(revision, patch_mode, &opts->pathspec);
+ return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
}
repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -523,7 +541,9 @@ static int checkout_paths(const struct checkout_opts *opts,
/* Now we are committed to check them out */
if (opts->checkout_worktree)
- errs |= checkout_worktree(opts);
+ errs |= checkout_worktree(opts, new_branch_info);
+ else
+ remove_marked_cache_entries(&the_index, 1);
/*
* Allow updating the index when checking out from the index.
@@ -584,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
}
static int reset_tree(struct tree *tree, const struct checkout_opts *o,
- int worktree, int *writeout_error)
+ int worktree, int *writeout_error,
+ struct branch_info *info)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
@@ -599,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.verbose_update = o->show_progress;
opts.src_index = &the_index;
opts.dst_index = &the_index;
+ init_checkout_metadata(&opts.meta, info->refname,
+ info->commit ? &info->commit->object.oid :
+ is_null_oid(&info->oid) ? &tree->object.oid :
+ &info->oid,
+ NULL);
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -618,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
}
}
-struct branch_info {
- const char *name; /* The short name used */
- const char *path; /* The full name of a real branch */
- struct commit *commit; /* The named commit */
- /*
- * if not null the branch is detached because it's already
- * checked out in this checkout
- */
- char *checkout;
-};
-
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
+ /*
+ * If this is a ref, resolve it; otherwise, look up the OID for our
+ * expression. Failure here is okay.
+ */
+ if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+ repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
if (strcmp(buf.buf, branch->name))
branch->name = xstrdup(buf.buf);
@@ -661,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
} else
new_tree = get_commit_tree(new_branch_info->commit);
if (opts->discard_changes) {
- ret = reset_tree(new_tree, opts, 1, writeout_error);
+ ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
if (ret)
return ret;
} else {
@@ -690,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.quiet = opts->merge && old_branch_info->commit;
topts.verbose_update = opts->show_progress;
topts.fn = twoway_merge;
+ init_checkout_metadata(&topts.meta, new_branch_info->refname,
+ new_branch_info->commit ?
+ &new_branch_info->commit->object.oid :
+ &new_branch_info->oid, NULL);
if (opts->overwrite_ignore) {
topts.dir = xcalloc(1, sizeof(*topts.dir));
topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -760,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
ret = reset_tree(new_tree,
opts, 1,
- writeout_error);
+ writeout_error, new_branch_info);
if (ret)
return ret;
o.ancestor = old_branch_info->name;
@@ -780,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
exit(128);
ret = reset_tree(new_tree,
opts, 0,
- writeout_error);
+ writeout_error, new_branch_info);
strbuf_release(&o.obuf);
strbuf_release(&old_commit_shortname);
if (ret)
@@ -863,7 +889,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
strbuf_addf(&msg, "checkout: moving from %s to %s",
old_desc ? old_desc : "(invalid)", new_branch_info->name);
else
- strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
+ strbuf_insertstr(&msg, 0, reflog_msg);
if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
/* Nothing to do. */
@@ -1115,12 +1141,43 @@ static void setup_new_branch_info_and_source_tree(
}
}
+static const char *parse_remote_branch(const char *arg,
+ struct object_id *rev,
+ int could_be_checkout_paths)
+{
+ int num_matches = 0;
+ const char *remote = unique_tracking_name(arg, rev, &num_matches);
+
+ if (remote && could_be_checkout_paths) {
+ die(_("'%s' could be both a local file and a tracking branch.\n"
+ "Please use -- (and optionally --no-guess) to disambiguate"),
+ arg);
+ }
+
+ if (!remote && num_matches > 1) {
+ if (advice_checkout_ambiguous_remote_branch_name) {
+ advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+ "you can do so by fully qualifying the name with the --track option:\n"
+ "\n"
+ " git checkout --track origin/<name>\n"
+ "\n"
+ "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
+ "one remote, e.g. the 'origin' remote, consider setting\n"
+ "checkout.defaultRemote=origin in your config."));
+ }
+
+ die(_("'%s' matched multiple (%d) remote tracking branches"),
+ arg, num_matches);
+ }
+
+ return remote;
+}
+
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new_branch_info,
struct checkout_opts *opts,
- struct object_id *rev,
- int *dwim_remotes_matched)
+ struct object_id *rev)
{
const char **new_branch = &opts->new_branch;
int argcount = 0;
@@ -1225,13 +1282,9 @@ static int parse_branchname_arg(int argc, const char **argv,
recover_with_dwim = 0;
if (recover_with_dwim) {
- const char *remote = unique_tracking_name(arg, rev,
- dwim_remotes_matched);
+ const char *remote = parse_remote_branch(arg, rev,
+ could_be_checkout_paths);
if (remote) {
- if (could_be_checkout_paths)
- die(_("'%s' could be both a local file and a tracking branch.\n"
- "Please use -- (and optionally --no-guess) to disambiguate"),
- arg);
*new_branch = arg;
arg = remote;
/* DWIMmed to create local branch, case (3).(b) */
@@ -1496,7 +1549,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
const char * const usagestr[])
{
struct branch_info new_branch_info;
- int dwim_remotes_matched = 0;
int parseopt_flags = 0;
memset(&new_branch_info, 0, sizeof(new_branch_info));
@@ -1604,8 +1656,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->track == BRANCH_TRACK_UNSPECIFIED &&
!opts->new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
- &new_branch_info, opts, &rev,
- &dwim_remotes_matched);
+ &new_branch_info, opts, &rev);
argv += n;
argc -= n;
} else if (!opts->accept_ref && opts->from_treeish) {
@@ -1682,28 +1733,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
}
UNLEAK(opts);
- if (opts->patch_mode || opts->pathspec.nr) {
- int ret = checkout_paths(opts, new_branch_info.name);
- if (ret && dwim_remotes_matched > 1 &&
- advice_checkout_ambiguous_remote_branch_name)
- advise(_("'%s' matched more than one remote tracking branch.\n"
- "We found %d remotes with a reference that matched. So we fell back\n"
- "on trying to resolve the argument as a path, but failed there too!\n"
- "\n"
- "If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
- "you can do so by fully qualifying the name with the --track option:\n"
- "\n"
- " git checkout --track origin/<name>\n"
- "\n"
- "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
- "one remote, e.g. the 'origin' remote, consider setting\n"
- "checkout.defaultRemote=origin in your config."),
- argv[0],
- dwim_remotes_matched);
- return ret;
- } else {
+ if (opts->patch_mode || opts->pathspec.nr)
+ return checkout_paths(opts, &new_branch_info);
+ else
return checkout_branch(opts, &new_branch_info);
- }
}
int cmd_checkout(int argc, const char **argv, const char *prefix)
diff --git a/builtin/clone.c b/builtin/clone.c
index 0d81707..ae27eef 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -420,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
struct dir_iterator *iter;
int iter_status;
unsigned int flags;
+ struct strbuf realpath = STRBUF_INIT;
mkdir_if_missing(dest->buf, 0777);
@@ -454,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
if (!option_no_hardlinks) {
- if (!link(real_path(src->buf), dest->buf))
+ strbuf_realpath(&realpath, src->buf, 1);
+ if (!link(realpath.buf, dest->buf))
continue;
if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);
@@ -468,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
strbuf_setlen(src, src_len);
die(_("failed to iterate over '%s'"), src->buf);
}
+
+ strbuf_release(&realpath);
}
static void clone_local(const char *src_repo, const char *dest_repo)
@@ -777,11 +781,11 @@ static int checkout(int submodule_progress)
if (!strcmp(head, "HEAD")) {
if (advice_detached_head)
detach_advice(oid_to_hex(&oid));
+ FREE_AND_NULL(head);
} else {
if (!starts_with(head, "refs/heads/"))
die(_("HEAD not found below refs/heads!"));
}
- free(head);
/* We need to be in the new work tree for the checkout */
setup_work_tree();
@@ -796,6 +800,7 @@ static int checkout(int submodule_progress)
opts.verbose_update = (option_verbosity >= 0);
opts.src_index = &the_index;
opts.dst_index = &the_index;
+ init_checkout_metadata(&opts.meta, head, &oid, NULL);
tree = parse_tree_indirect(&oid);
parse_tree(tree);
@@ -803,6 +808,8 @@ static int checkout(int submodule_progress)
if (unpack_trees(1, &t, &opts) < 0)
die(_("unable to checkout working tree"));
+ free(head);
+
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
@@ -830,6 +837,11 @@ static int checkout(int submodule_progress)
argv_array_push(&args, "--no-fetch");
}
+ if (option_single_branch >= 0)
+ argv_array_push(&args, option_single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
+
err = run_command_v_opt(args.argv, RUN_GIT_CMD);
argv_array_clear(&args);
}
@@ -1094,7 +1106,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
}
- init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
+ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET);
if (real_git_dir)
git_dir = real_git_dir;
@@ -1126,7 +1138,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_required_reference.nr || option_optional_reference.nr)
setup_reference();
- if (option_sparse_checkout && git_sparse_checkout_init(repo))
+ if (option_sparse_checkout && git_sparse_checkout_init(dir))
return 1;
remote = remote_get(option_origin);
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index e0c6fc4..d1ab662 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -34,9 +34,32 @@ static struct opts_commit_graph {
int progress;
} opts;
+static struct object_directory *find_odb(struct repository *r,
+ const char *obj_dir)
+{
+ struct object_directory *odb;
+ char *obj_dir_real = real_pathdup(obj_dir, 1);
+ struct strbuf odb_path_real = STRBUF_INIT;
+
+ prepare_alt_odb(r);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ strbuf_realpath(&odb_path_real, odb->path, 1);
+ if (!strcmp(obj_dir_real, odb_path_real.buf))
+ break;
+ }
+
+ free(obj_dir_real);
+ strbuf_release(&odb_path_real);
+
+ if (!odb)
+ die(_("could not find object directory matching %s"), obj_dir);
+ return odb;
+}
+
static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
+ struct object_directory *odb = NULL;
char *graph_name;
int open_ok;
int fd;
@@ -67,7 +90,8 @@ static int graph_verify(int argc, const char **argv)
if (opts.progress)
flags |= COMMIT_GRAPH_WRITE_PROGRESS;
- graph_name = get_commit_graph_filename(opts.obj_dir);
+ odb = find_odb(the_repository, opts.obj_dir);
+ graph_name = get_commit_graph_filename(odb);
open_ok = open_commit_graph(graph_name, &fd, &st);
if (!open_ok && errno != ENOENT)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
@@ -75,9 +99,9 @@ static int graph_verify(int argc, const char **argv)
FREE_AND_NULL(graph_name);
if (open_ok)
- graph = load_commit_graph_one_fd_st(fd, &st);
- else
- graph = read_commit_graph_one(the_repository, opts.obj_dir);
+ graph = load_commit_graph_one_fd_st(fd, &st, odb);
+ else
+ graph = read_commit_graph_one(the_repository, odb);
/* Return failure if open_ok predicted success */
if (!graph)
@@ -94,6 +118,7 @@ static int graph_write(int argc, const char **argv)
{
struct string_list *pack_indexes = NULL;
struct string_list *commit_hex = NULL;
+ struct object_directory *odb = NULL;
struct string_list lines;
int result = 0;
enum commit_graph_write_flags flags = 0;
@@ -145,9 +170,10 @@ static int graph_write(int argc, const char **argv)
flags |= COMMIT_GRAPH_WRITE_PROGRESS;
read_replace_refs = 0;
+ odb = find_odb(the_repository, opts.obj_dir);
if (opts.reachable) {
- if (write_commit_graph_reachable(opts.obj_dir, flags, &split_opts))
+ if (write_commit_graph_reachable(odb, flags, &split_opts))
return 1;
return 0;
}
@@ -169,7 +195,7 @@ static int graph_write(int argc, const char **argv)
UNLEAK(buf);
}
- if (write_commit_graph(opts.obj_dir,
+ if (write_commit_graph(odb,
pack_indexes,
commit_hex,
flags,
diff --git a/builtin/commit.c b/builtin/commit.c
index aa13323..d3e7781 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
" git commit --allow-empty\n"
"\n");
+static const char empty_rebase_pick_advice[] =
+N_("Otherwise, please use 'git rebase --skip'\n");
+
static const char empty_cherry_pick_advice_single[] =
N_("Otherwise, please use 'git cherry-pick --skip'\n");
@@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode;
static const char *cleanup_arg;
static enum commit_whence whence;
-static int sequencer_in_use;
static int use_editor = 1, include_status = 1;
static int have_option_m;
static struct strbuf message = STRBUF_INIT;
@@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path_merge_head(the_repository)))
whence = FROM_MERGE;
- else if (file_exists(git_path_cherry_pick_head(the_repository))) {
- whence = FROM_CHERRY_PICK;
- if (file_exists(git_path_seq_dir()))
- sequencer_in_use = 1;
- }
- else
+ else if (!sequencer_determine_whence(the_repository, &whence))
whence = FROM_COMMIT;
if (s)
s->whence = whence;
@@ -367,7 +364,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
die(_("index file corrupt"));
if (interactive) {
- char *old_index_env = NULL;
+ char *old_index_env = NULL, *old_repo_index_file;
hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
refresh_cache_or_die(refresh_flags);
@@ -375,12 +372,16 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
if (write_locked_index(&the_index, &index_lock, 0))
die(_("unable to create temporary index"));
+ old_repo_index_file = the_repository->index_file;
+ the_repository->index_file =
+ (char *)get_lock_file_path(&index_lock);
old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
- setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
+ setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
+ the_repository->index_file = old_repo_index_file;
if (old_index_env && *old_index_env)
setenv(INDEX_ENVIRONMENT, old_index_env, 1);
else
@@ -473,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
if (whence != FROM_COMMIT) {
if (whence == FROM_MERGE)
die(_("cannot do a partial commit during a merge."));
- else if (whence == FROM_CHERRY_PICK)
+ else if (is_from_cherry_pick(whence))
die(_("cannot do a partial commit during a cherry-pick."));
+ else if (is_from_rebase(whence))
+ die(_("cannot do a partial commit during a rebase."));
}
if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
@@ -791,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
else if (whence == FROM_MERGE)
hook_arg1 = "merge";
- else if (whence == FROM_CHERRY_PICK) {
+ else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) {
hook_arg1 = "commit";
hook_arg2 = "CHERRY_PICK_HEAD";
}
@@ -964,16 +967,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
+ s->hints = advice_status_hints;
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
- else if (whence == FROM_CHERRY_PICK) {
+ else if (is_from_cherry_pick(whence) ||
+ whence == FROM_REBASE_PICK) {
fputs(_(empty_cherry_pick_advice), stderr);
- if (!sequencer_in_use)
+ if (whence == FROM_CHERRY_PICK_SINGLE)
fputs(_(empty_cherry_pick_advice_single), stderr);
- else
+ else if (whence == FROM_CHERRY_PICK_MULTI)
fputs(_(empty_cherry_pick_advice_multi), stderr);
+ else
+ fputs(_(empty_rebase_pick_advice), stderr);
}
return 0;
}
@@ -1176,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (amend && whence != FROM_COMMIT) {
if (whence == FROM_MERGE)
die(_("You are in the middle of a merge -- cannot amend."));
- else if (whence == FROM_CHERRY_PICK)
+ else if (is_from_cherry_pick(whence))
die(_("You are in the middle of a cherry-pick -- cannot amend."));
+ else if (whence == FROM_REBASE_PICK)
+ die(_("You are in the middle of a rebase -- cannot amend."));
}
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
@@ -1199,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_message = edit_message;
if (amend && !use_message && !fixup_message)
use_message = "HEAD";
- if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
+ if (!use_message && !is_from_cherry_pick(whence) &&
+ !is_from_rebase(whence) && renew_authorship)
die(_("--reset-author can be used only with -C, -c or --amend."));
if (use_message) {
use_message_buffer = read_commit_message(use_message);
@@ -1208,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
author_message_buffer = use_message_buffer;
}
}
- if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+ if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) &&
+ !renew_authorship) {
author_message = "CHERRY_PICK_HEAD";
author_message_buffer = read_commit_message(author_message);
}
@@ -1626,8 +1637,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
reduce_heads_replace(&parents);
} else {
if (!reflog_msg)
- reflog_msg = (whence == FROM_CHERRY_PICK)
+ reflog_msg = is_from_cherry_pick(whence)
? "commit (cherry-pick)"
+ : is_from_rebase(whence)
+ ? "commit (rebase)"
: "commit";
commit_list_insert(current_head, &parents);
}
@@ -1654,7 +1667,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
}
if (amend) {
- const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+ const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
} else {
struct commit_extra_header **tail = &extra;
@@ -1688,7 +1701,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
"not exceeded, and then \"git restore --staged :/\" to recover."));
if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
- write_commit_graph_reachable(get_object_directory(), 0, NULL))
+ write_commit_graph_reachable(the_repository->objects->odb, 0, NULL))
return 1;
repo_rerere(the_repository, 0);
diff --git a/builtin/config.c b/builtin/config.c
index 98d65bc..ee4aef6 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -29,10 +29,11 @@ static int use_worktree_config;
static struct git_config_source given_config_source;
static int actions, type;
static char *default_value;
-static int end_null;
+static int end_nul;
static int respect_includes_opt = -1;
static struct config_options config_options;
static int show_origin;
+static int show_scope;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -151,10 +152,11 @@ static struct option builtin_config_options[] = {
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
OPT_GROUP(N_("Other")),
- OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
+ OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
+ OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
OPT_END(),
};
@@ -178,22 +180,34 @@ static void check_argc(int argc, int min, int max)
static void show_config_origin(struct strbuf *buf)
{
- const char term = end_null ? '\0' : '\t';
+ const char term = end_nul ? '\0' : '\t';
strbuf_addstr(buf, current_config_origin_type());
strbuf_addch(buf, ':');
- if (end_null)
+ if (end_nul)
strbuf_addstr(buf, current_config_name());
else
quote_c_style(current_config_name(), buf, NULL, 0);
strbuf_addch(buf, term);
}
+static void show_config_scope(struct strbuf *buf)
+{
+ const char term = end_nul ? '\0' : '\t';
+ const char *scope = config_scope_name(current_config_scope());
+
+ strbuf_addstr(buf, N_(scope));
+ strbuf_addch(buf, term);
+}
+
static int show_all_config(const char *key_, const char *value_, void *cb)
{
- if (show_origin) {
+ if (show_origin || show_scope) {
struct strbuf buf = STRBUF_INIT;
- show_config_origin(&buf);
+ if (show_scope)
+ show_config_scope(&buf);
+ if (show_origin)
+ show_config_origin(&buf);
/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
@@ -213,6 +227,8 @@ struct strbuf_list {
static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
+ if (show_scope)
+ show_config_scope(buf);
if (show_origin)
show_config_origin(buf);
if (show_keys)
@@ -622,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
!strcmp(given_config_source.file, "-")) {
given_config_source.file = NULL;
given_config_source.use_stdin = 1;
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
}
if (use_global_config) {
@@ -637,6 +654,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
*/
die(_("$HOME not set"));
+ given_config_source.scope = CONFIG_SCOPE_GLOBAL;
+
if (access_or_warn(user_config, R_OK, 0) &&
xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
given_config_source.file = xdg_config;
@@ -646,11 +665,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
free(xdg_config);
}
}
- else if (use_system_config)
+ else if (use_system_config) {
given_config_source.file = git_etc_gitconfig();
- else if (use_local_config)
+ given_config_source.scope = CONFIG_SCOPE_SYSTEM;
+ } else if (use_local_config) {
given_config_source.file = git_pathdup("config");
- else if (use_worktree_config) {
+ given_config_source.scope = CONFIG_SCOPE_LOCAL;
+ } else if (use_worktree_config) {
struct worktree **worktrees = get_worktrees(0);
if (repository_format_worktree_config)
given_config_source.file = git_pathdup("config.worktree");
@@ -662,13 +683,18 @@ int cmd_config(int argc, const char **argv, const char *prefix)
"section in \"git help worktree\" for details"));
else
given_config_source.file = git_pathdup("config");
+ given_config_source.scope = CONFIG_SCOPE_LOCAL;
free_worktrees(worktrees);
} else if (given_config_source.file) {
if (!is_absolute_path(given_config_source.file) && prefix)
given_config_source.file =
prefix_filename(prefix, given_config_source.file);
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
+ } else if (given_config_source.blob) {
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
}
+
if (respect_includes_opt == -1)
config_options.respect_includes = !given_config_source.file;
else
@@ -678,7 +704,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
config_options.git_dir = get_git_dir();
}
- if (end_null) {
+ if (end_nul) {
term = '\0';
delim = '\n';
key_delim = '\n';
diff --git a/builtin/describe.c b/builtin/describe.c
index b6df81d..21d2cb9 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -54,6 +54,7 @@ struct commit_name {
struct tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
+ unsigned misnamed:1;
struct object_id oid;
char *path;
};
@@ -132,6 +133,7 @@ static void add_to_known_names(const char *path,
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
+ e->misnamed = 0;
oidcpy(&e->oid, oid);
free(e->path);
e->path = xstrdup(path);
@@ -275,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst)
die(_("annotated tag %s not available"), n->path);
}
if (n->tag && !n->name_checked) {
- if (!n->tag->tag)
- die(_("annotated tag %s has no embedded name"), n->path);
- if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
- warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
+ if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) {
+ warning(_("tag '%s' is externally known as '%s'"),
+ n->path, n->tag->tag);
+ n->misnamed = 1;
+ }
n->name_checked = 1;
}
@@ -314,7 +317,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
* Exact match to an existing ref.
*/
append_name(n, dst);
- if (longformat)
+ if (n->misnamed || longformat)
append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
@@ -376,11 +379,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
if (!(c->object.flags & t->flag_within))
t->depth++;
}
+ /* Stop if last remaining path already covered by best candidate(s) */
if (annotated_cnt && !list) {
- if (debug)
- fprintf(stderr, _("finished search at %s\n"),
- oid_to_hex(&c->object.oid));
- break;
+ int best_depth = INT_MAX;
+ unsigned best_within = 0;
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = &all_matches[cur_match];
+ if (t->depth < best_depth) {
+ best_depth = t->depth;
+ best_within = t->flag_within;
+ } else if (t->depth == best_depth) {
+ best_within |= t->flag_within;
+ }
+ }
+ if ((c->object.flags & best_within) == best_within) {
+ if (debug)
+ fprintf(stderr, _("finished search at %s\n"),
+ oid_to_hex(&c->object.oid));
+ break;
+ }
}
while (parents) {
struct commit *p = parents->item;
@@ -449,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
}
append_name(all_matches[0].name, dst);
- if (abbrev)
+ if (all_matches[0].name->misnamed || abbrev)
append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index dbec4df..8586816 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -293,7 +293,8 @@ static void export_blob(const struct object_id *oid)
buf = read_object_file(oid, &type, &size);
if (!buf)
die("could not read blob %s", oid_to_hex(oid));
- if (check_object_signature(oid, buf, size, type_name(type)) < 0)
+ if (check_object_signature(the_repository, oid, buf, size,
+ type_name(type)) < 0)
die("oid mismatch in blob %s", oid_to_hex(oid));
object = parse_object_buffer(the_repository, oid, type,
size, buf, &eaten);
@@ -870,8 +871,7 @@ static void handle_tag(const char *name, struct tag *tag)
printf("reset %s\nfrom %s\n\n",
name, oid_to_hex(&null_oid));
}
- if (starts_with(name, "refs/tags/"))
- name += 10;
+ skip_prefix(name, "refs/tags/", &name);
printf("tag %s\n", name);
if (mark_tags) {
mark_next_object(&tag->object);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index eb10d4c..1097e1e 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -335,6 +335,7 @@ static void find_non_local_tags(const struct ref *refs,
struct string_list_item *remote_ref_item;
const struct ref *ref;
struct refname_hash_entry *item = NULL;
+ const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT;
refname_hash_init(&existing_refs);
refname_hash_init(&remote_refs);
@@ -353,10 +354,9 @@ static void find_non_local_tags(const struct ref *refs,
*/
if (ends_with(ref->name, "^{}")) {
if (item &&
- !has_object_file_with_flags(&ref->old_oid,
- OBJECT_INFO_QUICK) &&
+ !has_object_file_with_flags(&ref->old_oid, quick_flags) &&
!oidset_contains(&fetch_oids, &ref->old_oid) &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
!oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
item = NULL;
@@ -370,7 +370,7 @@ static void find_non_local_tags(const struct ref *refs,
* fetch.
*/
if (item &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
!oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
@@ -391,7 +391,7 @@ static void find_non_local_tags(const struct ref *refs,
* checked to see if it needs fetching.
*/
if (item &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
!oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
@@ -1872,7 +1872,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (progress)
commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS;
- write_commit_graph_reachable(get_object_directory(),
+ write_commit_graph_reachable(the_repository->objects->odb,
commit_graph_flags,
NULL);
}
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 05a92c5..172dfbd 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -503,12 +503,12 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
if (size == len)
; /* merely annotated */
- else if (!check_signature(buf, len, buf + len, size - len,
- &sigc)) {
- strbuf_addstr(&sig, sigc.gpg_output);
- signature_check_clear(&sigc);
- } else
+ else if (check_signature(buf, len, buf + len, size - len, &sigc) &&
+ !sigc.gpg_output)
strbuf_addstr(&sig, "gpg verification failed.\n");
+ else
+ strbuf_addstr(&sig, sigc.gpg_output);
+ signature_check_clear(&sigc);
if (!tag_number++) {
fmt_tag_signature(&tagbuf, &sig, buf, len);
diff --git a/builtin/gc.c b/builtin/gc.c
index 3f76bf4..8e0b9cf 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -686,7 +686,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
prepare_repo_settings(the_repository);
if (the_repository->settings.gc_write_commit_graph == 1)
- write_commit_graph_reachable(get_object_directory(),
+ write_commit_graph_reachable(the_repository->objects->odb,
!quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
NULL);
diff --git a/builtin/grep.c b/builtin/grep.c
index 50ce8d9..99e2685 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -24,6 +24,7 @@
#include "submodule.h"
#include "submodule-config.h"
#include "object-store.h"
+#include "packfile.h"
static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -32,7 +33,6 @@ static char const * const grep_usage[] = {
static int recurse_submodules;
-#define GREP_NUM_THREADS_DEFAULT 8
static int num_threads;
static pthread_t *threads;
@@ -91,8 +91,11 @@ static pthread_cond_t cond_result;
static int skip_first_line;
-static void add_work(struct grep_opt *opt, const struct grep_source *gs)
+static void add_work(struct grep_opt *opt, struct grep_source *gs)
{
+ if (opt->binary != GREP_BINARY_TEXT)
+ grep_source_load_driver(gs, opt->repo->index);
+
grep_lock();
while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
@@ -100,9 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs)
}
todo[todo_end].source = *gs;
- if (opt->binary != GREP_BINARY_TEXT)
- grep_source_load_driver(&todo[todo_end].source,
- opt->repo->index);
todo[todo_end].done = 0;
strbuf_reset(&todo[todo_end].out);
todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -200,12 +200,12 @@ static void start_threads(struct grep_opt *opt)
int i;
pthread_mutex_init(&grep_mutex, NULL);
- pthread_mutex_init(&grep_read_mutex, NULL);
pthread_mutex_init(&grep_attr_mutex, NULL);
pthread_cond_init(&cond_add, NULL);
pthread_cond_init(&cond_write, NULL);
pthread_cond_init(&cond_result, NULL);
grep_use_locks = 1;
+ enable_obj_read_lock();
for (i = 0; i < ARRAY_SIZE(todo); i++) {
strbuf_init(&todo[i].out, 0);
@@ -257,12 +257,12 @@ static int wait_all(void)
free(threads);
pthread_mutex_destroy(&grep_mutex);
- pthread_mutex_destroy(&grep_read_mutex);
pthread_mutex_destroy(&grep_attr_mutex);
pthread_cond_destroy(&cond_add);
pthread_cond_destroy(&cond_write);
pthread_cond_destroy(&cond_result);
grep_use_locks = 0;
+ disable_obj_read_lock();
return hit;
}
@@ -295,16 +295,6 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
return st;
}
-static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
-{
- void *data;
-
- grep_read_lock();
- data = read_object_file(oid, type, size);
- grep_read_unlock();
- return data;
-}
-
static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
const char *filename, int tree_name_len,
const char *path)
@@ -407,30 +397,28 @@ static int grep_submodule(struct grep_opt *opt,
{
struct repository subrepo;
struct repository *superproject = opt->repo;
- const struct submodule *sub = submodule_from_path(superproject,
- &null_oid, path);
+ const struct submodule *sub;
struct grep_opt subopt;
int hit;
- /*
- * NEEDSWORK: submodules functions need to be protected because they
- * access the object store via config_from_gitmodules(): the latter
- * uses get_oid() which, for now, relies on the global the_repository
- * object.
- */
- grep_read_lock();
+ sub = submodule_from_path(superproject, &null_oid, path);
- if (!is_submodule_active(superproject, path)) {
- grep_read_unlock();
+ if (!is_submodule_active(superproject, path))
return 0;
- }
- if (repo_submodule_init(&subrepo, superproject, sub)) {
- grep_read_unlock();
+ if (repo_submodule_init(&subrepo, superproject, sub))
return 0;
- }
- repo_read_gitmodules(&subrepo);
+ /*
+ * NEEDSWORK: repo_read_gitmodules() might call
+ * add_to_alternates_memory() via config_from_gitmodules(). This
+ * operation causes a race condition with concurrent object readings
+ * performed by the worker threads. That's why we need obj_read_lock()
+ * here. It should be removed once it's no longer necessary to add the
+ * subrepo's odbs to the in-memory alternates list.
+ */
+ obj_read_lock();
+ repo_read_gitmodules(&subrepo, 0);
/*
* NEEDSWORK: This adds the submodule's object directory to the list of
@@ -443,7 +431,7 @@ static int grep_submodule(struct grep_opt *opt,
* object.
*/
add_to_alternates_memory(subrepo.objects->odb->path);
- grep_read_unlock();
+ obj_read_unlock();
memcpy(&subopt, opt, sizeof(subopt));
subopt.repo = &subrepo;
@@ -455,14 +443,12 @@ static int grep_submodule(struct grep_opt *opt,
unsigned long size;
struct strbuf base = STRBUF_INIT;
+ obj_read_lock();
object = parse_object_or_die(oid, oid_to_hex(oid));
-
- grep_read_lock();
+ obj_read_unlock();
data = read_object_with_reference(&subrepo,
&object->oid, tree_type,
&size, NULL);
- grep_read_unlock();
-
if (!data)
die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
@@ -587,7 +573,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
void *data;
unsigned long size;
- data = lock_and_read_oid_file(&entry.oid, &type, &size);
+ data = read_object_file(&entry.oid, &type, &size);
if (!data)
die(_("unable to read tree (%s)"),
oid_to_hex(&entry.oid));
@@ -625,12 +611,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct strbuf base;
int hit, len;
- grep_read_lock();
data = read_object_with_reference(opt->repo,
&obj->oid, tree_type,
&size, NULL);
- grep_read_unlock();
-
if (!data)
die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
@@ -659,13 +642,18 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
for (i = 0; i < nr; i++) {
struct object *real_obj;
+
+ obj_read_lock();
real_obj = deref_tag(opt->repo, list->objects[i].item,
NULL, 0);
+ obj_read_unlock();
/* load the gitmodules file for this rev */
if (recurse_submodules) {
submodule_free(opt->repo);
+ obj_read_lock();
gitmodules_config_oid(&real_obj->oid);
+ obj_read_unlock();
}
if (grep_object(opt, pathspec, real_obj, list->objects[i].name,
list->objects[i].path)) {
@@ -958,6 +946,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
/* die the same way as if we did it at the beginning */
setup_git_directory();
}
+ /* Ignore --recurse-submodules if --no-index is given or implied */
+ if (!use_index)
+ recurse_submodules = 0;
/*
* skip a -- separator; we know it cannot be
@@ -1062,7 +1053,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
pathspec.recursive = 1;
pathspec.recurse_submodules = !!recurse_submodules;
- if (list.nr || cached || show_in_pager) {
+ if (recurse_submodules && untracked)
+ die(_("--untracked not supported with --recurse-submodules"));
+
+ if (show_in_pager) {
if (num_threads > 1)
warning(_("invalid option combination, ignoring --threads"));
num_threads = 1;
@@ -1072,7 +1066,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
} else if (num_threads < 0)
die(_("invalid number of threads specified (%d)"), num_threads);
else if (num_threads == 0)
- num_threads = HAVE_THREADS ? GREP_NUM_THREADS_DEFAULT : 1;
+ num_threads = HAVE_THREADS ? online_cpus() : 1;
if (num_threads > 1) {
if (!HAVE_THREADS)
@@ -1081,6 +1075,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
&& (opt.pre_context || opt.post_context ||
opt.file_break || opt.funcbody))
skip_first_line = 1;
+
+ /*
+ * Pre-read gitmodules (if not read already) and force eager
+ * initialization of packed_git to prevent racy lazy
+ * reading/initialization once worker threads are started.
+ */
+ if (recurse_submodules)
+ repo_read_gitmodules(the_repository, 1);
+ if (startup_info->have_repository)
+ (void)get_packed_git(the_repository);
+
start_threads(&opt);
} else {
/*
@@ -1115,9 +1120,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
}
- if (recurse_submodules && (!use_index || untracked))
- die(_("option not supported with --recurse-submodules"));
-
if (!show_in_pager && !opt.status_only)
setup_pager();
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 60a5591..d967d18 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -757,7 +757,8 @@ static int check_collison(struct object_entry *entry)
memset(&data, 0, sizeof(data));
data.entry = entry;
- data.st = open_istream(&entry->idx.oid, &type, &size, NULL);
+ data.st = open_istream(the_repository, &entry->idx.oid, &type, &size,
+ NULL);
if (!data.st)
return -1;
if (size != entry->size || type != entry->type)
@@ -948,7 +949,7 @@ static void resolve_delta(struct object_entry *delta_obj,
free(delta_data);
if (!result->data)
bad_object(delta_obj->idx.offset, _("failed to apply delta"));
- hash_object_file(result->data, result->size,
+ hash_object_file(the_hash_algo, result->data, result->size,
type_name(delta_obj->real_type), &delta_obj->idx.oid);
sha1_object(result->data, NULL, result->size, delta_obj->real_type,
&delta_obj->idx.oid);
@@ -1003,7 +1004,9 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
base->obj->real_type))
- BUG("child->real_type != OBJ_REF_DELTA");
+ die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
+ (uintmax_t)child->idx.offset,
+ oid_to_hex(&base->obj->idx.oid));
resolve_delta(child, base, result);
if (base->ref_first == base->ref_last && base->ofs_last == -1)
@@ -1383,8 +1386,9 @@ static void fix_unresolved_deltas(struct hashfile *f)
if (!base_obj->data)
continue;
- if (check_object_signature(&d->oid, base_obj->data,
- base_obj->size, type_name(type)))
+ if (check_object_signature(the_repository, &d->oid,
+ base_obj->data, base_obj->size,
+ type_name(type)))
die(_("local object %s is corrupt"), oid_to_hex(&d->oid));
base_obj->obj = append_obj_to_pack(f, d->oid.hash,
base_obj->data, base_obj->size, type);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 944ec77..0b7222e 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -20,6 +20,8 @@
#define TEST_FILEMODE 1
#endif
+#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH"
+
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
static const char *init_db_template_dir;
@@ -176,13 +178,36 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
return 1;
}
+void initialize_repository_version(int hash_algo)
+{
+ char repo_version_string[10];
+ int repo_version = GIT_REPO_VERSION;
+
+#ifndef ENABLE_SHA256
+ if (hash_algo != GIT_HASH_SHA1)
+ die(_("The hash algorithm %s is not supported in this build."), hash_algos[hash_algo].name);
+#endif
+
+ if (hash_algo != GIT_HASH_SHA1)
+ repo_version = GIT_REPO_VERSION_READ;
+
+ /* This forces creation of new config file */
+ xsnprintf(repo_version_string, sizeof(repo_version_string),
+ "%d", repo_version);
+ git_config_set("core.repositoryformatversion", repo_version_string);
+
+ if (hash_algo != GIT_HASH_SHA1)
+ git_config_set("extensions.objectformat",
+ hash_algos[hash_algo].name);
+}
+
static int create_default_files(const char *template_path,
- const char *original_git_dir)
+ const char *original_git_dir,
+ const struct repository_format *fmt)
{
struct stat st1;
struct strbuf buf = STRBUF_INIT;
char *path;
- char repo_version_string[10];
char junk[2];
int reinit;
int filemode;
@@ -244,10 +269,7 @@ static int create_default_files(const char *template_path,
exit(1);
}
- /* This forces creation of new config file */
- xsnprintf(repo_version_string, sizeof(repo_version_string),
- "%d", GIT_REPO_VERSION);
- git_config_set("core.repositoryformatversion", repo_version_string);
+ initialize_repository_version(fmt->hash_algo);
/* Check filemode trustability */
path = git_path_buf(&buf, "config");
@@ -340,12 +362,33 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
write_file(git_link, "gitdir: %s", git_dir);
}
+static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash)
+{
+ const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
+ /*
+ * If we already have an initialized repo, don't allow the user to
+ * specify a different algorithm, as that could cause corruption.
+ * Otherwise, if the user has specified one on the command line, use it.
+ */
+ if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo)
+ die(_("attempt to reinitialize repository with different hash"));
+ else if (hash != GIT_HASH_UNKNOWN)
+ repo_fmt->hash_algo = hash;
+ else if (env) {
+ int env_algo = hash_algo_by_name(env);
+ if (env_algo == GIT_HASH_UNKNOWN)
+ die(_("unknown hash algorithm '%s'"), env);
+ repo_fmt->hash_algo = env_algo;
+ }
+}
+
int init_db(const char *git_dir, const char *real_git_dir,
- const char *template_dir, unsigned int flags)
+ const char *template_dir, int hash, unsigned int flags)
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
char *original_git_dir = real_pathdup(git_dir, 1);
+ struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
if (real_git_dir) {
struct stat st;
@@ -356,12 +399,12 @@ int init_db(const char *git_dir, const char *real_git_dir,
if (!exist_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
- set_git_dir(real_path(real_git_dir));
+ set_git_dir(real_git_dir, 1);
git_dir = get_git_dir();
separate_git_dir(git_dir, original_git_dir);
}
else {
- set_git_dir(real_path(git_dir));
+ set_git_dir(git_dir, 1);
git_dir = get_git_dir();
}
startup_info->have_repository = 1;
@@ -378,9 +421,11 @@ int init_db(const char *git_dir, const char *real_git_dir,
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
- check_repository_format();
+ check_repository_format(&repo_fmt);
- reinit = create_default_files(template_dir, original_git_dir);
+ validate_hash_algorithm(&repo_fmt, hash);
+
+ reinit = create_default_files(template_dir, original_git_dir, &repo_fmt);
create_object_directory();
@@ -482,6 +527,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *work_tree;
const char *template_dir = NULL;
unsigned int flags = 0;
+ const char *object_format = NULL;
+ int hash_algo = GIT_HASH_UNKNOWN;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, N_("template-directory"),
N_("directory from which templates will be used")),
@@ -494,6 +541,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
+ OPT_STRING(0, "object-format", &object_format, N_("hash"),
+ N_("specify the hash algorithm to use")),
OPT_END()
};
@@ -546,6 +595,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
free(cwd);
}
+ if (object_format) {
+ hash_algo = hash_algo_by_name(object_format);
+ if (hash_algo == GIT_HASH_UNKNOWN)
+ die(_("unknown hash algorithm '%s'"), object_format);
+ }
+
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
@@ -597,5 +652,5 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
UNLEAK(work_tree);
flags |= INIT_DB_EXIST_OK;
- return init_db(git_dir, real_git_dir, template_dir, flags);
+ return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags);
}
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index e3f8da1..6719ac1 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv)
static int handle_fork_point(int argc, const char **argv)
{
struct object_id oid;
- char *refname;
struct commit *derived, *fork_point;
const char *commitname;
- switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
- case 0:
- die("No such ref: '%s'", argv[0]);
- case 1:
- break; /* good */
- default:
- die("Ambiguous refname: '%s'", argv[0]);
- }
-
commitname = (argc == 2) ? argv[1] : "HEAD";
if (get_oid(commitname, &oid))
die("Not a valid object name: '%s'", commitname);
derived = lookup_commit_reference(the_repository, &oid);
- fork_point = get_fork_point(refname, derived);
+ fork_point = get_fork_point(argv[0], derived);
if (!fork_point)
return 1;
diff --git a/builtin/merge.c b/builtin/merge.c
index 062e911..d127d22 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -62,6 +62,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash;
static int option_commit = -1;
static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
+static int check_trust_level = 1;
static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT;
static struct strategy **use_strategies;
@@ -631,6 +632,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
} else if (!strcmp(k, "commit.gpgsign")) {
sign_commit = git_config_bool(k, v) ? "" : NULL;
return 0;
+ } else if (!strcmp(k, "gpg.mintrustlevel")) {
+ check_trust_level = 0;
}
status = fmt_merge_msg_config(k, v, cb);
@@ -1397,7 +1400,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("Can merge only exactly one commit into empty head"));
if (verify_signatures)
- verify_merge_signature(remoteheads->item, verbosity);
+ verify_merge_signature(remoteheads->item, verbosity,
+ check_trust_level);
remote_head_oid = &remoteheads->item->object.oid;
read_empty(remote_head_oid, 0);
@@ -1420,7 +1424,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (verify_signatures) {
for (p = remoteheads; p; p = p->next) {
- verify_merge_signature(p->item, verbosity);
+ verify_merge_signature(p->item, verbosity,
+ check_trust_level);
}
}
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 6fb7dc8..4982d3a 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -29,8 +29,11 @@ static int verify_object(const struct object_id *oid, const char *expected_type)
const struct object_id *repl = lookup_replace_object(the_repository, oid);
if (buffer) {
- if (type == type_from_string(expected_type))
- ret = check_object_signature(repl, buffer, size, expected_type);
+ if (type == type_from_string(expected_type)) {
+ ret = check_object_signature(the_repository, repl,
+ buffer, size,
+ expected_type);
+ }
free(buffer);
}
return ret;
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 6b9e8c8..a9dcd25 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -16,15 +16,15 @@
*/
#define CUTOFF_DATE_SLOP 86400
-typedef struct rev_name {
- const char *tip_name;
+struct rev_name {
+ char *tip_name;
timestamp_t taggerdate;
int generation;
int distance;
int from_tag;
-} rev_name;
+};
-define_commit_slab(commit_rev_name, struct rev_name *);
+define_commit_slab(commit_rev_name, struct rev_name);
static timestamp_t cutoff = TIME_MAX;
static struct commit_rev_name rev_names;
@@ -32,16 +32,16 @@ static struct commit_rev_name rev_names;
/* How many generations are maximally preferred over _one_ merge traversal? */
#define MERGE_TRAVERSAL_WEIGHT 65535
-static struct rev_name *get_commit_rev_name(struct commit *commit)
+static int is_valid_rev_name(const struct rev_name *name)
{
- struct rev_name **slot = commit_rev_name_peek(&rev_names, commit);
-
- return slot ? *slot : NULL;
+ return name && (name->generation || name->tip_name);
}
-static void set_commit_rev_name(struct commit *commit, struct rev_name *name)
+static struct rev_name *get_commit_rev_name(const struct commit *commit)
{
- *commit_rev_name_at(&rev_names, commit) = name;
+ struct rev_name *name = commit_rev_name_peek(&rev_names, commit);
+
+ return is_valid_rev_name(name) ? name : NULL;
}
static int is_better_name(struct rev_name *name,
@@ -81,28 +81,54 @@ static int is_better_name(struct rev_name *name,
}
static struct rev_name *create_or_update_name(struct commit *commit,
- const char *tip_name,
timestamp_t taggerdate,
int generation, int distance,
int from_tag)
{
- struct rev_name *name = get_commit_rev_name(commit);
-
- if (name == NULL) {
- name = xmalloc(sizeof(*name));
- set_commit_rev_name(commit, name);
- goto copy_data;
- } else if (is_better_name(name, taggerdate, distance, from_tag)) {
-copy_data:
- name->tip_name = tip_name;
- name->taggerdate = taggerdate;
- name->generation = generation;
- name->distance = distance;
- name->from_tag = from_tag;
-
- return name;
- } else
- return NULL;
+ struct rev_name *name = commit_rev_name_at(&rev_names, commit);
+
+ if (is_valid_rev_name(name)) {
+ if (!is_better_name(name, taggerdate, distance, from_tag))
+ return NULL;
+
+ /*
+ * This string might still be shared with ancestors
+ * (generation > 0). We can release it here regardless,
+ * because the new name that has just won will be better
+ * for them as well, so name_rev() will replace these
+ * stale pointers when it processes the parents.
+ */
+ if (!name->generation)
+ free(name->tip_name);
+ }
+
+ name->taggerdate = taggerdate;
+ name->generation = generation;
+ name->distance = distance;
+ name->from_tag = from_tag;
+
+ return name;
+}
+
+static char *get_parent_name(const struct rev_name *name, int parent_number)
+{
+ struct strbuf sb = STRBUF_INIT;
+ size_t len;
+
+ strip_suffix(name->tip_name, "^0", &len);
+ if (name->generation > 0) {
+ strbuf_grow(&sb, len +
+ 1 + decimal_width(name->generation) +
+ 1 + decimal_width(parent_number));
+ strbuf_addf(&sb, "%.*s~%d^%d", (int)len, name->tip_name,
+ name->generation, parent_number);
+ } else {
+ strbuf_grow(&sb, len +
+ 1 + decimal_width(parent_number));
+ strbuf_addf(&sb, "%.*s^%d", (int)len, name->tip_name,
+ parent_number);
+ }
+ return strbuf_detach(&sb, NULL);
}
static void name_rev(struct commit *start_commit,
@@ -113,20 +139,20 @@ static void name_rev(struct commit *start_commit,
struct commit *commit;
struct commit **parents_to_queue = NULL;
size_t parents_to_queue_nr, parents_to_queue_alloc = 0;
- char *to_free = NULL;
+ struct rev_name *start_name;
parse_commit(start_commit);
if (start_commit->date < cutoff)
return;
- if (deref)
- tip_name = to_free = xstrfmt("%s^0", tip_name);
-
- if (!create_or_update_name(start_commit, tip_name, taggerdate, 0, 0,
- from_tag)) {
- free(to_free);
+ start_name = create_or_update_name(start_commit, taggerdate, 0, 0,
+ from_tag);
+ if (!start_name)
return;
- }
+ if (deref)
+ start_name->tip_name = xstrfmt("%s^0", tip_name);
+ else
+ start_name->tip_name = xstrdup(tip_name);
memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */
prio_queue_put(&queue, start_commit);
@@ -142,7 +168,7 @@ static void name_rev(struct commit *start_commit,
parents;
parents = parents->next, parent_number++) {
struct commit *parent = parents->item;
- const char *new_name;
+ struct rev_name *parent_name;
int generation, distance;
parse_commit(parent);
@@ -150,30 +176,23 @@ static void name_rev(struct commit *start_commit,
continue;
if (parent_number > 1) {
- size_t len;
-
- strip_suffix(name->tip_name, "^0", &len);
- if (name->generation > 0)
- new_name = xstrfmt("%.*s~%d^%d",
- (int)len,
- name->tip_name,
- name->generation,
- parent_number);
- else
- new_name = xstrfmt("%.*s^%d", (int)len,
- name->tip_name,
- parent_number);
generation = 0;
distance = name->distance + MERGE_TRAVERSAL_WEIGHT;
} else {
- new_name = name->tip_name;
generation = name->generation + 1;
distance = name->distance + 1;
}
- if (create_or_update_name(parent, new_name, taggerdate,
- generation, distance,
- from_tag)) {
+ parent_name = create_or_update_name(parent, taggerdate,
+ generation,
+ distance, from_tag);
+ if (parent_name) {
+ if (parent_number > 1)
+ parent_name->tip_name =
+ get_parent_name(name,
+ parent_number);
+ else
+ parent_name->tip_name = name->tip_name;
ALLOC_GROW(parents_to_queue,
parents_to_queue_nr + 1,
parents_to_queue_alloc);
@@ -228,6 +247,10 @@ static struct tip_table {
struct tip_table_entry {
struct object_id oid;
const char *refname;
+ struct commit *commit;
+ timestamp_t taggerdate;
+ unsigned int from_tag:1;
+ unsigned int deref:1;
} *table;
int nr;
int alloc;
@@ -235,13 +258,18 @@ static struct tip_table {
} tip_table;
static void add_to_tip_table(const struct object_id *oid, const char *refname,
- int shorten_unambiguous)
+ int shorten_unambiguous, struct commit *commit,
+ timestamp_t taggerdate, int from_tag, int deref)
{
refname = name_ref_abbrev(refname, shorten_unambiguous);
ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
oidcpy(&tip_table.table[tip_table.nr].oid, oid);
tip_table.table[tip_table.nr].refname = xstrdup(refname);
+ tip_table.table[tip_table.nr].commit = commit;
+ tip_table.table[tip_table.nr].taggerdate = taggerdate;
+ tip_table.table[tip_table.nr].from_tag = from_tag;
+ tip_table.table[tip_table.nr].deref = deref;
tip_table.nr++;
tip_table.sorted = 0;
}
@@ -252,12 +280,30 @@ static int tipcmp(const void *a_, const void *b_)
return oidcmp(&a->oid, &b->oid);
}
+static int cmp_by_tag_and_age(const void *a_, const void *b_)
+{
+ const struct tip_table_entry *a = a_, *b = b_;
+ int cmp;
+
+ /* Prefer tags. */
+ cmp = b->from_tag - a->from_tag;
+ if (cmp)
+ return cmp;
+
+ /* Older is better. */
+ if (a->taggerdate < b->taggerdate)
+ return -1;
+ return a->taggerdate != b->taggerdate;
+}
+
static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
{
struct object *o = parse_object(the_repository, oid);
struct name_ref_data *data = cb_data;
int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
+ int from_tag = 0;
+ struct commit *commit = NULL;
timestamp_t taggerdate = TIME_MAX;
if (data->tags_only && !starts_with(path, "refs/tags/"))
@@ -306,8 +352,6 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
return 0;
}
- add_to_tip_table(oid, path, can_abbreviate_output);
-
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
@@ -317,17 +361,35 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
taggerdate = t->date;
}
if (o && o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- int from_tag = starts_with(path, "refs/tags/");
-
+ commit = (struct commit *)o;
+ from_tag = starts_with(path, "refs/tags/");
if (taggerdate == TIME_MAX)
taggerdate = commit->date;
- path = name_ref_abbrev(path, can_abbreviate_output);
- name_rev(commit, xstrdup(path), taggerdate, from_tag, deref);
}
+
+ add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,
+ from_tag, deref);
return 0;
}
+static void name_tips(void)
+{
+ int i;
+
+ /*
+ * Try to set better names first, so that worse ones spread
+ * less.
+ */
+ QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
+ for (i = 0; i < tip_table.nr; i++) {
+ struct tip_table_entry *e = &tip_table.table[i];
+ if (e->commit) {
+ name_rev(e->commit, e->refname, e->taggerdate,
+ e->from_tag, e->deref);
+ }
+ }
+}
+
static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
{
struct tip_table_entry *table = table_;
@@ -357,11 +419,11 @@ static const char *get_exact_ref_match(const struct object *o)
static const char *get_rev_name(const struct object *o, struct strbuf *buf)
{
struct rev_name *n;
- struct commit *c;
+ const struct commit *c;
if (o->type != OBJ_COMMIT)
return get_exact_ref_match(o);
- c = (struct commit *) o;
+ c = (const struct commit *) o;
n = get_commit_rev_name(c);
if (!n)
return NULL;
@@ -540,6 +602,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
cutoff = TIME_MIN;
}
for_each_ref(name_ref, &data);
+ name_tips();
if (transform_stdin) {
char buffer[2048];
diff --git a/builtin/notes.c b/builtin/notes.c
index 95456f3..35e468e 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -622,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
strbuf_grow(&d.buf, size + 1);
if (d.buf.len && prev_buf && size)
- strbuf_insert(&d.buf, 0, "\n", 1);
+ strbuf_insertstr(&d.buf, 0, "\n");
if (prev_buf && size)
strbuf_insert(&d.buf, 0, prev_buf, size);
free(prev_buf);
@@ -745,7 +745,7 @@ static int merge_commit(struct notes_merge_options *o)
memset(&pretty_ctx, 0, sizeof(pretty_ctx));
format_commit_message(partial, "%s", &msg, &pretty_ctx);
strbuf_trim(&msg);
- strbuf_insert(&msg, 0, "notes: ", 7);
+ strbuf_insertstr(&msg, 0, "notes: ");
update_ref(msg.buf, o->local_ref, &oid,
is_null_oid(&parent_oid) ? NULL : &parent_oid,
0, UPDATE_REFS_DIE_ON_ERR);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 393c20a..dc7c58c 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -92,10 +92,11 @@ static struct progress *progress_state;
static struct packed_git *reuse_packfile;
static uint32_t reuse_packfile_objects;
-static off_t reuse_packfile_offset;
+static struct bitmap *reuse_packfile_bitmap;
static int use_bitmap_index_default = 1;
static int use_bitmap_index = -1;
+static int allow_pack_reuse = 1;
static enum {
WRITE_BITMAP_FALSE = 0,
WRITE_BITMAP_QUIET,
@@ -303,7 +304,8 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
if (!usable_delta) {
if (oe_type(entry) == OBJ_BLOB &&
oe_size_greater_than(&to_pack, entry, big_file_threshold) &&
- (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL)
+ (st = open_istream(the_repository, &entry->idx.oid, &type,
+ &size, NULL)) != NULL)
buf = NULL;
else {
buf = read_object_file(&entry->idx.oid, &type, &size);
@@ -784,57 +786,186 @@ static struct object_entry **compute_write_order(void)
return wo;
}
-static off_t write_reused_pack(struct hashfile *f)
+
+/*
+ * A reused set of objects. All objects in a chunk have the same
+ * relative position in the original packfile and the generated
+ * packfile.
+ */
+
+static struct reused_chunk {
+ /* The offset of the first object of this chunk in the original
+ * packfile. */
+ off_t original;
+ /* The offset of the first object of this chunk in the generated
+ * packfile minus "original". */
+ off_t difference;
+} *reused_chunks;
+static int reused_chunks_nr;
+static int reused_chunks_alloc;
+
+static void record_reused_object(off_t where, off_t offset)
{
- unsigned char buffer[8192];
- off_t to_write, total;
- int fd;
+ if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset)
+ return;
- if (!is_pack_valid(reuse_packfile))
- die(_("packfile is invalid: %s"), reuse_packfile->pack_name);
+ ALLOC_GROW(reused_chunks, reused_chunks_nr + 1,
+ reused_chunks_alloc);
+ reused_chunks[reused_chunks_nr].original = where;
+ reused_chunks[reused_chunks_nr].difference = offset;
+ reused_chunks_nr++;
+}
- fd = git_open(reuse_packfile->pack_name);
- if (fd < 0)
- die_errno(_("unable to open packfile for reuse: %s"),
- reuse_packfile->pack_name);
+/*
+ * Binary search to find the chunk that "where" is in. Note
+ * that we're not looking for an exact match, just the first
+ * chunk that contains it (which implicitly ends at the start
+ * of the next chunk.
+ */
+static off_t find_reused_offset(off_t where)
+{
+ int lo = 0, hi = reused_chunks_nr;
+ while (lo < hi) {
+ int mi = lo + ((hi - lo) / 2);
+ if (where == reused_chunks[mi].original)
+ return reused_chunks[mi].difference;
+ if (where < reused_chunks[mi].original)
+ hi = mi;
+ else
+ lo = mi + 1;
+ }
- if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
- die_errno(_("unable to seek in reused packfile"));
+ /*
+ * The first chunk starts at zero, so we can't have gone below
+ * there.
+ */
+ assert(lo);
+ return reused_chunks[lo-1].difference;
+}
- if (reuse_packfile_offset < 0)
- reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
+static void write_reused_pack_one(size_t pos, struct hashfile *out,
+ struct pack_window **w_curs)
+{
+ off_t offset, next, cur;
+ enum object_type type;
+ unsigned long size;
- total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
+ offset = reuse_packfile->revindex[pos].offset;
+ next = reuse_packfile->revindex[pos + 1].offset;
- while (to_write) {
- int read_pack = xread(fd, buffer, sizeof(buffer));
+ record_reused_object(offset, offset - hashfile_total(out));
- if (read_pack <= 0)
- die_errno(_("unable to read from reused packfile"));
+ cur = offset;
+ type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
+ assert(type >= 0);
- if (read_pack > to_write)
- read_pack = to_write;
+ if (type == OBJ_OFS_DELTA) {
+ off_t base_offset;
+ off_t fixup;
- hashwrite(f, buffer, read_pack);
- to_write -= read_pack;
+ unsigned char header[MAX_PACK_OBJECT_HEADER];
+ unsigned len;
+
+ base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset);
+ assert(base_offset != 0);
+
+ /* Convert to REF_DELTA if we must... */
+ if (!allow_ofs_delta) {
+ int base_pos = find_revindex_position(reuse_packfile, base_offset);
+ struct object_id base_oid;
+
+ nth_packed_object_id(&base_oid, reuse_packfile,
+ reuse_packfile->revindex[base_pos].nr);
+
+ len = encode_in_pack_object_header(header, sizeof(header),
+ OBJ_REF_DELTA, size);
+ hashwrite(out, header, len);
+ hashwrite(out, base_oid.hash, the_hash_algo->rawsz);
+ copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+ return;
+ }
+
+ /* Otherwise see if we need to rewrite the offset... */
+ fixup = find_reused_offset(offset) -
+ find_reused_offset(base_offset);
+ if (fixup) {
+ unsigned char ofs_header[10];
+ unsigned i, ofs_len;
+ off_t ofs = offset - base_offset - fixup;
+
+ len = encode_in_pack_object_header(header, sizeof(header),
+ OBJ_OFS_DELTA, size);
+
+ i = sizeof(ofs_header) - 1;
+ ofs_header[i] = ofs & 127;
+ while (ofs >>= 7)
+ ofs_header[--i] = 128 | (--ofs & 127);
+
+ ofs_len = sizeof(ofs_header) - i;
+
+ hashwrite(out, header, len);
+ hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len);
+ copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+ return;
+ }
+
+ /* ...otherwise we have no fixup, and can write it verbatim */
+ }
+
+ copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset);
+}
+
+static size_t write_reused_pack_verbatim(struct hashfile *out,
+ struct pack_window **w_curs)
+{
+ size_t pos = 0;
+
+ while (pos < reuse_packfile_bitmap->word_alloc &&
+ reuse_packfile_bitmap->words[pos] == (eword_t)~0)
+ pos++;
+
+ if (pos) {
+ off_t to_write;
+
+ written = (pos * BITS_IN_EWORD);
+ to_write = reuse_packfile->revindex[written].offset
+ - sizeof(struct pack_header);
+
+ /* We're recording one chunk, not one object. */
+ record_reused_object(sizeof(struct pack_header), 0);
+ hashflush(out);
+ copy_pack_data(out, reuse_packfile, w_curs,
+ sizeof(struct pack_header), to_write);
- /*
- * We don't know the actual number of objects written,
- * only how many bytes written, how many bytes total, and
- * how many objects total. So we can fake it by pretending all
- * objects we are writing are the same size. This gives us a
- * smooth progress meter, and at the end it matches the true
- * answer.
- */
- written = reuse_packfile_objects *
- (((double)(total - to_write)) / total);
display_progress(progress_state, written);
}
+ return pos;
+}
+
+static void write_reused_pack(struct hashfile *f)
+{
+ size_t i = 0;
+ uint32_t offset;
+ struct pack_window *w_curs = NULL;
+
+ if (allow_ofs_delta)
+ i = write_reused_pack_verbatim(f, &w_curs);
- close(fd);
- written = reuse_packfile_objects;
- display_progress(progress_state, written);
- return reuse_packfile_offset - sizeof(struct pack_header);
+ for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
+ eword_t word = reuse_packfile_bitmap->words[i];
+ size_t pos = (i * BITS_IN_EWORD);
+
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+ if ((word >> offset) == 0)
+ break;
+
+ offset += ewah_bit_ctz64(word >> offset);
+ write_reused_pack_one(pos + offset, f, &w_curs);
+ display_progress(progress_state, ++written);
+ }
+ }
+
+ unuse_pack(&w_curs);
}
static const char no_split_warning[] = N_(
@@ -867,11 +998,9 @@ static void write_pack_file(void)
offset = write_pack_header(f, nr_remaining);
if (reuse_packfile) {
- off_t packfile_size;
assert(pack_to_stdout);
-
- packfile_size = write_reused_pack(f);
- offset += packfile_size;
+ write_reused_pack(f);
+ offset = hashfile_total(f);
}
nr_written = 0;
@@ -1000,6 +1129,10 @@ static int have_duplicate_entry(const struct object_id *oid,
{
struct object_entry *entry;
+ if (reuse_packfile_bitmap &&
+ bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid))
+ return 1;
+
entry = packlist_find(&to_pack, oid);
if (!entry)
return 0;
@@ -1486,23 +1619,17 @@ static void cleanup_preferred_base(void)
* deltify other objects against, in order to avoid
* circular deltas.
*/
-static int can_reuse_delta(const unsigned char *base_sha1,
+static int can_reuse_delta(const struct object_id *base_oid,
struct object_entry *delta,
struct object_entry **base_out)
{
struct object_entry *base;
- struct object_id base_oid;
-
- if (!base_sha1)
- return 0;
-
- oidread(&base_oid, base_sha1);
/*
* First see if we're already sending the base (or it's explicitly in
* our "excluded" list).
*/
- base = packlist_find(&to_pack, &base_oid);
+ base = packlist_find(&to_pack, base_oid);
if (base) {
if (!in_same_island(&delta->idx.oid, &base->idx.oid))
return 0;
@@ -1515,9 +1642,9 @@ static int can_reuse_delta(const unsigned char *base_sha1,
* even if it was buried too deep in history to make it into the
* packing list.
*/
- if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, &base_oid)) {
+ if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) {
if (use_delta_islands) {
- if (!in_same_island(&delta->idx.oid, &base_oid))
+ if (!in_same_island(&delta->idx.oid, base_oid))
return 0;
}
*base_out = NULL;
@@ -1534,7 +1661,8 @@ static void check_object(struct object_entry *entry)
if (IN_PACK(entry)) {
struct packed_git *p = IN_PACK(entry);
struct pack_window *w_curs = NULL;
- const unsigned char *base_ref = NULL;
+ int have_base = 0;
+ struct object_id base_ref;
struct object_entry *base_entry;
unsigned long used, used_0;
unsigned long avail;
@@ -1575,9 +1703,13 @@ static void check_object(struct object_entry *entry)
unuse_pack(&w_curs);
return;
case OBJ_REF_DELTA:
- if (reuse_delta && !entry->preferred_base)
- base_ref = use_pack(p, &w_curs,
- entry->in_pack_offset + used, NULL);
+ if (reuse_delta && !entry->preferred_base) {
+ oidread(&base_ref,
+ use_pack(p, &w_curs,
+ entry->in_pack_offset + used,
+ NULL));
+ have_base = 1;
+ }
entry->in_pack_header_size = used + the_hash_algo->rawsz;
break;
case OBJ_OFS_DELTA:
@@ -1607,13 +1739,15 @@ static void check_object(struct object_entry *entry)
revidx = find_pack_revindex(p, ofs);
if (!revidx)
goto give_up;
- base_ref = nth_packed_object_sha1(p, revidx->nr);
+ if (!nth_packed_object_id(&base_ref, p, revidx->nr))
+ have_base = 1;
}
entry->in_pack_header_size = used + used_0;
break;
}
- if (can_reuse_delta(base_ref, entry, &base_entry)) {
+ if (have_base &&
+ can_reuse_delta(&base_ref, entry, &base_entry)) {
oe_set_type(entry, entry->in_pack_type);
SET_SIZE(entry, in_pack_size); /* delta size */
SET_DELTA_SIZE(entry, in_pack_size);
@@ -1623,7 +1757,7 @@ static void check_object(struct object_entry *entry)
entry->delta_sibling_idx = base_entry->delta_child_idx;
SET_DELTA_CHILD(base_entry, entry);
} else {
- SET_DELTA_EXT(entry, base_ref);
+ SET_DELTA_EXT(entry, &base_ref);
}
unuse_pack(&w_curs);
@@ -2552,6 +2686,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
free(p);
}
+static int obj_is_packed(const struct object_id *oid)
+{
+ return packlist_find(&to_pack, oid) ||
+ (reuse_packfile_bitmap &&
+ bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid));
+}
+
static void add_tag_chain(const struct object_id *oid)
{
struct tag *tag;
@@ -2563,7 +2704,7 @@ static void add_tag_chain(const struct object_id *oid)
* it was included via bitmaps, we would not have parsed it
* previously).
*/
- if (packlist_find(&to_pack, oid))
+ if (obj_is_packed(oid))
return;
tag = lookup_tag(the_repository, oid);
@@ -2587,7 +2728,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag,
if (starts_with(path, "refs/tags/") && /* is a tag? */
!peel_ref(path, &peeled) && /* peelable? */
- packlist_find(&to_pack, &peeled)) /* object packed? */
+ obj_is_packed(&peeled)) /* object packed? */
add_tag_chain(oid);
return 0;
}
@@ -2655,6 +2796,7 @@ static void prepare_pack(int window, int depth)
if (nr_deltas && n > 1) {
unsigned nr_done = 0;
+
if (progress)
progress_state = start_progress(_("Compressing objects"),
nr_deltas);
@@ -2699,6 +2841,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
use_bitmap_index_default = git_config_bool(k, v);
return 0;
}
+ if (!strcmp(k, "pack.allowpackreuse")) {
+ allow_pack_reuse = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "pack.threads")) {
delta_search_threads = git_config_int(k, v);
if (delta_search_threads < 0)
@@ -2909,7 +3055,7 @@ static void add_objects_in_unpacked_packs(void)
in_pack.alloc);
for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_oid(&oid, p, i);
+ nth_packed_object_id(&oid, p, i);
o = lookup_unknown_object(&oid);
if (!(o->flags & OBJECT_ADDED))
mark_in_pack_object(o, p, &in_pack);
@@ -3013,7 +3159,7 @@ static void loosen_unused_packed_objects(void)
die(_("cannot open pack index"));
for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_oid(&oid, p, i);
+ nth_packed_object_id(&oid, p, i);
if (!packlist_find(&to_pack, &oid) &&
!has_sha1_pack_kept_or_nonlocal(&oid) &&
!loosened_object_can_be_discarded(&oid, p->mtime))
@@ -3030,8 +3176,8 @@ static void loosen_unused_packed_objects(void)
*/
static int pack_options_allow_reuse(void)
{
- return pack_to_stdout &&
- allow_ofs_delta &&
+ return allow_pack_reuse &&
+ pack_to_stdout &&
!ignore_packed_keep_on_disk &&
!ignore_packed_keep_in_core &&
(!local || !have_non_local_packs) &&
@@ -3040,7 +3186,7 @@ static int pack_options_allow_reuse(void)
static int get_object_list_from_bitmap(struct rev_info *revs)
{
- if (!(bitmap_git = prepare_bitmap_walk(revs)))
+ if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options)))
return -1;
if (pack_options_allow_reuse() &&
@@ -3048,13 +3194,14 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
bitmap_git,
&reuse_packfile,
&reuse_packfile_objects,
- &reuse_packfile_offset)) {
+ &reuse_packfile_bitmap)) {
assert(reuse_packfile_objects);
nr_result += reuse_packfile_objects;
display_progress(progress_state, nr_result);
}
- traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap);
+ traverse_bitmap_commit_list(bitmap_git, revs,
+ &add_object_entry_from_bitmap);
return 0;
}
@@ -3322,9 +3469,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
- sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
+ sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
prepare_repo_settings(the_repository);
- if (!sparse && the_repository->settings.pack_use_sparse != -1)
+ if (sparse < 0)
sparse = the_repository->settings.pack_use_sparse;
reset_pack_idx_option(&pack_idx_opts);
@@ -3418,7 +3565,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (filter_options.choice) {
if (!pack_to_stdout)
die(_("cannot use --filter without --stdout"));
- use_bitmap_index = 0;
}
/*
@@ -3509,7 +3655,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (progress)
fprintf_ln(stderr,
_("Total %"PRIu32" (delta %"PRIu32"),"
- " reused %"PRIu32" (delta %"PRIu32")"),
- written, written_delta, reused, reused_delta);
+ " reused %"PRIu32" (delta %"PRIu32"),"
+ " pack-reused %"PRIu32),
+ written, written_delta, reused, reused_delta,
+ reuse_packfile_objects);
return 0;
}
diff --git a/builtin/pull.c b/builtin/pull.c
index d25ff13..e42665b 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -15,6 +15,7 @@
#include "sha1-array.h"
#include "remote.h"
#include "dir.h"
+#include "rebase.h"
#include "refs.h"
#include "refspec.h"
#include "revision.h"
@@ -26,15 +27,6 @@
#include "commit-reach.h"
#include "sequencer.h"
-enum rebase_type {
- REBASE_INVALID = -1,
- REBASE_FALSE = 0,
- REBASE_TRUE,
- REBASE_PRESERVE,
- REBASE_MERGES,
- REBASE_INTERACTIVE
-};
-
/**
* Parses the value of --rebase. If value is a false value, returns
* REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
@@ -45,22 +37,9 @@ enum rebase_type {
static enum rebase_type parse_config_rebase(const char *key, const char *value,
int fatal)
{
- int v = git_parse_maybe_bool(value);
-
- if (!v)
- return REBASE_FALSE;
- else if (v > 0)
- return REBASE_TRUE;
- else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
- return REBASE_PRESERVE;
- else if (!strcmp(value, "merges") || !strcmp(value, "m"))
- return REBASE_MERGES;
- else if (!strcmp(value, "interactive") || !strcmp(value, "i"))
- return REBASE_INTERACTIVE;
- /*
- * Please update _git_config() in git-completion.bash when you
- * add new rebase modes.
- */
+ enum rebase_type v = rebase_parse_value(value);
+ if (v != REBASE_INVALID)
+ return v;
if (fatal)
die(_("Invalid value for %s: %s"), key, value);
@@ -107,6 +86,7 @@ static char *opt_ff;
static char *opt_verify_signatures;
static int opt_autostash = -1;
static int config_autostash;
+static int check_trust_level = 1;
static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
static char *opt_gpg_sign;
@@ -347,6 +327,22 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
+ if (opt_verbosity >= 0 &&
+ (!opt_ff || strcmp(opt_ff, "--ff-only"))) {
+ warning(_("Pulling without specifying how to reconcile divergent branches is\n"
+ "discouraged. You can squelch this message by running one of the following\n"
+ "commands sometime before your next pull:\n"
+ "\n"
+ " git config pull.rebase false # merge (the default strategy)\n"
+ " git config pull.rebase true # rebase\n"
+ " git config pull.ff only # fast-forward only\n"
+ "\n"
+ "You can replace \"git config\" with \"git config --global\" to set a default\n"
+ "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+ "or --ff-only on the command line to override the configured default per\n"
+ "invocation.\n"));
+ }
+
return REBASE_FALSE;
}
@@ -355,6 +351,8 @@ static enum rebase_type config_get_rebase(void)
*/
static int git_pull_config(const char *var, const char *value, void *cb)
{
+ int status;
+
if (!strcmp(var, "rebase.autostash")) {
config_autostash = git_config_bool(var, value);
return 0;
@@ -362,7 +360,14 @@ static int git_pull_config(const char *var, const char *value, void *cb)
recurse_submodules = git_config_bool(var, value) ?
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
return 0;
+ } else if (!strcmp(var, "gpg.mintrustlevel")) {
+ check_trust_level = 0;
}
+
+ status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+
return git_default_config(var, value, cb);
}
@@ -587,7 +592,8 @@ static int pull_into_void(const struct object_id *merge_head,
die(_("unable to access commit %s"),
oid_to_hex(merge_head));
- verify_merge_signature(commit, opt_verbosity);
+ verify_merge_signature(commit, opt_verbosity,
+ check_trust_level);
}
/*
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 8081741..27a07d4 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -44,14 +44,22 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
enum rebase_type {
REBASE_UNSPECIFIED = -1,
- REBASE_AM,
+ REBASE_APPLY,
REBASE_MERGE,
- REBASE_INTERACTIVE,
REBASE_PRESERVE_MERGES
};
+enum empty_type {
+ EMPTY_UNSPECIFIED = -1,
+ EMPTY_DROP,
+ EMPTY_KEEP,
+ EMPTY_ASK
+};
+
struct rebase_options {
enum rebase_type type;
+ enum empty_type empty;
+ const char *default_backend;
const char *state_dir;
struct commit *upstream;
const char *upstream_name;
@@ -77,7 +85,6 @@ struct rebase_options {
const char *action;
int signoff;
int allow_rerere_autoupdate;
- int keep_empty;
int autosquash;
char *gpg_sign_opt;
int autostash;
@@ -92,6 +99,8 @@ struct rebase_options {
#define REBASE_OPTIONS_INIT { \
.type = REBASE_UNSPECIFIED, \
+ .empty = EMPTY_UNSPECIFIED, \
+ .default_backend = "merge", \
.flags = REBASE_NO_QUIET, \
.git_am_opts = ARGV_ARRAY_INIT, \
.git_format_patch_opt = STRBUF_INIT \
@@ -110,6 +119,9 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
replay.allow_empty = 1;
replay.allow_empty_message = opts->allow_empty_message;
+ replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+ replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+ replay.quiet = !(opts->flags & REBASE_NO_QUIET);
replay.verbose = opts->flags & REBASE_VERBOSE;
replay.reschedule_failed_exec = opts->reschedule_failed_exec;
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
@@ -246,21 +258,17 @@ static int edit_todo_file(unsigned flags)
}
static int get_revision_ranges(struct commit *upstream, struct commit *onto,
- const char **head_hash,
+ struct object_id *orig_head, const char **head_hash,
char **revisions, char **shortrevisions)
{
struct commit *base_rev = upstream ? upstream : onto;
const char *shorthead;
- struct object_id orig_head;
- if (get_oid("HEAD", &orig_head))
- return error(_("no HEAD?"));
-
- *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
+ *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ);
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
*head_hash);
- shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
+ shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
if (upstream) {
const char *shortrev;
@@ -314,12 +322,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
struct replay_opts replay = get_replay_opts(opts);
struct string_list commands = STRING_LIST_INIT_DUP;
- if (prepare_branch_to_be_rebased(the_repository, &replay,
- opts->switch_to))
- return -1;
-
- if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
- &revisions, &shortrevisions))
+ if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head,
+ &head_hash, &revisions, &shortrevisions))
return -1;
if (init_basic_state(&replay,
@@ -337,8 +341,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
argv_array_pushl(&make_script_args, "", revisions, NULL);
if (opts->restrict_revision)
- argv_array_push(&make_script_args,
- oid_to_hex(&opts->restrict_revision->object.oid));
+ argv_array_pushf(&make_script_args, "^%s",
+ oid_to_hex(&opts->restrict_revision->object.oid));
ret = sequencer_make_script(the_repository, &todo_list.buf,
make_script_args.argc, make_script_args.argv,
@@ -367,7 +371,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
return ret;
}
-static int run_rebase_interactive(struct rebase_options *opts,
+static int run_sequencer_rebase(struct rebase_options *opts,
enum action command)
{
unsigned flags = 0;
@@ -375,7 +379,6 @@ static int run_rebase_interactive(struct rebase_options *opts,
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
- flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
@@ -439,6 +442,21 @@ static int run_rebase_interactive(struct rebase_options *opts,
return ret;
}
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+ int unset)
+{
+ struct rebase_options *opts = opt->value;
+
+ BUG_ON_OPT_ARG(arg);
+
+ /*
+ * If we ever want to remap --keep-empty to --empty=keep, insert:
+ * opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
+ */
+ opts->type = REBASE_MERGE;
+ return 0;
+}
+
static const char * const builtin_rebase_interactive_usage[] = {
N_("git rebase--interactive [<options>]"),
NULL
@@ -452,9 +470,13 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
REBASE_FORCE),
- OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
- OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
- N_("allow commits with empty messages")),
+ { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+ N_("(DEPRECATED) keep empty commits"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+ parse_opt_keep_empty },
+ OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
+ N_("allow commits with empty messages"),
+ PARSE_OPT_HIDDEN),
OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
N_("keep original branch points of cousins")),
@@ -524,28 +546,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
warning(_("--[no-]rebase-cousins has no effect without "
"--rebase-merges"));
- return !!run_rebase_interactive(&opts, command);
+ return !!run_sequencer_rebase(&opts, command);
}
-static int is_interactive(struct rebase_options *opts)
+static int is_merge(struct rebase_options *opts)
{
- return opts->type == REBASE_INTERACTIVE ||
+ return opts->type == REBASE_MERGE ||
opts->type == REBASE_PRESERVE_MERGES;
}
-static void imply_interactive(struct rebase_options *opts, const char *option)
+static void imply_merge(struct rebase_options *opts, const char *option)
{
switch (opts->type) {
- case REBASE_AM:
+ case REBASE_APPLY:
die(_("%s requires an interactive rebase"), option);
break;
- case REBASE_INTERACTIVE:
+ case REBASE_MERGE:
case REBASE_PRESERVE_MERGES:
break;
- case REBASE_MERGE:
- /* we now implement --merge via --interactive */
default:
- opts->type = REBASE_INTERACTIVE; /* implied */
+ opts->type = REBASE_MERGE; /* implied */
break;
}
}
@@ -671,8 +691,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
write_file(state_dir_path("orig-head", opts), "%s",
oid_to_hex(&opts->orig_head));
- write_file(state_dir_path("quiet", opts), "%s",
- opts->flags & REBASE_NO_QUIET ? "" : "t");
+ if (!(opts->flags & REBASE_NO_QUIET))
+ write_file(state_dir_path("quiet", opts), "%s", "");
if (opts->flags & REBASE_VERBOSE)
write_file(state_dir_path("verbose", opts), "%s", "");
if (opts->strategy)
@@ -754,7 +774,7 @@ static int finish_rebase(struct rebase_options *opts)
* user should see them.
*/
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
- if (opts->type == REBASE_INTERACTIVE) {
+ if (opts->type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -848,6 +868,7 @@ static int reset_head(struct object_id *oid, const char *action,
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
unpack_tree_opts.update = 1;
unpack_tree_opts.merge = 1;
+ init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
if (!detach_head)
unpack_tree_opts.reset = 1;
@@ -1087,8 +1108,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
int status;
const char *backend, *backend_func;
- if (opts->type == REBASE_INTERACTIVE) {
- /* Run builtin interactive rebase */
+ if (opts->type == REBASE_MERGE) {
+ /* Run sequencer-based rebase */
setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
setenv("GIT_SEQUENCE_EDITOR", ":", 1);
@@ -1101,11 +1122,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
opts->gpg_sign_opt = tmp;
}
- status = run_rebase_interactive(opts, action);
+ status = run_sequencer_rebase(opts, action);
goto finished_rebase;
}
- if (opts->type == REBASE_AM) {
+ if (opts->type == REBASE_APPLY) {
status = run_am(opts);
goto finished_rebase;
}
@@ -1125,8 +1146,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
add_var(&script_snippet, "revisions", opts->revisions);
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
- add_var(&script_snippet, "GIT_QUIET",
- opts->flags & REBASE_NO_QUIET ? "" : "t");
sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
add_var(&script_snippet, "git_am_opt", buf.buf);
strbuf_release(&buf);
@@ -1144,7 +1163,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
opts->allow_rerere_autoupdate ?
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
- add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
add_var(&script_snippet, "cmd", opts->cmd);
@@ -1162,7 +1180,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
add_var(&script_snippet, "git_format_patch_opt",
opts->git_format_patch_opt.buf);
- if (is_interactive(opts) &&
+ if (is_merge(opts) &&
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
strbuf_addstr(&script_snippet,
"GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
@@ -1187,8 +1205,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
finished_rebase:
if (opts->dont_finish_rebase)
; /* do nothing */
- else if (opts->type == REBASE_INTERACTIVE)
- ; /* interactive rebase cleans up after itself */
+ else if (opts->type == REBASE_MERGE)
+ ; /* merge backend cleans up after itself */
else if (status == 0) {
if (!file_exists(state_dir_path("stopped-sha", opts)))
finish_rebase(opts);
@@ -1246,6 +1264,10 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0;
}
+ if (!strcmp(var, "rebase.backend")) {
+ return git_config_string(&opts->default_backend, var, value);
+ }
+
return git_default_config(var, value, data);
}
@@ -1309,6 +1331,18 @@ done:
return res && is_linear_history(onto, head);
}
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+ struct rebase_options *opts = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+ BUG_ON_OPT_ARG(arg);
+
+ opts->type = REBASE_APPLY;
+
+ return 0;
+}
+
/* -i followed by -m is still -i */
static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
{
@@ -1317,7 +1351,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
- if (!is_interactive(opts))
+ if (!is_merge(opts))
opts->type = REBASE_MERGE;
return 0;
@@ -1332,12 +1366,35 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
- opts->type = REBASE_INTERACTIVE;
+ opts->type = REBASE_MERGE;
opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
return 0;
}
+static enum empty_type parse_empty_value(const char *value)
+{
+ if (!strcasecmp(value, "drop"))
+ return EMPTY_DROP;
+ else if (!strcasecmp(value, "keep"))
+ return EMPTY_KEEP;
+ else if (!strcasecmp(value, "ask"))
+ return EMPTY_ASK;
+
+ die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+ struct rebase_options *options = opt->value;
+ enum empty_type value = parse_empty_value(arg);
+
+ BUG_ON_OPT_NEG(unset);
+
+ options->empty = value;
+ return 0;
+}
+
static void NORETURN error_on_missing_default_upstream(void)
{
struct branch *current_branch = branch_get(NULL);
@@ -1373,14 +1430,14 @@ static void set_reflog_action(struct rebase_options *options)
const char *env;
struct strbuf buf = STRBUF_INIT;
- if (!is_interactive(options))
+ if (!is_merge(options))
return;
env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
if (env && strcmp("rebase", env))
return; /* only override it if it is "rebase" */
- strbuf_addf(&buf, "rebase -i (%s)", options->action);
+ strbuf_addf(&buf, "rebase (%s)", options->action);
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
strbuf_release(&buf);
}
@@ -1418,6 +1475,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
struct object_id squash_onto;
char *squash_onto_name = NULL;
int reschedule_failed_exec = -1;
+ int allow_preemptive_ff = 1;
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
@@ -1428,7 +1486,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
N_("allow pre-rebase hook to run")),
OPT_NEGBIT('q', "quiet", &options.flags,
N_("be quiet. implies --no-stat"),
- REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+ REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
OPT_BIT('v', "verbose", &options.flags,
N_("display a diffstat of what changed upstream"),
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
@@ -1469,6 +1527,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_CMDMODE(0, "show-current-patch", &action,
N_("show the patch file being applied or merged"),
ACTION_SHOW_CURRENT_PATCH),
+ { OPTION_CALLBACK, 0, "apply", &options, NULL,
+ N_("use apply strategies to rebase"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ parse_opt_am },
{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
N_("use merging strategies to rebase"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1482,8 +1544,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
"ignoring them"),
REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
- OPT_BOOL('k', "keep-empty", &options.keep_empty,
- N_("preserve empty commits during rebase")),
+ OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
+ N_("how to handle commits that become empty"),
+ PARSE_OPT_NONEG, parse_opt_empty),
+ { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
+ N_("(DEPRECATED) keep empty commits"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+ parse_opt_keep_empty },
OPT_BOOL(0, "autosquash", &options.autosquash,
N_("move commits that begin with "
"squash!/fixup! under -i")),
@@ -1495,9 +1562,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
N_("add exec lines after each commit of the "
"editable list")),
- OPT_BOOL(0, "allow-empty-message",
- &options.allow_empty_message,
- N_("allow rebasing commits with empty messages")),
+ OPT_BOOL_F(0, "allow-empty-message",
+ &options.allow_empty_message,
+ N_("allow rebasing commits with empty messages"),
+ PARSE_OPT_HIDDEN),
{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
N_("mode"),
N_("try to rebase merges instead of skipping them"),
@@ -1537,7 +1605,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("It looks like 'git am' is in progress. Cannot rebase."));
if (is_directory(apply_dir())) {
- options.type = REBASE_AM;
+ options.type = REBASE_APPLY;
options.state_dir = apply_dir();
} else if (is_directory(merge_dir())) {
strbuf_reset(&buf);
@@ -1549,7 +1617,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/interactive", merge_dir());
if(file_exists(buf.buf)) {
- options.type = REBASE_INTERACTIVE;
+ options.type = REBASE_MERGE;
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
} else
options.type = REBASE_MERGE;
@@ -1589,12 +1657,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("No rebase in progress?"));
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
- if (action == ACTION_EDIT_TODO && !is_interactive(&options))
+ if (action == ACTION_EDIT_TODO && !is_merge(&options))
die(_("The --edit-todo action can only be used during "
"interactive rebase."));
if (trace2_is_enabled()) {
- if (is_interactive(&options))
+ if (is_merge(&options))
trace2_cmd_mode("interactive");
else if (exec.nr)
trace2_cmd_mode("interactive-exec");
@@ -1670,7 +1738,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
goto cleanup;
}
case ACTION_QUIT: {
- if (options.type == REBASE_INTERACTIVE) {
+ if (options.type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1719,13 +1787,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
state_dir_base, cmd_live_rebase, buf.buf);
}
+ if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+ (action != ACTION_NONE) ||
+ (exec.nr > 0) ||
+ options.autosquash) {
+ allow_preemptive_ff = 0;
+ }
+
for (i = 0; i < options.git_am_opts.argc; i++) {
const char *option = options.git_am_opts.argv[i], *p;
if (!strcmp(option, "--committer-date-is-author-date") ||
!strcmp(option, "--ignore-date") ||
!strcmp(option, "--whitespace=fix") ||
!strcmp(option, "--whitespace=strip"))
- options.flags |= REBASE_FORCE;
+ allow_preemptive_ff = 0;
else if (skip_prefix(option, "-C", &p)) {
while (*p)
if (!isdigit(*(p++)))
@@ -1745,8 +1820,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!(options.flags & REBASE_NO_QUIET))
argv_array_push(&options.git_am_opts, "-q");
- if (options.keep_empty)
- imply_interactive(&options, "--keep-empty");
+ if (options.empty != EMPTY_UNSPECIFIED)
+ imply_merge(&options, "--empty");
if (gpg_sign) {
free(options.gpg_sign_opt);
@@ -1756,7 +1831,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (exec.nr) {
int i;
- imply_interactive(&options, "--exec");
+ imply_merge(&options, "--exec");
strbuf_reset(&buf);
for (i = 0; i < exec.nr; i++)
@@ -1772,7 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
else if (strcmp("no-rebase-cousins", rebase_merges))
die(_("Unknown mode: %s"), rebase_merges);
options.rebase_merges = 1;
- imply_interactive(&options, "--rebase-merges");
+ imply_merge(&options, "--rebase-merges");
}
if (strategy_options.nr) {
@@ -1791,10 +1866,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.strategy) {
options.strategy = xstrdup(options.strategy);
switch (options.type) {
- case REBASE_AM:
+ case REBASE_APPLY:
die(_("--strategy requires --merge or --interactive"));
case REBASE_MERGE:
- case REBASE_INTERACTIVE:
case REBASE_PRESERVE_MERGES:
/* compatible */
break;
@@ -1807,47 +1881,65 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
}
if (options.type == REBASE_MERGE)
- imply_interactive(&options, "--merge");
+ imply_merge(&options, "--merge");
if (options.root && !options.onto_name)
- imply_interactive(&options, "--root without --onto");
+ imply_merge(&options, "--root without --onto");
if (isatty(2) && options.flags & REBASE_NO_QUIET)
strbuf_addstr(&options.git_format_patch_opt, " --progress");
+ if (options.git_am_opts.argc || options.type == REBASE_APPLY) {
+ /* all am options except -q are compatible only with --apply */
+ for (i = options.git_am_opts.argc - 1; i >= 0; i--)
+ if (strcmp(options.git_am_opts.argv[i], "-q"))
+ break;
+
+ if (i >= 0) {
+ if (is_merge(&options))
+ die(_("cannot combine apply options with "
+ "merge options"));
+ else
+ options.type = REBASE_APPLY;
+ }
+ }
+
+ if (options.type == REBASE_UNSPECIFIED) {
+ if (!strcmp(options.default_backend, "merge"))
+ imply_merge(&options, "--merge");
+ else if (!strcmp(options.default_backend, "apply"))
+ options.type = REBASE_APPLY;
+ else
+ die(_("Unknown rebase backend: %s"),
+ options.default_backend);
+ }
+
switch (options.type) {
case REBASE_MERGE:
- case REBASE_INTERACTIVE:
case REBASE_PRESERVE_MERGES:
options.state_dir = merge_dir();
break;
- case REBASE_AM:
+ case REBASE_APPLY:
options.state_dir = apply_dir();
break;
default:
- /* the default rebase backend is `--am` */
- options.type = REBASE_AM;
- options.state_dir = apply_dir();
- break;
+ BUG("options.type was just set above; should be unreachable.");
}
- if (reschedule_failed_exec > 0 && !is_interactive(&options))
+ if (options.empty == EMPTY_UNSPECIFIED) {
+ if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+ options.empty = EMPTY_ASK;
+ else if (exec.nr > 0)
+ options.empty = EMPTY_KEEP;
+ else
+ options.empty = EMPTY_DROP;
+ }
+ if (reschedule_failed_exec > 0 && !is_merge(&options))
die(_("--reschedule-failed-exec requires "
"--exec or --interactive"));
if (reschedule_failed_exec >= 0)
options.reschedule_failed_exec = reschedule_failed_exec;
- if (options.git_am_opts.argc) {
- /* all am options except -q are compatible only with --am */
- for (i = options.git_am_opts.argc - 1; i >= 0; i--)
- if (strcmp(options.git_am_opts.argv[i], "-q"))
- break;
-
- if (is_interactive(&options) && i >= 0)
- die(_("cannot combine am options with either "
- "interactive or merge options"));
- }
-
if (options.signoff) {
if (options.type == REBASE_PRESERVE_MERGES)
die("cannot combine '--signoff' with "
@@ -1953,10 +2045,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/* Is it a local branch? */
strbuf_reset(&buf);
strbuf_addf(&buf, "refs/heads/%s", branch_name);
- if (!read_ref(buf.buf, &options.orig_head))
+ if (!read_ref(buf.buf, &options.orig_head)) {
+ die_if_checked_out(buf.buf, 1);
options.head_name = xstrdup(buf.buf);
/* If not is it a valid ref (branch or commit)? */
- else if (!get_oid(branch_name, &options.orig_head))
+ } else if (!get_oid(branch_name, &options.orig_head))
options.head_name = NULL;
else
die(_("fatal: no such branch/commit '%s'"),
@@ -2053,30 +2146,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/*
* Check if we are already based on onto with linear history,
* in which case we could fast-forward without replacing the commits
- * with new commits recreated by replaying their changes. This
- * optimization must not be done if this is an interactive rebase.
+ * with new commits recreated by replaying their changes.
+ *
+ * Note that can_fast_forward() initializes merge_base, so we have to
+ * call it before checking allow_preemptive_ff.
*/
if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
&options.orig_head, &merge_base) &&
- !is_interactive(&options)) {
+ allow_preemptive_ff) {
int flag;
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
- struct object_id oid;
-
- if (get_oid(options.switch_to, &oid) < 0) {
- ret = !!error(_("could not parse '%s'"),
- options.switch_to);
- goto cleanup;
- }
-
strbuf_reset(&buf);
strbuf_addf(&buf, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options.switch_to);
- if (reset_head(&oid, "checkout",
+ if (reset_head(&options.orig_head, "checkout",
options.head_name,
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
NULL, buf.buf) < 0) {
@@ -2141,7 +2228,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
diff_flush(&opts);
}
- if (is_interactive(&options))
+ if (is_merge(&options))
goto run_rebase;
/* Detach HEAD and reset the tree */
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 411e0b4..2cc18bb 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -27,6 +27,7 @@
#include "object-store.h"
#include "protocol.h"
#include "commit-reach.h"
+#include "worktree.h"
static const char * const receive_pack_usage[] = {
N_("git receive-pack <git-dir>"),
@@ -816,16 +817,6 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
-static int is_ref_checked_out(const char *ref)
-{
- if (is_bare_repository())
- return 0;
-
- if (!head_name)
- return 0;
- return !strcmp(head_name, ref);
-}
-
static char *refuse_unconfigured_deny_msg =
N_("By default, updating the current branch in a non-bare repository\n"
"is denied, because it will make the index and work tree inconsistent\n"
@@ -997,16 +988,26 @@ static const char *push_to_checkout(unsigned char *hash,
return NULL;
}
-static const char *update_worktree(unsigned char *sha1)
+static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)
{
- const char *retval;
- const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
+ const char *retval, *work_tree, *git_dir = NULL;
struct argv_array env = ARGV_ARRAY_INIT;
+ if (worktree && worktree->path)
+ work_tree = worktree->path;
+ else if (git_work_tree_cfg)
+ work_tree = git_work_tree_cfg;
+ else
+ work_tree = "..";
+
if (is_bare_repository())
return "denyCurrentBranch = updateInstead needs a worktree";
+ if (worktree)
+ git_dir = get_worktree_git_dir(worktree);
+ if (!git_dir)
+ git_dir = get_git_dir();
- argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+ argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
if (!find_hook(push_to_checkout_hook))
retval = push_to_deploy(sha1, &env, work_tree);
@@ -1026,6 +1027,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
struct object_id *old_oid = &cmd->old_oid;
struct object_id *new_oid = &cmd->new_oid;
int do_update_worktree = 0;
+ const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name);
/* only refs/... are allowed */
if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
@@ -1037,7 +1039,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
free(namespaced_name);
namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
- if (is_ref_checked_out(namespaced_name)) {
+ if (worktree) {
switch (deny_current_branch) {
case DENY_IGNORE:
break;
@@ -1069,7 +1071,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
return "deletion prohibited";
}
- if (head_name && !strcmp(namespaced_name, head_name)) {
+ if (worktree || (head_name && !strcmp(namespaced_name, head_name))) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
@@ -1118,7 +1120,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
}
if (do_update_worktree) {
- ret = update_worktree(new_oid->hash);
+ ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name));
if (ret)
return ret;
}
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 4d34309..81dfd56 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -560,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
flags |= EXPIRE_REFLOGS_DRY_RUN;
- else if (starts_with(arg, "--expire=")) {
- if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
+ else if (skip_prefix(arg, "--expire=", &arg)) {
+ if (parse_expiry_date(arg, &cb.cmd.expire_total))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
- else if (starts_with(arg, "--expire-unreachable=")) {
- if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
+ else if (skip_prefix(arg, "--expire-unreachable=", &arg)) {
+ if (parse_expiry_date(arg, &cb.cmd.expire_unreachable))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
diff --git a/builtin/remote.c b/builtin/remote.c
index 96bbe82..555d4c8 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -6,6 +6,7 @@
#include "string-list.h"
#include "strbuf.h"
#include "run-command.h"
+#include "rebase.h"
#include "refs.h"
#include "refspec.h"
#include "object-store.h"
@@ -248,9 +249,8 @@ static int add(int argc, const char **argv)
struct branch_info {
char *remote_name;
struct string_list merge;
- enum {
- NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES
- } rebase;
+ enum rebase_type rebase;
+ char *push_remote_name;
};
static struct string_list branch_list = STRING_LIST_INIT_NODUP;
@@ -264,59 +264,69 @@ static const char *abbrev_ref(const char *name, const char *prefix)
static int config_read_branches(const char *key, const char *value, void *cb)
{
- if (starts_with(key, "branch.")) {
- const char *orig_key = key;
- char *name;
- struct string_list_item *item;
- struct branch_info *info;
- enum { REMOTE, MERGE, REBASE } type;
- size_t key_len;
-
- key += 7;
- if (strip_suffix(key, ".remote", &key_len)) {
- name = xmemdupz(key, key_len);
- type = REMOTE;
- } else if (strip_suffix(key, ".merge", &key_len)) {
- name = xmemdupz(key, key_len);
- type = MERGE;
- } else if (strip_suffix(key, ".rebase", &key_len)) {
- name = xmemdupz(key, key_len);
- type = REBASE;
- } else
- return 0;
+ const char *orig_key = key;
+ char *name;
+ struct string_list_item *item;
+ struct branch_info *info;
+ enum { REMOTE, MERGE, REBASE, PUSH_REMOTE } type;
+ size_t key_len;
- item = string_list_insert(&branch_list, name);
+ if (!starts_with(key, "branch."))
+ return 0;
- if (!item->util)
- item->util = xcalloc(1, sizeof(struct branch_info));
- info = item->util;
- if (type == REMOTE) {
- if (info->remote_name)
- warning(_("more than one %s"), orig_key);
- info->remote_name = xstrdup(value);
- } else if (type == MERGE) {
- char *space = strchr(value, ' ');
- value = abbrev_branch(value);
- while (space) {
- char *merge;
- merge = xstrndup(value, space - value);
- string_list_append(&info->merge, merge);
- value = abbrev_branch(space + 1);
- space = strchr(value, ' ');
- }
- string_list_append(&info->merge, xstrdup(value));
- } else {
- int v = git_parse_maybe_bool(value);
- if (v >= 0)
- info->rebase = v;
- else if (!strcmp(value, "preserve"))
- info->rebase = NORMAL_REBASE;
- else if (!strcmp(value, "merges"))
- info->rebase = REBASE_MERGES;
- else if (!strcmp(value, "interactive"))
- info->rebase = INTERACTIVE_REBASE;
+ key += strlen("branch.");
+ if (strip_suffix(key, ".remote", &key_len))
+ type = REMOTE;
+ else if (strip_suffix(key, ".merge", &key_len))
+ type = MERGE;
+ else if (strip_suffix(key, ".rebase", &key_len))
+ type = REBASE;
+ else if (strip_suffix(key, ".pushremote", &key_len))
+ type = PUSH_REMOTE;
+ else
+ return 0;
+ name = xmemdupz(key, key_len);
+
+ item = string_list_insert(&branch_list, name);
+
+ if (!item->util)
+ item->util = xcalloc(1, sizeof(struct branch_info));
+ info = item->util;
+ switch (type) {
+ case REMOTE:
+ if (info->remote_name)
+ warning(_("more than one %s"), orig_key);
+ info->remote_name = xstrdup(value);
+ break;
+ case MERGE: {
+ char *space = strchr(value, ' ');
+ value = abbrev_branch(value);
+ while (space) {
+ char *merge;
+ merge = xstrndup(value, space - value);
+ string_list_append(&info->merge, merge);
+ value = abbrev_branch(space + 1);
+ space = strchr(value, ' ');
}
+ string_list_append(&info->merge, xstrdup(value));
+ break;
+ }
+ case REBASE:
+ /*
+ * Consider invalid values as false and check the
+ * truth value with >= REBASE_TRUE.
+ */
+ info->rebase = rebase_parse_value(value);
+ break;
+ case PUSH_REMOTE:
+ if (info->push_remote_name)
+ warning(_("more than one %s"), orig_key);
+ info->push_remote_name = xstrdup(value);
+ break;
+ default:
+ BUG("unexpected type=%d", type);
}
+
return 0;
}
@@ -605,6 +615,56 @@ static int migrate_file(struct remote *remote)
return 0;
}
+struct push_default_info
+{
+ const char *old_name;
+ enum config_scope scope;
+ struct strbuf origin;
+ int linenr;
+};
+
+static int config_read_push_default(const char *key, const char *value,
+ void *cb)
+{
+ struct push_default_info* info = cb;
+ if (strcmp(key, "remote.pushdefault") ||
+ !value || strcmp(value, info->old_name))
+ return 0;
+
+ info->scope = current_config_scope();
+ strbuf_reset(&info->origin);
+ strbuf_addstr(&info->origin, current_config_name());
+ info->linenr = current_config_line();
+
+ return 0;
+}
+
+static void handle_push_default(const char* old_name, const char* new_name)
+{
+ struct push_default_info push_default = {
+ old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 };
+ git_config(config_read_push_default, &push_default);
+ if (push_default.scope >= CONFIG_SCOPE_COMMAND)
+ ; /* pass */
+ else if (push_default.scope >= CONFIG_SCOPE_LOCAL) {
+ int result = git_config_set_gently("remote.pushDefault",
+ new_name);
+ if (new_name && result && result != CONFIG_NOTHING_SET)
+ die(_("could not set '%s'"), "remote.pushDefault");
+ else if (!new_name && result && result != CONFIG_NOTHING_SET)
+ die(_("could not unset '%s'"), "remote.pushDefault");
+ } else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) {
+ /* warn */
+ warning(_("The %s configuration remote.pushDefault in:\n"
+ "\t%s:%d\n"
+ "now names the non-existent remote '%s'"),
+ config_scope_name(push_default.scope),
+ push_default.origin.buf, push_default.linenr,
+ old_name);
+ }
+}
+
+
static int mv(int argc, const char **argv)
{
struct option options[] = {
@@ -680,6 +740,11 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf, "branch.%s.remote", item->string);
git_config_set(buf.buf, rename.new_name);
}
+ if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.pushremote", item->string);
+ git_config_set(buf.buf, rename.new_name);
+ }
}
if (!refspec_updated)
@@ -735,6 +800,9 @@ static int mv(int argc, const char **argv)
die(_("creating '%s' failed"), buf.buf);
}
string_list_clear(&remote_branches, 1);
+
+ handle_push_default(rename.old_name, rename.new_name);
+
return 0;
}
@@ -781,6 +849,13 @@ static int rm(int argc, const char **argv)
die(_("could not unset '%s'"), buf.buf);
}
}
+ if (info->push_remote_name && !strcmp(info->push_remote_name, remote->name)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.pushremote", item->string);
+ result = git_config_set_gently(buf.buf, NULL);
+ if (result && result != CONFIG_NOTHING_SET)
+ die(_("could not unset '%s'"), buf.buf);
+ }
}
/*
@@ -813,6 +888,8 @@ static int rm(int argc, const char **argv)
strbuf_addf(&buf, "remote.%s", remote->name);
if (git_config_rename_section(buf.buf, NULL) < 1)
return error(_("Could not remove config section '%s'"), buf.buf);
+
+ handle_push_default(remote->name, NULL);
}
return result;
@@ -943,7 +1020,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb
return 0;
if ((n = strlen(branch_item->string)) > show_info->width)
show_info->width = n;
- if (branch_info->rebase)
+ if (branch_info->rebase >= REBASE_TRUE)
show_info->any_rebase = 1;
item = string_list_insert(show_info->list, branch_item->string);
@@ -960,16 +1037,16 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
int width = show_info->width + 4;
int i;
- if (branch_info->rebase && branch_info->merge.nr > 1) {
+ if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) {
error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
item->string);
return 0;
}
printf(" %-*s ", show_info->width, item->string);
- if (branch_info->rebase) {
+ if (branch_info->rebase >= REBASE_TRUE) {
const char *msg;
- if (branch_info->rebase == INTERACTIVE_REBASE)
+ if (branch_info->rebase == REBASE_INTERACTIVE)
msg = _("rebases interactively onto remote %s");
else if (branch_info->rebase == REBASE_MERGES)
msg = _("rebases interactively (with merges) onto "
diff --git a/builtin/replace.c b/builtin/replace.c
index bd92dc6..b36d17a 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -409,7 +409,8 @@ static int check_one_mergetag(struct commit *commit,
struct tag *tag;
int i;
- hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);
+ hash_object_file(the_hash_algo, extra->value, extra->len,
+ type_name(OBJ_TAG), &tag_oid);
tag = lookup_tag(the_repository, &tag_oid);
if (!tag)
return error(_("bad mergetag in commit '%s'"), ref);
diff --git a/builtin/reset.c b/builtin/reset.c
index 18228c3..4c63411 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -46,7 +46,7 @@ static inline int is_merge(void)
return !access(git_path_merge_head(the_repository), F_OK);
}
-static int reset_index(const struct object_id *oid, int reset_type, int quiet)
+static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet)
{
int i, nr = 0;
struct tree_desc desc[2];
@@ -60,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
opts.dst_index = &the_index;
opts.fn = oneway_merge;
opts.merge = 1;
+ init_checkout_metadata(&opts.meta, ref, oid, NULL);
if (!quiet)
opts.verbose_update = 1;
switch (reset_type) {
@@ -418,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
}
}
} else {
- int err = reset_index(&oid, reset_type, quiet);
+ struct object_id dummy;
+ char *ref = NULL;
+ int err;
+
+ dwim_ref(rev, strlen(rev), &dummy, &ref);
+ if (ref && !starts_with(ref, "refs/"))
+ ref = NULL;
+
+ err = reset_index(ref, &oid, reset_type, quiet);
if (reset_type == KEEP && !err)
- err = reset_index(&oid, MIXED, quiet);
+ err = reset_index(ref, &oid, MIXED, quiet);
if (err)
die(_("Could not reset index file to revision '%s'."), rev);
+ free(ref);
}
if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index e28d62e..f520111 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -253,11 +253,26 @@ static int finish_object(struct object *obj, const char *name, void *cb_data)
static void show_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
+ struct rev_info *revs = info->revs;
+
if (finish_object(obj, name, cb_data))
return;
display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET)
return;
+
+ if (revs->count) {
+ /*
+ * The object count is always accumulated in the .count_right
+ * field for traversal that is not a left-right traversal,
+ * and cmd_rev_list() made sure that a .count request that
+ * wants to count non-commit objects, which is handled by
+ * the show_object() callback, does not ask for .left_right.
+ */
+ revs->count_right++;
+ return;
+ }
+
if (arg_show_object_names)
show_object_with_name(stdout, obj, name);
else
@@ -364,6 +379,79 @@ static inline int parse_missing_action_value(const char *value)
return 0;
}
+static int try_bitmap_count(struct rev_info *revs,
+ struct list_objects_filter_options *filter)
+{
+ uint32_t commit_count = 0,
+ tag_count = 0,
+ tree_count = 0,
+ blob_count = 0;
+ int max_count;
+ struct bitmap_index *bitmap_git;
+
+ /* This function only handles counting, not general traversal. */
+ if (!revs->count)
+ return -1;
+
+ /*
+ * A bitmap result can't know left/right, etc, because we don't
+ * actually traverse.
+ */
+ if (revs->left_right || revs->cherry_mark)
+ return -1;
+
+ /*
+ * If we're counting reachable objects, we can't handle a max count of
+ * commits to traverse, since we don't know which objects go with which
+ * commit.
+ */
+ if (revs->max_count >= 0 &&
+ (revs->tag_objects || revs->tree_objects || revs->blob_objects))
+ return -1;
+
+ /*
+ * This must be saved before doing any walking, since the revision
+ * machinery will count it down to zero while traversing.
+ */
+ max_count = revs->max_count;
+
+ bitmap_git = prepare_bitmap_walk(revs, filter);
+ if (!bitmap_git)
+ return -1;
+
+ count_bitmap_commit_list(bitmap_git, &commit_count,
+ revs->tree_objects ? &tree_count : NULL,
+ revs->blob_objects ? &blob_count : NULL,
+ revs->tag_objects ? &tag_count : NULL);
+ if (max_count >= 0 && max_count < commit_count)
+ commit_count = max_count;
+
+ printf("%d\n", commit_count + tree_count + blob_count + tag_count);
+ free_bitmap_index(bitmap_git);
+ return 0;
+}
+
+static int try_bitmap_traversal(struct rev_info *revs,
+ struct list_objects_filter_options *filter)
+{
+ struct bitmap_index *bitmap_git;
+
+ /*
+ * We can't use a bitmap result with a traversal limit, since the set
+ * of commits we'd get would be essentially random.
+ */
+ if (revs->max_count >= 0)
+ return -1;
+
+ bitmap_git = prepare_bitmap_walk(revs, filter);
+ if (!bitmap_git)
+ return -1;
+
+ traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast);
+ free_bitmap_index(bitmap_git);
+ return 0;
+}
+
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -521,8 +609,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (revs.show_notes)
die(_("rev-list does not support display of notes"));
- if (filter_options.choice && use_bitmap_index)
- die(_("cannot combine --use-bitmap-index with object filtering"));
+ if (revs.count &&
+ (revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
+ (revs.left_right || revs.cherry_mark))
+ die(_("marked counting is incompatible with --objects"));
save_commit_buffer = (revs.verbose_header ||
revs.grep_filter.pattern_list ||
@@ -533,28 +623,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (show_progress)
progress = start_delayed_progress(show_progress, 0);
- if (use_bitmap_index && !revs.prune) {
- if (revs.count && !revs.left_right && !revs.cherry_mark) {
- uint32_t commit_count;
- int max_count = revs.max_count;
- struct bitmap_index *bitmap_git;
- if ((bitmap_git = prepare_bitmap_walk(&revs))) {
- count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL);
- if (max_count >= 0 && max_count < commit_count)
- commit_count = max_count;
- printf("%d\n", commit_count);
- free_bitmap_index(bitmap_git);
- return 0;
- }
- } else if (revs.max_count < 0 &&
- revs.tag_objects && revs.tree_objects && revs.blob_objects) {
- struct bitmap_index *bitmap_git;
- if ((bitmap_git = prepare_bitmap_walk(&revs))) {
- traverse_bitmap_commit_list(bitmap_git, &show_object_fast);
- free_bitmap_index(bitmap_git);
- return 0;
- }
- }
+ if (use_bitmap_index) {
+ if (!try_bitmap_count(&revs, &filter_options))
+ return 0;
+ if (!try_bitmap_traversal(&revs, &filter_options))
+ return 0;
}
if (prepare_revision_walk(&revs))
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 7a00da8..0605643 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -808,9 +808,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--show-superproject-working-tree")) {
- const char *superproject = get_superproject_working_tree();
- if (superproject)
- puts(superproject);
+ struct strbuf superproject = STRBUF_INIT;
+ if (get_superproject_working_tree(&superproject))
+ puts(superproject.buf);
+ strbuf_release(&superproject);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
@@ -857,7 +858,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!gitdir && !prefix)
gitdir = ".git";
if (gitdir) {
- puts(real_path(gitdir));
+ struct strbuf realpath = STRBUF_INIT;
+ strbuf_realpath(&realpath, gitdir, 1);
+ puts(realpath.buf);
+ strbuf_release(&realpath);
continue;
}
}
diff --git a/builtin/rm.c b/builtin/rm.c
index 19ce95a..4858631 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -235,7 +235,8 @@ static int check_local_mod(struct object_id *head, int index_only)
}
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-static int ignore_unmatch = 0;
+static int ignore_unmatch = 0, pathspec_file_nul;
+static char *pathspec_from_file;
static struct option builtin_rm_options[] = {
OPT__DRY_RUN(&show_only, N_("dry run")),
@@ -245,6 +246,8 @@ static struct option builtin_rm_options[] = {
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
N_("exit with a zero status even if nothing matched")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
};
@@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, builtin_rm_options,
builtin_rm_usage, 0);
- if (!argc)
- usage_with_options(builtin_rm_usage, builtin_rm_options);
+
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv);
+
+ if (pathspec_from_file) {
+ if (pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
+ if (!pathspec.nr)
+ die(_("No pathspec was given. Which files should I remove?"));
if (!index_only)
setup_work_tree();
@@ -270,9 +289,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
- parse_pathspec(&pathspec, 0,
- PATHSPEC_PREFER_CWD,
- prefix, argv);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL);
seen = xcalloc(pathspec.nr, 1);
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 35d7f51..8c90cbb 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -536,7 +536,7 @@ static void append_one_rev(const char *av)
append_ref(av, &revkey, 0);
return;
}
- if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+ if (strpbrk(av, "*?[")) {
/* glob style match */
int saved_matches = ref_name_cnt;
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index b3bed89..740da4b 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -13,11 +13,12 @@
#include "resolve-undo.h"
#include "unpack-trees.h"
#include "wt-status.h"
+#include "quote.h"
static const char *empty_base = "";
static char const * const builtin_sparse_checkout_usage[] = {
- N_("git sparse-checkout (init|list|set|disable) <options>"),
+ N_("git sparse-checkout (init|list|set|add|disable) <options>"),
NULL
};
@@ -77,8 +78,10 @@ static int sparse_checkout_list(int argc, const char **argv)
string_list_sort(&sl);
- for (i = 0; i < sl.nr; i++)
- printf("%s\n", sl.items[i].string);
+ for (i = 0; i < sl.nr; i++) {
+ quote_c_style(sl.items[i].string, NULL, stdout, 0);
+ printf("\n");
+ }
return 0;
}
@@ -140,6 +143,22 @@ static int update_working_directory(struct pattern_list *pl)
return result;
}
+static char *escaped_pattern(char *pattern)
+{
+ char *p = pattern;
+ struct strbuf final = STRBUF_INIT;
+
+ while (*p) {
+ if (is_glob_special(*p))
+ strbuf_addch(&final, '\\');
+
+ strbuf_addch(&final, *p);
+ p++;
+ }
+
+ return strbuf_detach(&final, NULL);
+}
+
static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
{
int i;
@@ -164,10 +183,11 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
fprintf(fp, "/*\n!/*/\n");
for (i = 0; i < sl.nr; i++) {
- char *pattern = sl.items[i].string;
+ char *pattern = escaped_pattern(sl.items[i].string);
if (strlen(pattern))
fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
+ free(pattern);
}
string_list_clear(&sl, 0);
@@ -185,8 +205,9 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
string_list_remove_duplicates(&sl, 0);
for (i = 0; i < sl.nr; i++) {
- char *pattern = sl.items[i].string;
+ char *pattern = escaped_pattern(sl.items[i].string);
fprintf(fp, "%s/\n", pattern);
+ free(pattern);
}
}
@@ -199,6 +220,10 @@ static int write_patterns_and_update(struct pattern_list *pl)
int result;
sparse_filename = get_sparse_checkout_filename();
+
+ if (safe_create_leading_directories(sparse_filename))
+ die(_("failed to create directory for sparse-checkout file"));
+
fd = hold_lock_file_for_update(&lk, sparse_filename,
LOCK_DIE_ON_ERROR);
@@ -369,17 +394,20 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
strbuf_trim_trailing_dir_sep(line);
+ if (strbuf_normalize_path(line))
+ die(_("could not normalize path %s"), line->buf);
+
if (!line->len)
return;
if (line->buf[0] != '/')
- strbuf_insert(line, 0, "/", 1);
+ strbuf_insertstr(line, 0, "/");
insert_recursive_pattern(pl, line);
}
static char const * const builtin_sparse_checkout_set_usage[] = {
- N_("git sparse-checkout set (--stdin | <patterns>)"),
+ N_("git sparse-checkout (set|add) (--stdin | <patterns>)"),
NULL
};
@@ -387,45 +415,38 @@ static struct sparse_checkout_set_opts {
int use_stdin;
} set_opts;
-static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
+static void add_patterns_from_input(struct pattern_list *pl,
+ int argc, const char **argv)
{
int i;
- struct pattern_list pl;
- int result;
- int changed_config = 0;
-
- static struct option builtin_sparse_checkout_set_options[] = {
- OPT_BOOL(0, "stdin", &set_opts.use_stdin,
- N_("read patterns from standard in")),
- OPT_END(),
- };
-
- repo_read_index(the_repository);
- require_clean_work_tree(the_repository,
- N_("set sparse-checkout patterns"), NULL, 1, 0);
-
- memset(&pl, 0, sizeof(pl));
-
- argc = parse_options(argc, argv, prefix,
- builtin_sparse_checkout_set_options,
- builtin_sparse_checkout_set_usage,
- PARSE_OPT_KEEP_UNKNOWN);
-
if (core_sparse_checkout_cone) {
struct strbuf line = STRBUF_INIT;
- hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
- hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
- pl.use_cone_patterns = 1;
+ hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
+ hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
+ pl->use_cone_patterns = 1;
if (set_opts.use_stdin) {
- while (!strbuf_getline(&line, stdin))
- strbuf_to_cone_pattern(&line, &pl);
+ struct strbuf unquoted = STRBUF_INIT;
+ while (!strbuf_getline(&line, stdin)) {
+ if (line.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, line.buf, NULL))
+ die(_("unable to unquote C-style string '%s'"),
+ line.buf);
+
+ strbuf_swap(&unquoted, &line);
+ }
+
+ strbuf_to_cone_pattern(&line, pl);
+ }
+
+ strbuf_release(&unquoted);
} else {
for (i = 0; i < argc; i++) {
strbuf_setlen(&line, 0);
strbuf_addstr(&line, argv[i]);
- strbuf_to_cone_pattern(&line, &pl);
+ strbuf_to_cone_pattern(&line, pl);
}
}
} else {
@@ -435,14 +456,85 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
while (!strbuf_getline(&line, stdin)) {
size_t len;
char *buf = strbuf_detach(&line, &len);
- add_pattern(buf, empty_base, 0, &pl, 0);
+ add_pattern(buf, empty_base, 0, pl, 0);
}
} else {
for (i = 0; i < argc; i++)
- add_pattern(argv[i], empty_base, 0, &pl, 0);
+ add_pattern(argv[i], empty_base, 0, pl, 0);
+ }
+ }
+}
+
+enum modify_type {
+ REPLACE,
+ ADD,
+};
+
+static void add_patterns_cone_mode(int argc, const char **argv,
+ struct pattern_list *pl)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct pattern_entry *pe;
+ struct hashmap_iter iter;
+ struct pattern_list existing;
+ char *sparse_filename = get_sparse_checkout_filename();
+
+ add_patterns_from_input(pl, argc, argv);
+
+ memset(&existing, 0, sizeof(existing));
+ existing.use_cone_patterns = core_sparse_checkout_cone;
+
+ if (add_patterns_from_file_to_list(sparse_filename, "", 0,
+ &existing, NULL))
+ die(_("unable to load existing sparse-checkout patterns"));
+ free(sparse_filename);
+
+ hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) {
+ if (!hashmap_contains_parent(&pl->recursive_hashmap,
+ pe->pattern, &buffer) ||
+ !hashmap_contains_parent(&pl->parent_hashmap,
+ pe->pattern, &buffer)) {
+ strbuf_reset(&buffer);
+ strbuf_addstr(&buffer, pe->pattern);
+ insert_recursive_pattern(pl, &buffer);
}
}
+ clear_pattern_list(&existing);
+ strbuf_release(&buffer);
+}
+
+static void add_patterns_literal(int argc, const char **argv,
+ struct pattern_list *pl)
+{
+ char *sparse_filename = get_sparse_checkout_filename();
+ if (add_patterns_from_file_to_list(sparse_filename, "", 0,
+ pl, NULL))
+ die(_("unable to load existing sparse-checkout patterns"));
+ free(sparse_filename);
+ add_patterns_from_input(pl, argc, argv);
+}
+
+static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
+{
+ int result;
+ int changed_config = 0;
+ struct pattern_list pl;
+ memset(&pl, 0, sizeof(pl));
+
+ switch (m) {
+ case ADD:
+ if (core_sparse_checkout_cone)
+ add_patterns_cone_mode(argc, argv, &pl);
+ else
+ add_patterns_literal(argc, argv, &pl);
+ break;
+
+ case REPLACE:
+ add_patterns_from_input(&pl, argc, argv);
+ break;
+ }
+
if (!core_apply_sparse_checkout) {
set_config(MODE_ALL_PATTERNS);
core_apply_sparse_checkout = 1;
@@ -458,6 +550,27 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
return result;
}
+static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
+ enum modify_type m)
+{
+ static struct option builtin_sparse_checkout_set_options[] = {
+ OPT_BOOL(0, "stdin", &set_opts.use_stdin,
+ N_("read patterns from standard in")),
+ OPT_END(),
+ };
+
+ repo_read_index(the_repository);
+ require_clean_work_tree(the_repository,
+ N_("set sparse-checkout patterns"), NULL, 1, 0);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_sparse_checkout_set_options,
+ builtin_sparse_checkout_set_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ return modify_pattern_list(argc, argv, m);
+}
+
static int sparse_checkout_disable(int argc, const char **argv)
{
struct pattern_list pl;
@@ -506,7 +619,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[0], "init"))
return sparse_checkout_init(argc, argv);
if (!strcmp(argv[0], "set"))
- return sparse_checkout_set(argc, argv, prefix);
+ return sparse_checkout_set(argc, argv, prefix, REPLACE);
+ if (!strcmp(argv[0], "add"))
+ return sparse_checkout_set(argc, argv, prefix, ADD);
if (!strcmp(argv[0], "disable"))
return sparse_checkout_disable(argc, argv);
}
diff --git a/builtin/stash.c b/builtin/stash.c
index 4ad3adf..6d586ef 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = {
N_("git stash clear"),
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+ " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
" [--] [<pathspec>...]]"),
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [<message>]"),
@@ -701,6 +702,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
static int show_stat = 1;
static int show_patch;
+static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
{
@@ -712,7 +714,11 @@ static int git_stash_config(const char *var, const char *value, void *cb)
show_patch = git_config_bool(var, value);
return 0;
}
- return git_default_config(var, value, cb);
+ if (!strcmp(var, "stash.usebuiltin")) {
+ use_legacy_stash = !git_config_bool(var, value);
+ return 0;
+ }
+ return git_diff_basic_config(var, value, cb);
}
static int show_stash(int argc, const char **argv, const char *prefix)
@@ -749,7 +755,6 @@ static int show_stash(int argc, const char **argv, const char *prefix)
* any options.
*/
if (revision_args.argc == 1) {
- git_config(git_stash_config, NULL);
if (show_stat)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
@@ -998,9 +1003,9 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
{
int ret = 0;
struct child_process cp_read_tree = CHILD_PROCESS_INIT;
- struct child_process cp_add_i = CHILD_PROCESS_INIT;
struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
struct index_state istate = { NULL };
+ char *old_index_env = NULL, *old_repo_index_file;
remove_path(stash_index_path.buf);
@@ -1014,16 +1019,19 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
}
/* Find out what the user wants. */
- cp_add_i.git_cmd = 1;
- argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
- "--", NULL);
- add_pathspecs(&cp_add_i.args, ps);
- argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
- if (run_command(&cp_add_i)) {
- ret = -1;
- goto done;
- }
+ old_repo_index_file = the_repository->index_file;
+ the_repository->index_file = stash_index_path.buf;
+ old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
+ setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
+
+ ret = run_add_interactive(NULL, "--patch=stash", ps);
+
+ the_repository->index_file = old_repo_index_file;
+ if (old_index_env && *old_index_env)
+ setenv(INDEX_ENVIRONMENT, old_index_env, 1);
+ else
+ unsetenv(INDEX_ENVIRONMENT);
+ FREE_AND_NULL(old_index_env);
/* State of the working tree. */
if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
@@ -1448,13 +1456,17 @@ done:
return ret;
}
-static int push_stash(int argc, const char **argv, const char *prefix)
+static int push_stash(int argc, const char **argv, const char *prefix,
+ int push_assumed)
{
+ int force_assume = 0;
int keep_index = -1;
int patch_mode = 0;
int include_untracked = 0;
int quiet = 0;
+ int pathspec_file_nul = 0;
const char *stash_msg = NULL;
+ const char *pathspec_from_file = NULL;
struct pathspec ps;
struct option options[] = {
OPT_BOOL('k', "keep-index", &keep_index,
@@ -1468,16 +1480,45 @@ static int push_stash(int argc, const char **argv, const char *prefix)
N_("include ignore files"), 2),
OPT_STRING('m', "message", &stash_msg, N_("message"),
N_("stash message")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END()
};
- if (argc)
+ if (argc) {
+ force_assume = !strcmp(argv[0], "-p");
argc = parse_options(argc, argv, prefix, options,
git_stash_push_usage,
- 0);
+ PARSE_OPT_KEEP_DASHDASH);
+ }
+
+ if (argc) {
+ if (!strcmp(argv[0], "--")) {
+ argc--;
+ argv++;
+ } else if (push_assumed && !force_assume) {
+ die("subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'",
+ argv[0]);
+ }
+ }
parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
prefix, argv);
+
+ if (pathspec_from_file) {
+ if (patch_mode)
+ die(_("--pathspec-from-file is incompatible with --patch"));
+
+ if (ps.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&ps, 0,
+ PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
include_untracked);
}
@@ -1522,32 +1563,8 @@ static int save_stash(int argc, const char **argv, const char *prefix)
return ret;
}
-static int use_builtin_stash(void)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
- struct strbuf out = STRBUF_INIT;
- int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
-
- if (env != -1)
- return env;
-
- argv_array_pushl(&cp.args,
- "config", "--bool", "stash.usebuiltin", NULL);
- cp.git_cmd = 1;
- if (capture_command(&cp, &out, 6)) {
- strbuf_release(&out);
- return 1;
- }
-
- strbuf_trim(&out);
- ret = !strcmp("true", out.buf);
- strbuf_release(&out);
- return ret;
-}
-
int cmd_stash(int argc, const char **argv, const char *prefix)
{
- int i = -1;
pid_t pid = getpid();
const char *index_file;
struct argv_array args = ARGV_ARRAY_INIT;
@@ -1556,21 +1573,12 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
OPT_END()
};
- if (!use_builtin_stash()) {
- const char *path = mkpath("%s/git-legacy-stash",
- git_exec_path());
-
- if (sane_execvp(path, (char **)argv) < 0)
- die_errno(_("could not exec %s"), path);
- else
- BUG("sane_execvp() returned???");
- }
+ git_config(git_stash_config, NULL);
- prefix = setup_git_directory();
- trace_repo_setup(prefix);
- setup_work_tree();
-
- git_config(git_diff_basic_config, NULL);
+ if (use_legacy_stash ||
+ !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
+ warning(_("the stash.useBuiltin support has been removed!\n"
+ "See its entry in 'git help config' for details."));
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
@@ -1580,7 +1588,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
(uintmax_t)pid);
if (!argc)
- return !!push_stash(0, NULL, prefix);
+ return !!push_stash(0, NULL, prefix, 0);
else if (!strcmp(argv[0], "apply"))
return !!apply_stash(argc, argv, prefix);
else if (!strcmp(argv[0], "clear"))
@@ -1600,45 +1608,15 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[0], "create"))
return !!create_stash(argc, argv, prefix);
else if (!strcmp(argv[0], "push"))
- return !!push_stash(argc, argv, prefix);
+ return !!push_stash(argc, argv, prefix, 0);
else if (!strcmp(argv[0], "save"))
return !!save_stash(argc, argv, prefix);
else if (*argv[0] != '-')
usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
git_stash_usage, options);
- if (strcmp(argv[0], "-p")) {
- while (++i < argc && strcmp(argv[i], "--")) {
- /*
- * `akpqu` is a string which contains all short options,
- * except `-m` which is verified separately.
- */
- if ((strlen(argv[i]) == 2) && *argv[i] == '-' &&
- strchr("akpqu", argv[i][1]))
- continue;
-
- if (!strcmp(argv[i], "--all") ||
- !strcmp(argv[i], "--keep-index") ||
- !strcmp(argv[i], "--no-keep-index") ||
- !strcmp(argv[i], "--patch") ||
- !strcmp(argv[i], "--quiet") ||
- !strcmp(argv[i], "--include-untracked"))
- continue;
-
- /*
- * `-m` and `--message=` are verified separately because
- * they need to be immediately followed by a string
- * (i.e.`-m"foobar"` or `--message="foobar"`).
- */
- if (starts_with(argv[i], "-m") ||
- starts_with(argv[i], "--message="))
- continue;
-
- usage_with_options(git_stash_usage, options);
- }
- }
-
+ /* Assume 'stash push' */
argv_array_push(&args, "push");
argv_array_pushv(&args, argv);
- return !!push_stash(args.argc, args.argv, prefix);
+ return !!push_stash(args.argc, args.argv, prefix, 1);
}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c72931e..1a4b391 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -444,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list,
fn(list->entries[i], cb_data);
}
-struct cb_foreach {
+struct foreach_cb {
int argc;
const char **argv;
const char *prefix;
int quiet;
int recursive;
};
-#define CB_FOREACH_INIT { 0 }
+#define FOREACH_CB_INIT { 0 }
static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
void *cb_data)
{
- struct cb_foreach *info = cb_data;
+ struct foreach_cb *info = cb_data;
const char *path = list_item->name;
const struct object_id *ce_oid = &list_item->oid;
@@ -557,7 +557,7 @@ cleanup:
static int module_foreach(int argc, const char **argv, const char *prefix)
{
- struct cb_foreach info = CB_FOREACH_INIT;
+ struct foreach_cb info = FOREACH_CB_INIT;
struct pathspec pathspec;
struct module_list list = MODULE_LIST_INIT;
@@ -782,6 +782,8 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
struct argv_array diff_files_args = ARGV_ARRAY_INIT;
struct rev_info rev;
int diff_files_result;
+ struct strbuf buf = STRBUF_INIT;
+ const char *git_dir;
if (!submodule_from_path(the_repository, &null_oid, path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -794,10 +796,18 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
goto cleanup;
}
- if (!is_submodule_active(the_repository, path)) {
+ strbuf_addf(&buf, "%s/.git", path);
+ git_dir = read_gitfile(buf.buf);
+ if (!git_dir)
+ git_dir = buf.buf;
+
+ if (!is_submodule_active(the_repository, path) ||
+ !is_git_directory(git_dir)) {
print_status(flags, '-', path, ce_oid, displaypath);
+ strbuf_release(&buf);
goto cleanup;
}
+ strbuf_release(&buf);
argv_array_pushl(&diff_files_args, "diff-files",
"--ignore-submodules=dirty", "--quiet", "--",
@@ -1225,7 +1235,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
static int clone_submodule(const char *path, const char *gitdir, const char *url,
const char *depth, struct string_list *reference, int dissociate,
- int quiet, int progress)
+ int quiet, int progress, int single_branch)
{
struct child_process cp = CHILD_PROCESS_INIT;
@@ -1247,6 +1257,10 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
argv_array_push(&cp.args, "--dissociate");
if (gitdir && *gitdir)
argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+ if (single_branch >= 0)
+ argv_array_push(&cp.args, single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
argv_array_push(&cp.args, "--");
argv_array_push(&cp.args, url);
@@ -1373,6 +1387,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
struct string_list reference = STRING_LIST_INIT_NODUP;
int dissociate = 0, require_init = 0;
char *sm_alternate = NULL, *error_strategy = NULL;
+ int single_branch = -1;
struct option module_clone_options[] = {
OPT_STRING(0, "prefix", &prefix,
@@ -1400,12 +1415,15 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("force cloning progress")),
OPT_BOOL(0, "require-init", &require_init,
N_("disallow cloning into non-empty directory")),
+ OPT_BOOL(0, "single-branch", &single_branch,
+ N_("clone only one branch, HEAD or --branch")),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
+ "[--single-branch] "
"--url <url> --path <path>"),
NULL
};
@@ -1438,7 +1456,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
prepare_possible_alternates(name, &reference);
if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
- quiet, progress))
+ quiet, progress, single_branch))
die(_("clone of '%s' into submodule path '%s' failed"),
url, path);
} else {
@@ -1562,6 +1580,7 @@ struct submodule_update_clone {
const char *depth;
const char *recursive_prefix;
const char *prefix;
+ int single_branch;
/* to be consumed by git-submodule.sh */
struct update_clone_data *update_clone;
@@ -1576,10 +1595,14 @@ struct submodule_update_clone {
int max_jobs;
};
-#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
- SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \
- NULL, NULL, NULL, \
- NULL, 0, 0, 0, NULL, 0, 0, 1}
+#define SUBMODULE_UPDATE_CLONE_INIT { \
+ .list = MODULE_LIST_INIT, \
+ .update = SUBMODULE_UPDATE_STRATEGY_INIT, \
+ .recommend_shallow = -1, \
+ .references = STRING_LIST_INIT_DUP, \
+ .single_branch = -1, \
+ .max_jobs = 1, \
+}
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -1718,6 +1741,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
argv_array_push(&child->args, "--dissociate");
if (suc->depth)
argv_array_push(&child->args, suc->depth);
+ if (suc->single_branch >= 0)
+ argv_array_push(&child->args, suc->single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
cleanup:
strbuf_reset(&displaypath_sb);
@@ -1897,6 +1924,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
N_("force cloning progress")),
OPT_BOOL(0, "require-init", &suc.require_init,
N_("disallow cloning into non-empty directory")),
+ OPT_BOOL(0, "single-branch", &suc.single_branch,
+ N_("clone only one branch, HEAD or --branch")),
OPT_END()
};
diff --git a/builtin/tag.c b/builtin/tag.c
index e0a4c25..cc30d34 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,
if (type <= OBJ_NONE)
die(_("bad object type."));
- if (type == OBJ_TAG && advice_nested_tag)
- advise(_(message_advice_nested_tag), tag, object_ref);
+ if (type == OBJ_TAG)
+ advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),
+ tag, object_ref);
strbuf_addf(&header,
"object %s\n"
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 9100964..dd4a75e 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -265,7 +265,8 @@ static void write_object(unsigned nr, enum object_type type,
} else {
struct object *obj;
int eaten;
- hash_object_file(buf, size, type_name(type), &obj_list[nr].oid);
+ hash_object_file(the_hash_algo, buf, size, type_name(type),
+ &obj_list[nr].oid);
added_object(nr, type, buf, size);
obj = parse_object_buffer(the_repository, &obj_list[nr].oid,
type, size, buf,
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d6bc526..d99db35 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -234,14 +234,7 @@ static void validate_worktree_add(const char *path, const struct add_opts *opts)
die(_("'%s' already exists"), path);
worktrees = get_worktrees(0);
- /*
- * find_worktree()'s suffix matching may undesirably find the main
- * rather than a linked worktree (for instance, when the basenames
- * of the main worktree and the one being created are the same).
- * We're only interested in linked worktrees, so skip the main
- * worktree with +1.
- */
- wt = find_worktree(worktrees + 1, NULL, path);
+ wt = find_worktree_by_path(worktrees, path);
if (!wt)
goto done;
@@ -265,7 +258,7 @@ static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
- struct strbuf sb = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
const char *name;
struct child_process cp = CHILD_PROCESS_INIT;
struct argv_array child_env = ARGV_ARRAY_INIT;
@@ -337,9 +330,11 @@ static int add_worktree(const char *path, const char *refname,
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
- write_file(sb.buf, "%s", real_path(sb_git.buf));
+ strbuf_realpath(&realpath, sb_git.buf, 1);
+ write_file(sb.buf, "%s", realpath.buf);
+ strbuf_realpath(&realpath, get_git_common_dir(), 1);
write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
- real_path(get_git_common_dir()), name);
+ realpath.buf, name);
/*
* This is to keep resolve_ref() happy. We need a valid HEAD
* or is_git_directory() will reject the directory. Any value which
@@ -425,6 +420,7 @@ done:
strbuf_release(&sb_repo);
strbuf_release(&sb_git);
strbuf_release(&sb_name);
+ strbuf_release(&realpath);
return ret;
}
diff --git a/cache-tree.c b/cache-tree.c
index 1bd1b23..a537a80 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -407,13 +407,15 @@ static int update_one(struct cache_tree *it,
if (repair) {
struct object_id oid;
- hash_object_file(buffer.buf, buffer.len, tree_type, &oid);
+ hash_object_file(the_hash_algo, buffer.buf, buffer.len,
+ tree_type, &oid);
if (has_object_file_with_flags(&oid, OBJECT_INFO_SKIP_FETCH_OBJECT))
oidcpy(&it->oid, &oid);
else
to_invalidate = 1;
} else if (dryrun) {
- hash_object_file(buffer.buf, buffer.len, tree_type, &it->oid);
+ hash_object_file(the_hash_algo, buffer.buf, buffer.len,
+ tree_type, &it->oid);
} else if (write_object_file(buffer.buf, buffer.len, tree_type,
&it->oid)) {
strbuf_release(&buffer);
@@ -826,9 +828,10 @@ static void verify_one(struct repository *r,
i++;
}
strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0');
- strbuf_add(&tree_buf, oid->hash, the_hash_algo->rawsz);
+ strbuf_add(&tree_buf, oid->hash, r->hash_algo->rawsz);
}
- hash_object_file(tree_buf.buf, tree_buf.len, tree_type, &new_oid);
+ hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, tree_type,
+ &new_oid);
if (!oideq(&new_oid, &it->oid))
BUG("cache-tree for path %.*s does not match. "
"Expected %s got %s", len, path->buf,
diff --git a/cache.h b/cache.h
index cbfaead..c77b958 100644
--- a/cache.h
+++ b/cache.h
@@ -324,7 +324,7 @@ struct index_state {
struct hashmap dir_hash;
struct object_id oid;
struct untracked_cache *untracked;
- uint64_t fsmonitor_last_update;
+ char *fsmonitor_last_update;
struct ewah_bitmap *fsmonitor_dirty;
struct mem_pool *ce_mem_pool;
struct progress *progress;
@@ -543,7 +543,7 @@ const char *get_git_common_dir(void);
char *get_object_directory(void);
char *get_index_file(void);
char *get_graft_file(struct repository *r);
-void set_git_dir(const char *path);
+void set_git_dir(const char *path, int make_realpath);
int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
int get_common_dir(struct strbuf *sb, const char *gitdir);
const char *get_git_namespace(void);
@@ -627,7 +627,9 @@ int path_inside_repo(const char *prefix, const char *path);
#define INIT_DB_EXIST_OK 0x0002
int init_db(const char *git_dir, const char *real_git_dir,
- const char *template_dir, unsigned int flags);
+ const char *template_dir, int hash_algo,
+ unsigned int flags);
+void initialize_repository_version(int hash_algo);
void sanitize_stdfds(void);
int daemonize(void);
@@ -1086,8 +1088,10 @@ int verify_repository_format(const struct repository_format *format,
* and die if it is a version we don't understand. Generally one would
* set_git_dir() before calling this, and use it only for "are we in a valid
* repo?".
+ *
+ * If successful and fmt is not NULL, fill fmt with data.
*/
-void check_repository_format(void);
+void check_repository_format(struct repository_format *fmt);
#define MTIME_CHANGED 0x0001
#define CTIME_CHANGED 0x0002
@@ -1314,8 +1318,6 @@ static inline int is_absolute_path(const char *path)
int is_directory(const char *);
char *strbuf_realpath(struct strbuf *resolved, const char *path,
int die_on_error);
-const char *real_path(const char *path);
-const char *real_path_if_valid(const char *path);
char *real_pathdup(const char *path, int die_on_error);
const char *absolute_path(const char *path);
char *absolute_pathdup(const char *path);
@@ -1363,7 +1365,8 @@ int git_open_cloexec(const char *name, int flags);
int unpack_loose_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
int parse_loose_header(const char *hdr, unsigned long *sizep);
-int check_object_signature(const struct object_id *oid, void *buf, unsigned long size, const char *type);
+int check_object_signature(struct repository *r, const struct object_id *oid,
+ void *buf, unsigned long size, const char *type);
int finalize_object_file(const char *tmpfile, const char *filename);
@@ -1480,6 +1483,9 @@ int set_disambiguate_hint_config(const char *var, const char *value);
int get_sha1_hex(const char *hex, unsigned char *sha1);
int get_oid_hex(const char *hex, struct object_id *sha1);
+/* Like get_oid_hex, but for an arbitrary hash algorithm. */
+int get_oid_hex_algop(const char *hex, struct object_id *oid, const struct git_hash_algo *algop);
+
/*
* Read `len` pairs of hexadecimal digits from `hex` and write the
* values to `binary` as `len` bytes. Return 0 on success, or -1 if
@@ -1515,6 +1521,20 @@ char *oid_to_hex(const struct object_id *oid); /* same static buffer */
*/
int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
+/* Like parse_oid_hex, but for an arbitrary hash algorithm. */
+int parse_oid_hex_algop(const char *hex, struct object_id *oid, const char **end,
+ const struct git_hash_algo *algo);
+
+
+/*
+ * These functions work like get_oid_hex and parse_oid_hex, but they will parse
+ * a hex value for any algorithm. The algorithm is detected based on the length
+ * and the algorithm in use is returned. If this is not a hex object ID in any
+ * algorithm, returns GIT_HASH_UNKNOWN.
+ */
+int get_oid_hex_any(const char *hex, struct object_id *oid);
+int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end);
+
/*
* This reads short-hand syntax that not only evaluates to a commit
* object name, but also can act as if the end user spelled the name
@@ -1678,6 +1698,7 @@ struct checkout {
const char *base_dir;
int base_dir_len;
struct delayed_checkout *delayed_checkout;
+ struct checkout_metadata meta;
unsigned force:1,
quiet:1,
not_new:1,
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index cd59855..497fd32 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -40,11 +40,11 @@ osx-clang|osx-gcc)
test -z "$BREW_INSTALL_PACKAGES" ||
brew install $BREW_INSTALL_PACKAGES
brew link --force gettext
- brew cask install perforce || {
+ brew cask install --no-quarantine perforce || {
# Update the definitions and try again
cask_repo="$(brew --repository)"/Library/Taps/homebrew/homebrew-cask &&
git -C "$cask_repo" pull --no-stat &&
- brew cask install perforce
+ brew cask install --no-quarantine perforce
} ||
brew install caskroom/cask/perforce
case "$jobname" in
diff --git a/ci/lib.sh b/ci/lib.sh
index a90d0dc..c3a8cd2 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -162,6 +162,9 @@ linux-clang|linux-gcc)
if [ "$jobname" = linux-gcc ]
then
export CC=gcc-8
+ MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+ else
+ MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
fi
export GIT_TEST_HTTPD=true
@@ -182,6 +185,9 @@ osx-clang|osx-gcc)
if [ "$jobname" = osx-gcc ]
then
export CC=gcc-9
+ MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+ else
+ MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
fi
# t9810 occasionally fails on Travis CI OS X
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index ff0ef7f..4df54c4 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -20,6 +20,7 @@ linux-gcc)
export GIT_TEST_OE_DELTA_SIZE=5
export GIT_TEST_COMMIT_GRAPH=1
export GIT_TEST_MULTI_PACK_INDEX=1
+ export GIT_TEST_ADD_I_USE_BUILTIN=1
make test
;;
linux-gcc-4.8)
diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh
index b3e76ef..de41888 100755
--- a/ci/test-documentation.sh
+++ b/ci/test-documentation.sh
@@ -7,6 +7,7 @@
filter_log () {
sed -e '/^GIT_VERSION = /d' \
+ -e "/constant Gem::ConfigMap is deprecated/d" \
-e '/^ \* new asciidoc flags$/d' \
-e '/stripped namespace before processing/d' \
-e '/Attributed.*IDs for element/d' \
diff --git a/color.c b/color.c
index ebb222e..64f52a4 100644
--- a/color.c
+++ b/color.c
@@ -24,6 +24,14 @@ const char *column_colors_ansi[] = {
GIT_COLOR_RESET,
};
+enum {
+ COLOR_BACKGROUND_OFFSET = 10,
+ COLOR_FOREGROUND_ANSI = 30,
+ COLOR_FOREGROUND_RGB = 38,
+ COLOR_FOREGROUND_256 = 38,
+ COLOR_FOREGROUND_BRIGHT_ANSI = 90,
+};
+
/* Ignore the RESET at the end when giving the size */
const int column_colors_ansi_max = ARRAY_SIZE(column_colors_ansi) - 1;
@@ -61,15 +69,38 @@ static int get_hex_color(const char *in, unsigned char *out)
return 0;
}
-static int parse_color(struct color *out, const char *name, int len)
+/*
+ * If an ANSI color is recognized in "name", fill "out" and return 0.
+ * Otherwise, leave out unchanged and return -1.
+ */
+static int parse_ansi_color(struct color *out, const char *name, int len)
{
/* Positions in array must match ANSI color codes */
static const char * const color_names[] = {
"black", "red", "green", "yellow",
"blue", "magenta", "cyan", "white"
};
- char *end;
int i;
+ int color_offset = COLOR_FOREGROUND_ANSI;
+
+ if (strncasecmp(name, "bright", 6) == 0) {
+ color_offset = COLOR_FOREGROUND_BRIGHT_ANSI;
+ name += 6;
+ len -= 6;
+ }
+ for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+ if (match_word(name, len, color_names[i])) {
+ out->type = COLOR_ANSI;
+ out->value = i + color_offset;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int parse_color(struct color *out, const char *name, int len)
+{
+ char *end;
long val;
/* First try the special word "normal"... */
@@ -89,12 +120,8 @@ static int parse_color(struct color *out, const char *name, int len)
}
/* Then pick from our human-readable color names... */
- for (i = 0; i < ARRAY_SIZE(color_names); i++) {
- if (match_word(name, len, color_names[i])) {
- out->type = COLOR_ANSI;
- out->value = i;
- return 0;
- }
+ if (parse_ansi_color(out, name, len) == 0) {
+ return 0;
}
/* And finally try a literal 256-color-mode number */
@@ -109,10 +136,15 @@ static int parse_color(struct color *out, const char *name, int len)
else if (val < 0) {
out->type = COLOR_NORMAL;
return 0;
- /* Rewrite low numbers as more-portable standard colors. */
+ /* Rewrite 0-7 as more-portable standard colors. */
} else if (val < 8) {
out->type = COLOR_ANSI;
- out->value = val;
+ out->value = val + COLOR_FOREGROUND_ANSI;
+ return 0;
+ /* Rewrite 8-15 as more-portable aixterm colors. */
+ } else if (val < 16) {
+ out->type = COLOR_ANSI;
+ out->value = val - 8 + COLOR_FOREGROUND_BRIGHT_ANSI;
return 0;
} else if (val < 256) {
out->type = COLOR_256;
@@ -166,23 +198,26 @@ int color_parse(const char *value, char *dst)
* already have the ANSI escape code in it. "out" should have enough
* space in it to fit any color.
*/
-static char *color_output(char *out, int len, const struct color *c, char type)
+static char *color_output(char *out, int len, const struct color *c, int background)
{
+ int offset = 0;
+
+ if (background)
+ offset = COLOR_BACKGROUND_OFFSET;
switch (c->type) {
case COLOR_UNSPECIFIED:
case COLOR_NORMAL:
break;
case COLOR_ANSI:
- if (len < 2)
- BUG("color parsing ran out of space");
- *out++ = type;
- *out++ = '0' + c->value;
+ out += xsnprintf(out, len, "%d", c->value + offset);
break;
case COLOR_256:
- out += xsnprintf(out, len, "%c8;5;%d", type, c->value);
+ out += xsnprintf(out, len, "%d;5;%d", COLOR_FOREGROUND_256 + offset,
+ c->value);
break;
case COLOR_RGB:
- out += xsnprintf(out, len, "%c8;2;%d;%d;%d", type,
+ out += xsnprintf(out, len, "%d;2;%d;%d;%d",
+ COLOR_FOREGROUND_RGB + offset,
c->red, c->green, c->blue);
break;
}
@@ -279,14 +314,12 @@ int color_parse_mem(const char *value, int value_len, char *dst)
if (!color_empty(&fg)) {
if (sep++)
OUT(';');
- /* foreground colors are all in the 3x range */
- dst = color_output(dst, end - dst, &fg, '3');
+ dst = color_output(dst, end - dst, &fg, 0);
}
if (!color_empty(&bg)) {
if (sep++)
OUT(';');
- /* background colors are all in the 4x range */
- dst = color_output(dst, end - dst, &bg, '4');
+ dst = color_output(dst, end - dst, &bg, 1);
}
OUT('m');
}
diff --git a/commit-graph.c b/commit-graph.c
index b205e65..f013a84 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -44,30 +44,21 @@
/* Remember to update object flag allocation in object.h */
#define REACHABLE (1u<<15)
-char *get_commit_graph_filename(const char *obj_dir)
+char *get_commit_graph_filename(struct object_directory *odb)
{
- char *filename = xstrfmt("%s/info/commit-graph", obj_dir);
- char *normalized = xmalloc(strlen(filename) + 1);
- normalize_path_copy(normalized, filename);
- free(filename);
- return normalized;
+ return xstrfmt("%s/info/commit-graph", odb->path);
}
-static char *get_split_graph_filename(const char *obj_dir,
+static char *get_split_graph_filename(struct object_directory *odb,
const char *oid_hex)
{
- char *filename = xstrfmt("%s/info/commit-graphs/graph-%s.graph",
- obj_dir,
- oid_hex);
- char *normalized = xmalloc(strlen(filename) + 1);
- normalize_path_copy(normalized, filename);
- free(filename);
- return normalized;
+ return xstrfmt("%s/info/commit-graphs/graph-%s.graph", odb->path,
+ oid_hex);
}
-static char *get_chain_filename(const char *obj_dir)
+static char *get_chain_filename(struct object_directory *odb)
{
- return xstrfmt("%s/info/commit-graphs/commit-graph-chain", obj_dir);
+ return xstrfmt("%s/info/commit-graphs/commit-graph-chain", odb->path);
}
static uint8_t oid_version(void)
@@ -117,7 +108,8 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st)
return 1;
}
-struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st)
+struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
+ struct object_directory *odb)
{
void *graph_map;
size_t graph_size;
@@ -133,7 +125,9 @@ struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st)
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
ret = parse_commit_graph(graph_map, fd, graph_size);
- if (!ret) {
+ if (ret)
+ ret->odb = odb;
+ else {
munmap(graph_map, graph_size);
close(fd);
}
@@ -308,7 +302,8 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
return graph;
}
-static struct commit_graph *load_commit_graph_one(const char *graph_file)
+static struct commit_graph *load_commit_graph_one(const char *graph_file,
+ struct object_directory *odb)
{
struct stat st;
@@ -319,7 +314,7 @@ static struct commit_graph *load_commit_graph_one(const char *graph_file)
if (!open_ok)
return NULL;
- g = load_commit_graph_one_fd_st(fd, &st);
+ g = load_commit_graph_one_fd_st(fd, &st, odb);
if (g)
g->filename = xstrdup(graph_file);
@@ -327,15 +322,13 @@ static struct commit_graph *load_commit_graph_one(const char *graph_file)
return g;
}
-static struct commit_graph *load_commit_graph_v1(struct repository *r, const char *obj_dir)
+static struct commit_graph *load_commit_graph_v1(struct repository *r,
+ struct object_directory *odb)
{
- char *graph_name = get_commit_graph_filename(obj_dir);
- struct commit_graph *g = load_commit_graph_one(graph_name);
+ char *graph_name = get_commit_graph_filename(odb);
+ struct commit_graph *g = load_commit_graph_one(graph_name, odb);
free(graph_name);
- if (g)
- g->obj_dir = obj_dir;
-
return g;
}
@@ -372,14 +365,15 @@ static int add_graph_to_chain(struct commit_graph *g,
return 1;
}
-static struct commit_graph *load_commit_graph_chain(struct repository *r, const char *obj_dir)
+static struct commit_graph *load_commit_graph_chain(struct repository *r,
+ struct object_directory *odb)
{
struct commit_graph *graph_chain = NULL;
struct strbuf line = STRBUF_INIT;
struct stat st;
struct object_id *oids;
int i = 0, valid = 1, count;
- char *chain_name = get_chain_filename(obj_dir);
+ char *chain_name = get_chain_filename(odb);
FILE *fp;
int stat_res;
@@ -412,14 +406,12 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, const
valid = 0;
for (odb = r->objects->odb; odb; odb = odb->next) {
- char *graph_name = get_split_graph_filename(odb->path, line.buf);
- struct commit_graph *g = load_commit_graph_one(graph_name);
+ char *graph_name = get_split_graph_filename(odb, line.buf);
+ struct commit_graph *g = load_commit_graph_one(graph_name, odb);
free(graph_name);
if (g) {
- g->obj_dir = odb->path;
-
if (add_graph_to_chain(g, graph_chain, oids, i)) {
graph_chain = g;
valid = 1;
@@ -442,23 +434,25 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, const
return graph_chain;
}
-struct commit_graph *read_commit_graph_one(struct repository *r, const char *obj_dir)
+struct commit_graph *read_commit_graph_one(struct repository *r,
+ struct object_directory *odb)
{
- struct commit_graph *g = load_commit_graph_v1(r, obj_dir);
+ struct commit_graph *g = load_commit_graph_v1(r, odb);
if (!g)
- g = load_commit_graph_chain(r, obj_dir);
+ g = load_commit_graph_chain(r, odb);
return g;
}
-static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)
+static void prepare_commit_graph_one(struct repository *r,
+ struct object_directory *odb)
{
if (r->objects->commit_graph)
return;
- r->objects->commit_graph = read_commit_graph_one(r, obj_dir);
+ r->objects->commit_graph = read_commit_graph_one(r, odb);
}
/*
@@ -505,7 +499,7 @@ static int prepare_commit_graph(struct repository *r)
for (odb = r->objects->odb;
!r->objects->commit_graph && odb;
odb = odb->next)
- prepare_commit_graph_one(r, odb->path);
+ prepare_commit_graph_one(r, odb);
return !!r->objects->commit_graph;
}
@@ -772,7 +766,7 @@ struct packed_oid_list {
struct write_commit_graph_context {
struct repository *r;
- char *obj_dir;
+ struct object_directory *odb;
char *graph_name;
struct packed_oid_list oids;
struct packed_commit_list commits;
@@ -1149,7 +1143,7 @@ static int add_ref_to_list(const char *refname,
return 0;
}
-int write_commit_graph_reachable(const char *obj_dir,
+int write_commit_graph_reachable(struct object_directory *odb,
enum commit_graph_write_flags flags,
const struct split_commit_graph_opts *split_opts)
{
@@ -1157,7 +1151,7 @@ int write_commit_graph_reachable(const char *obj_dir,
int result;
for_each_ref(add_ref_to_list, &list);
- result = write_commit_graph(obj_dir, NULL, &list,
+ result = write_commit_graph(odb, NULL, &list,
flags, split_opts);
string_list_clear(&list, 0);
@@ -1172,7 +1166,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
struct strbuf packname = STRBUF_INIT;
int dirlen;
- strbuf_addf(&packname, "%s/pack/", ctx->obj_dir);
+ strbuf_addf(&packname, "%s/pack/", ctx->odb->path);
dirlen = packname.len;
if (ctx->report_progress) {
strbuf_addf(&progress_title,
@@ -1368,10 +1362,10 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
strbuf_addf(&tmp_file,
"%s/info/commit-graphs/tmp_graph_XXXXXX",
- ctx->obj_dir);
+ ctx->odb->path);
ctx->graph_name = strbuf_detach(&tmp_file, NULL);
} else {
- ctx->graph_name = get_commit_graph_filename(ctx->obj_dir);
+ ctx->graph_name = get_commit_graph_filename(ctx->odb);
}
if (safe_create_leading_directories(ctx->graph_name)) {
@@ -1382,7 +1376,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
if (ctx->split) {
- char *lock_name = get_chain_filename(ctx->obj_dir);
+ char *lock_name = get_chain_filename(ctx->odb);
hold_lock_file_for_update(&lk, lock_name, LOCK_DIE_ON_ERROR);
@@ -1470,7 +1464,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
if (ctx->split && ctx->base_graph_name && ctx->num_commit_graphs_after > 1) {
char *new_base_hash = xstrdup(oid_to_hex(&ctx->new_base_graph->oid));
- char *new_base_name = get_split_graph_filename(ctx->new_base_graph->obj_dir, new_base_hash);
+ char *new_base_name = get_split_graph_filename(ctx->new_base_graph->odb, new_base_hash);
free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 2]);
free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 2]);
@@ -1506,12 +1500,12 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
}
} else {
- char *graph_name = get_commit_graph_filename(ctx->obj_dir);
+ char *graph_name = get_commit_graph_filename(ctx->odb);
unlink(graph_name);
}
ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(oid_to_hex(&file_hash));
- final_graph_name = get_split_graph_filename(ctx->obj_dir,
+ final_graph_name = get_split_graph_filename(ctx->odb,
ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]);
ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1] = final_graph_name;
@@ -1553,7 +1547,7 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
while (g && (g->num_commits <= size_mult * num_commits ||
(max_commits && num_commits > max_commits))) {
- if (strcmp(g->obj_dir, ctx->obj_dir))
+ if (g->odb != ctx->odb)
break;
num_commits += g->num_commits;
@@ -1565,10 +1559,10 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
ctx->new_base_graph = g;
if (ctx->num_commit_graphs_after == 2) {
- char *old_graph_name = get_commit_graph_filename(g->obj_dir);
+ char *old_graph_name = get_commit_graph_filename(g->odb);
if (!strcmp(g->filename, old_graph_name) &&
- strcmp(g->obj_dir, ctx->obj_dir)) {
+ g->odb != ctx->odb) {
ctx->num_commit_graphs_after = 1;
ctx->new_base_graph = NULL;
}
@@ -1663,19 +1657,15 @@ static void merge_commit_graphs(struct write_commit_graph_context *ctx)
{
struct commit_graph *g = ctx->r->objects->commit_graph;
uint32_t current_graph_number = ctx->num_commit_graphs_before;
- struct strbuf progress_title = STRBUF_INIT;
while (g && current_graph_number >= ctx->num_commit_graphs_after) {
current_graph_number--;
- if (ctx->report_progress) {
- strbuf_addstr(&progress_title, _("Merging commit-graph"));
- ctx->progress = start_delayed_progress(progress_title.buf, 0);
- }
+ if (ctx->report_progress)
+ ctx->progress = start_delayed_progress(_("Merging commit-graph"), 0);
merge_commit_graph(ctx, g);
stop_progress(&ctx->progress);
- strbuf_release(&progress_title);
g = g->base_graph;
}
@@ -1719,13 +1709,13 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
if (ctx->split_opts && ctx->split_opts->expire_time)
expire_time -= ctx->split_opts->expire_time;
if (!ctx->split) {
- char *chain_file_name = get_chain_filename(ctx->obj_dir);
+ char *chain_file_name = get_chain_filename(ctx->odb);
unlink(chain_file_name);
free(chain_file_name);
ctx->num_commit_graphs_after = 0;
}
- strbuf_addstr(&path, ctx->obj_dir);
+ strbuf_addstr(&path, ctx->odb->path);
strbuf_addstr(&path, "/info/commit-graphs");
dir = opendir(path.buf);
@@ -1764,7 +1754,7 @@ out:
strbuf_release(&path);
}
-int write_commit_graph(const char *obj_dir,
+int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
struct string_list *commit_hex,
enum commit_graph_write_flags flags,
@@ -1772,7 +1762,6 @@ int write_commit_graph(const char *obj_dir,
{
struct write_commit_graph_context *ctx;
uint32_t i, count_distinct = 0;
- size_t len;
int res = 0;
if (!commit_graph_compatible(the_repository))
@@ -1780,14 +1769,7 @@ int write_commit_graph(const char *obj_dir,
ctx = xcalloc(1, sizeof(struct write_commit_graph_context));
ctx->r = the_repository;
-
- /* normalize object dir with no trailing slash */
- ctx->obj_dir = xmallocz(strlen(obj_dir) + 1);
- normalize_path_copy(ctx->obj_dir, obj_dir);
- len = strlen(ctx->obj_dir);
- if (len && ctx->obj_dir[len - 1] == '/')
- ctx->obj_dir[len - 1] = 0;
-
+ ctx->odb = odb;
ctx->append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0;
ctx->report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0;
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
@@ -1824,7 +1806,7 @@ int write_commit_graph(const char *obj_dir,
ctx->oids.alloc = split_opts->max_commits;
if (ctx->append) {
- prepare_commit_graph_one(ctx->r, ctx->obj_dir);
+ prepare_commit_graph_one(ctx->r, ctx->odb);
if (ctx->r->objects->commit_graph)
ctx->oids.alloc += ctx->r->objects->commit_graph->num_commits;
}
@@ -1898,7 +1880,6 @@ cleanup:
free(ctx->graph_name);
free(ctx->commits.list);
free(ctx->oids.list);
- free(ctx->obj_dir);
if (ctx->commit_graph_filenames_after) {
for (i = 0; i < ctx->num_commit_graphs_after; i++) {
diff --git a/commit-graph.h b/commit-graph.h
index 7f5c933..e87a6f6 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -5,13 +5,14 @@
#include "repository.h"
#include "string-list.h"
#include "cache.h"
+#include "object-store.h"
#define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH"
#define GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD "GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD"
struct commit;
-char *get_commit_graph_filename(const char *obj_dir);
+char *get_commit_graph_filename(struct object_directory *odb);
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
/*
@@ -48,7 +49,7 @@ struct commit_graph {
uint32_t num_commits;
struct object_id oid;
char *filename;
- const char *obj_dir;
+ struct object_directory *odb;
uint32_t num_commits_in_base;
struct commit_graph *base_graph;
@@ -60,8 +61,10 @@ struct commit_graph {
const unsigned char *chunk_base_graphs;
};
-struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st);
-struct commit_graph *read_commit_graph_one(struct repository *r, const char *obj_dir);
+struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
+ struct object_directory *odb);
+struct commit_graph *read_commit_graph_one(struct repository *r,
+ struct object_directory *odb);
struct commit_graph *parse_commit_graph(void *graph_map, int fd,
size_t graph_size);
@@ -91,10 +94,10 @@ struct split_commit_graph_opts {
* is not compatible with the commit-graph feature, then the
* methods will return 0 without writing a commit-graph.
*/
-int write_commit_graph_reachable(const char *obj_dir,
+int write_commit_graph_reachable(struct object_directory *odb,
enum commit_graph_write_flags flags,
const struct split_commit_graph_opts *split_opts);
-int write_commit_graph(const char *obj_dir,
+int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
struct string_list *commit_hex,
enum commit_graph_write_flags flags,
diff --git a/commit-slab.h b/commit-slab.h
index 69bf0c8..05b3f28 100644
--- a/commit-slab.h
+++ b/commit-slab.h
@@ -24,7 +24,12 @@
* - int *indegree_peek(struct indegree *, struct commit *);
*
* This function is similar to indegree_at(), but it will return NULL
- * until a call to indegree_at() was made for the commit.
+ * if the location to store the data associated with the given commit
+ * has not been allocated yet.
+ * Note that the location to store the data might have already been
+ * allocated even if no indegree_at() call has been made for that commit
+ * yet; in this case this function returns a pointer to a
+ * zero-initialized location.
*
* - void init_indegree(struct indegree *);
* void init_indegree_with_stride(struct indegree *, int);
diff --git a/commit.c b/commit.c
index 434ec03..c7099da 100644
--- a/commit.c
+++ b/commit.c
@@ -927,12 +927,22 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
struct commit_list *bases;
int i;
struct commit *ret = NULL;
+ char *full_refname;
+
+ switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+ case 0:
+ die("No such ref: '%s'", refname);
+ case 1:
+ break; /* good */
+ default:
+ die("Ambiguous refname: '%s'", refname);
+ }
memset(&revs, 0, sizeof(revs));
revs.initial = 1;
- for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
+ for_each_reflog_ent(full_refname, collect_one_reflog_ent, &revs);
- if (!revs.nr && !get_oid(refname, &oid))
+ if (!revs.nr)
add_one_commit(&oid, &revs);
for (i = 0; i < revs.nr; i++)
@@ -958,17 +968,26 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
cleanup_return:
free_commit_list(bases);
+ free(full_refname);
return ret;
}
-static const char gpg_sig_header[] = "gpgsig";
-static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+/*
+ * Indexed by hash algorithm identifier.
+ */
+static const char *gpg_sig_headers[] = {
+ NULL,
+ "gpgsig",
+ "gpgsig-sha256",
+};
static int do_sign_commit(struct strbuf *buf, const char *keyid)
{
struct strbuf sig = STRBUF_INIT;
int inspos, copypos;
const char *eoh;
+ const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+ int gpg_sig_header_len = strlen(gpg_sig_header);
/* find the end of the header */
eoh = strstr(buf->buf, "\n\n");
@@ -993,7 +1012,7 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)
strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
inspos += gpg_sig_header_len;
}
- strbuf_insert(buf, inspos++, " ", 1);
+ strbuf_insertstr(buf, inspos++, " ");
strbuf_insert(buf, inspos, bol, len);
inspos += len;
copypos += len;
@@ -1010,6 +1029,8 @@ int parse_signed_commit(const struct commit *commit,
const char *buffer = get_commit_buffer(commit, &size);
int in_signature, saw_signature = -1;
const char *line, *tail;
+ const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+ int gpg_sig_header_len = strlen(gpg_sig_header);
line = buffer;
tail = buffer + size;
@@ -1056,11 +1077,17 @@ int remove_signature(struct strbuf *buf)
if (in_signature && line[0] == ' ')
sig_end = next;
- else if (starts_with(line, gpg_sig_header) &&
- line[gpg_sig_header_len] == ' ') {
- sig_start = line;
- sig_end = next;
- in_signature = 1;
+ else if (starts_with(line, "gpgsig")) {
+ int i;
+ for (i = 1; i < GIT_HASH_NALGOS; i++) {
+ const char *p;
+ if (skip_prefix(line, gpg_sig_headers[i], &p) &&
+ *p == ' ') {
+ sig_start = line;
+ sig_end = next;
+ in_signature = 1;
+ }
+ }
} else {
if (*line == '\n')
/* dump the whole remainder of the buffer */
@@ -1136,21 +1163,23 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
return ret;
}
-void verify_merge_signature(struct commit *commit, int verbosity)
+void verify_merge_signature(struct commit *commit, int verbosity,
+ int check_trust)
{
char hex[GIT_MAX_HEXSZ + 1];
struct signature_check signature_check;
+ int ret;
memset(&signature_check, 0, sizeof(signature_check));
- check_commit_signature(commit, &signature_check);
+ ret = check_commit_signature(commit, &signature_check);
find_unique_abbrev_r(hex, &commit->object.oid, DEFAULT_ABBREV);
switch (signature_check.result) {
case 'G':
+ if (ret || (check_trust && signature_check.trust_level < TRUST_MARGINAL))
+ die(_("Commit %s has an untrusted GPG signature, "
+ "allegedly by %s."), hex, signature_check.signer);
break;
- case 'U':
- die(_("Commit %s has an untrusted GPG signature, "
- "allegedly by %s."), hex, signature_check.signer);
case 'B':
die(_("Commit %s has a bad GPG signature "
"allegedly by %s."), hex, signature_check.signer);
diff --git a/commit.h b/commit.h
index 221cdaa..008a0fa 100644
--- a/commit.h
+++ b/commit.h
@@ -383,8 +383,18 @@ int compare_commits_by_author_date(const void *a_, const void *b_, void *unused)
* Verify a single commit with check_commit_signature() and die() if it is not
* a good signature. This isn't really suitable for general use, but is a
* helper to implement consistent logic for pull/merge --verify-signatures.
+ *
+ * The check_trust parameter is meant for backward-compatibility. The GPG
+ * interface verifies key trust with a default trust level that is below the
+ * default trust level for merge operations. Its value should be non-zero if
+ * the user hasn't set a minimum trust level explicitly in their configuration.
+ *
+ * If the user has set a minimum trust level, then that value should be obeyed
+ * and check_trust should be zero, even if the configured trust level is below
+ * the default trust level for merges.
*/
-void verify_merge_signature(struct commit *commit, int verbose);
+void verify_merge_signature(struct commit *commit, int verbose,
+ int check_trust);
int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused);
diff --git a/compat/mingw.c b/compat/mingw.c
index 402c1ad..d14065d 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -13,6 +13,19 @@
static const int delay[] = { 0, 1, 10, 20, 40 };
+void open_in_gdb(void)
+{
+ static struct child_process cp = CHILD_PROCESS_INIT;
+ extern char *_pgmptr;
+
+ argv_array_pushl(&cp.args, "mintty", "gdb", NULL);
+ argv_array_pushf(&cp.args, "--pid=%d", getpid());
+ cp.clean_on_exit = 1;
+ if (start_command(&cp) < 0)
+ die_errno("Could not start gdb");
+ sleep(1);
+}
+
int err_win_to_posix(DWORD winerr)
{
int error = ENOSYS;
@@ -1232,7 +1245,7 @@ static char *path_lookup(const char *cmd, int exe_only)
int len = strlen(cmd);
int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe");
- if (strchr(cmd, '/') || strchr(cmd, '\\'))
+ if (strpbrk(cmd, "/\\"))
return xstrdup(cmd);
path = mingw_getenv("PATH");
diff --git a/compat/mingw.h b/compat/mingw.h
index 714bc1d..e6fe810 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -599,6 +599,16 @@ int wmain(int argc, const wchar_t **w_argv);
int main(int argc, const char **argv);
/*
+ * For debugging: if a problem occurs, say, in a Git process that is spawned
+ * from another Git process which in turn is spawned from yet another Git
+ * process, it can be quite daunting to figure out what is going on.
+ *
+ * Call this function to open a new MinTTY (this assumes you are in Git for
+ * Windows' SDK) with a GDB that attaches to the current process right away.
+ */
+extern void open_in_gdb(void);
+
+/*
* Used by Pthread API implementation for Windows
*/
int err_win_to_posix(DWORD winerr);
diff --git a/compat/obstack.h b/compat/obstack.h
index 01e7c81..f90a46d 100644
--- a/compat/obstack.h
+++ b/compat/obstack.h
@@ -135,8 +135,10 @@ extern "C" {
alignment relative to 0. */
#define __PTR_ALIGN(B, P, A) \
- __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \
- P, A)
+ (sizeof (PTR_INT_TYPE) < sizeof(void *) ? \
+ __BPTR_ALIGN((B), (P), (A)) : \
+ (void *)__BPTR_ALIGN((PTR_INT_TYPE)(void *)0, (PTR_INT_TYPE)(P), (A)) \
+ )
#include <string.h>
diff --git a/compat/poll/poll.c b/compat/poll/poll.c
index 0e95dd4..afa6d24 100644
--- a/compat/poll/poll.c
+++ b/compat/poll/poll.c
@@ -139,22 +139,10 @@ win32_compute_revents (HANDLE h, int *p_sought)
INPUT_RECORD *irbuffer;
DWORD avail, nbuffer;
BOOL bRet;
- IO_STATUS_BLOCK iosb;
- FILE_PIPE_LOCAL_INFORMATION fpli;
- static PNtQueryInformationFile NtQueryInformationFile;
- static BOOL once_only;
switch (GetFileType (h))
{
case FILE_TYPE_PIPE:
- if (!once_only)
- {
- NtQueryInformationFile = (PNtQueryInformationFile)(void (*)(void))
- GetProcAddress (GetModuleHandleW (L"ntdll.dll"),
- "NtQueryInformationFile");
- once_only = TRUE;
- }
-
happened = 0;
if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
{
@@ -166,22 +154,9 @@ win32_compute_revents (HANDLE h, int *p_sought)
else
{
- /* It was the write-end of the pipe. Check if it is writable.
- If NtQueryInformationFile fails, optimistically assume the pipe is
- writable. This could happen on Win9x, where NtQueryInformationFile
- is not available, or if we inherit a pipe that doesn't permit
- FILE_READ_ATTRIBUTES access on the write end (I think this should
- not happen since WinXP SP2; WINE seems fine too). Otherwise,
- ensure that enough space is available for atomic writes. */
- memset (&iosb, 0, sizeof (iosb));
- memset (&fpli, 0, sizeof (fpli));
-
- if (!NtQueryInformationFile
- || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
- FilePipeLocalInformation)
- || fpli.WriteQuotaAvailable >= PIPE_BUF
- || (fpli.OutboundQuota < PIPE_BUF &&
- fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+ /* It was the write-end of the pipe. Unfortunately there is no
+ reliable way of knowing if it can be written without blocking.
+ Just say that it's all good. */
happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
}
return happened;
diff --git a/compat/regex/regex.h b/compat/regex/regex.h
index 08a2609..2d34128 100644
--- a/compat/regex/regex.h
+++ b/compat/regex/regex.h
@@ -41,6 +41,11 @@
extern "C" {
#endif
+#define regcomp git_regcomp
+#define regexec git_regexec
+#define regerror git_regerror
+#define regfree git_regfree
+
/* The following two types have to be signed and unsigned integer type
wide enough to hold a value of a pointer. For most ANSI compilers
ptrdiff_t and size_t should be likely OK. Still size of these two
diff --git a/compat/terminal.c b/compat/terminal.c
index fa13ee6..35bca03 100644
--- a/compat/terminal.c
+++ b/compat/terminal.c
@@ -2,6 +2,9 @@
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
+#include "run-command.h"
+#include "string-list.h"
+#include "hashmap.h"
#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
@@ -32,7 +35,7 @@ static void restore_term(void)
term_fd = -1;
}
-static int disable_echo(void)
+static int disable_bits(tcflag_t bits)
{
struct termios t;
@@ -43,7 +46,7 @@ static int disable_echo(void)
old_term = t;
sigchain_push_common(restore_term_on_signal);
- t.c_lflag &= ~ECHO;
+ t.c_lflag &= ~bits;
if (!tcsetattr(term_fd, TCSAFLUSH, &t))
return 0;
@@ -53,17 +56,44 @@ error:
return -1;
}
+static int disable_echo(void)
+{
+ return disable_bits(ECHO);
+}
+
+static int enable_non_canonical(void)
+{
+ return disable_bits(ICANON | ECHO);
+}
+
#elif defined(GIT_WINDOWS_NATIVE)
#define INPUT_PATH "CONIN$"
#define OUTPUT_PATH "CONOUT$"
#define FORCE_TEXT "t"
+static int use_stty = 1;
+static struct string_list stty_restore = STRING_LIST_INIT_DUP;
static HANDLE hconin = INVALID_HANDLE_VALUE;
static DWORD cmode;
static void restore_term(void)
{
+ if (use_stty) {
+ int i;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (stty_restore.nr == 0)
+ return;
+
+ argv_array_push(&cp.args, "stty");
+ for (i = 0; i < stty_restore.nr; i++)
+ argv_array_push(&cp.args, stty_restore.items[i].string);
+ run_command(&cp);
+ string_list_clear(&stty_restore, 0);
+ return;
+ }
+
if (hconin == INVALID_HANDLE_VALUE)
return;
@@ -72,8 +102,39 @@ static void restore_term(void)
hconin = INVALID_HANDLE_VALUE;
}
-static int disable_echo(void)
+static int disable_bits(DWORD bits)
{
+ if (use_stty) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ argv_array_push(&cp.args, "stty");
+
+ if (bits & ENABLE_LINE_INPUT) {
+ string_list_append(&stty_restore, "icanon");
+ argv_array_push(&cp.args, "-icanon");
+ }
+
+ if (bits & ENABLE_ECHO_INPUT) {
+ string_list_append(&stty_restore, "echo");
+ argv_array_push(&cp.args, "-echo");
+ }
+
+ if (bits & ENABLE_PROCESSED_INPUT) {
+ string_list_append(&stty_restore, "-ignbrk");
+ string_list_append(&stty_restore, "intr");
+ string_list_append(&stty_restore, "^c");
+ argv_array_push(&cp.args, "ignbrk");
+ argv_array_push(&cp.args, "intr");
+ argv_array_push(&cp.args, "");
+ }
+
+ if (run_command(&cp) == 0)
+ return 0;
+
+ /* `stty` could not be executed; access the Console directly */
+ use_stty = 0;
+ }
+
hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
@@ -82,7 +143,7 @@ static int disable_echo(void)
GetConsoleMode(hconin, &cmode);
sigchain_push_common(restore_term_on_signal);
- if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) {
+ if (!SetConsoleMode(hconin, cmode & ~bits)) {
CloseHandle(hconin);
hconin = INVALID_HANDLE_VALUE;
return -1;
@@ -91,6 +152,47 @@ static int disable_echo(void)
return 0;
}
+static int disable_echo(void)
+{
+ return disable_bits(ENABLE_ECHO_INPUT);
+}
+
+static int enable_non_canonical(void)
+{
+ return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+}
+
+/*
+ * Override `getchar()`, as the default implementation does not use
+ * `ReadFile()`.
+ *
+ * This poses a problem when we want to see whether the standard
+ * input has more characters, as the default of Git for Windows is to start the
+ * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
+ * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
+ * `ReadFile()` to be called first to work properly (it only reports 0
+ * available bytes, otherwise).
+ *
+ * So let's just override `getchar()` with a version backed by `ReadFile()` and
+ * go our merry ways from here.
+ */
+static int mingw_getchar(void)
+{
+ DWORD read = 0;
+ unsigned char ch;
+
+ if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
+ return EOF;
+
+ if (!read) {
+ error("Unexpected 0 read");
+ return EOF;
+ }
+
+ return ch;
+}
+#define getchar mingw_getchar
+
#endif
#ifndef FORCE_TEXT
@@ -137,6 +239,126 @@ char *git_terminal_prompt(const char *prompt, int echo)
return buf.buf;
}
+/*
+ * The `is_known_escape_sequence()` function returns 1 if the passed string
+ * corresponds to an Escape sequence that the terminal capabilities contains.
+ *
+ * To avoid depending on ncurses or other platform-specific libraries, we rely
+ * on the presence of the `infocmp` executable to do the job for us (failing
+ * silently if the program is not available or refused to run).
+ */
+struct escape_sequence_entry {
+ struct hashmap_entry entry;
+ char sequence[FLEX_ARRAY];
+};
+
+static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
+ const struct escape_sequence_entry *e1,
+ const struct escape_sequence_entry *e2,
+ const void *keydata)
+{
+ return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
+}
+
+static int is_known_escape_sequence(const char *sequence)
+{
+ static struct hashmap sequences;
+ static int initialized;
+
+ if (!initialized) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ char *p, *eol;
+
+ hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
+ NULL, 0);
+
+ argv_array_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
+ if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
+ strbuf_setlen(&buf, 0);
+
+ for (eol = p = buf.buf; *p; p = eol + 1) {
+ p = strchr(p, '=');
+ if (!p)
+ break;
+ p++;
+ eol = strchrnul(p, '\n');
+
+ if (starts_with(p, "\\E")) {
+ char *comma = memchr(p, ',', eol - p);
+ struct escape_sequence_entry *e;
+
+ p[0] = '^';
+ p[1] = '[';
+ FLEX_ALLOC_MEM(e, sequence, p, comma - p);
+ hashmap_entry_init(&e->entry,
+ strhash(e->sequence));
+ hashmap_add(&sequences, &e->entry);
+ }
+ if (!*eol)
+ break;
+ }
+ initialized = 1;
+ }
+
+ return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
+}
+
+int read_key_without_echo(struct strbuf *buf)
+{
+ static int warning_displayed;
+ int ch;
+
+ if (warning_displayed || enable_non_canonical() < 0) {
+ if (!warning_displayed) {
+ warning("reading single keystrokes not supported on "
+ "this platform; reading line instead");
+ warning_displayed = 1;
+ }
+
+ return strbuf_getline(buf, stdin);
+ }
+
+ strbuf_reset(buf);
+ ch = getchar();
+ if (ch == EOF) {
+ restore_term();
+ return EOF;
+ }
+ strbuf_addch(buf, ch);
+
+ if (ch == '\033' /* ESC */) {
+ /*
+ * We are most likely looking at an Escape sequence. Let's try
+ * to read more bytes, waiting at most half a second, assuming
+ * that the sequence is complete if we did not receive any byte
+ * within that time.
+ *
+ * Start by replacing the Escape byte with ^[ */
+ strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
+
+ /*
+ * Query the terminal capabilities once about all the Escape
+ * sequences it knows about, so that we can avoid waiting for
+ * half a second when we know that the sequence is complete.
+ */
+ while (!is_known_escape_sequence(buf->buf)) {
+ struct pollfd pfd = { .fd = 0, .events = POLLIN };
+
+ if (poll(&pfd, 1, 500) < 1)
+ break;
+
+ ch = getchar();
+ if (ch == EOF)
+ return 0;
+ strbuf_addch(buf, ch);
+ }
+ }
+
+ restore_term();
+ return 0;
+}
+
#else
char *git_terminal_prompt(const char *prompt, int echo)
@@ -144,4 +366,23 @@ char *git_terminal_prompt(const char *prompt, int echo)
return getpass(prompt);
}
+int read_key_without_echo(struct strbuf *buf)
+{
+ static int warning_displayed;
+ const char *res;
+
+ if (!warning_displayed) {
+ warning("reading single keystrokes not supported on this "
+ "platform; reading line instead");
+ warning_displayed = 1;
+ }
+
+ res = getpass("");
+ strbuf_reset(buf);
+ if (!res)
+ return EOF;
+ strbuf_addstr(buf, res);
+ return 0;
+}
+
#endif
diff --git a/compat/terminal.h b/compat/terminal.h
index 97db7cd..a9d52b8 100644
--- a/compat/terminal.h
+++ b/compat/terminal.h
@@ -3,4 +3,7 @@
char *git_terminal_prompt(const char *prompt, int echo);
+/* Read a single keystroke, without echoing it to the terminal */
+int read_key_without_echo(struct strbuf *buf);
+
#endif /* COMPAT_TERMINAL_H */
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index ec95a3b..d9f71b7 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -45,9 +45,9 @@ while (@ARGV) {
} elsif ("$arg" eq "-liconv") {
push(@args, "libiconv.lib");
} elsif ("$arg" eq "-lcrypto") {
- push(@args, "libeay32.lib");
+ push(@args, "libcrypto.lib");
} elsif ("$arg" eq "-lssl") {
- push(@args, "ssleay32.lib");
+ push(@args, "libssl.lib");
} elsif ("$arg" eq "-lcurl") {
my $lib = "";
# Newer vcpkg definitions call this libcurl_imp.lib; Do we
diff --git a/config.c b/config.c
index d75f88c..d17d2bd 100644
--- a/config.c
+++ b/config.c
@@ -204,7 +204,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
prefix = slash - path.buf + 1 /* slash */;
} else if (!is_absolute_path(pat->buf))
- strbuf_insert(pat, 0, "**/", 3);
+ strbuf_insertstr(pat, 0, "**/");
add_trailing_starstar_for_dir(pat);
@@ -1702,6 +1702,7 @@ static int do_git_config_sequence(const struct config_options *opts,
char *xdg_config = xdg_config_home("config");
char *user_config = expand_user_path("~/.gitconfig", 0);
char *repo_config;
+ enum config_scope prev_parsing_scope = current_parsing_scope;
if (opts->commondir)
repo_config = mkpathdup("%s/config", opts->commondir);
@@ -1724,15 +1725,12 @@ static int do_git_config_sequence(const struct config_options *opts,
if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file(fn, user_config, data);
- current_parsing_scope = CONFIG_SCOPE_REPO;
+ current_parsing_scope = CONFIG_SCOPE_LOCAL;
if (!opts->ignore_repo && repo_config &&
!access_or_die(repo_config, R_OK, 0))
ret += git_config_from_file(fn, repo_config, data);
- /*
- * Note: this should have a new scope, CONFIG_SCOPE_WORKTREE.
- * But let's not complicate things before it's actually needed.
- */
+ current_parsing_scope = CONFIG_SCOPE_WORKTREE;
if (!opts->ignore_worktree && repository_format_worktree_config) {
char *path = git_pathdup("config.worktree");
if (!access_or_die(path, R_OK, 0))
@@ -1740,11 +1738,11 @@ static int do_git_config_sequence(const struct config_options *opts,
free(path);
}
- current_parsing_scope = CONFIG_SCOPE_CMDLINE;
+ current_parsing_scope = CONFIG_SCOPE_COMMAND;
if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
die(_("unable to parse command-line config"));
- current_parsing_scope = CONFIG_SCOPE_UNKNOWN;
+ current_parsing_scope = prev_parsing_scope;
free(xdg_config);
free(user_config);
free(repo_config);
@@ -1765,6 +1763,9 @@ int config_with_options(config_fn_t fn, void *data,
data = &inc;
}
+ if (config_source)
+ current_parsing_scope = config_source->scope;
+
/*
* If we have a specific filename, use it. Otherwise, follow the
* regular lookup sequence.
@@ -3297,6 +3298,26 @@ const char *current_config_origin_type(void)
}
}
+const char *config_scope_name(enum config_scope scope)
+{
+ switch (scope) {
+ case CONFIG_SCOPE_SYSTEM:
+ return "system";
+ case CONFIG_SCOPE_GLOBAL:
+ return "global";
+ case CONFIG_SCOPE_LOCAL:
+ return "local";
+ case CONFIG_SCOPE_WORKTREE:
+ return "worktree";
+ case CONFIG_SCOPE_COMMAND:
+ return "command";
+ case CONFIG_SCOPE_SUBMODULE:
+ return "submodule";
+ default:
+ return "unknown";
+ }
+}
+
const char *current_config_name(void)
{
const char *name;
@@ -3317,6 +3338,14 @@ enum config_scope current_config_scope(void)
return current_parsing_scope;
}
+int current_config_line(void)
+{
+ if (current_config_kvi)
+ return current_config_kvi->linenr;
+ else
+ return cf->linenr;
+}
+
int lookup_config(const char **mapping, int nr_mapping, const char *var)
{
int i;
diff --git a/config.h b/config.h
index 91fd4c5..9b3773f 100644
--- a/config.h
+++ b/config.h
@@ -35,10 +35,22 @@ struct object_id;
#define CONFIG_REGEX_NONE ((void *)1)
+enum config_scope {
+ CONFIG_SCOPE_UNKNOWN = 0,
+ CONFIG_SCOPE_SYSTEM,
+ CONFIG_SCOPE_GLOBAL,
+ CONFIG_SCOPE_LOCAL,
+ CONFIG_SCOPE_WORKTREE,
+ CONFIG_SCOPE_COMMAND,
+ CONFIG_SCOPE_SUBMODULE,
+};
+const char *config_scope_name(enum config_scope scope);
+
struct git_config_source {
unsigned int use_stdin:1;
const char *file;
const char *blob;
+ enum config_scope scope;
};
enum config_origin_type {
@@ -294,17 +306,10 @@ int config_error_nonbool(const char *);
int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
-enum config_scope {
- CONFIG_SCOPE_UNKNOWN = 0,
- CONFIG_SCOPE_SYSTEM,
- CONFIG_SCOPE_GLOBAL,
- CONFIG_SCOPE_REPO,
- CONFIG_SCOPE_CMDLINE,
-};
-
enum config_scope current_config_scope(void);
const char *current_config_origin_type(void);
const char *current_config_name(void);
+int current_config_line(void);
/**
* Include Directives
diff --git a/config.mak.dev b/config.mak.dev
index bf1f3fc..cd4a82a 100644
--- a/config.mak.dev
+++ b/config.mak.dev
@@ -9,7 +9,6 @@ endif
DEVELOPER_CFLAGS += -Wall
DEVELOPER_CFLAGS += -Wdeclaration-after-statement
DEVELOPER_CFLAGS += -Wformat-security
-DEVELOPER_CFLAGS += -Wno-format-zero-length
DEVELOPER_CFLAGS += -Wold-style-definition
DEVELOPER_CFLAGS += -Woverflow
DEVELOPER_CFLAGS += -Wpointer-arith
@@ -17,6 +16,8 @@ DEVELOPER_CFLAGS += -Wstrict-prototypes
DEVELOPER_CFLAGS += -Wunused
DEVELOPER_CFLAGS += -Wvla
+DEVELOPER_CFLAGS += -DENABLE_SHA256
+
ifndef COMPILER_FEATURES
COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
endif
diff --git a/connected.c b/connected.c
index 846f2e4..3135b71 100644
--- a/connected.c
+++ b/connected.c
@@ -61,7 +61,11 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
* object is a promisor object. Instead, just make sure we
* received, in a promisor packfile, the objects pointed to by
* each wanted ref.
+ *
+ * Before checking for promisor packs, be sure we have the
+ * latest pack-files loaded into memory.
*/
+ reprepare_packed_git(the_repository);
do {
struct packed_git *p;
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index fba8a3f..0709785 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -343,9 +343,9 @@ sub handleLinkLine
} elsif ("$part" eq "-lz") {
push(@libs, "zlib.lib");
} elsif ("$part" eq "-lcrypto") {
- push(@libs, "libeay32.lib");
+ push(@libs, "libcrypto.lib");
} elsif ("$part" eq "-lssl") {
- push(@libs, "ssleay32.lib");
+ push(@libs, "libssl.lib");
} elsif ("$part" eq "-lcurl") {
push(@libs, "libcurl.lib");
} elsif ("$part" eq "-lexpat") {
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e4d9ff4..c21786f 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1069,15 +1069,32 @@ __git_aliased_command ()
done
}
-# __git_find_on_cmdline requires 1 argument
+# Check whether one of the given words is present on the command line,
+# and print the first word found.
+#
+# Usage: __git_find_on_cmdline [<option>]... "<wordlist>"
+# --show-idx: Optionally show the index of the found word in the $words array.
__git_find_on_cmdline ()
{
- local word subcommand c=1
+ local word c=1 show_idx
+
+ while test $# -gt 1; do
+ case "$1" in
+ --show-idx) show_idx=y ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
+ local wordlist="$1"
+
while [ $c -lt $cword ]; do
- word="${words[c]}"
- for subcommand in $1; do
- if [ "$subcommand" = "$word" ]; then
- echo "$subcommand"
+ for word in $wordlist; do
+ if [ "$word" = "${words[c]}" ]; then
+ if [ -n "$show_idx" ]; then
+ echo "$c $word"
+ else
+ echo "$word"
+ fi
return
fi
done
@@ -1180,6 +1197,7 @@ __git_count_arguments ()
__git_whitespacelist="nowarn warn error error-all fix"
__git_patchformat="mbox stgit stgit-series hg mboxrd"
+__git_showcurrentpatch="diff raw"
__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
_git_am ()
@@ -1198,6 +1216,10 @@ _git_am ()
__gitcomp "$__git_patchformat" "" "${cur##--patch-format=}"
return
;;
+ --show-current-patch=*)
+ __gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
+ return
+ ;;
--*)
__gitcomp_builtin am "" \
"$__git_am_inprogress_options"
@@ -1470,9 +1492,16 @@ __git_diff_algorithms="myers minimal patience histogram"
__git_diff_submodule_formats="diff log short"
+__git_color_moved_opts="no default plain blocks zebra dimmed-zebra"
+
+__git_color_moved_ws_opts="no ignore-space-at-eol ignore-space-change
+ ignore-all-space allow-indentation-change"
+
__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
+ --color-moved --color-moved= --no-color-moved
+ --color-moved-ws= --no-color-moved-ws
--full-index --binary --abbrev --diff-filter=
--find-copies-harder --ignore-cr-at-eol
--text --ignore-space-at-eol --ignore-space-change
@@ -1503,6 +1532,14 @@ _git_diff ()
__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
return
;;
+ --color-moved=*)
+ __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
+ return
+ ;;
+ --color-moved-ws=*)
+ __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
+ return
+ ;;
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
--base --ours --theirs --no-index
@@ -2718,6 +2755,27 @@ _git_show_branch ()
__git_complete_revlist
}
+_git_sparse_checkout ()
+{
+ local subcommands="list init set disable"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ return
+ fi
+
+ case "$subcommand,$cur" in
+ init,--*)
+ __gitcomp "--cone"
+ ;;
+ set,--*)
+ __gitcomp "--stdin"
+ ;;
+ *)
+ ;;
+ esac
+}
+
_git_stash ()
{
local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked'
@@ -2969,33 +3027,83 @@ _git_whatchanged ()
_git_log
}
+__git_complete_worktree_paths ()
+{
+ local IFS=$'\n'
+ __gitcomp_nl "$(git worktree list --porcelain |
+ # Skip the first entry: it's the path of the main worktree,
+ # which can't be moved, removed, locked, etc.
+ sed -n -e '2,$ s/^worktree //p')"
+}
+
_git_worktree ()
{
local subcommands="add list lock move prune remove unlock"
- local subcommand="$(__git_find_on_cmdline "$subcommands")"
- if [ -z "$subcommand" ]; then
+ local subcommand subcommand_idx
+
+ subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")"
+ subcommand_idx="${subcommand% *}"
+ subcommand="${subcommand#* }"
+
+ case "$subcommand,$cur" in
+ ,*)
__gitcomp "$subcommands"
- else
- case "$subcommand,$cur" in
- add,--*)
- __gitcomp_builtin worktree_add
- ;;
- list,--*)
- __gitcomp_builtin worktree_list
- ;;
- lock,--*)
- __gitcomp_builtin worktree_lock
- ;;
- prune,--*)
- __gitcomp_builtin worktree_prune
- ;;
- remove,--*)
- __gitcomp "--force"
+ ;;
+ *,--*)
+ __gitcomp_builtin worktree_$subcommand
+ ;;
+ add,*) # usage: git worktree add [<options>] <path> [<commit-ish>]
+ # Here we are not completing an --option, it's either the
+ # path or a ref.
+ case "$prev" in
+ -b|-B) # Complete refs for branch to be created/reseted.
+ __git_complete_refs
;;
- *)
+ -*) # The previous word is an -o|--option without an
+ # unstuck argument: have to complete the path for
+ # the new worktree, so don't list anything, but let
+ # Bash fall back to filename completion.
+ ;;
+ *) # The previous word is not an --option, so it must
+ # be either the 'add' subcommand, the unstuck
+ # argument of an option (e.g. branch for -b|-B), or
+ # the path for the new worktree.
+ if [ $cword -eq $((subcommand_idx+1)) ]; then
+ # Right after the 'add' subcommand: have to
+ # complete the path, so fall back to Bash
+ # filename completion.
+ :
+ else
+ case "${words[cword-2]}" in
+ -b|-B) # After '-b <branch>': have to
+ # complete the path, so fall back
+ # to Bash filename completion.
+ ;;
+ *) # After the path: have to complete
+ # the ref to be checked out.
+ __git_complete_refs
+ ;;
+ esac
+ fi
;;
esac
- fi
+ ;;
+ lock,*|remove,*|unlock,*)
+ __git_complete_worktree_paths
+ ;;
+ move,*)
+ if [ $cword -eq $((subcommand_idx+1)) ]; then
+ # The first parameter must be an existing working
+ # tree to be moved.
+ __git_complete_worktree_paths
+ else
+ # The second parameter is the destination: it could
+ # be any path, so don't list anything, but let Bash
+ # fall back to filename completion.
+ :
+ fi
+ ;;
+ esac
}
__git_complete_common () {
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 1d510cd..014cd7c 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -429,11 +429,7 @@ __git_ps1 ()
__git_eread "$g/rebase-merge/head-name" b
__git_eread "$g/rebase-merge/msgnum" step
__git_eread "$g/rebase-merge/end" total
- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
- else
- r="|REBASE-m"
- fi
+ r="|REBASE"
else
if [ -d "$g/rebase-apply" ]; then
__git_eread "$g/rebase-apply/next" step
diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore
new file mode 100644
index 0000000..d41cdde
--- /dev/null
+++ b/contrib/credential/netrc/.gitignore
@@ -0,0 +1 @@
+git-credential-netrc
diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile
index 6174e3b..c284fb8 100644
--- a/contrib/credential/netrc/Makefile
+++ b/contrib/credential/netrc/Makefile
@@ -1,8 +1,30 @@
# The default target of this Makefile is...
all::
-test:
+SCRIPT_PERL = git-credential-netrc.perl
+GIT_ROOT_DIR = ../../..
+HERE = contrib/credential/netrc
+
+SCRIPT_PERL_FULL = $(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+
+all:: build
+
+build:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ build-perl-script
+
+install: build
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ install-perl-script
+
+clean:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ clean-perl-script
+
+test: build
./t-git-credential-netrc.sh
-testverbose:
+testverbose: build
./t-git-credential-netrc.sh -d -v
+
+.PHONY: all build install clean test testverbose
diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc.perl
index ebfc123..bc57cc6 100755
--- a/contrib/credential/netrc/git-credential-netrc
+++ b/contrib/credential/netrc/git-credential-netrc.perl
@@ -423,7 +423,7 @@ sub load_config {
# load settings from git config
my $options = shift;
# set from command argument, gpg.program option, or default to gpg
- $options->{'gpg'} //= Git->repository()->config('gpg.program')
+ $options->{'gpg'} //= Git::config('gpg.program')
// 'gpg';
log_verbose("using $options{'gpg'} for GPG operations");
}
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
index e800d9f..d50ce26 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -139,6 +139,8 @@ foreach my $tar_file (@ARGV)
print FI "\n";
}
+ next if ($typeflag eq 'g'); # ignore global header
+
my $path;
if ($prefix) {
$path = "$prefix/$name";
diff --git a/convert.c b/convert.c
index 797e0bd..5aa87d4 100644
--- a/convert.c
+++ b/convert.c
@@ -797,6 +797,7 @@ static void handle_filter_error(const struct strbuf *filter_status,
static int apply_multi_file_filter(const char *path, const char *src, size_t len,
int fd, struct strbuf *dst, const char *cmd,
const unsigned int wanted_capability,
+ const struct checkout_metadata *meta,
struct delayed_checkout *dco)
{
int err;
@@ -855,6 +856,24 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
if (err)
goto done;
+ if (meta && meta->refname) {
+ err = packet_write_fmt_gently(process->in, "ref=%s\n", meta->refname);
+ if (err)
+ goto done;
+ }
+
+ if (meta && !is_null_oid(&meta->treeish)) {
+ err = packet_write_fmt_gently(process->in, "treeish=%s\n", oid_to_hex(&meta->treeish));
+ if (err)
+ goto done;
+ }
+
+ if (meta && !is_null_oid(&meta->blob)) {
+ err = packet_write_fmt_gently(process->in, "blob=%s\n", oid_to_hex(&meta->blob));
+ if (err)
+ goto done;
+ }
+
if ((entry->supported_capabilities & CAP_DELAY) &&
dco && dco->state == CE_CAN_DELAY) {
can_delay = 1;
@@ -971,6 +990,7 @@ static struct convert_driver {
static int apply_filter(const char *path, const char *src, size_t len,
int fd, struct strbuf *dst, struct convert_driver *drv,
const unsigned int wanted_capability,
+ const struct checkout_metadata *meta,
struct delayed_checkout *dco)
{
const char *cmd = NULL;
@@ -990,7 +1010,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
return apply_single_file_filter(path, src, len, fd, dst, cmd);
else if (drv->process && *drv->process)
return apply_multi_file_filter(path, src, len, fd, dst,
- drv->process, wanted_capability, dco);
+ drv->process, wanted_capability, meta, dco);
return 0;
}
@@ -1146,7 +1166,7 @@ static int ident_to_worktree(const char *src, size_t len,
/* are we "faking" in place editing ? */
if (src == buf->buf)
to_free = strbuf_detach(buf, NULL);
- hash_object_file(src, len, "blob", &oid);
+ hash_object_file(the_hash_algo, src, len, "blob", &oid);
strbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));
for (;;) {
@@ -1368,7 +1388,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
if (!ca.drv->required)
return 0;
- return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
+ return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
}
const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
@@ -1406,7 +1426,7 @@ int convert_to_git(const struct index_state *istate,
convert_attrs(istate, &ca, path);
- ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
+ ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL, NULL);
if (!ret && ca.drv && ca.drv->required)
die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
@@ -1441,7 +1461,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,
assert(ca.drv);
assert(ca.drv->clean || ca.drv->process);
- if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+ if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL))
die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
@@ -1452,7 +1472,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,
static int convert_to_working_tree_internal(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
- int normalizing, struct delayed_checkout *dco)
+ int normalizing,
+ const struct checkout_metadata *meta,
+ struct delayed_checkout *dco)
{
int ret = 0, ret_filter = 0;
struct conv_attrs ca;
@@ -1484,7 +1506,7 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
}
ret_filter = apply_filter(
- path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco);
+ path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
if (!ret_filter && ca.drv && ca.drv->required)
die(_("%s: smudge filter %s failed"), path, ca.drv->name);
@@ -1494,22 +1516,24 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
int async_convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta,
void *dco)
{
- return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
+ return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
}
int convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
- size_t len, struct strbuf *dst)
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta)
{
- return convert_to_working_tree_internal(istate, path, src, len, dst, 0, NULL);
+ return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
}
int renormalize_buffer(const struct index_state *istate, const char *path,
const char *src, size_t len, struct strbuf *dst)
{
- int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL);
+ int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
if (ret) {
src = dst->buf;
len = dst->len;
@@ -1940,7 +1964,7 @@ static struct stream_filter *ident_filter(const struct object_id *oid)
* the contents cannot be filtered without reading the whole thing
* in-core.
*
- * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * Note that you would be crazy to set CRLF, smudge/clean or ident to a
* large binary blob you would want us not to slurp into the memory!
*/
struct stream_filter *get_stream_filter(const struct index_state *istate,
@@ -1982,3 +2006,25 @@ int stream_filter(struct stream_filter *filter,
{
return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
}
+
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+ const struct object_id *treeish,
+ const struct object_id *blob)
+{
+ memset(meta, 0, sizeof(*meta));
+ if (refname)
+ meta->refname = refname;
+ if (treeish)
+ oidcpy(&meta->treeish, treeish);
+ if (blob)
+ oidcpy(&meta->blob, blob);
+}
+
+void clone_checkout_metadata(struct checkout_metadata *dst,
+ const struct checkout_metadata *src,
+ const struct object_id *blob)
+{
+ memcpy(dst, src, sizeof(*dst));
+ if (blob)
+ oidcpy(&dst->blob, blob);
+}
diff --git a/convert.h b/convert.h
index 3710969..e29d102 100644
--- a/convert.h
+++ b/convert.h
@@ -4,10 +4,10 @@
#ifndef CONVERT_H
#define CONVERT_H
+#include "hash.h"
#include "string-list.h"
struct index_state;
-struct object_id;
struct strbuf;
#define CONV_EOL_RNDTRP_DIE (1<<0) /* Die if CRLF to LF to CRLF is different */
@@ -57,6 +57,12 @@ struct delayed_checkout {
struct string_list paths;
};
+struct checkout_metadata {
+ const char *refname;
+ struct object_id treeish;
+ struct object_id blob;
+};
+
extern enum eol core_eol;
extern char *check_roundtrip_encoding;
const char *get_cached_convert_stats_ascii(const struct index_state *istate,
@@ -71,10 +77,12 @@ int convert_to_git(const struct index_state *istate,
struct strbuf *dst, int conv_flags);
int convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
- size_t len, struct strbuf *dst);
+ size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta);
int async_convert_to_working_tree(const struct index_state *istate,
const char *path, const char *src,
size_t len, struct strbuf *dst,
+ const struct checkout_metadata *meta,
void *dco);
int async_query_available_blobs(const char *cmd,
struct string_list *available_paths);
@@ -95,6 +103,23 @@ int would_convert_to_git_filter_fd(const struct index_state *istate,
const char *path);
/*
+ * Initialize the checkout metadata with the given values. Any argument may be
+ * NULL if it is not applicable. The treeish should be a commit if that is
+ * available, and a tree otherwise.
+ *
+ * The refname is not copied and must be valid for the lifetime of the struct.
+ * THe object IDs are copied.
+ */
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+ const struct object_id *treeish,
+ const struct object_id *blob);
+
+/* Copy the metadata from src to dst, updating the blob. */
+void clone_checkout_metadata(struct checkout_metadata *dst,
+ const struct checkout_metadata *src,
+ const struct object_id *blob);
+
+/*
* Reset the internal list of attributes used by convert_to_git and
* convert_to_working_tree.
*/
diff --git a/credential.c b/credential.c
index 62be651..108d9e1 100644
--- a/credential.c
+++ b/credential.c
@@ -6,6 +6,7 @@
#include "url.h"
#include "prompt.h"
#include "sigchain.h"
+#include "urlmatch.h"
void credential_init(struct credential *c)
{
@@ -40,7 +41,7 @@ static int credential_config_callback(const char *var, const char *value,
void *data)
{
struct credential *c = data;
- const char *key, *dot;
+ const char *key;
if (!skip_prefix(var, "credential.", &key))
return 0;
@@ -48,31 +49,16 @@ static int credential_config_callback(const char *var, const char *value,
if (!value)
return config_error_nonbool(var);
- dot = strrchr(key, '.');
- if (dot) {
- struct credential want = CREDENTIAL_INIT;
- char *url = xmemdupz(key, dot - key);
- int matched;
-
- credential_from_url(&want, url);
- matched = credential_match(&want, c);
-
- credential_clear(&want);
- free(url);
-
- if (!matched)
- return 0;
- key = dot + 1;
- }
-
if (!strcmp(key, "helper")) {
if (*value)
string_list_append(&c->helpers, value);
else
string_list_clear(&c->helpers, 0);
} else if (!strcmp(key, "username")) {
- if (!c->username)
+ if (!c->username_from_proto) {
+ free(c->username);
c->username = xstrdup(value);
+ }
}
else if (!strcmp(key, "usehttppath"))
c->use_http_path = git_config_bool(var, value);
@@ -87,11 +73,43 @@ static int proto_is_http(const char *s)
return !strcmp(s, "https") || !strcmp(s, "http");
}
+static void credential_describe(struct credential *c, struct strbuf *out);
+static void credential_format(struct credential *c, struct strbuf *out);
+
+static int select_all(const struct urlmatch_item *a,
+ const struct urlmatch_item *b)
+{
+ return 0;
+}
+
static void credential_apply_config(struct credential *c)
{
+ char *normalized_url;
+ struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+ struct strbuf url = STRBUF_INIT;
+
+ if (!c->host)
+ die(_("refusing to work with credential missing host field"));
+ if (!c->protocol)
+ die(_("refusing to work with credential missing protocol field"));
+
if (c->configured)
return;
- git_config(credential_config_callback, c);
+
+ config.section = "credential";
+ config.key = NULL;
+ config.collect_fn = credential_config_callback;
+ config.cascade_fn = NULL;
+ config.select_fn = select_all;
+ config.cb = c;
+
+ credential_format(c, &url);
+ normalized_url = url_normalize(url.buf, &config.url);
+
+ git_config(urlmatch_config_entry, &config);
+ free(normalized_url);
+ strbuf_release(&url);
+
c->configured = 1;
if (!c->use_http_path && proto_is_http(c->protocol)) {
@@ -112,6 +130,23 @@ static void credential_describe(struct credential *c, struct strbuf *out)
strbuf_addf(out, "/%s", c->path);
}
+static void credential_format(struct credential *c, struct strbuf *out)
+{
+ if (!c->protocol)
+ return;
+ strbuf_addf(out, "%s://", c->protocol);
+ if (c->username && *c->username) {
+ strbuf_add_percentencode(out, c->username);
+ strbuf_addch(out, '@');
+ }
+ if (c->host)
+ strbuf_addstr(out, c->host);
+ if (c->path) {
+ strbuf_addch(out, '/');
+ strbuf_add_percentencode(out, c->path);
+ }
+}
+
static char *credential_ask_one(const char *what, struct credential *c,
int flags)
{
@@ -163,6 +198,7 @@ int credential_read(struct credential *c, FILE *fp)
if (!strcmp(key, "username")) {
free(c->username);
c->username = xstrdup(value);
+ c->username_from_proto = 1;
} else if (!strcmp(key, "password")) {
free(c->password);
c->password = xstrdup(value);
@@ -191,20 +227,25 @@ int credential_read(struct credential *c, FILE *fp)
return 0;
}
-static void credential_write_item(FILE *fp, const char *key, const char *value)
+static void credential_write_item(FILE *fp, const char *key, const char *value,
+ int required)
{
+ if (!value && required)
+ BUG("credential value for %s is missing", key);
if (!value)
return;
+ if (strchr(value, '\n'))
+ die("credential value for %s contains newline", key);
fprintf(fp, "%s=%s\n", key, value);
}
void credential_write(const struct credential *c, FILE *fp)
{
- credential_write_item(fp, "protocol", c->protocol);
- credential_write_item(fp, "host", c->host);
- credential_write_item(fp, "path", c->path);
- credential_write_item(fp, "username", c->username);
- credential_write_item(fp, "password", c->password);
+ credential_write_item(fp, "protocol", c->protocol, 1);
+ credential_write_item(fp, "host", c->host, 1);
+ credential_write_item(fp, "path", c->path, 0);
+ credential_write_item(fp, "username", c->username, 0);
+ credential_write_item(fp, "password", c->password, 0);
}
static int run_credential_helper(struct credential *c,
@@ -322,7 +363,22 @@ void credential_reject(struct credential *c)
c->approved = 0;
}
-void credential_from_url(struct credential *c, const char *url)
+static int check_url_component(const char *url, int quiet,
+ const char *name, const char *value)
+{
+ if (!value)
+ return 0;
+ if (!strchr(value, '\n'))
+ return 0;
+
+ if (!quiet)
+ warning(_("url contains a newline in its %s component: %s"),
+ name, url);
+ return -1;
+}
+
+int credential_from_url_gently(struct credential *c, const char *url,
+ int quiet)
{
const char *at, *colon, *cp, *slash, *host, *proto_end;
@@ -335,8 +391,11 @@ void credential_from_url(struct credential *c, const char *url)
* (3) proto://<user>:<pass>@<host>/...
*/
proto_end = strstr(url, "://");
- if (!proto_end)
- return;
+ if (!proto_end || proto_end == url) {
+ if (!quiet)
+ warning(_("url has no scheme: %s"), url);
+ return -1;
+ }
cp = proto_end + 3;
at = strchr(cp, '@');
colon = strchr(cp, ':');
@@ -349,18 +408,20 @@ void credential_from_url(struct credential *c, const char *url)
else if (!colon || at <= colon) {
/* Case (2) */
c->username = url_decode_mem(cp, at - cp);
+ if (c->username && *c->username)
+ c->username_from_proto = 1;
host = at + 1;
} else {
/* Case (3) */
c->username = url_decode_mem(cp, colon - cp);
+ if (c->username && *c->username)
+ c->username_from_proto = 1;
c->password = url_decode_mem(colon + 1, at - (colon + 1));
host = at + 1;
}
- if (proto_end - url > 0)
- c->protocol = xmemdupz(url, proto_end - url);
- if (slash - host > 0)
- c->host = url_decode_mem(host, slash - host);
+ c->protocol = xmemdupz(url, proto_end - url);
+ c->host = url_decode_mem(host, slash - host);
/* Trim leading and trailing slashes from path */
while (*slash == '/')
slash++;
@@ -371,4 +432,19 @@ void credential_from_url(struct credential *c, const char *url)
while (p > c->path && *p == '/')
*p-- = '\0';
}
+
+ if (check_url_component(url, quiet, "username", c->username) < 0 ||
+ check_url_component(url, quiet, "password", c->password) < 0 ||
+ check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
+ check_url_component(url, quiet, "host", c->host) < 0 ||
+ check_url_component(url, quiet, "path", c->path) < 0)
+ return -1;
+
+ return 0;
+}
+
+void credential_from_url(struct credential *c, const char *url)
+{
+ if (credential_from_url_gently(c, url, 0) < 0)
+ die(_("credential url cannot be parsed: %s"), url);
}
diff --git a/credential.h b/credential.h
index 5772d50..d99ec42 100644
--- a/credential.h
+++ b/credential.h
@@ -90,96 +90,6 @@
* return status;
* }
* -----------------------------------------------------------------------
- *
- * Credential Helpers
- * ------------------
- *
- * Credential helpers are programs executed by Git to fetch or save
- * credentials from and to long-term storage (where "long-term" is simply
- * longer than a single Git process; e.g., credentials may be stored
- * in-memory for a few minutes, or indefinitely on disk).
- *
- * Each helper is specified by a single string in the configuration
- * variable `credential.helper` (and others, see Documentation/git-config.txt).
- * The string is transformed by Git into a command to be executed using
- * these rules:
- *
- * 1. If the helper string begins with "!", it is considered a shell
- * snippet, and everything after the "!" becomes the command.
- *
- * 2. Otherwise, if the helper string begins with an absolute path, the
- * verbatim helper string becomes the command.
- *
- * 3. Otherwise, the string "git credential-" is prepended to the helper
- * string, and the result becomes the command.
- *
- * The resulting command then has an "operation" argument appended to it
- * (see below for details), and the result is executed by the shell.
- *
- * Here are some example specifications:
- *
- * ----------------------------------------------------
- * # run "git credential-foo"
- * foo
- *
- * # same as above, but pass an argument to the helper
- * foo --bar=baz
- *
- * # the arguments are parsed by the shell, so use shell
- * # quoting if necessary
- * foo --bar="whitespace arg"
- *
- * # you can also use an absolute path, which will not use the git wrapper
- * /path/to/my/helper --with-arguments
- *
- * # or you can specify your own shell snippet
- * !f() { echo "password=`cat $HOME/.secret`"; }; f
- * ----------------------------------------------------
- *
- * Generally speaking, rule (3) above is the simplest for users to specify.
- * Authors of credential helpers should make an effort to assist their
- * users by naming their program "git-credential-$NAME", and putting it in
- * the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
- * to enable it with `git config credential.helper $NAME`.
- *
- * When a helper is executed, it will have one "operation" argument
- * appended to its command line, which is one of:
- *
- * `get`::
- *
- * Return a matching credential, if any exists.
- *
- * `store`::
- *
- * Store the credential, if applicable to the helper.
- *
- * `erase`::
- *
- * Remove a matching credential, if any, from the helper's storage.
- *
- * The details of the credential will be provided on the helper's stdin
- * stream. The exact format is the same as the input/output format of the
- * `git credential` plumbing command (see the section `INPUT/OUTPUT
- * FORMAT` in Documentation/git-credential.txt for a detailed specification).
- *
- * For a `get` operation, the helper should produce a list of attributes
- * on stdout in the same format. 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).
- *
- * 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 receives any other operation, it should silently ignore the
- * request. This leaves room for future operations to be added (older
- * helpers will just ignore the new requests).
- *
*/
@@ -208,7 +118,8 @@ struct credential {
unsigned approved:1,
configured:1,
quit:1,
- use_http_path:1;
+ use_http_path:1,
+ username_from_proto:1;
char *username;
char *password;
@@ -262,8 +173,21 @@ void credential_reject(struct credential *);
int credential_read(struct credential *, FILE *);
void credential_write(const struct credential *, FILE *);
-/* Parse a URL into broken-down credential fields. */
+/*
+ * 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.
+ *
+ * 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
+ * an error but leave the broken state in the credential object for further
+ * examination. The non-gentle form will issue a warning to stderr and return
+ * an empty credential.
+ */
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);
diff --git a/csum-file.c b/csum-file.c
index 53ce37f..0f35fa5 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -157,7 +157,7 @@ void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpo
{
hashflush(f);
checkpoint->offset = f->total;
- checkpoint->ctx = f->ctx;
+ the_hash_algo->clone_fn(&checkpoint->ctx, &f->ctx);
}
int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
diff --git a/csum-file.h b/csum-file.h
index a98b1ee..f9cbd31 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -42,6 +42,15 @@ void hashflush(struct hashfile *f);
void crc32_begin(struct hashfile *);
uint32_t crc32_end(struct hashfile *);
+/*
+ * Returns the total number of bytes fed to the hashfile so far (including ones
+ * that have not been written out to the descriptor yet).
+ */
+static inline off_t hashfile_total(struct hashfile *f)
+{
+ return f->total + f->offset;
+}
+
static inline void hashwrite_u8(struct hashfile *f, uint8_t data)
{
hashwrite(f, &data, sizeof(data));
diff --git a/diff.c b/diff.c
index 8e2914c..1010d80 100644
--- a/diff.c
+++ b/diff.c
@@ -414,14 +414,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
return 0;
}
- if (!strcmp(var, "diff.wserrorhighlight")) {
- int val = parse_ws_error_highlight(value);
- if (val < 0)
- return -1;
- ws_error_highlight_default = val;
- return 0;
- }
-
if (git_color_config(var, value, cb) < 0)
return -1;
@@ -450,6 +442,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return color_parse(value, diff_colors[slot]);
}
+ if (!strcmp(var, "diff.wserrorhighlight")) {
+ int val = parse_ws_error_highlight(value);
+ if (val < 0)
+ return -1;
+ ws_error_highlight_default = val;
+ return 0;
+ }
+
/* like GNU diff's --suppress-blank-empty option */
if (!strcmp(var, "diff.suppressblankempty") ||
/* for backwards compatibility */
@@ -4024,7 +4024,7 @@ int diff_populate_filespec(struct repository *r,
return 0;
}
}
- s->data = read_object_file(&s->oid, &type, &s->size);
+ s->data = repo_read_object_file(r, &s->oid, &type, &s->size);
if (!s->data)
die("unable to read %s", oid_to_hex(&s->oid));
s->should_free = 1;
@@ -4062,6 +4062,9 @@ static void prep_temp_blob(struct index_state *istate,
struct strbuf tempfile = STRBUF_INIT;
char *path_dup = xstrdup(path);
const char *base = basename(path_dup);
+ struct checkout_metadata meta;
+
+ init_checkout_metadata(&meta, NULL, NULL, oid);
/* Generate "XXXXXX_basename.ext" */
strbuf_addstr(&tempfile, "XXXXXX_");
@@ -4071,7 +4074,7 @@ static void prep_temp_blob(struct index_state *istate,
if (!temp->tempfile)
die_errno("unable to create temp-file");
if (convert_to_working_tree(istate, path,
- (const char *)blob, (size_t)size, &buf)) {
+ (const char *)blob, (size_t)size, &buf, &meta)) {
blob = buf.buf;
size = buf.len;
}
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 531d7ad..e189f40 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -263,8 +263,8 @@ static unsigned int hash_filespec(struct repository *r,
if (!filespec->oid_valid) {
if (diff_populate_filespec(r, filespec, 0))
return 0;
- hash_object_file(filespec->data, filespec->size, "blob",
- &filespec->oid);
+ hash_object_file(r->hash_algo, filespec->data, filespec->size,
+ "blob", &filespec->oid);
}
return oidhash(&filespec->oid);
}
diff --git a/dir.c b/dir.c
index 7d25522..0ffb1b3 100644
--- a/dir.c
+++ b/dir.c
@@ -41,7 +41,8 @@ struct cached_dir {
int nr_files;
int nr_dirs;
- struct dirent *de;
+ const char *d_name;
+ int d_type;
const char *file;
struct untracked_cache_dir *ucd;
};
@@ -50,8 +51,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct index_state *istate, const char *path, int len,
struct untracked_cache_dir *untracked,
int check_only, int stop_at_first_file, const struct pathspec *pathspec);
-static int get_dtype(struct dirent *de, struct index_state *istate,
- const char *path, int len);
+static int resolve_dtype(int dtype, struct index_state *istate,
+ const char *path, int len);
int count_slashes(const char *s)
{
@@ -635,11 +636,42 @@ int pl_hashmap_cmp(const void *unused_cmp_data,
return strncmp(ee1->pattern, ee2->pattern, min_len);
}
+static char *dup_and_filter_pattern(const char *pattern)
+{
+ char *set, *read;
+ size_t count = 0;
+ char *result = xstrdup(pattern);
+
+ set = result;
+ read = result;
+
+ while (*read) {
+ /* skip escape characters (once) */
+ if (*read == '\\')
+ read++;
+
+ *set = *read;
+
+ set++;
+ read++;
+ count++;
+ }
+ *set = 0;
+
+ if (count > 2 &&
+ *(set - 1) == '*' &&
+ *(set - 2) == '/')
+ *(set - 2) = 0;
+
+ return result;
+}
+
static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
{
struct pattern_entry *translated;
char *truncated;
char *data = NULL;
+ const char *prev, *cur, *next;
if (!pl->use_cone_patterns)
return;
@@ -656,17 +688,57 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
return;
}
+ if (given->patternlen < 2 ||
+ *given->pattern == '*' ||
+ strstr(given->pattern, "**")) {
+ /* Not a cone pattern. */
+ warning(_("unrecognized pattern: '%s'"), given->pattern);
+ goto clear_hashmaps;
+ }
+
+ prev = given->pattern;
+ cur = given->pattern + 1;
+ next = given->pattern + 2;
+
+ while (*cur) {
+ /* Watch for glob characters '*', '\', '[', '?' */
+ if (!is_glob_special(*cur))
+ goto increment;
+
+ /* But only if *prev != '\\' */
+ if (*prev == '\\')
+ goto increment;
+
+ /* But allow the initial '\' */
+ if (*cur == '\\' &&
+ is_glob_special(*next))
+ goto increment;
+
+ /* But a trailing '/' then '*' is fine */
+ if (*prev == '/' &&
+ *cur == '*' &&
+ *next == 0)
+ goto increment;
+
+ /* Not a cone pattern. */
+ warning(_("unrecognized pattern: '%s'"), given->pattern);
+ goto clear_hashmaps;
+
+ increment:
+ prev++;
+ cur++;
+ next++;
+ }
+
if (given->patternlen > 2 &&
!strcmp(given->pattern + given->patternlen - 2, "/*")) {
if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
/* Not a cone pattern. */
- pl->use_cone_patterns = 0;
warning(_("unrecognized pattern: '%s'"), given->pattern);
goto clear_hashmaps;
}
- truncated = xstrdup(given->pattern);
- truncated[given->patternlen - 2] = 0;
+ truncated = dup_and_filter_pattern(given->pattern);
translated = xmalloc(sizeof(struct pattern_entry));
translated->pattern = truncated;
@@ -700,7 +772,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
translated = xmalloc(sizeof(struct pattern_entry));
- translated->pattern = xstrdup(given->pattern);
+ translated->pattern = dup_and_filter_pattern(given->pattern);
translated->patternlen = given->patternlen;
hashmap_entry_init(&translated->ent,
ignore_case ?
@@ -1002,8 +1074,8 @@ static int add_patterns(const char *fname, const char *base, int baselen,
oidcpy(&oid_stat->oid,
&istate->cache[pos]->oid);
else
- hash_object_file(buf, size, "blob",
- &oid_stat->oid);
+ hash_object_file(the_hash_algo, buf, size,
+ "blob", &