summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml9
-rw-r--r--.github/workflows/check-whitespace.yml26
-rw-r--r--.github/workflows/main.yml177
-rw-r--r--Documentation/CodingGuidelines45
-rw-r--r--Documentation/Makefile77
-rw-r--r--Documentation/MyFirstContribution.txt4
-rw-r--r--Documentation/MyFirstObjectWalk.txt4
-rw-r--r--Documentation/RelNotes/1.6.0.3.txt2
-rw-r--r--Documentation/RelNotes/1.8.4.txt2
-rw-r--r--Documentation/RelNotes/2.29.0.txt4
-rw-r--r--Documentation/RelNotes/2.33.0.txt279
-rw-r--r--Documentation/RelNotes/2.33.1.txt138
-rw-r--r--Documentation/RelNotes/2.33.2.txt15
-rw-r--r--Documentation/RelNotes/2.33.3.txt4
-rw-r--r--Documentation/RelNotes/2.8.0.txt2
-rw-r--r--Documentation/SubmittingPatches208
-rw-r--r--Documentation/config/blame.txt2
-rw-r--r--Documentation/config/color.txt5
-rw-r--r--Documentation/config/diff.txt7
-rw-r--r--Documentation/config/fetch.txt3
-rw-r--r--Documentation/config/gui.txt2
-rw-r--r--Documentation/config/merge.txt12
-rw-r--r--Documentation/config/pack.txt23
-rw-r--r--Documentation/config/push.txt13
-rw-r--r--Documentation/config/sendemail.txt3
-rw-r--r--Documentation/config/submodule.txt5
-rw-r--r--Documentation/config/transfer.txt14
-rw-r--r--Documentation/diff-options.txt16
-rw-r--r--Documentation/fetch-options.txt13
-rw-r--r--Documentation/git-am.txt2
-rw-r--r--Documentation/git-branch.txt3
-rw-r--r--Documentation/git-bugreport.txt4
-rw-r--r--Documentation/git-bundle.txt147
-rw-r--r--Documentation/git-column.txt2
-rw-r--r--Documentation/git-commit.txt2
-rw-r--r--Documentation/git-config.txt31
-rw-r--r--Documentation/git-cvsserver.txt27
-rw-r--r--Documentation/git-describe.txt14
-rw-r--r--Documentation/git-diff.txt10
-rw-r--r--Documentation/git-fetch.txt2
-rw-r--r--Documentation/git-log.txt4
-rw-r--r--Documentation/git-merge.txt2
-rw-r--r--Documentation/git-pack-objects.txt6
-rw-r--r--Documentation/git-pull.txt23
-rw-r--r--Documentation/git-push.txt4
-rw-r--r--Documentation/git-rebase.txt27
-rw-r--r--Documentation/git-repack.txt4
-rw-r--r--Documentation/git-send-email.txt25
-rw-r--r--Documentation/git-version.txt28
-rw-r--r--Documentation/git-worktree.txt6
-rw-r--r--Documentation/git.txt4
-rw-r--r--Documentation/gitignore.txt11
-rw-r--r--Documentation/gittutorial.txt2
-rw-r--r--Documentation/glossary-content.txt4
-rw-r--r--Documentation/merge-options.txt47
-rw-r--r--Documentation/merge-strategies.txt48
-rw-r--r--Documentation/pretty-options.txt6
-rw-r--r--Documentation/rev-list-options.txt10
-rw-r--r--Documentation/revisions.txt23
-rw-r--r--Documentation/technical/api-trace2.txt4
-rw-r--r--Documentation/technical/directory-rename-detection.txt14
-rw-r--r--Documentation/technical/hash-function-transition.txt2
-rw-r--r--Documentation/technical/packfile-uri.txt15
-rw-r--r--Documentation/technical/partial-clone.txt6
-rw-r--r--Documentation/technical/protocol-v2.txt2
-rw-r--r--Documentation/technical/remembering-renames.txt671
-rw-r--r--Documentation/user-manual.txt2
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile46
l---------RelNotes2
-rw-r--r--add-patch.c1
-rw-r--r--advice.c5
-rw-r--r--advice.h2
-rw-r--r--apply.c28
-rw-r--r--archive.c9
-rw-r--r--attr.c2
-rw-r--r--builtin/add.c7
-rw-r--r--builtin/am.c4
-rw-r--r--builtin/archive.c4
-rw-r--r--builtin/bisect--helper.c2
-rw-r--r--builtin/branch.c2
-rw-r--r--builtin/bugreport.c5
-rw-r--r--builtin/bundle.c74
-rw-r--r--builtin/cat-file.c10
-rw-r--r--builtin/check-ignore.c3
-rw-r--r--builtin/checkout--worker.c2
-rw-r--r--builtin/checkout.c10
-rw-r--r--builtin/clean.c6
-rw-r--r--builtin/clone.c44
-rw-r--r--builtin/column.c2
-rw-r--r--builtin/commit-tree.c4
-rw-r--r--builtin/commit.c40
-rw-r--r--builtin/diff-index.c9
-rw-r--r--builtin/diff.c4
-rw-r--r--builtin/difftool.c7
-rw-r--r--builtin/fast-export.c1
-rw-r--r--builtin/fetch.c9
-rw-r--r--builtin/for-each-repo.c14
-rw-r--r--builtin/gc.c93
-rw-r--r--builtin/grep.c3
-rw-r--r--builtin/hash-object.c6
-rw-r--r--builtin/help.c17
-rw-r--r--builtin/index-pack.c60
-rw-r--r--builtin/log.c6
-rw-r--r--builtin/ls-files.c3
-rw-r--r--builtin/ls-remote.c2
-rw-r--r--builtin/mailsplit.c4
-rw-r--r--builtin/merge-ours.c4
-rw-r--r--builtin/merge-tree.c5
-rw-r--r--builtin/merge.c27
-rw-r--r--builtin/mktree.c2
-rw-r--r--builtin/multi-pack-index.c2
-rw-r--r--builtin/mv.c5
-rw-r--r--builtin/notes.c4
-rw-r--r--builtin/pack-objects.c38
-rw-r--r--builtin/pull.c81
-rw-r--r--builtin/push.c79
-rw-r--r--builtin/rebase.c5
-rw-r--r--builtin/receive-pack.c5
-rw-r--r--builtin/repack.c2
-rw-r--r--builtin/rerere.c4
-rw-r--r--builtin/rev-list.c33
-rw-r--r--builtin/rev-parse.c30
-rw-r--r--builtin/send-pack.c1
-rw-r--r--builtin/show-branch.c24
-rw-r--r--builtin/sparse-checkout.c10
-rw-r--r--builtin/stash.c29
-rw-r--r--builtin/submodule--helper.c426
-rw-r--r--builtin/tag.c4
-rw-r--r--builtin/update-index.c4
-rw-r--r--builtin/update-ref.c14
-rw-r--r--builtin/worktree.c21
-rw-r--r--bulk-checkin.c34
-rw-r--r--bundle.c64
-rw-r--r--bundle.h21
-rw-r--r--cache-tree.c13
-rw-r--r--cache.h11
-rw-r--r--cbtree.c167
-rw-r--r--cbtree.h56
-rwxr-xr-xcheck_bindir13
-rw-r--r--chunk-format.c12
-rwxr-xr-xci/install-dependencies.sh5
-rwxr-xr-xci/install-docker-dependencies.sh4
-rwxr-xr-xci/lib.sh1
-rwxr-xr-xci/run-build-and-tests.sh10
-rw-r--r--combine-diff.c5
-rw-r--r--commit-graph.c15
-rw-r--r--commit.c2
-rw-r--r--compat/mingw.c21
-rw-r--r--compat/mmap.c7
-rw-r--r--config.c17
-rw-r--r--config.h4
-rw-r--r--config.mak.uname12
-rw-r--r--connect.c4
-rw-r--r--contrib/buildsystems/CMakeLists.txt48
-rw-r--r--contrib/coccinelle/xopen.cocci16
-rw-r--r--contrib/completion/git-completion.bash16
-rw-r--r--contrib/completion/git-completion.tcsh5
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c1
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c1
-rw-r--r--contrib/hooks/multimail/CHANGES285
-rw-r--r--contrib/hooks/multimail/CONTRIBUTING.rst60
-rw-r--r--contrib/hooks/multimail/README.Git12
-rw-r--r--contrib/hooks/multimail/README.migrate-from-post-receive-email145
-rw-r--r--contrib/hooks/multimail/README.rst774
-rw-r--r--contrib/hooks/multimail/doc/customizing-emails.rst56
-rw-r--r--contrib/hooks/multimail/doc/gerrit.rst56
-rw-r--r--contrib/hooks/multimail/doc/gitolite.rst118
-rw-r--r--contrib/hooks/multimail/doc/troubleshooting.rst78
-rwxr-xr-xcontrib/hooks/multimail/git_multimail.py4346
-rwxr-xr-xcontrib/hooks/multimail/migrate-mailhook-config274
-rwxr-xr-xcontrib/hooks/multimail/post-receive.example101
-rwxr-xr-xcontrib/mw-to-git/t/t9360-mw-to-git-clone.sh2
-rwxr-xr-xcontrib/mw-to-git/t/t9362-mw-to-git-utf8.sh4
-rwxr-xr-xcontrib/subtree/git-subtree.sh12
-rw-r--r--convert.c2
-rw-r--r--credential.c5
-rw-r--r--credential.h4
-rw-r--r--csum-file.c118
-rw-r--r--csum-file.h7
-rw-r--r--date.c2
-rwxr-xr-xdetect-compiler9
-rw-r--r--diff-lib.c23
-rw-r--r--diff-merges.c31
-rw-r--r--diff-merges.h2
-rw-r--r--diff.c43
-rw-r--r--diff.h7
-rw-r--r--diffcore-pickaxe.c106
-rw-r--r--diffcore-rename.c187
-rw-r--r--diffcore.h3
-rw-r--r--dir.c56
-rw-r--r--dir.h6
-rw-r--r--entry.c11
-rw-r--r--entry.h3
-rw-r--r--environment.c8
-rw-r--r--fetch-pack.c9
-rw-r--r--fmt-merge-msg.c6
-rw-r--r--gettext.c1
-rw-r--r--git-compat-util.h9
-rwxr-xr-xgit-cvsserver.perl7
-rwxr-xr-xgit-p4.py7
-rwxr-xr-xgit-send-email.perl234
-rwxr-xr-xgit-submodule.sh90
-rw-r--r--graph.c2
-rw-r--r--grep.c2
-rw-r--r--hash.h18
-rw-r--r--http.c6
-rw-r--r--imap-send.c19
-rw-r--r--json-writer.c6
-rw-r--r--json-writer.h5
-rw-r--r--khash.h14
-rw-r--r--list-objects-filter-options.c2
-rw-r--r--list-objects.c3
-rw-r--r--ll-merge.c10
-rw-r--r--log-tree.c24
-rw-r--r--mailinfo.c6
-rw-r--r--mailmap.c3
-rw-r--r--merge-ort.c871
-rw-r--r--merge-ort.h2
-rw-r--r--merge-recursive.c39
-rw-r--r--merge.c3
-rw-r--r--mergetools/kdiff39
-rw-r--r--midx.c14
-rw-r--r--object-file.c98
-rw-r--r--object-name.c28
-rw-r--r--object-store.h14
-rw-r--r--object.c20
-rw-r--r--object.h20
-rw-r--r--oidtree.c109
-rw-r--r--oidtree.h22
-rw-r--r--pack-bitmap.c34
-rw-r--r--pack-check.c11
-rw-r--r--pack-revindex.h4
-rw-r--r--pack-write.c68
-rw-r--r--pack.h10
-rw-r--r--packfile.c4
-rw-r--r--pager.c16
-rw-r--r--parallel-checkout.c13
-rw-r--r--parse-options.c2
-rw-r--r--pathspec.h2
-rw-r--r--perl/Git.pm32
-rw-r--r--perl/Git/SVN.pm2
-rw-r--r--pkt-line.c2
-rw-r--r--po/README.md395
-rw-r--r--po/TEAMS6
-rw-r--r--po/bg.po4028
-rw-r--r--po/ca.po926
-rw-r--r--po/de.po4576
-rw-r--r--po/es.po7824
-rw-r--r--po/fr.po7871
-rw-r--r--po/git.pot4453
-rw-r--r--po/id.po5232
-rw-r--r--po/it.po6
-rw-r--r--po/ko.po8
-rw-r--r--po/pt_PT.po14282
-rw-r--r--po/sv.po4592
-rw-r--r--po/tr.po4602
-rw-r--r--po/vi.po4576
-rw-r--r--po/zh_CN.po4587
-rw-r--r--po/zh_TW.po5105
-rw-r--r--pretty.c4
-rw-r--r--pretty.h8
-rw-r--r--promisor-remote.c110
-rw-r--r--promisor-remote.h28
-rw-r--r--protocol-caps.c5
-rw-r--r--protocol-caps.h2
-rw-r--r--protocol.c2
-rw-r--r--quote.h1
-rw-r--r--range-diff.c35
-rw-r--r--reachable.c18
-rw-r--r--read-cache.c237
-rw-r--r--ref-filter.c223
-rw-r--r--refs.c2
-rw-r--r--refs/debug.c3
-rw-r--r--refs/files-backend.c2
-rw-r--r--refs/packed-backend.c4
-rw-r--r--refs/ref-cache.c2
-rw-r--r--refs/refs-internal.h3
-rw-r--r--remote-curl.c2
-rw-r--r--remote.c8
-rw-r--r--repository.c10
-rw-r--r--repository.h5
-rw-r--r--reset.c4
-rw-r--r--revision.c5
-rw-r--r--revision.h6
-rw-r--r--run-command.c23
-rw-r--r--run-command.h15
-rw-r--r--send-pack.c17
-rw-r--r--sequencer.c156
-rw-r--r--serve.c2
-rw-r--r--server-info.c1
-rw-r--r--setup.c33
-rw-r--r--sh-i18n--envsubst.c6
-rw-r--r--shell.c2
-rw-r--r--sideband.c23
-rw-r--r--sparse-index.c30
-rw-r--r--split-index.c3
-rw-r--r--strbuf.c4
-rw-r--r--strbuf.h5
-rw-r--r--string-list.c18
-rw-r--r--string-list.h15
-rw-r--r--strmap.c3
-rw-r--r--strvec.c5
-rw-r--r--strvec.h4
-rw-r--r--submodule.c17
-rw-r--r--t/README6
-rw-r--r--t/helper/test-advise.c2
-rw-r--r--t/helper/test-fast-rebase.c54
-rw-r--r--t/helper/test-getcwd.c26
-rw-r--r--t/helper/test-hash-speed.c2
-rw-r--r--t/helper/test-hash.c2
-rw-r--r--t/helper/test-match-trees.c2
-rw-r--r--t/helper/test-oidtree.c49
-rw-r--r--t/helper/test-partial-clone.c43
-rw-r--r--t/helper/test-pkt-line.c12
-rw-r--r--t/helper/test-reach.c2
-rw-r--r--t/helper/test-ref-store.c2
-rw-r--r--t/helper/test-tool.c3
-rw-r--r--t/helper/test-tool.h3
-rw-r--r--t/lib-git-svn.sh22
-rw-r--r--t/lib-rebase.sh56
-rw-r--r--t/lib-submodule-update.sh3
-rwxr-xr-xt/perf/aggregate.perl5
-rwxr-xr-xt/perf/p2000-sparse-operations.sh47
-rwxr-xr-xt/perf/p4209-pickaxe.sh70
-rw-r--r--t/perf/perf-lib.sh7
-rwxr-xr-xt/perf/run25
-rwxr-xr-xt/t0000-basic.sh73
-rwxr-xr-xt/t0001-init.sh5
-rwxr-xr-xt/t0021-conversion.sh71
-rwxr-xr-xt/t0069-oidtree.sh49
-rwxr-xr-xt/t0410-partial-clone.sh23
-rwxr-xr-xt/t1006-cat-file.sh22
-rwxr-xr-xt/t1022-read-tree-partial-clone.sh33
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh357
-rwxr-xr-xt/t1301-shared-repo.sh2
-rwxr-xr-xt/t1307-config-blob.sh4
-rwxr-xr-xt/t1350-config-hooks-path.sh1
-rwxr-xr-xt/t1400-update-ref.sh34
-rwxr-xr-xt/t1401-symbolic-ref.sh25
-rwxr-xr-xt/t1403-show-ref.sh6
-rwxr-xr-xt/t1404-update-ref-errors.sh30
-rwxr-xr-xt/t1407-worktree-ref-store.sh9
-rwxr-xr-xt/t1413-reflog-detach.sh5
-rwxr-xr-xt/t1414-reflog-walk.sh4
-rwxr-xr-xt/t1415-worktree-refs.sh15
-rwxr-xr-xt/t2017-checkout-orphan.sh2
-rwxr-xr-xt/t2030-unresolve-info.sh3
-rwxr-xr-xt/t2400-worktree-add.sh16
-rwxr-xr-xt/t3200-branch.sh13
-rwxr-xr-xt/t3202-show-branch-octopus.sh70
-rwxr-xr-xt/t3202-show-branch.sh149
-rwxr-xr-xt/t3210-pack-refs.sh2
-rwxr-xr-xt/t3400-rebase.sh10
-rwxr-xr-xt/t3403-rebase-skip.sh13
-rwxr-xr-xt/t3404-rebase-interactive.sh13
-rwxr-xr-xt/t3418-rebase-continue.sh18
-rwxr-xr-xt/t3430-rebase-merges.sh38
-rwxr-xr-xt/t3436-rebase-more-options.sh29
-rwxr-xr-xt/t3513-revert-submodule.sh4
-rwxr-xr-xt/t3800-mktag.sh121
-rwxr-xr-xt/t3903-stash.sh60
-rwxr-xr-xt/t3920-crlf-messages.sh2
-rwxr-xr-xt/t4006-diff-mode.sh6
-rwxr-xr-xt/t4013-diff-various.sh26
-rw-r--r--t/t4013/diff.diff-tree_-m_master11
-rw-r--r--t/t4013/diff.log_-m_--raw_master61
-rw-r--r--t/t4013/diff.log_-m_--stat_master66
-rwxr-xr-xt/t4030-diff-textconv.sh8
-rwxr-xr-xt/t4045-diff-relative.sh53
-rwxr-xr-xt/t4103-apply-binary.sh23
-rwxr-xr-xt/t4108-apply-threeway.sh45
-rwxr-xr-xt/t4151-am-abort.sh39
-rwxr-xr-xt/t4202-log.sh32
-rwxr-xr-xt/t4203-mailmap.sh2
-rwxr-xr-xt/t4205-log-pretty-formats.sh2
-rwxr-xr-xt/t4209-log-pickaxe.sh114
-rwxr-xr-xt/t4258-am-quoted-cr.sh2
-rwxr-xr-xt/t5000-tar-tree.sh122
-rwxr-xr-xt/t5300-pack-object.sh104
-rwxr-xr-xt/t5304-prune.sh83
-rwxr-xr-xt/t5319-multi-pack-index.sh18
-rwxr-xr-xt/t5323-pack-redundant.sh4
-rwxr-xr-xt/t5406-remote-rejects.sh1
-rwxr-xr-xt/t5407-post-rewrite-hook.sh2
-rwxr-xr-xt/t5409-colorize-remote-messages.sh1
-rw-r--r--t/t5411/common-functions.sh54
-rw-r--r--t/t5411/once-0010-report-status-v1.sh4
-rw-r--r--t/t5411/test-0000-standard-git-push.sh82
-rw-r--r--t/t5411/test-0001-standard-git-push--porcelain.sh90
-rw-r--r--t/t5411/test-0003-pre-receive-declined--porcelain.sh8
-rw-r--r--t/t5411/test-0011-no-hook-error.sh40
-rw-r--r--t/t5411/test-0012-no-hook-error--porcelain.sh42
-rw-r--r--t/t5411/test-0013-bad-protocol.sh62
-rw-r--r--t/t5411/test-0014-bad-protocol--porcelain.sh80
-rw-r--r--t/t5411/test-0020-report-ng.sh32
-rw-r--r--t/t5411/test-0021-report-ng--porcelain.sh36
-rw-r--r--t/t5411/test-0022-report-unexpect-ref.sh26
-rw-r--r--t/t5411/test-0023-report-unexpect-ref--porcelain.sh28
-rw-r--r--t/t5411/test-0024-report-unknown-ref.sh18
-rw-r--r--t/t5411/test-0025-report-unknown-ref--porcelain.sh20
-rw-r--r--t/t5411/test-0026-push-options.sh58
-rw-r--r--t/t5411/test-0027-push-options--porcelain.sh62
-rw-r--r--t/t5411/test-0030-report-ok.sh20
-rw-r--r--t/t5411/test-0031-report-ok--porcelain.sh22
-rw-r--r--t/t5411/test-0032-report-with-options.sh186
-rw-r--r--t/t5411/test-0033-report-with-options--porcelain.sh200
-rw-r--r--t/t5411/test-0034-report-ft.sh22
-rw-r--r--t/t5411/test-0035-report-ft--porcelain.sh24
-rw-r--r--t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh132
-rw-r--r--t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh138
-rw-r--r--t/t5411/test-0038-report-mixed-refs.sh74
-rw-r--r--t/t5411/test-0039-report-mixed-refs--porcelain.sh76
-rw-r--r--t/t5411/test-0040-process-all-refs.sh80
-rw-r--r--t/t5411/test-0041-process-all-refs--porcelain.sh82
-rw-r--r--t/t5411/test-0050-proc-receive-refs-with-modifiers.sh90
-rwxr-xr-xt/t5505-remote.sh2
-rwxr-xr-xt/t5510-fetch.sh13
-rwxr-xr-xt/t5516-fetch-push.sh4
-rwxr-xr-xt/t5520-pull.sh30
-rwxr-xr-xt/t5521-pull-options.sh4
-rwxr-xr-xt/t5524-pull-msg.sh4
-rwxr-xr-xt/t5548-push-porcelain.sh97
-rwxr-xr-xt/t5549-fetch-push-http.sh72
-rwxr-xr-xt/t5551-http-fetch-smart.sh24
-rwxr-xr-xt/t5553-set-upstream.sh14
-rwxr-xr-xt/t5562-http-backend-content-length.sh2
-rw-r--r--t/t5562/invoke-with-content-length.pl16
-rwxr-xr-xt/t5570-git-daemon.sh2
-rwxr-xr-xt/t5582-fetch-negative-refspec.sh1
-rwxr-xr-xt/t5600-clone-fail-cleanup.sh7
-rwxr-xr-xt/t5601-clone.sh3
-rwxr-xr-xt/t5604-clone-reference.sh4
-rwxr-xr-xt/t5607-clone-bundle.sh72
-rwxr-xr-xt/t5702-protocol-v2.sh35
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh208
-rwxr-xr-xt/t5704-protocol-violations.sh15
-rwxr-xr-xt/t5705-session-id-in-capabilities.sh11
-rwxr-xr-xt/t6006-rev-list-format.sh80
-rwxr-xr-xt/t6020-bundle-misc.sh93
-rwxr-xr-xt/t6041-bisect-submodule.sh4
-rwxr-xr-xt/t6120-describe.sh192
-rwxr-xr-xt/t6300-for-each-ref.sh29
-rwxr-xr-xt/t6400-merge-df.sh16
-rwxr-xr-xt/t6402-merge-rename.sh150
-rwxr-xr-xt/t6406-merge-attr.sh18
-rwxr-xr-xt/t6409-merge-subtree.sh6
-rwxr-xr-xt/t6417-merge-ours-theirs.sh10
-rwxr-xr-xt/t6421-merge-partial-clone.sh440
-rwxr-xr-xt/t6423-merge-rename-directories.sh237
-rwxr-xr-xt/t6429-merge-sequence-rename-caching.sh700
-rwxr-xr-xt/t6500-gc.sh46
-rwxr-xr-xt/t7003-filter-branch.sh7
-rwxr-xr-xt/t7400-submodule-basic.sh13
-rwxr-xr-xt/t7406-submodule-update.sh10
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh11
-rwxr-xr-xt/t7508-status.sh2
-rwxr-xr-xt/t7509-commit-authorship.sh4
-rwxr-xr-xt/t7519-status-fsmonitor.sh49
-rwxr-xr-xt/t7600-merge.sh19
-rwxr-xr-xt/t7601-merge-pull-config.sh244
-rwxr-xr-xt/t7603-merge-reduce-heads.sh2
-rwxr-xr-xt/t7800-difftool.sh69
-rwxr-xr-xt/t7810-grep.sh9
-rwxr-xr-xt/t7816-grep-binary-pattern.sh4
-rwxr-xr-xt/t7900-maintenance.sh19
-rwxr-xr-xt/t9001-send-email.sh122
-rwxr-xr-xt/t9002-column.sh18
-rwxr-xr-xt/t9100-git-svn-basic.sh16
-rwxr-xr-xt/t9115-git-svn-dcommit-funky-renames.sh6
-rwxr-xr-xt/t9129-git-svn-i18n-commitencoding.sh4
-rwxr-xr-xt/t9300-fast-import.sh3
-rwxr-xr-xt/t9351-fast-export-anonymize.sh10
-rwxr-xr-xt/t9400-git-cvsserver-server.sh9
-rwxr-xr-xt/t9802-git-p4-filetype.sh4
-rwxr-xr-xt/t9902-completion.sh28
-rw-r--r--t/test-lib-functions.sh132
-rw-r--r--t/test-lib.sh103
-rw-r--r--trace2.h2
-rw-r--r--trace2/tr2_dst.c18
-rw-r--r--transport.c18
-rw-r--r--tree-diff.c4
-rw-r--r--unicode-width.h44
-rw-r--r--unpack-trees.c182
-rw-r--r--upload-pack.c18
-rw-r--r--userdiff.c2
-rw-r--r--worktree.c1
-rw-r--r--wrapper.c4
-rw-r--r--wt-status.c70
-rw-r--r--wt-status.h1
-rw-r--r--xdiff-interface.c27
-rw-r--r--xdiff-interface.h31
-rw-r--r--xdiff/xdiff.h1
-rw-r--r--xdiff/xdiffi.c22
-rw-r--r--xdiff/xemit.c3
495 files changed, 49760 insertions, 46177 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index c2f5fe3..e114ffe 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,8 +2,15 @@ env:
CIRRUS_CLONE_DEPTH: 1
freebsd_12_task:
+ env:
+ GIT_PROVE_OPTS: "--timer --jobs 10"
+ GIT_TEST_OPTS: "--no-chain-lint --no-bin-wrappers"
+ MAKEFLAGS: "-j4"
+ DEFAULT_TEST_TARGET: prove
+ DEVELOPER: 1
freebsd_instance:
- image: freebsd-12-1-release-amd64
+ image_family: freebsd-12-2
+ memory: 2G
install_script:
pkg install -y gettext gmake perl5
create_user_script:
diff --git a/.github/workflows/check-whitespace.yml b/.github/workflows/check-whitespace.yml
index f148305..8c4358d 100644
--- a/.github/workflows/check-whitespace.yml
+++ b/.github/workflows/check-whitespace.yml
@@ -12,15 +12,9 @@ jobs:
check-whitespace:
runs-on: ubuntu-latest
steps:
- - name: Set commit count
- shell: bash
- run: echo "COMMIT_DEPTH=$((1+$COMMITS))" >>$GITHUB_ENV
- env:
- COMMITS: ${{ github.event.pull_request.commits }}
-
- uses: actions/checkout@v2
with:
- fetch-depth: ${{ env.COMMIT_DEPTH }}
+ fetch-depth: 0
- name: git log --check
id: check_out
@@ -47,25 +41,9 @@ jobs:
echo "${dash} ${etc}"
;;
esac
- done <<< $(git log --check --pretty=format:"---% h% s" -${{github.event.pull_request.commits}})
+ done <<< $(git log --check --pretty=format:"---% h% s" ${{github.event.pull_request.base.sha}}..)
if test -n "${log}"
then
- echo "::set-output name=checkout::"${log}""
exit 2
fi
-
- - name: Add Check Output as Comment
- uses: actions/github-script@v3
- id: add-comment
- env:
- log: ${{ steps.check_out.outputs.checkout }}
- with:
- script: |
- await github.issues.createComment({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `Whitespace errors found in workflow ${{ github.workflow }}:\n\n\`\`\`\n${process.env.log.replace(/\\n/g, "\n")}\n\`\`\``
- })
- if: ${{ failure() }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 73856ba..b053b01 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -81,44 +81,21 @@ jobs:
if: needs.ci-config.outputs.enabled == 'yes'
runs-on: windows-latest
steps:
- - uses: actions/checkout@v1
- - name: download git-sdk-64-minimal
- shell: bash
- run: |
- ## Get artifact
- urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
- id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" |
- jq -r ".value[] | .id")
- download_url="$(curl "$urlbase/$id/artifacts" |
- jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')"
- curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \
- -o artifacts.zip "$download_url"
-
- ## Unzip and remove the artifact
- unzip artifacts.zip
- rm artifacts.zip
+ - uses: actions/checkout@v2
+ - uses: git-for-windows/setup-git-for-windows-sdk@v1
- name: build
- shell: powershell
+ shell: bash
env:
HOME: ${{runner.workspace}}
- MSYSTEM: MINGW64
NO_PERL: 1
- run: |
- & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
-
- ci/make-test-artifacts.sh artifacts
- "@
- - name: upload build artifacts
- uses: actions/upload-artifact@v1
+ run: ci/make-test-artifacts.sh artifacts
+ - name: zip up tracked files
+ run: git archive -o artifacts/tracked.tar.gz HEAD
+ - name: upload tracked files and build artifacts
+ uses: actions/upload-artifact@v2
with:
name: windows-artifacts
path: artifacts
- - name: upload git-sdk-64-minimal
- uses: actions/upload-artifact@v1
- with:
- name: git-sdk-64-minimal
- path: git-sdk-64-minimal
windows-test:
runs-on: windows-latest
needs: [windows-build]
@@ -127,37 +104,25 @@ jobs:
matrix:
nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- - uses: actions/checkout@v1
- - name: download build artifacts
- uses: actions/download-artifact@v1
+ - name: download tracked files and build artifacts
+ uses: actions/download-artifact@v2
with:
name: windows-artifacts
path: ${{github.workspace}}
- - name: extract build artifacts
+ - name: extract tracked files and build artifacts
shell: bash
- run: tar xf artifacts.tar.gz
- - name: download git-sdk-64-minimal
- uses: actions/download-artifact@v1
- with:
- name: git-sdk-64-minimal
- path: ${{github.workspace}}/git-sdk-64-minimal/
+ run: tar xf artifacts.tar.gz && tar xf tracked.tar.gz
+ - uses: git-for-windows/setup-git-for-windows-sdk@v1
- name: test
- shell: powershell
- run: |
- & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- # Let Git ignore the SDK
- printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
-
- ci/run-test-slice.sh ${{matrix.nr}} 10
- "@
+ shell: bash
+ run: ci/run-test-slice.sh ${{matrix.nr}} 10
- name: ci/print-test-failures.sh
if: failure()
- shell: powershell
- run: |
- & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh
+ shell: bash
+ run: ci/print-test-failures.sh
- name: Upload failed tests' directories
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v2
with:
name: failed-tests-windows
path: ${{env.FAILED_TEST_ARTIFACTS}}
@@ -165,27 +130,12 @@ jobs:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
env:
- MSYSTEM: MINGW64
NO_PERL: 1
GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
runs-on: windows-latest
steps:
- - uses: actions/checkout@v1
- - name: download git-sdk-64-minimal
- shell: bash
- run: |
- ## Get artifact
- urlbase=https://dev.azure.com/git-for-windows/git/_apis/build/builds
- id=$(curl "$urlbase?definitions=22&statusFilter=completed&resultFilter=succeeded&\$top=1" |
- jq -r ".value[] | .id")
- download_url="$(curl "$urlbase/$id/artifacts" |
- jq -r '.value[] | select(.name == "git-sdk-64-minimal").resource.downloadUrl')"
- curl --connect-timeout 10 --retry 5 --retry-delay 0 --retry-max-time 240 \
- -o artifacts.zip "$download_url"
-
- ## Unzip and remove the artifact
- unzip artifacts.zip
- rm artifacts.zip
+ - uses: actions/checkout@v2
+ - uses: git-for-windows/setup-git-for-windows-sdk@v1
- name: initialize vcpkg
uses: actions/checkout@v2
with:
@@ -203,75 +153,60 @@ jobs:
- name: add msbuild to PATH
uses: microsoft/setup-msbuild@v1
- name: copy dlls to root
- shell: powershell
- run: |
- & compat\vcbuild\vcpkg_copy_dlls.bat release
- if (!$?) { exit(1) }
+ shell: cmd
+ run: compat\vcbuild\vcpkg_copy_dlls.bat release
- name: generate Visual Studio solution
shell: bash
run: |
cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \
- -DMSGFMT_EXE=`pwd`/git-sdk-64-minimal/mingw64/bin/msgfmt.exe -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON
+ -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON
- name: MSBuild
run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142
- name: bundle artifact tar
- shell: powershell
+ shell: bash
env:
MSVC: 1
VCPKG_ROOT: ${{github.workspace}}\compat\vcbuild\vcpkg
run: |
- & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- mkdir -p artifacts &&
- eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts 2>&1 | grep ^tar)\"
- "@
- - name: upload build artifacts
- uses: actions/upload-artifact@v1
+ mkdir -p artifacts &&
+ eval "$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts NO_GETTEXT=YesPlease 2>&1 | grep ^tar)"
+ - name: zip up tracked files
+ run: git archive -o artifacts/tracked.tar.gz HEAD
+ - name: upload tracked files and build artifacts
+ uses: actions/upload-artifact@v2
with:
name: vs-artifacts
path: artifacts
vs-test:
runs-on: windows-latest
- needs: [vs-build, windows-build]
+ needs: vs-build
strategy:
fail-fast: false
matrix:
nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- - uses: actions/checkout@v1
- - name: download git-sdk-64-minimal
- uses: actions/download-artifact@v1
- with:
- name: git-sdk-64-minimal
- path: ${{github.workspace}}/git-sdk-64-minimal/
- - name: download build artifacts
- uses: actions/download-artifact@v1
+ - uses: git-for-windows/setup-git-for-windows-sdk@v1
+ - name: download tracked files and build artifacts
+ uses: actions/download-artifact@v2
with:
name: vs-artifacts
path: ${{github.workspace}}
- - name: extract build artifacts
+ - name: extract tracked files and build artifacts
shell: bash
- run: tar xf artifacts.tar.gz
+ run: tar xf artifacts.tar.gz && tar xf tracked.tar.gz
- name: test
- shell: powershell
+ shell: bash
env:
- MSYSTEM: MINGW64
NO_SVN_TESTS: 1
GIT_TEST_SKIP_REBASE_P: 1
- run: |
- & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- # Let Git ignore the SDK and the test-cache
- printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude
-
- ci/run-test-slice.sh ${{matrix.nr}} 10
- "@
+ run: ci/run-test-slice.sh ${{matrix.nr}} 10
- name: ci/print-test-failures.sh
if: failure()
- shell: powershell
- run: |
- & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh
+ shell: bash
+ run: ci/print-test-failures.sh
- name: Upload failed tests' directories
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v2
with:
name: failed-tests-windows
path: ${{env.FAILED_TEST_ARTIFACTS}}
@@ -302,14 +237,14 @@ jobs:
jobname: ${{matrix.vector.jobname}}
runs-on: ${{matrix.vector.pool}}
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
- run: ci/install-dependencies.sh
- run: ci/run-build-and-tests.sh
- run: ci/print-test-failures.sh
if: failure()
- name: Upload failed tests' directories
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v2
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
@@ -324,6 +259,8 @@ jobs:
image: alpine
- jobname: Linux32
image: daald/ubuntu32:xenial
+ - jobname: pedantic
+ image: fedora
env:
jobname: ${{matrix.vector.jobname}}
runs-on: ubuntu-latest
@@ -347,9 +284,29 @@ jobs:
jobname: StaticAnalysis
runs-on: ubuntu-18.04
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
- run: ci/install-dependencies.sh
- run: ci/run-static-analysis.sh
+ sparse:
+ needs: ci-config
+ if: needs.ci-config.outputs.enabled == 'yes'
+ env:
+ jobname: sparse
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Download a current `sparse` package
+ # Ubuntu's `sparse` version is too old for us
+ uses: git-for-windows/get-azure-pipelines-artifact@v0
+ with:
+ repository: git/git
+ definitionId: 10
+ artifact: sparse-20.04
+ - name: Install the current `sparse` package
+ run: sudo dpkg -i sparse-20.04/sparse_*.deb
+ - uses: actions/checkout@v2
+ - name: Install other dependencies
+ run: ci/install-dependencies.sh
+ - run: make sparse
documentation:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
@@ -357,6 +314,6 @@ jobs:
jobname: Documentation
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
- run: ci/install-dependencies.sh
- run: ci/test-documentation.sh
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index e3af089..711cb91 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -551,6 +551,51 @@ Writing Documentation:
documentation, please see the documentation-related advice in the
Documentation/SubmittingPatches file).
+ In order to ensure the documentation is inclusive, avoid assuming
+ that an unspecified example person is male or female, and think
+ twice before using "he", "him", "she", or "her". Here are some
+ tips to avoid use of gendered pronouns:
+
+ - Prefer succinctness and matter-of-factly describing functionality
+ in the abstract. E.g.
+
+ --short:: Emit output in the short-format.
+
+ and avoid something like these overly verbose alternatives:
+
+ --short:: Use this to emit output in the short-format.
+ --short:: You can use this to get output in the short-format.
+ --short:: A user who prefers shorter output could....
+ --short:: Should a person and/or program want shorter output, he
+ she/they/it can...
+
+ This practice often eliminates the need to involve human actors in
+ your description, but it is a good practice regardless of the
+ avoidance of gendered pronouns.
+
+ - When it becomes awkward to stick to this style, prefer "you" when
+ addressing the the hypothetical user, and possibly "we" when
+ discussing how the program might react to the user. E.g.
+
+ You can use this option instead of --xyz, but we might remove
+ support for it in future versions.
+
+ while keeping in mind that you can probably be less verbose, e.g.
+
+ Use this instead of --xyz. This option might be removed in future
+ versions.
+
+ - If you still need to refer to an example person that is
+ third-person singular, you may resort to "singular they" to avoid
+ "he/she/him/her", e.g.
+
+ A contributor asks their upstream to pull from them.
+
+ Note that this sounds ungrammatical and unnatural to those who
+ learned that "they" is only used for third-person plural, e.g.
+ those who learn English as a second language in some parts of the
+ world.
+
Every user-visible change should be reflected in the documentation.
The same general rule as for code applies -- imitate the existing
conventions.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 2aae4c9..f5605b7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -139,6 +139,7 @@ ASCIIDOC_CONF = -f asciidoc.conf
ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA) $(ASCIIDOC_CONF) \
-amanversion=$(GIT_VERSION) \
-amanmanual='Git Manual' -amansource='Git'
+ASCIIDOC_DEPS = asciidoc.conf GIT-ASCIIDOCFLAGS
TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)
TXT_TO_XML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_DOCBOOK)
MANPAGE_XSL = manpage-normal.xsl
@@ -193,6 +194,7 @@ ASCIIDOC_DOCBOOK = docbook5
ASCIIDOC_EXTRA += -acompat-mode -atabsize=8
ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions
ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+ASCIIDOC_DEPS = asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
DBLATEX_COMMON =
XMLTO_EXTRA += --skip-validation
XMLTO_EXTRA += -x manpage.xsl
@@ -294,9 +296,7 @@ docdep_prereqs = \
cmd-list.made $(cmds_txt)
doc.dep : $(docdep_prereqs) $(DOC_DEP_TXT) build-docdep.perl
- $(QUIET_GEN)$(RM) $@+ $@ && \
- $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
- mv $@+ $@
+ $(QUIET_GEN)$(PERL_PATH) ./build-docdep.perl >$@ $(QUIET_STDERR)
ifneq ($(MAKECMDGOALS),clean)
-include doc.dep
@@ -316,8 +316,7 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
$(cmds_txt): cmd-list.made
cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
- $(QUIET_GEN)$(RM) $@ && \
- $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(cmds_txt) $(QUIET_STDERR) && \
+ $(QUIET_GEN)$(PERL_PATH) ./cmd-list.perl ../command-list.txt $(cmds_txt) $(QUIET_STDERR) && \
date >$@
mergetools_txt = mergetools-diff.txt mergetools-merge.txt
@@ -325,7 +324,7 @@ mergetools_txt = mergetools-diff.txt mergetools-merge.txt
$(mergetools_txt): mergetools-list.made
mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
- $(QUIET_GEN)$(RM) $@ && \
+ $(QUIET_GEN) \
$(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \
. ../git-mergetool--lib.sh && \
show_tool_names can_diff "* " || :' >mergetools-diff.txt && \
@@ -354,32 +353,23 @@ clean:
$(RM) manpage-base-url.xsl
$(RM) GIT-ASCIIDOCFLAGS
-$(MAN_HTML): %.html : %.txt asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_HTML) -d manpage -o $@+ $< && \
- mv $@+ $@
+$(MAN_HTML): %.html : %.txt $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_HTML) -d manpage -o $@ $<
-$(OBSOLETE_HTML): %.html : %.txto asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_HTML) -o $@+ $< && \
- mv $@+ $@
+$(OBSOLETE_HTML): %.html : %.txto $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_HTML) -o $@ $<
manpage-base-url.xsl: manpage-base-url.xsl.in
$(QUIET_GEN)sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
%.1 %.5 %.7 : %.xml manpage-base-url.xsl $(wildcard manpage*.xsl)
- $(QUIET_XMLTO)$(RM) $@ && \
- $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
+ $(QUIET_XMLTO)$(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
-%.xml : %.txt asciidoc.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_XML) -d manpage -o $@+ $< && \
- mv $@+ $@
+%.xml : %.txt $(ASCIIDOC_DEPS)
+ $(QUIET_ASCIIDOC)$(TXT_TO_XML) -d manpage -o $@ $<
user-manual.xml: user-manual.txt user-manual.conf asciidoctor-extensions.rb GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
- $(TXT_TO_XML) -d book -o $@+ $< && \
- mv $@+ $@
+ $(QUIET_ASCIIDOC)$(TXT_TO_XML) -d book -o $@ $<
technical/api-index.txt: technical/api-index-skel.txt \
technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
@@ -400,46 +390,35 @@ XSLTOPTS += --stringparam html.stylesheet docbook-xsl.css
XSLTOPTS += --param generate.consistent.ids 1
user-manual.html: user-manual.xml $(XSLT)
- $(QUIET_XSLTPROC)$(RM) $@+ $@ && \
- xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \
- mv $@+ $@
+ $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
git.info: user-manual.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
user-manual.texi: user-manual.xml
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
- $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
- $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
- rm $@++ && \
- mv $@+ $@
+ $(QUIET_DB2TEXI)$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@+ && \
+ $(PERL_PATH) fix-texi.perl <$@+ >$@ && \
+ $(RM) $@+
user-manual.pdf: user-manual.xml
- $(QUIET_DBLATEX)$(RM) $@+ $@ && \
- $(DBLATEX) -o $@+ $(DBLATEX_COMMON) $< && \
- mv $@+ $@
+ $(QUIET_DBLATEX)$(DBLATEX) -o $@ $(DBLATEX_COMMON) $<
gitman.texi: $(MAN_XML) cat-texi.perl texi.xsl
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+ $(QUIET_DB2TEXI) \
($(foreach xml,$(sort $(MAN_XML)),xsltproc -o $(xml)+ texi.xsl $(xml) && \
$(DOCBOOK2X_TEXI) --encoding=UTF-8 --to-stdout $(xml)+ && \
- rm $(xml)+ &&) true) > $@++ && \
- $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
- rm $@++ && \
- mv $@+ $@
+ $(RM) $(xml)+ &&) true) > $@+ && \
+ $(PERL_PATH) cat-texi.perl $@ <$@+ >$@ && \
+ $(RM) $@+
gitman.info: gitman.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
- $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
- $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
- mv $@+ $@
+ $(QUIET_DB2TEXI)$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@
howto-index.txt: howto-index.sh $(HOWTO_TXT)
- $(QUIET_GEN)$(RM) $@+ $@ && \
- '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(HOWTO_TXT)) >$@+ && \
- mv $@+ $@
+ $(QUIET_GEN)'$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(HOWTO_TXT)) >$@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
$(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt
@@ -448,10 +427,9 @@ WEBDOC_DEST = /pub/software/scm/git/docs
howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
$(patsubst %.txt,%.html,$(HOWTO_TXT)): %.html : %.txt GIT-ASCIIDOCFLAGS
- $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+ $(QUIET_ASCIIDOC) \
sed -e '1,/^$$/d' $< | \
- $(TXT_TO_HTML) - >$@+ && \
- mv $@+ $@
+ $(TXT_TO_HTML) - >$@
install-webdoc : html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
@@ -492,4 +470,7 @@ doc-l10n install-l10n::
$(MAKE) -C po $@
endif
+# Delete the target file on error
+.DELETE_ON_ERROR:
+
.PHONY: FORCE
diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index af0a9da..015cf24 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -47,7 +47,7 @@ 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
+==== https://web.libera.chat/#git-devel[#git-devel] on Libera Chat
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
@@ -827,7 +827,7 @@ either examining recent pull requests where someone has been granted `/allow`
(https://github.com/gitgitgadget/git/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+%22%2Fallow%22[Search:
is:pr is:open "/allow"]), in which case both the author and the person who
granted the `/allow` can now `/allow` you, or by inquiring on the
-https://webchat.freenode.net/#git-devel[#git-devel] IRC channel on Freenode
+https://web.libera.chat/#git-devel[#git-devel] IRC channel on Libera Chat
linking your pull request and asking for someone to `/allow` you.
If the CI fails, you can update your changes with `git rebase -i` and push your
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt
index 2d10eea..45eb84d 100644
--- a/Documentation/MyFirstObjectWalk.txt
+++ b/Documentation/MyFirstObjectWalk.txt
@@ -691,7 +691,7 @@ help understand. In our case, that means we omit trees and blobs not directly
referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only
`HEAD` in the `pending` list.)
-First, we'll need to `#include "list-objects-filter-options.h`" and set up the
+First, we'll need to `#include "list-objects-filter-options.h"` and set up the
`struct list_objects_filter_options` at the top of the function.
----
@@ -779,7 +779,7 @@ Count all the objects within and modify the print statement:
while ((oid = oidset_iter_next(&oit)))
omitted_count++;
- printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n",
+ printf("commits %d\nblobs %d\ntags %d\ntrees %d\nomitted %d\n",
commit_count, blob_count, tag_count, tree_count, omitted_count);
----
diff --git a/Documentation/RelNotes/1.6.0.3.txt b/Documentation/RelNotes/1.6.0.3.txt
index ae05778..ad36c0f 100644
--- a/Documentation/RelNotes/1.6.0.3.txt
+++ b/Documentation/RelNotes/1.6.0.3.txt
@@ -50,7 +50,7 @@ Fixes since v1.6.0.2
if the working tree is currently dirty.
* "git for-each-ref --format=%(subject)" fixed for commits with no
- no newline in the message body.
+ newline in the message body.
* "git remote" fixed to protect printf from user input.
diff --git a/Documentation/RelNotes/1.8.4.txt b/Documentation/RelNotes/1.8.4.txt
index 255e185..2e75299 100644
--- a/Documentation/RelNotes/1.8.4.txt
+++ b/Documentation/RelNotes/1.8.4.txt
@@ -365,7 +365,7 @@ details).
(merge 2fbd4f9 mh/maint-lockfile-overflow later to maint).
* Invocations of "git checkout" used internally by "git rebase" were
- counted as "checkout", and affected later "git checkout -" to the
+ counted as "checkout", and affected later "git checkout -", which took
the user to an unexpected place.
(merge 3bed291 rr/rebase-checkout-reflog later to maint).
diff --git a/Documentation/RelNotes/2.29.0.txt b/Documentation/RelNotes/2.29.0.txt
index 06ba2f8..1f41302 100644
--- a/Documentation/RelNotes/2.29.0.txt
+++ b/Documentation/RelNotes/2.29.0.txt
@@ -184,8 +184,8 @@ Performance, Internal Implementation, Development Support etc.
the ref backend in use, as its format is much richer than the
normal refs, and written directly by "git fetch" as a plain file..
- * An unused binary has been discarded, and and a bunch of commands
- have been turned into into built-in.
+ * An unused binary has been discarded, and a bunch of commands
+ have been turned into built-in.
* A handful of places in in-tree code still relied on being able to
execute the git subcommands, especially built-ins, in "git-foo"
diff --git a/Documentation/RelNotes/2.33.0.txt b/Documentation/RelNotes/2.33.0.txt
new file mode 100644
index 0000000..893c18b
--- /dev/null
+++ b/Documentation/RelNotes/2.33.0.txt
@@ -0,0 +1,279 @@
+Git 2.33 Release Notes
+======================
+
+Updates since Git 2.32
+----------------------
+
+UI, Workflows & Features
+
+ * "git send-email" learned the "--sendmail-cmd" command line option
+ and the "sendemail.sendmailCmd" configuration variable, which is a
+ more sensible approach than the current way of repurposing the
+ "smtp-server" that is meant to name the server to instead name the
+ command to talk to the server.
+
+ * The userdiff pattern for C# learned the token "record".
+
+ * "git rev-list" learns to omit the "commit <object-name>" header
+ lines from the output with the `--no-commit-header` option.
+
+ * "git worktree add --lock" learned to record why the worktree is
+ locked with a custom message.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The code to handle the "--format" option in "for-each-ref" and
+ friends made too many string comparisons on %(atom)s used in the
+ format string, which has been corrected by converting them into
+ enum when the format string is parsed.
+
+ * Use the hashfile API in the codepath that writes the index file to
+ reduce code duplication.
+
+ * Repeated rename detections in a sequence of mergy operations have
+ been optimized out for the 'ort' merge strategy.
+
+ * Preliminary clean-up of tests before the main reftable changes
+ hits the codebase.
+
+ * The backend for "diff -G/-S" has been updated to use pcre2 engine
+ when available.
+
+ * Use ".DELETE_ON_ERROR" pseudo target to simplify our Makefile.
+
+ * Code cleanup around struct_type_init() functions.
+
+ * "git send-email" optimization.
+
+ * GitHub Actions / CI update.
+ (merge 0dc787a9f2 js/ci-windows-update later to maint).
+
+ * Object accesses in repositories with many alternate object store
+ have been optimized.
+
+ * "git log" has been optimized not to waste cycles to load ref
+ decoration data that may not be needed.
+
+ * Many "printf"-like helper functions we have have been annotated
+ with __attribute__() to catch placeholder/parameter mismatches.
+
+ * Tests that cover protocol bits have been updated and helpers
+ used there have been consolidated.
+
+ * The CI gained a new job to run "make sparse" check.
+
+ * "git status" codepath learned to work with sparsely populated index
+ without hydrating it fully.
+
+ * A guideline for gender neutral documentation has been added.
+
+ * Documentation on "git diff -l<n>" and diff.renameLimit have been
+ updated, and the defaults for these limits have been raised.
+
+ * The completion support used to offer alternate spelling of options
+ that exist only for compatibility, which has been corrected.
+
+ * "TEST_OUTPUT_DIRECTORY=there make test" failed to work, which has
+ been corrected.
+
+ * "git bundle" gained more test coverage.
+
+ * "git read-tree" had a codepath where blobs are fetched one-by-one
+ from the promisor remote, which has been corrected to fetch in bulk.
+
+ * Rewrite of "git submodule" in C continues.
+
+ * "git checkout" and "git commit" learn to work without unnecessarily
+ expanding sparse indexes.
+
+
+Fixes since v2.32
+-----------------
+
+ * We historically rejected a very short string as an author name
+ while accepting a patch e-mail, which has been loosened.
+ (merge 72ee47ceeb ef/mailinfo-short-name later to maint).
+
+ * The parallel checkout codepath did not initialize object ID field
+ used to talk to the worker processes in a futureproof way.
+
+ * Rewrite code that triggers undefined behaviour warning.
+ (merge aafa5df0df jn/size-t-casted-to-off-t-fix later to maint).
+
+ * The description of "fast-forward" in the glossary has been updated.
+ (merge e22f2daed0 ry/clarify-fast-forward-in-glossary later to maint).
+
+ * Recent "git clone" left a temporary directory behind when the
+ transport layer returned an failure.
+ (merge 6aacb7d861 jk/clone-clean-upon-transport-error later to maint).
+
+ * "git fetch" over protocol v2 left its side of the socket open after
+ it finished speaking, which unnecessarily wasted the resource on
+ the other side.
+ (merge ae1a7eefff jk/fetch-pack-v2-half-close-early later to maint).
+
+ * The command line completion (in contrib/) learned that "git diff"
+ takes the "--anchored" option.
+ (merge d1e7c2cac9 tb/complete-diff-anchored later to maint).
+
+ * "git-svn" tests assumed that "locale -a", which is used to pick an
+ available UTF-8 locale, is available everywhere. A knob has been
+ introduced to allow testers to specify a suitable locale to use.
+ (merge 482c962de4 dd/svn-test-wo-locale-a later to maint).
+
+ * Update "git subtree" to work better on Windows.
+ (merge 77f37de39f js/subtree-on-windows-fix later to maint).
+
+ * Remove multimail from contrib/
+ (merge f74d11471f js/no-more-multimail later to maint).
+
+ * Make the codebase MSAN clean.
+ (merge 4dbc55e87d ah/uninitialized-reads-fix later to maint).
+
+ * Work around inefficient glob substitution in older versions of bash
+ by rewriting parts of a test.
+ (merge eb87c6f559 jx/t6020-with-older-bash later to maint).
+
+ * Avoid duplicated work while building reachability bitmaps.
+ (merge aa9ad6fee5 jk/bitmap-tree-optim later to maint).
+
+ * We broke "GIT_SKIP_TESTS=t?000" to skip certain tests in recent
+ update, which got fixed.
+
+ * The side-band demultiplexer that is used to display progress output
+ from the remote end did not clear the line properly when the end of
+ line hits at a packet boundary, which has been corrected.
+
+ * Some test scripts assumed that readlink(1) was universally
+ installed and available, which is not the case.
+ (merge 7c0afdf23c jk/test-without-readlink-1 later to maint).
+
+ * Recent update to completion script (in contrib/) broke those who
+ use the __git_complete helper to define completion to their custom
+ command.
+ (merge cea232194d fw/complete-cmd-idx-fix later to maint).
+
+ * Output from some of our tests were affected by the width of the
+ terminal that they were run in, which has been corrected by
+ exporting a fixed value in the COLUMNS environment.
+ (merge c49a177bec ab/fix-columns-to-80-during-tests later to maint).
+
+ * On Windows, mergetool has been taught to find kdiff3.exe just like
+ it finds winmerge.exe.
+ (merge 47eb4c6890 ms/mergetools-kdiff3-on-windows later to maint).
+
+ * When we cannot figure out how wide the terminal is, we use a
+ fallback value of 80 ourselves (which cannot be avoided), but when
+ we run the pager, we export it in COLUMNS, which forces the pager
+ to use the hardcoded value, even when the pager is perfectly
+ capable to figure it out itself. Stop exporting COLUMNS when we
+ fall back on the hardcoded default value for our own use.
+ (merge 9b6e2c8b98 js/stop-exporting-bogus-columns later to maint).
+
+ * "git cat-file --batch-all-objects"" misbehaved when "--batch" is in
+ use and did not ask for certain object traits.
+ (merge ee02ac6164 zh/cat-file-batch-fix later to maint).
+
+ * Some code and doc clarification around "git push".
+
+ * The "union" conflict resultion variant misbehaved when used with
+ binary merge driver.
+ (merge 382b601acd jk/union-merge-binary later to maint).
+
+ * Prevent "git p4" from failing to submit changes to binary file.
+ (merge 54662d5958 dc/p4-binary-submit-fix later to maint).
+
+ * "git grep --and -e foo" ought to have been diagnosed as an error
+ but instead segfaulted, which has been corrected.
+ (merge fe7fe62d8d rs/grep-parser-fix later to maint).
+
+ * The merge code had funny interactions between content based rename
+ detection and directory rename detection.
+ (merge 3585d0ea23 en/merge-dir-rename-corner-case-fix later to maint).
+
+ * When rebuilding the multi-pack index file reusing an existing one,
+ we used to blindly trust the existing file and ended up carrying
+ corrupted data into the updated file, which has been corrected.
+ (merge f89ecf7988 tb/midx-use-checksum later to maint).
+
+ * Update the location of system-side configuration file on Windows.
+ (merge e355307692 js/gfw-system-config-loc-fix later to maint).
+
+ * Code recently added to support common ancestry negotiation during
+ "git push" did not sanity check its arguments carefully enough.
+ (merge eff40457a4 ab/fetch-negotiate-segv-fix later to maint).
+
+ * Update the documentation not to assume users are of certain gender
+ and adds to guidelines to do so.
+ (merge 46a237f42f ds/gender-neutral-doc later to maint).
+
+ * "git commit --allow-empty-message" won't abort the operation upon
+ an empty message, but the hint shown in the editor said otherwise.
+ (merge 6f70f00b4f hj/commit-allow-empty-message later to maint).
+
+ * The code that gives an error message in "git multi-pack-index" when
+ no subcommand is given tried to print a NULL pointer as a strong,
+ which has been corrected.
+ (merge 88617d11f9 tb/reverse-midx later to maint).
+
+ * CI update.
+ (merge a066a90db6 js/ci-check-whitespace-updates later to maint).
+
+ * Documentation fix for "git pull --rebase=no".
+ (merge d3236becec fc/pull-no-rebase-merges-theirs-into-ours later to maint).
+
+ * A race between repacking and using pack bitmaps has been corrected.
+ (merge dc1daacdcc jk/check-pack-valid-before-opening-bitmap later to maint).
+
+ * The local changes stashed by "git merge --autostash" were lost when
+ the merge failed in certain ways, which has been corrected.
+
+ * Windows rmdir() equivalent behaves differently from POSIX ones in
+ that when used on a symbolic link that points at a directory, the
+ target directory gets removed, which has been corrected.
+ (merge 3e7d4888e5 tb/mingw-rmdir-symlink-to-directory later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge bfe35a6165 ah/doc-describe later to maint).
+ (merge f302c1e4aa jc/clarify-revision-range later to maint).
+ (merge 3127ff90ea tl/fix-packfile-uri-doc later to maint).
+ (merge a84216c684 jk/doc-color-pager later to maint).
+ (merge 4e0a64a713 ab/trace2-squelch-gcc-warning later to maint).
+ (merge 225f7fa847 ps/rev-list-object-type-filter later to maint).
+ (merge 5317dfeaed dd/honor-users-tar-in-tests later to maint).
+ (merge ace6d8e3d6 tk/partial-clone-repack-doc later to maint).
+ (merge 7ba68e0cf1 js/trace2-discard-event-docfix later to maint).
+ (merge 8603c419d3 fc/doc-default-to-upstream-config later to maint).
+ (merge 1d72b604ef jk/revision-squelch-gcc-warning later to maint).
+ (merge abcb66c614 ar/typofix later to maint).
+ (merge 9853830787 ah/graph-typofix later to maint).
+ (merge aac578492d ab/config-hooks-path-testfix later to maint).
+ (merge 98c7656a18 ar/more-typofix later to maint).
+ (merge 6fb9195f6c jk/doc-max-pack-size later to maint).
+ (merge 4184cbd635 ar/mailinfo-memcmp-to-skip-prefix later to maint).
+ (merge 91d2347033 ar/doc-libera-chat-in-my-first-contrib later to maint).
+ (merge 338abb0f04 ab/cmd-foo-should-return later to maint).
+ (merge 546096a5cb ab/xdiff-bug-cleanup later to maint).
+ (merge b7b793d1e7 ab/progress-cleanup later to maint).
+ (merge d94f9b8e90 ba/object-info later to maint).
+ (merge 52ff891c03 ar/test-code-cleanup later to maint).
+ (merge a0538e5c8b dd/document-log-decorate-default later to maint).
+ (merge ce24797d38 mr/cmake later to maint).
+ (merge 9eb542f2ee ab/pre-auto-gc-hook-test later to maint).
+ (merge 9fffc38583 bk/doc-commit-typofix later to maint).
+ (merge 1cf823d8f0 ks/submodule-cleanup later to maint).
+ (merge ebbf5d2b70 js/config-mak-windows-pcre-fix later to maint).
+ (merge 617480d75b hn/refs-iterator-peel-returns-boolean later to maint).
+ (merge 6a24cc71ed ar/submodule-helper-include-cleanup later to maint).
+ (merge 5632e838f8 rs/khash-alloc-cleanup later to maint).
+ (merge b1d87fbaf1 jk/typofix later to maint).
+ (merge e04170697a ab/gitignore-discovery-doc later to maint).
+ (merge 8232a0ff48 dl/packet-read-response-end-fix later to maint).
+ (merge eb448631fb dl/diff-merge-base later to maint).
+ (merge c510928a25 hn/refs-debug-empty-prefix later to maint).
+ (merge ddcb189d9d tb/bitmap-type-filter-comment-fix later to maint).
+ (merge 878b399734 pb/submodule-recurse-doc later to maint).
+ (merge 734283855f jk/config-env-doc later to maint).
+ (merge 482e1488a9 ab/getcwd-test later to maint).
+ (merge f0b922473e ar/doc-markup-fix later to maint).
diff --git a/Documentation/RelNotes/2.33.1.txt b/Documentation/RelNotes/2.33.1.txt
new file mode 100644
index 0000000..b71738e
--- /dev/null
+++ b/Documentation/RelNotes/2.33.1.txt
@@ -0,0 +1,138 @@
+Git 2.33.1 Release Notes
+========================
+
+This primarily is to backport various fixes accumulated during the
+development towards Git 2.34, the next feature release.
+
+
+Fixes since v2.33
+-----------------
+
+ * The unicode character width table (used for output alignment) has
+ been updated.
+
+ * Input validation of "git pack-objects --stdin-packs" has been
+ corrected.
+
+ * Bugfix for common ancestor negotiation recently introduced in "git
+ push" codepath.
+
+ * "git pull" had various corner cases that were not well thought out
+ around its --rebase backend, e.g. "git pull --ff-only" did not stop
+ but went ahead and rebased when the history on other side is not a
+ descendant of our history. The series tries to fix them up.
+
+ * "git apply" miscounted the bytes and failed to read to the end of
+ binary hunks.
+
+ * "git range-diff" code clean-up.
+
+ * "git commit --fixup" now works with "--edit" again, after it was
+ broken in v2.32.
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+ new version has a blocker bug for that architecture.
+
+ * Checking out all the paths from HEAD during the last conflicted
+ step in "git rebase" and continuing would cause the step to be
+ skipped (which is expected), but leaves MERGE_MSG file behind in
+ $GIT_DIR and confuses the next "git commit", which has been
+ corrected.
+
+ * Various bugs in "git rebase -r" have been fixed.
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+ failure, which has been corrected to just return an error to the
+ caller to be handled.
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+ when there are unmerged paths.
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+ even when --quiet and/or --no-progress options were given.
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+ ref that points at a missing commit, which has been corrected.
+
+ * Build update for Apple clang.
+
+ * The parser for the "--nl" option of "git column" has been
+ corrected.
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+ forgot to take the ref namespaces into account when handling
+ want-ref requests.
+
+ * The sparse-index support can corrupt the index structure by storing
+ a stale and/or uninitialized data, which has been corrected.
+
+ * Buggy tests could damage repositories outside the throw-away test
+ area we created. We now by default export GIT_CEILING_DIRECTORIES
+ to limit the damage from such a stray test.
+
+ * Even when running "git send-email" without its own threaded
+ discussion support, a threading related header in one message is
+ carried over to the subsequent message to result in an unwanted
+ threading, which has been corrected.
+
+ * The output from "git fast-export", when its anonymization feature
+ is in use, showed an annotated tag incorrectly.
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+
+ * "git maintenance" scheduler fix for macOS.
+
+ * A pathname in an advice message has been made cut-and-paste ready.
+
+ * The "git apply -3" code path learned not to bother the lower level
+ merge machinery when the three-way merge can be trivially resolved
+ without the content level merge.
+
+ * The code that optionally creates the *.rev reverse index file has
+ been optimized to avoid needless computation when it is not writing
+ the file out.
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+ corrected.
+
+ * The order in which various files that make up a single (conceptual)
+ packfile has been reevaluated and straightened up. This matters in
+ correctness, as an incomplete set of files must not be shown to a
+ running Git.
+
+ * The "mode" word is useless in a call to open(2) that does not
+ create a new file. Such a call in the files backend of the ref
+ subsystem has been cleaned up.
+
+ * "git update-ref --stdin" failed to flush its output as needed,
+ which potentially led the conversation to a deadlock.
+
+ * When "git am --abort" fails to abort correctly, it still exited
+ with exit status of 0, which has been corrected.
+
+ * Correct nr and alloc members of strvec struct to be of type size_t.
+
+ * "git stash", where the tentative change involves changing a
+ directory to a file (or vice versa), was confused, which has been
+ corrected.
+
+ * "git clone" from a repository whose HEAD is unborn into a bare
+ repository didn't follow the branch name the other side used, which
+ is corrected.
+
+ * "git cvsserver" had a long-standing bug in its authentication code,
+ which has finally been corrected (it is unclear and is a separate
+ question if anybody is seriously using it, though).
+
+ * "git difftool --dir-diff" mishandled symbolic links.
+
+ * Sensitive data in the HTTP trace were supposed to be redacted, but
+ we failed to do so in HTTP/2 requests.
+
+ * "make clean" has been updated to remove leftover .depend/
+ directories, even when it is not told to use them to compute header
+ dependencies.
+
+ * Protocol v0 clients can get stuck parsing a malformed feature line.
+
+Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.33.2.txt b/Documentation/RelNotes/2.33.2.txt
new file mode 100644
index 0000000..e504489
--- /dev/null
+++ b/Documentation/RelNotes/2.33.2.txt
@@ -0,0 +1,15 @@
+Git v2.33.2 Release Notes
+=========================
+
+This release merges up the fixes that appear in v2.30.3, v2.31.2
+and v2.32.1 to address the security issue CVE-2022-24765; see
+the release notes for these versions for details.
+
+In addition, it contains the following fixes:
+
+ * Squelch over-eager warning message added during this cycle.
+
+ * A bug in "git rebase -r" has been fixed.
+
+ * One CI task based on Fedora image noticed a not-quite-kosher
+ construct recently, which has been corrected.
diff --git a/Documentation/RelNotes/2.33.3.txt b/Documentation/RelNotes/2.33.3.txt
new file mode 100644
index 0000000..e2bada1
--- /dev/null
+++ b/Documentation/RelNotes/2.33.3.txt
@@ -0,0 +1,4 @@
+Git Documentation/RelNotes/2.33.3.txt Release Notes
+=========================
+
+This release merges up the fixes that appear in v2.33.3.
diff --git a/Documentation/RelNotes/2.8.0.txt b/Documentation/RelNotes/2.8.0.txt
index 27320b6..3845328 100644
--- a/Documentation/RelNotes/2.8.0.txt
+++ b/Documentation/RelNotes/2.8.0.txt
@@ -377,7 +377,7 @@ notes for details).
on that order.
* "git show 'HEAD:Foo[BAR]Baz'" did not interpret the argument as a
- rev, i.e. the object named by the the pathname with wildcard
+ rev, i.e. the object named by the pathname with wildcard
characters in a tree object.
(merge aac4fac nd/dwim-wildcards-as-pathspecs later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 55287d7..e409022 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -74,10 +74,9 @@ the feature triggers the new behavior when it should, and to show the
feature does not trigger when it shouldn't. After any code change, make
sure that the entire test suite passes.
-If you have an account at GitHub (and you can get one for free to work
-on open source projects), you can use their Travis CI integration to
-test your changes on Linux, Mac (and hopefully soon Windows). See
-GitHub-Travis CI hints section for details.
+Pushing to a fork of https://github.com/git/git will use their CI
+integration to test your changes on Linux, Mac and Windows. See the
+<<GHCI,GitHub CI>> section for details.
Do not forget to update the documentation to describe the updated
behavior and make sure that the resulting documentation set formats
@@ -167,6 +166,85 @@ or, on an older version of Git without support for --pretty=reference:
git show -s --date=short --pretty='format:%h (%s, %ad)' <commit>
....
+[[sign-off]]
+=== Certify your work by adding your `Signed-off-by` trailer
+
+To improve tracking of who did what, we ask you to certify that you
+wrote the patch or have the right to pass it on under the same license
+as ours, by "signing off" your patch. Without sign-off, we cannot
+accept your patches.
+
+If (and only if) you certify the below D-C-O:
+
+[[dco]]
+.Developer's Certificate of Origin 1.1
+____
+By making a contribution to this project, I certify that:
+
+a. The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+b. The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+c. The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+d. I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+____
+
+you add a "Signed-off-by" trailer to your commit, that looks like
+this:
+
+....
+ Signed-off-by: Random J Developer <random@developer.example.org>
+....
+
+This line can be added by Git if you run the git-commit command with
+the -s option.
+
+Notice that you can place your own `Signed-off-by` trailer when
+forwarding somebody else's patch with the above rules for
+D-C-O. Indeed you are encouraged to do so. Do not forget to
+place an in-body "From: " line at the beginning to properly attribute
+the change to its true author (see (2) above).
+
+This procedure originally came from the Linux kernel project, so our
+rule is quite similar to theirs, but what exactly it means to sign-off
+your patch differs from project to project, so it may be different
+from that of the project you are accustomed to.
+
+[[real-name]]
+Also notice that a real name is used in the `Signed-off-by` trailer. Please
+don't hide your real name.
+
+[[commit-trailers]]
+If you like, you can put extra tags at the end:
+
+. `Reported-by:` is used to credit someone who found the bug that
+ the patch attempts to fix.
+. `Acked-by:` says that the person who is more familiar with the area
+ the patch attempts to modify liked the patch.
+. `Reviewed-by:`, unlike the other tags, can only be offered by the
+ reviewers themselves when they are completely satisfied with the
+ patch after a detailed analysis.
+. `Tested-by:` is used to indicate that the person applied the patch
+ and found it to have the desired effect.
+
+You can also create your own tag or use one that's in common usage
+such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:".
+
[[git-tools]]
=== Generate your patch using Git tools out of your commits.
@@ -302,86 +380,6 @@ Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and
`Tested-by:` lines as necessary to credit people who helped your
patch, and "cc:" them when sending such a final version for inclusion.
-[[sign-off]]
-=== Certify your work by adding your `Signed-off-by` trailer
-
-To improve tracking of who did what, we ask you to certify that you
-wrote the patch or have the right to pass it on under the same license
-as ours, by "signing off" your patch. Without sign-off, we cannot
-accept your patches.
-
-If (and only if) you certify the below D-C-O:
-
-[[dco]]
-.Developer's Certificate of Origin 1.1
-____
-By making a contribution to this project, I certify that:
-
-a. The contribution was created in whole or in part by me and I
- have the right to submit it under the open source license
- indicated in the file; or
-
-b. The contribution is based upon previous work that, to the best
- of my knowledge, is covered under an appropriate open source
- license and I have the right under that license to submit that
- work with modifications, whether created in whole or in part
- by me, under the same open source license (unless I am
- permitted to submit under a different license), as indicated
- in the file; or
-
-c. The contribution was provided directly to me by some other
- person who certified (a), (b) or (c) and I have not modified
- it.
-
-d. I understand and agree that this project and the contribution
- are public and that a record of the contribution (including all
- personal information I submit with it, including my sign-off) is
- maintained indefinitely and may be redistributed consistent with
- this project or the open source license(s) involved.
-____
-
-you add a "Signed-off-by" trailer to your commit, that looks like
-this:
-
-....
- Signed-off-by: Random J Developer <random@developer.example.org>
-....
-
-This line can be added by Git if you run the git-commit command with
-the -s option.
-
-Notice that you can place your own `Signed-off-by` trailer when
-forwarding somebody else's patch with the above rules for
-D-C-O. Indeed you are encouraged to do so. Do not forget to
-place an in-body "From: " line at the beginning to properly attribute
-the change to its true author (see (2) above).
-
-This procedure originally came from the Linux kernel project, so our
-rule is quite similar to theirs, but what exactly it means to sign-off
-your patch differs from project to project, so it may be different
-from that of the project you are accustomed to.
-
-[[real-name]]
-Also notice that a real name is used in the `Signed-off-by` trailer. Please
-don't hide your real name.
-
-[[commit-trailers]]
-If you like, you can put extra tags at the end:
-
-. `Reported-by:` is used to credit someone who found the bug that
- the patch attempts to fix.
-. `Acked-by:` says that the person who is more familiar with the area
- the patch attempts to modify liked the patch.
-. `Reviewed-by:`, unlike the other tags, can only be offered by the
- reviewer and means that she is completely satisfied that the patch
- is ready for application. It is usually offered only after a
- detailed review.
-. `Tested-by:` is used to indicate that the person applied the patch
- and found it to have the desired effect.
-
-You can also create your own tag or use one that's in common usage
-such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:".
-
== Subsystems with dedicated maintainers
Some parts of the system have dedicated maintainers with their own
@@ -450,13 +448,12 @@ their trees themselves.
entitled "What's cooking in git.git" and "What's in git.git" giving
the status of various proposed changes.
-[[travis]]
-== GitHub-Travis CI hints
+== GitHub CI[[GHCI]]]
-With an account at GitHub (you can get one for free to work on open
-source projects), you can use Travis CI to test your changes on Linux,
-Mac (and hopefully soon Windows). You can find a successful example
-test build here: https://travis-ci.org/git/git/builds/120473209
+With an account at GitHub, you can use GitHub CI to test your changes
+on Linux, Mac and Windows. See
+https://github.com/git/git/actions/workflows/main.yml for examples of
+recent CI runs.
Follow these steps for the initial setup:
@@ -464,31 +461,18 @@ Follow these steps for the initial setup:
You can find detailed instructions how to fork here:
https://help.github.com/articles/fork-a-repo/
-. Open the Travis CI website: https://travis-ci.org
-
-. Press the "Sign in with GitHub" button.
-
-. Grant Travis CI permissions to access your GitHub account.
- You can find more information about the required permissions here:
- https://docs.travis-ci.com/user/github-oauth-scopes
-
-. Open your Travis CI profile page: https://travis-ci.org/profile
-
-. Enable Travis CI builds for your Git fork.
-
-After the initial setup, Travis CI will run whenever you push new changes
+After the initial setup, CI will run whenever you push new changes
to your fork of Git on GitHub. You can monitor the test state of all your
-branches here: https://travis-ci.org/__<Your GitHub handle>__/git/branches
+branches here: https://github.com/<Your GitHub handle>/git/actions/workflows/main.yml
If a branch did not pass all test cases then it is marked with a red
-cross. In that case you can click on the failing Travis CI job and
-scroll all the way down in the log. Find the line "<-- Click here to see
-detailed test output!" and click on the triangle next to the log line
-number to expand the detailed test output. Here is such a failing
-example: https://travis-ci.org/git/git/jobs/122676187
-
-Fix the problem and push your fix to your Git fork. This will trigger
-a new Travis CI build to ensure all tests pass.
+cross. In that case you can click on the failing job and navigate to
+"ci/run-build-and-tests.sh" and/or "ci/print-test-failures.sh". You
+can also download "Artifacts" which are tarred (or zipped) archives
+with test data relevant for debugging.
+
+Then fix the problem and push your fix to your GitHub fork. This will
+trigger a new CI build to ensure all tests pass.
[[mua]]
== MUA specific hints
diff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt
index 9468e85..4d047c1 100644
--- a/Documentation/config/blame.txt
+++ b/Documentation/config/blame.txt
@@ -27,7 +27,7 @@ blame.ignoreRevsFile::
file names will reset the list of ignored revisions. This option will
be handled before the command line option `--ignore-revs-file`.
-blame.markUnblamables::
+blame.markUnblamableLines::
Mark lines that were changed by an ignored revision that we could not
attribute to another commit with a '*' in the output of
linkgit:git-blame[1].
diff --git a/Documentation/config/color.txt b/Documentation/config/color.txt
index d5daacb..e05d520 100644
--- a/Documentation/config/color.txt
+++ b/Documentation/config/color.txt
@@ -127,8 +127,9 @@ color.interactive.<slot>::
interactive commands.
color.pager::
- A boolean to enable/disable colored output when the pager is in
- use (default is true).
+ A boolean to specify whether `auto` color modes should colorize
+ output going to the pager. Defaults to true; set this to false
+ if your pager does not understand ANSI color codes.
color.push::
A boolean to enable/disable color in push errors. May be set to
diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index 2d3331f..32f8483 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -118,9 +118,10 @@ diff.orderFile::
relative to the top of the working tree.
diff.renameLimit::
- The number of files to consider when performing the copy/rename
- detection; equivalent to the 'git diff' option `-l`. This setting
- has no effect if rename detection is turned off.
+ The number of files to consider in the exhaustive portion of
+ copy/rename detection; equivalent to the 'git diff' option
+ `-l`. If not set, the default value is currently 1000. This
+ setting has no effect if rename detection is turned off.
diff.renames::
Whether and how Git detects renames. If set to "false",
diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index 6af6f5e..63748c0 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -69,7 +69,8 @@ fetch.negotiationAlgorithm::
setting defaults to "skipping".
Unknown values will cause 'git fetch' to error out.
+
-See also the `--negotiation-tip` option for linkgit:git-fetch[1].
+See also the `--negotiate-only` and `--negotiation-tip` options to
+linkgit:git-fetch[1].
fetch.showForcedUpdates::
Set to false to enable `--no-show-forced-updates` in
diff --git a/Documentation/config/gui.txt b/Documentation/config/gui.txt
index d30831a..0c087fd 100644
--- a/Documentation/config/gui.txt
+++ b/Documentation/config/gui.txt
@@ -11,7 +11,7 @@ gui.displayUntracked::
in the file list. The default is "true".
gui.encoding::
- Specifies the default encoding to use for displaying of
+ Specifies the default character encoding to use for displaying of
file contents in linkgit:git-gui[1] and linkgit:gitk[1].
It can be overridden by setting the 'encoding' attribute
for relevant files (see linkgit:gitattributes[5]).
diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt
index cb2ed58..e27cc63 100644
--- a/Documentation/config/merge.txt
+++ b/Documentation/config/merge.txt
@@ -14,7 +14,7 @@ merge.defaultToUpstream::
branches at the remote named by `branch.<current branch>.remote`
are consulted, and then they are mapped via `remote.<remote>.fetch`
to their corresponding remote-tracking branches, and the tips of
- these tracking branches are merged.
+ these tracking branches are merged. Defaults to true.
merge.ff::
By default, Git does not create an extra merge commit when merging
@@ -33,10 +33,12 @@ merge.verifySignatures::
include::fmt-merge-msg.txt[]
merge.renameLimit::
- The number of files to consider when performing rename detection
- during a merge; if not specified, defaults to the value of
- diff.renameLimit. This setting has no effect if rename detection
- is turned off.
+ The number of files to consider in the exhaustive portion of
+ rename detection during a merge. If not specified, defaults
+ to the value of diff.renameLimit. If neither
+ merge.renameLimit nor diff.renameLimit are specified,
+ currently defaults to 7000. This setting has no effect if
+ rename detection is turned off.
merge.renames::
Whether Git detects renames. If set to "false", rename detection
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index c0844d8..763f7af 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -99,12 +99,23 @@ pack.packSizeLimit::
packing to a file when repacking, i.e. the git:// protocol
is unaffected. It can be overridden by the `--max-pack-size`
option of linkgit:git-repack[1]. Reaching this limit results
- in the creation of multiple packfiles; which in turn prevents
- bitmaps from being created.
- The minimum size allowed is limited to 1 MiB.
- The default is unlimited.
- Common unit suffixes of 'k', 'm', or 'g' are
- supported.
+ in the creation of multiple packfiles.
++
+Note that this option is rarely useful, and may result in a larger total
+on-disk size (because Git will not store deltas between packs), as well
+as worse runtime performance (object lookup within multiple packs is
+slower than a single pack, and optimizations like reachability bitmaps
+cannot cope with multiple packs).
++
+If you need to actively run Git using smaller packfiles (e.g., because your
+filesystem does not support large files), this option may help. But if
+your goal is to transmit a packfile over a medium that supports limited
+sizes (e.g., removable media that cannot store the whole repository),
+you are likely better off creating a single large packfile and splitting
+it using a generic multi-volume archive tool (e.g., Unix `split`).
++
+The minimum size allowed is limited to 1 MiB. The default is unlimited.
+Common unit suffixes of 'k', 'm', or 'g' are supported.
pack.useBitmaps::
When true, git will use pack bitmaps (if available) when packing
diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt
index f2667b2..6320336 100644
--- a/Documentation/config/push.txt
+++ b/Documentation/config/push.txt
@@ -24,15 +24,14 @@ push.default::
* `tracking` - This is a deprecated synonym for `upstream`.
-* `simple` - in centralized workflow, work like `upstream` with an
- added safety to refuse to push if the upstream branch's name is
- different from the local one.
+* `simple` - pushes the current branch with the same name on the remote.
+
-When pushing to a remote that is different from the remote you normally
-pull from, work as `current`. This is the safest option and is suited
-for beginners.
+If you are working on a centralized workflow (pushing to the same repository you
+pull from, which is typically `origin`), then you need to configure an upstream
+branch with the same name.
+
-This mode has become the default in Git 2.0.
+This mode is the default since Git 2.0, and is the safest option suited for
+beginners.
* `matching` - push all branches having the same name on both ends.
This makes the repository you are pushing to remember the set of
diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt
index cbc5af4..50baa5d 100644
--- a/Documentation/config/sendemail.txt
+++ b/Documentation/config/sendemail.txt
@@ -8,9 +8,6 @@ sendemail.smtpEncryption::
See linkgit:git-send-email[1] for description. Note that this
setting is not subject to the 'identity' mechanism.
-sendemail.smtpssl (deprecated)::
- Deprecated alias for 'sendemail.smtpEncryption = ssl'.
-
sendemail.smtpsslcertpath::
Path to ca-certificates (either a directory or a single file).
Set it to an empty string to disable certificate verification.
diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt
index d7a63c8..ee454f8 100644
--- a/Documentation/config/submodule.txt
+++ b/Documentation/config/submodule.txt
@@ -58,8 +58,9 @@ submodule.active::
commands. See linkgit:gitsubmodules[7] for details.
submodule.recurse::
- Specifies if commands recurse into submodules by default. This
- applies to all commands that have a `--recurse-submodules` option
+ A boolean indicating if commands should enable the `--recurse-submodules`
+ option by default.
+ Applies to all commands that support this option
(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
`restore` and `switch`) except `clone` and `ls-files`.
Defaults to false.
diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt
index 505126a..b49429e 100644
--- a/Documentation/config/transfer.txt
+++ b/Documentation/config/transfer.txt
@@ -52,13 +52,17 @@ If you have multiple hideRefs values, later entries override earlier ones
(and entries in more-specific config files override less-specific ones).
+
If a namespace is in use, the namespace prefix is stripped from each
-reference before it is matched against `transfer.hiderefs` patterns.
+reference before it is matched against `transfer.hiderefs` patterns. In
+order to match refs before stripping, add a `^` in front of the ref name. If
+you combine `!` and `^`, `!` must be specified first.
++
For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
-is omitted from the advertisements but `refs/heads/master` and
-`refs/namespaces/bar/refs/heads/master` are still advertised as so-called
-"have" lines. In order to match refs before stripping, add a `^` in front of
-the ref name. If you combine `!` and `^`, `!` must be specified first.
+is omitted from the advertisements. If `uploadpack.allowRefInWant` is set,
+`upload-pack` will treat `want-ref refs/heads/master` in a protocol v2
+`fetch` command as if `refs/namespaces/foo/refs/heads/master` did not exist.
+`receive-pack`, on the other hand, will still advertise the object id the
+ref is pointing to without mentioning its name (a so-called ".have" line).
+
Even if you hide refs, a client may still be able to steal the target
objects via the techniques described in the "SECURITY" section of the
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 530d115..c89d530 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -588,11 +588,17 @@ When used together with `-B`, omit also the preimage in the deletion part
of a delete/create pair.
-l<num>::
- The `-M` and `-C` options require O(n^2) processing time where n
- is the number of potential rename/copy targets. This
- option prevents rename/copy detection from running if
- the number of rename/copy targets exceeds the specified
- number.
+ The `-M` and `-C` options involve some preliminary steps that
+ can detect subsets of renames/copies cheaply, followed by an
+ exhaustive fallback portion that compares all remaining
+ unpaired destinations to all relevant sources. (For renames,
+ only remaining unpaired sources are relevant; for copies, all
+ original sources are relevant.) For N sources and
+ destinations, this exhaustive check is O(N^2). This option
+ prevents the exhaustive portion of rename/copy detection from
+ running if the number of source/destination files involved
+ exceeds the specified number. Defaults to diff.renameLimit.
+ Note that a value of 0 is treated as unlimited.
ifndef::git-format-patch[]
--diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]::
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 9e7b4e1..e967ff1 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -62,8 +62,17 @@ The argument to this option may be a glob on ref names, a ref, or the (possibly
abbreviated) SHA-1 of a commit. Specifying a glob is equivalent to specifying
this option multiple times, one for each matching ref name.
+
-See also the `fetch.negotiationAlgorithm` configuration variable
-documented in linkgit:git-config[1].
+See also the `fetch.negotiationAlgorithm` and `push.negotiate`
+configuration variables documented in linkgit:git-config[1], and the
+`--negotiate-only` option below.
+
+--negotiate-only::
+ Do not fetch anything from the server, and instead print the
+ ancestors of the provided `--negotiation-tip=*` arguments,
+ which we have in common with the server.
++
+Internally this is used to implement the `push.negotiate` option, see
+linkgit:git-config[1].
--dry-run::
Show what would be done, without making any changes.
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 8714dfc..0a4a984 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -178,6 +178,8 @@ default. You can use `--no-utf8` to override this.
--abort::
Restore the original branch and abort the patching operation.
+ Revert contents of files involved in the am operation to their
+ pre-am state.
--quit::
Abort the patching operation but keep HEAD and the index
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a5..5449767 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -118,7 +118,8 @@ OPTIONS
Reset <branchname> to <startpoint>, even if <branchname> exists
already. Without `-f`, 'git branch' refuses to change an existing branch.
In combination with `-d` (or `--delete`), allow deleting the
- branch irrespective of its merged status. In combination with
+ branch irrespective of its merged status, or whether it even
+ points to a valid commit. In combination with
`-m` (or `--move`), allow renaming the branch even if the new
branch name already exists, the same applies for `-c` (or `--copy`).
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
index 66e88c2..d8817bf 100644
--- a/Documentation/git-bugreport.txt
+++ b/Documentation/git-bugreport.txt
@@ -40,8 +40,8 @@ OPTIONS
-------
-o <path>::
--output-directory <path>::
- Place the resulting bug report file in `<path>` instead of the root of
- the Git repository.
+ Place the resulting bug report file in `<path>` instead of the current
+ directory.
-s <format>::
--suffix <format>::
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 53804ca..ac0d003 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -18,21 +18,48 @@ SYNOPSIS
DESCRIPTION
-----------
-Some workflows require that one or more branches of development on one
-machine be replicated on another machine, but the two machines cannot
-be directly connected, and therefore the interactive Git protocols (git,
-ssh, http) cannot be used.
-
-The 'git bundle' command packages objects and references in an archive
-at the originating machine, which can then be imported into another
-repository using 'git fetch', 'git pull', or 'git clone',
-after moving the archive by some means (e.g., by sneakernet).
-
-As no
-direct connection between the repositories exists, the user must specify a
-basis for the bundle that is held by the destination repository: the
-bundle assumes that all objects in the basis are already in the
-destination repository.
+Create, unpack, and manipulate "bundle" files. Bundles are used for
+the "offline" transfer of Git objects without an active "server"
+sitting on the other side of the network connection.
+
+They can be used to create both incremental and full backups of a
+repository, and to relay the state of the references in one repository
+to another.
+
+Git commands that fetch or otherwise "read" via protocols such as
+`ssh://` and `https://` can also operate on bundle files. It is
+possible linkgit:git-clone[1] a new repository from a bundle, to use
+linkgit:git-fetch[1] to fetch from one, and to list the references
+contained within it with linkgit:git-ls-remote[1]. There's no
+corresponding "write" support, i.e.a 'git push' into a bundle is not
+supported.
+
+See the "EXAMPLES" section below for examples of how to use bundles.
+
+BUNDLE FORMAT
+-------------
+
+Bundles are `.pack` files (see linkgit:git-pack-objects[1]) with a
+header indicating what references are contained within the bundle.
+
+Like the the packed archive format itself bundles can either be
+self-contained, or be created using exclusions.
+See the "OBJECT PREREQUISITES" section below.
+
+Bundles created using revision exclusions are "thin packs" created
+using the `--thin` option to linkgit:git-pack-objects[1], and
+unbundled using the `--fix-thin` option to linkgit:git-index-pack[1].
+
+There is no option to create a "thick pack" when using revision
+exclusions, users should not be concerned about the difference. By
+using "thin packs" bundles created using exclusions are smaller in
+size. That they're "thin" under the hood is merely noted here as a
+curiosity, and as a reference to other documentation
+
+See link:technical/bundle-format.html[the `bundle-format`
+documentation] for more details and the discussion of "thin pack" in
+link:technical/pack-format.html[the pack format documentation] for
+further details.
OPTIONS
-------
@@ -117,28 +144,88 @@ unbundle <file>::
SPECIFYING REFERENCES
---------------------
-'git bundle' will only package references that are shown by
-'git show-ref': this includes heads, tags, and remote heads. References
-such as `master~1` cannot be packaged, but are perfectly suitable for
-defining the basis. More than one reference may be packaged, and more
-than one basis can be specified. The objects packaged are those not
-contained in the union of the given bases. Each basis can be
-specified explicitly (e.g. `^master~10`), or implicitly (e.g.
-`master~10..master`, `--since=10.days.ago master`).
+Revisions must accompanied by reference names to be packaged in a
+bundle.
+
+More than one reference may be packaged, and more than one set of prerequisite objects can
+be specified. The objects packaged are those not contained in the
+union of the prerequisites.
+
+The 'git bundle create' command resolves the reference names for you
+using the same rules as `git rev-parse --abbrev-ref=loose`. Each
+prerequisite can be specified explicitly (e.g. `^master~10`), or implicitly
+(e.g. `master~10..master`, `--since=10.days.ago master`).
+
+All of these simple cases are OK (assuming we have a "master" and
+"next" branch):
+
+----------------
+$ git bundle create master.bundle master
+$ echo master | git bundle create master.bundle --stdin
+$ git bundle create master-and-next.bundle master next
+$ (echo master; echo next) | git bundle create master-and-next.bundle --stdin
+----------------
+
+And so are these (and the same but omitted `--stdin` examples):
+
+----------------
+$ git bundle create recent-master.bundle master~10..master
+$ git bundle create recent-updates.bundle master~10..master next~5..next
+----------------
+
+A revision name or a range whose right-hand-side cannot be resolved to
+a reference is not accepted:
+
+----------------
+$ git bundle create HEAD.bundle $(git rev-parse HEAD)
+fatal: Refusing to create empty bundle.
+$ git bundle create master-yesterday.bundle master~10..master~5
+fatal: Refusing to create empty bundle.
+----------------
+
+OBJECT PREREQUISITES
+--------------------
+
+When creating bundles it is possible to create a self-contained bundle
+that can be unbundled in a repository with no common history, as well
+as providing negative revisions to exclude objects needed in the
+earlier parts of the history.
+
+Feeding a revision such as `new` to `git bundle create` will create a
+bundle file that contains all the objects reachable from the revision
+`new`. That bundle can be unbundled in any repository to obtain a full
+history that leads to the revision `new`:
+
+----------------
+$ git bundle create full.bundle new
+----------------
+
+A revision range such as `old..new` will produce a bundle file that
+will require the revision `old` (and any objects reachable from it)
+to exist for the bundle to be "unbundle"-able:
+
+----------------
+$ git bundle create full.bundle old..new
+----------------
+
+A self-contained bundle without any prerequisites can be extracted
+into anywhere, even into an empty repository, or be cloned from
+(i.e., `new`, but not `old..new`).
-It is very important that the basis used be held by the destination.
It is okay to err on the side of caution, causing the bundle file
to contain objects already in the destination, as these are ignored
when unpacking at the destination.
-`git clone` can use any bundle created without negative refspecs
-(e.g., `new`, but not `old..new`).
If you want to match `git clone --mirror`, which would include your
refs such as `refs/remotes/*`, use `--all`.
If you want to provide the same set of refs that a clone directly
from the source repository would get, use `--branches --tags` for
the `<git-rev-list-args>`.
+The 'git bundle verify' command can be used to check whether your
+recipient repository has the required prerequisite commits for a
+bundle.
+
EXAMPLES
--------
@@ -149,7 +236,7 @@ but we can move data from A to B via some mechanism (CD, email, etc.).
We want to update R2 with development made on the branch master in R1.
To bootstrap the process, you can first create a bundle that does not have
-any basis. You can use a tag to remember up to what commit you last
+any prerequisites. You can use a tag to remember up to what commit you last
processed, in order to make it easy to later update the other repository
with an incremental bundle:
@@ -200,7 +287,7 @@ machineB$ git pull
If you know up to what commit the intended recipient repository should
have the necessary objects, you can use that knowledge to specify the
-basis, giving a cut-off point to limit the revisions and objects that go
+prerequisites, giving a cut-off point to limit the revisions and objects that go
in the resulting bundle. The previous example used the lastR2bundle tag
for this purpose, but you can use any other options that you would give to
the linkgit:git-log[1] command. Here are more examples:
@@ -211,7 +298,7 @@ You can use a tag that is present in both:
$ git bundle create mybundle v1.0.0..master
----------------
-You can use a basis based on time:
+You can use a prerequisite based on time:
----------------
$ git bundle create mybundle --since=10.days master
@@ -224,7 +311,7 @@ $ git bundle create mybundle -10 master
----------------
You can run `git-bundle verify` to see if you can extract from a bundle
-that was created with a basis:
+that was created with a prerequisite:
----------------
$ git bundle verify mybundle
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
index f58e9c4..6cea9ab 100644
--- a/Documentation/git-column.txt
+++ b/Documentation/git-column.txt
@@ -39,7 +39,7 @@ OPTIONS
--indent=<string>::
String to be printed at the beginning of each line.
---nl=<N>::
+--nl=<string>::
String to be printed at the end of each line,
including newline character.
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 340c5fb..95fec5f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -72,7 +72,7 @@ OPTIONS
-p::
--patch::
- Use the interactive patch selection interface to chose
+ Use the interactive patch selection interface to choose
which changes to commit. See linkgit:git-add[1] for
details.
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 5cddada..992225f 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -71,6 +71,10 @@ codes are:
On success, the command returns the exit code 0.
+A list of all available configuration variables can be obtained using the
+`git help --config` command.
+
+[[OPTIONS]]
OPTIONS
-------
@@ -143,7 +147,13 @@ See also <<FILES>>.
-f config-file::
--file config-file::
- Use the given config file instead of the one specified by GIT_CONFIG.
+ For writing options: write to the specified file rather than the
+ repository `.git/config`.
++
+For reading options: read only from the specified file rather than from all
+available files.
++
+See also <<FILES>>.
--blob blob::
Similar to `--file` but use the given blob instead of a file. E.g.
@@ -325,21 +335,14 @@ All writing options will per default write to the repository specific
configuration file. Note that this also affects options like `--replace-all`
and `--unset`. *'git config' will only ever change one file at a time*.
-You can override these rules either by command-line options or by environment
-variables. The `--global`, `--system` and `--worktree` options will limit
-the file used to the global, system-wide or per-worktree file respectively.
-The `GIT_CONFIG` environment variable has a similar effect, but you
-can specify any filename you want.
+You can override these rules using the `--global`, `--system`,
+`--local`, `--worktree`, and `--file` command-line options; see
+<<OPTIONS>> above.
ENVIRONMENT
-----------
-GIT_CONFIG::
- Take the configuration from the given file instead of .git/config.
- Using the "--global" option forces this to ~/.gitconfig. Using the
- "--system" option forces this to $(prefix)/etc/gitconfig.
-
GIT_CONFIG_GLOBAL::
GIT_CONFIG_SYSTEM::
Take the configuration from the given files instead from global or
@@ -367,6 +370,12 @@ This is useful for cases where you want to spawn multiple git commands
with a common configuration but cannot depend on a configuration file,
for example when writing scripts.
+GIT_CONFIG::
+ If no `--file` option is provided to `git config`, use the file
+ given by `GIT_CONFIG` as if it were provided via `--file`. This
+ variable has no effect on other Git commands, and is mostly for
+ historical compatibility; there is generally no reason to use it
+ instead of the `--file` option.
[[EXAMPLES]]
EXAMPLES
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index f2e4a47..4dc57ed 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -99,7 +99,7 @@ looks like
------
-Only anonymous access is provided by pserve by default. To commit you
+Only anonymous access is provided by pserver by default. To commit you
will have to create pserver accounts, simply add a gitcvs.authdb
setting in the config file of the repositories you want the cvsserver
to allow writes to, for example:
@@ -114,21 +114,20 @@ The format of these files is username followed by the encrypted password,
for example:
------
- myuser:$1Oyx5r9mdGZ2
- myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./
+ myuser:sqkNi8zPf01HI
+ myuser:$1$9K7FzU28$VfF6EoPYCJEYcVQwATgOP/
+ myuser:$5$.NqmNH1vwfzGpV8B$znZIcumu1tNLATgV2l6e1/mY8RzhUDHMOaVOeL1cxV3
------
You can use the 'htpasswd' facility that comes with Apache to make these
-files, but Apache's MD5 crypt method differs from the one used by most C
-library's crypt() function, so don't use the -m option.
+files, but only with the -d option (or -B if your system suports it).
-Alternatively you can produce the password with perl's crypt() operator:
------
- perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password
------
+Preferably use the system specific utility that manages password hash
+creation in your platform (e.g. mkpasswd in Linux, encrypt in OpenBSD or
+pwhash in NetBSD) and paste it in the right location.
Then provide your password via the pserver method, for example:
------
- cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name>
+ cvs -d:pserver:someuser:somepassword@server:/path/repo.git co <HEAD_name>
------
No special setup is needed for SSH access, other than having Git tools
in the PATH. If you have clients that do not accept the CVS_SERVER
@@ -138,7 +137,7 @@ Note: Newer CVS versions (>= 1.12.11) also support specifying
CVS_SERVER directly in CVSROOT like
------
-cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
+ cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
------
This has the advantage that it will be saved in your 'CVS/Root' files and
you don't need to worry about always setting the correct environment
@@ -186,8 +185,8 @@ allowing access over SSH.
+
--
------
- export CVSROOT=:ext:user@server:/var/git/project.git
- export CVS_SERVER="git cvsserver"
+ export CVSROOT=:ext:user@server:/var/git/project.git
+ export CVS_SERVER="git cvsserver"
------
--
4. For SSH clients that will make commits, make sure their server-side
@@ -203,7 +202,7 @@ allowing access over SSH.
`project-master` directory:
+
------
- cvs co -d project-master master
+ cvs co -d project-master master
------
[[dbbackend]]
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index a88f6ae..c6a79c2 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -63,9 +63,10 @@ OPTIONS
Automatically implies --tags.
--abbrev=<n>::
- Instead of using the default 7 hexadecimal digits as the
- abbreviated object name, use <n> digits, or as many digits
- as needed to form a unique object name. An <n> of 0
+ Instead of using the default number of hexadecimal digits (which
+ will vary according to the number of objects in the repository with
+ a default of 7) of the abbreviated object name, use <n> digits, or
+ as many digits as needed to form a unique object name. An <n> of 0
will suppress long format, only showing the closest tag.
--candidates=<n>::
@@ -139,8 +140,11 @@ at the end.
The number of additional commits is the number
of commits which would be displayed by "git log v1.0.4..parent".
-The hash suffix is "-g" + unambiguous abbreviation for the tip commit
-of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`).
+The hash suffix is "-g" + an unambigous abbreviation for the tip commit
+of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`). The
+length of the abbreviation scales as the repository grows, using the
+approximate number of objects in the repository and a bit of math
+around the birthday paradox, and defaults to a minimum of 7.
The "g" prefix stands for "git" and is used to allow describing the version of
a software depending on the SCM the software is managed with. This is useful
in an environment where people may use different SCMs.
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index 7f4c8a8..6236c75 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -51,16 +51,20 @@ files on disk.
--staged is a synonym of --cached.
+
If --merge-base is given, instead of using <commit>, use the merge base
-of <commit> and HEAD. `git diff --merge-base A` is equivalent to
-`git diff $(git merge-base A HEAD)`.
+of <commit> and HEAD. `git diff --cached --merge-base A` is equivalent to
+`git diff --cached $(git merge-base A HEAD)`.
-'git diff' [<options>] <commit> [--] [<path>...]::
+'git diff' [<options>] [--merge-base] <commit> [--] [<path>...]::
This form is to view the changes you have in your
working tree relative to the named <commit>. You can
use HEAD to compare it with the latest commit, or a
branch name to compare with the tip of a different
branch.
++
+If --merge-base is given, instead of using <commit>, use the merge base
+of <commit> and HEAD. `git diff --merge-base A` is equivalent to
+`git diff $(git merge-base A HEAD)`.
'git diff' [<options>] [--merge-base] <commit> <commit> [--] [<path>...]::
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index 9067c20..550c16c 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -133,7 +133,7 @@ remember to run that, set `fetch.prune` globally, or
linkgit:git-config[1].
Here's where things get tricky and more specific. The pruning feature
-doesn't actually care about branches, instead it'll prune local <->
+doesn't actually care about branches, instead it'll prune local <-->
remote-references as a function of the refspec of the remote (see
`<refspec>` and <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> above).
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 1bbf865..0498e7b 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -39,7 +39,9 @@ OPTIONS
full ref name (including prefix) will be printed. If 'auto' is
specified, then if the output is going to a terminal, the ref names
are shown as if 'short' were given, otherwise no ref names are
- shown. The default option is 'short'.
+ shown. The option `--decorate` is short-hand for `--decorate=short`.
+ Default to configuration value of `log.decorate` if configured,
+ otherwise, `auto`.
--decorate-refs=<pattern>::
--decorate-refs-exclude=<pattern>::
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 3819fad..e4f3352 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -61,6 +61,8 @@ merge has resulted in conflicts.
OPTIONS
-------
+:git-merge: 1
+
include::merge-options.txt[]
-m <msg>::
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 25d9fbe..dbfd1f9 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -128,10 +128,10 @@ depth is 4095.
into multiple independent packfiles, each not larger than the
given size. The size can be suffixed with
"k", "m", or "g". The minimum size allowed is limited to 1 MiB.
- This option
- prevents the creation of a bitmap index.
The default is unlimited, unless the config variable
- `pack.packSizeLimit` is set.
+ `pack.packSizeLimit` is set. Note that this option may result in
+ a larger and slower repository; see the discussion in
+ `pack.packSizeLimit`.
--honor-pack-keep::
This flag causes an object already in a local pack that
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 5c3fb67..aef757e 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -15,14 +15,17 @@ SYNOPSIS
DESCRIPTION
-----------
-Incorporates changes from a remote repository into the current
-branch. In its default mode, `git pull` is shorthand for
-`git fetch` followed by `git merge FETCH_HEAD`.
-
-More precisely, 'git pull' runs 'git fetch' with the given
-parameters and calls 'git merge' to merge the retrieved branch
-heads into the current branch.
-With `--rebase`, it runs 'git rebase' instead of 'git merge'.
+Incorporates changes from a remote repository into the current branch.
+If the current branch is behind the remote, then by default it will
+fast-forward the current branch to match the remote. If the current
+branch and the remote have diverged, the user needs to specify how to
+reconcile the divergent branches with `--rebase` or `--no-rebase` (or
+the corresponding configuration option in `pull.rebase`).
+
+More precisely, `git pull` runs `git fetch` with the given parameters
+and then depending on configuration options or command line flags,
+will call either `git rebase` or `git merge` to reconcile diverging
+branches.
<repository> should be the name of a remote repository as
passed to linkgit:git-fetch[1]. <refspec> can name an
@@ -117,7 +120,7 @@ When set to `preserve` (deprecated in favor of `merges`), rebase with the
`--preserve-merges` option passed to `git rebase` so that locally created
merge commits will not be flattened.
+
-When false, merge the current branch into the upstream branch.
+When false, merge the upstream branch into the current branch.
+
When `interactive`, enable the interactive mode of rebase.
+
@@ -132,7 +135,7 @@ published that history already. Do *not* use this option
unless you have read linkgit:git-rebase[1] carefully.
--no-rebase::
- Override earlier --rebase.
+ This is shorthand for --rebase=false.
Options related to fetching
~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index a953c7c..2f25aa3 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -244,8 +244,8 @@ Imagine that you have to rebase what you have already published.
You will have to bypass the "must fast-forward" rule in order to
replace the history you originally published with the rebased history.
If somebody else built on top of your original history while you are
-rebasing, the tip of the branch at the remote may advance with her
-commit, and blindly pushing with `--force` will lose her work.
+rebasing, the tip of the branch at the remote may advance with their
+commit, and blindly pushing with `--force` will lose their work.
+
This option allows you to say that you expect the history you are
updating is what you rebased and want to replace. If the remote ref
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 55af6fd..73d49ec 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -340,9 +340,7 @@ See also INCOMPATIBLE OPTIONS below.
-m::
--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. This is the default.
+ Using merging strategies to rebase (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
@@ -354,9 +352,8 @@ See also INCOMPATIBLE OPTIONS below.
-s <strategy>::
--strategy=<strategy>::
- Use the given merge strategy.
- If there is no `-s` option 'git merge-recursive' is used
- instead. This implies --merge.
+ Use the given merge strategy, instead of the default
+ `recursive`. This implies `--merge`.
+
Because 'git rebase' replays each commit from the working branch
on top of the <upstream> branch using the given strategy, using
@@ -530,7 +527,7 @@ The `--rebase-merges` mode is similar in spirit to the deprecated
where commits can be reordered, inserted and dropped at will.
+
It is currently only possible to recreate the merge commits using the
-`recursive` merge strategy; Different merge strategies can be used only via
+`recursive` merge strategy; different merge strategies can be used only via
explicit `exec git merge -s <strategy> [...]` commands.
+
See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
@@ -1219,12 +1216,16 @@ successful merge so that the user can edit the message.
If a `merge` command fails for any reason other than merge conflicts (i.e.
when the merge operation did not even start), it is rescheduled immediately.
-At this time, the `merge` command will *always* use the `recursive`
-merge strategy for regular merges, and `octopus` for octopus merges,
-with no way to choose a different one. To work around
-this, an `exec` command can be used to call `git merge` explicitly,
-using the fact that the labels are worktree-local refs (the ref
-`refs/rewritten/onto` would correspond to the label `onto`, for example).
+By default, the `merge` command will use the `recursive` merge
+strategy for regular merges, and `octopus` for octopus merges. One
+can specify a default strategy for all merges using the `--strategy`
+argument when invoking rebase, or can override specific merges in the
+interactive list of commands by using an `exec` command to call `git
+merge` explicitly with a `--strategy` argument. Note that when
+calling `git merge` explicitly like this, you can make use of the fact
+that the labels are worktree-local refs (the ref `refs/rewritten/onto`
+would correspond to the label `onto`, for example) in order to refer
+to the branches you want to merge.
Note: the first command (`label onto`) labels the revision onto which
the commits are rebased; The name `onto` is just a convention, as a nod
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index ef310f3..24c00c9 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -121,7 +121,9 @@ depth is 4095.
If specified, multiple packfiles may be created, which also
prevents the creation of a bitmap index.
The default is unlimited, unless the config variable
- `pack.packSizeLimit` is set.
+ `pack.packSizeLimit` is set. Note that this option may result in
+ a larger and slower repository; see the discussion in
+ `pack.packSizeLimit`.
-b::
--write-bitmap-index::
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 93708ae..3db4eab 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -167,6 +167,14 @@ Sending
`sendemail.envelopeSender` configuration variable; if that is
unspecified, choosing the envelope sender is left to your MTA.
+--sendmail-cmd=<command>::
+ Specify a command to run to send the email. The command should
+ be sendmail-like; specifically, it must support the `-i` option.
+ The command will be executed in the shell if necessary. Default
+ is the value of `sendemail.sendmailcmd`. If unspecified, and if
+ --smtp-server is also unspecified, git-send-email will search
+ for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH.
+
--smtp-encryption=<encryption>::
Specify the encryption to use, either 'ssl' or 'tls'. Any other
value reverts to plain SMTP. Default is the value of
@@ -211,13 +219,16 @@ a password is obtained using 'git-credential'.
--smtp-server=<host>::
If set, specifies the outgoing SMTP server to use (e.g.
- `smtp.example.com` or a raw IP address). Alternatively it can
- specify a full pathname of a sendmail-like program instead;
- the program must support the `-i` option. Default value can
- be specified by the `sendemail.smtpServer` configuration
- option; the built-in default is to search for `sendmail` in
- `/usr/sbin`, `/usr/lib` and $PATH if such program is
- available, falling back to `localhost` otherwise.
+ `smtp.example.com` or a raw IP address). If unspecified, and if
+ `--sendmail-cmd` is also unspecified, the default is to search
+ for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH if such a
+ program is available, falling back to `localhost` otherwise.
++
+For backward compatibility, this option can also specify a full pathname
+of a sendmail-like program instead; the program must support the `-i`
+option. This method does not support passing arguments or using plain
+command names. For those use cases, consider using `--sendmail-cmd`
+instead.
--smtp-server-port=<port>::
Specifies a port different from the default port (SMTP
diff --git a/Documentation/git-version.txt b/Documentation/git-version.txt
new file mode 100644
index 0000000..80fa775
--- /dev/null
+++ b/Documentation/git-version.txt
@@ -0,0 +1,28 @@
+git-version(1)
+==============
+
+NAME
+----
+git-version - Display version information about Git
+
+SYNOPSIS
+--------
+[verse]
+'git version' [--build-options]
+
+DESCRIPTION
+-----------
+With no options given, the version of 'git' is printed on the standard output.
+
+Note that `git --version` is identical to `git version` because the
+former is internally converted into the latter.
+
+OPTIONS
+-------
+--build-options::
+ Include additional information about how git was built for diagnostic
+ purposes.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index f1bb1fa..8a7cbdd 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
SYNOPSIS
--------
[verse]
-'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
+'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] [-b <new-branch>] <path> [<commit-ish>]
'git worktree list' [--porcelain]
'git worktree lock' [--reason <string>] <worktree>
'git worktree move' <worktree> <new-path>
@@ -242,7 +242,7 @@ With `list`, annotate missing working trees as prunable if they are
older than `<time>`.
--reason <string>::
- With `lock`, an explanation why the working tree is locked.
+ With `lock` or with `add --lock`, an explanation why the working tree is locked.
<worktree>::
Working trees can be identified by path, either relative or
@@ -387,7 +387,7 @@ These annotations are:
------------
$ git worktree list
/path/to/linked-worktree abcd1234 [master]
-/path/to/locked-worktreee acbd5678 (brancha) locked
+/path/to/locked-worktree acbd5678 (brancha) locked
/path/to/prunable-worktree 5678abc (detached HEAD) prunable
------------
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 6dd241e..6bb06d0 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -41,6 +41,10 @@ OPTIONS
-------
--version::
Prints the Git suite version that the 'git' program came from.
++
+This option is internally converted to `git version ...` and accepts
+the same options as the linkgit:git-version[1] command. If `--help` is
+also given, it takes precedence over `--version`.
--help::
Prints the synopsis and a list of the most commonly used
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 53e7d5c..f8a1fc2 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -27,12 +27,11 @@ precedence, the last matching pattern decides the outcome):
them.
* Patterns read from a `.gitignore` file in the same directory
- as the path, or in any parent directory, with patterns in the
- higher level files (up to the toplevel of the work tree) being overridden
- by those in lower level files down to the directory containing the file.
- These patterns match relative to the location of the
- `.gitignore` file. A project normally includes such
- `.gitignore` files in its repository, containing patterns for
+ as the path, or in any parent directory (up to the top-level of the working
+ tree), with patterns in the higher level files being overridden by those in
+ lower level files down to the directory containing the file. These patterns
+ match relative to the location of the `.gitignore` file. A project normally
+ includes such `.gitignore` files in its repository, containing patterns for
files generated as part of the project build.
* Patterns read from `$GIT_DIR/info/exclude`.
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 59ef5ce..0e0b863 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -322,7 +322,7 @@ initiating this "pull". If Bob's work conflicts with what Alice did since
their histories forked, Alice will use her working tree and the index to
resolve conflicts, and existing local changes will interfere with the
conflict resolution process (Git will still perform the fetch but will
-refuse to merge --- Alice will have to get rid of her local changes in
+refuse to merge -- Alice will have to get rid of her local changes in
some way and pull again when this happens).
Alice can peek at what Bob did without merging first, using the "fetch"
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 67c7a50..c077971 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -146,8 +146,8 @@ current branch integrates with) obviously do not work, as there is no
<<def_revision,revision>> and you are "merging" another
<<def_branch,branch>>'s changes that happen to be a descendant of what
you have. In such a case, you do not make a new <<def_merge,merge>>
- <<def_commit,commit>> but instead just update to his
- revision. This will happen frequently on a
+ <<def_commit,commit>> but instead just update your branch to point at the same
+ revision as the branch you are merging. This will happen frequently on a
<<def_remote_tracking_branch,remote-tracking branch>> of a remote
<<def_repository,repository>>.
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index eb0aabd..86f277a 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -2,6 +2,9 @@
--no-commit::
Perform the merge and commit the result. This option can
be used to override --no-commit.
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
+
With --no-commit perform the merge and stop just before creating
a merge commit, to give the user a chance to inspect and further
@@ -39,6 +42,7 @@ set to `no` at the beginning of them.
to `MERGE_MSG` before being passed on to the commit machinery in the
case of a merge conflict.
+ifdef::git-merge[]
--ff::
--no-ff::
--ff-only::
@@ -47,6 +51,22 @@ set to `no` at the beginning of them.
default unless merging an annotated (and possibly signed) tag
that is not stored in its natural place in the `refs/tags/`
hierarchy, in which case `--no-ff` is assumed.
+endif::git-merge[]
+ifdef::git-pull[]
+--ff-only::
+ Only update to the new history if there is no divergent local
+ history. This is the default when no method for reconciling
+ divergent histories is provided (via the --rebase=* flags).
+
+--ff::
+--no-ff::
+ When merging rather than rebasing, specifies how a merge is
+ handled when the merged-in history is already a descendant of
+ the current history. If merging is requested, `--ff` is the
+ default unless merging an annotated (and possibly signed) tag
+ that is not stored in its natural place in the `refs/tags/`
+ hierarchy, in which case `--no-ff` is assumed.
+endif::git-pull[]
+
With `--ff`, when possible resolve the merge as a fast-forward (only
update the branch pointer to match the merged branch; do not create a
@@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.
+
With `--no-ff`, create a merge commit in all cases, even when the merge
could instead be resolved as a fast-forward.
+ifdef::git-merge[]
+
With `--ff-only`, resolve the merge as a fast-forward when possible.
When not possible, refuse to merge and exit with a non-zero status.
+endif::git-merge[]
-S[<keyid>]::
--gpg-sign[=<keyid>]::
@@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.
In addition to branch names, populate the log message with
one-line descriptions from at most <n> actual commits that are being
merged. See also linkgit:git-fmt-merge-msg[1].
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
+
With --no-log do not list one-line descriptions from the
actual commits being merged.
@@ -102,18 +127,25 @@ With --no-squash perform the merge and commit the result. This
option can be used to override --squash.
+
With --squash, --commit is not allowed, and will fail.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
--no-verify::
This option bypasses the pre-merge and commit-msg hooks.
See also linkgit:githooks[5].
+ifdef::git-pull[]
+ Only useful when merging.
+endif::git-pull[]
-s <strategy>::
--strategy=<strategy>::
Use the given merge strategy; can be supplied more than
once to specify them in the order they should be tried.
If there is no `-s` option, a built-in list of strategies
- is used instead ('git merge-recursive' when merging a single
- head, 'git merge-octopus' otherwise).
+ is used instead (`recursive` when merging a single head,
+ `octopus` otherwise).
-X <option>::
--strategy-option=<option>::
@@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.
default trust model, this means the signing key has been signed by
a trusted key. If the tip commit of the side branch is not signed
with a valid key, the merge is aborted.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
--summary::
--no-summary::
@@ -154,7 +190,8 @@ endif::git-pull[]
--autostash::
--no-autostash::
Automatically create a temporary stash entry before the operation
- begins, and apply it after the operation ends. This means
+ begins, record it in the special ref `MERGE_AUTOSTASH`
+ and apply it after the operation ends. This means
that you can run the operation on a dirty worktree. However, use
with care: the final stash application after a successful
merge might result in non-trivial conflicts.
@@ -166,3 +203,7 @@ endif::git-pull[]
projects that started their lives independently. As that is
a very rare occasion, no configuration variable to enable
this by default exists and will not be added.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 2912de7..210f0f8 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -6,13 +6,6 @@ backend 'merge strategies' to be chosen with `-s` option. Some strategies
can also take their own options, which can be passed by giving `-X<option>`
arguments to `git merge` and/or `git pull`.
-resolve::
- This can only resolve two heads (i.e. the current branch
- and another branch you pulled from) using a 3-way merge
- algorithm. It tries to carefully detect criss-cross
- merge ambiguities and is considered generally safe and
- fast.
-
recursive::
This can only resolve two heads using a 3-way merge
algorithm. When there is more than one common
@@ -23,9 +16,9 @@ recursive::
causing mismerges by tests done on actual merge commits
taken from Linux 2.6 kernel development history.
Additionally this can detect and handle merges involving
- renames, but currently cannot make use of detected
- copies. This is the default merge strategy when pulling
- or merging one branch.
+ renames. It does not make use of detected copies. This
+ is the default merge strategy when pulling or merging one
+ branch.
+
The 'recursive' strategy can take the following options:
@@ -44,17 +37,14 @@ theirs;;
no 'theirs' merge strategy to confuse this merge option with.
patience;;
- With this option, 'merge-recursive' spends a little extra time
- to avoid mismerges that sometimes occur due to unimportant
- matching lines (e.g., braces from distinct functions). Use
- this when the branches to be merged have diverged wildly.
- See also linkgit:git-diff[1] `--patience`.
+ Deprecated synonym for `diff-algorithm=patience`.
diff-algorithm=[patience|minimal|histogram|myers];;
- Tells 'merge-recursive' to use a different diff algorithm, which
- can help avoid mismerges that occur due to unimportant matching
- lines (such as braces from distinct functions). See also
- linkgit:git-diff[1] `--diff-algorithm`.
+ Use a different diff algorithm while merging, which can help
+ avoid mismerges that occur due to unimportant matching lines
+ (such as braces from distinct functions). See also
+ linkgit:git-diff[1] `--diff-algorithm`. Defaults to the
+ `diff.algorithm` config setting.
ignore-space-change;;
ignore-all-space;;
@@ -105,6 +95,26 @@ subtree[=<path>];;
is prefixed (or stripped from the beginning) to make the shape of
two trees to match.
+ort::
+ This is meant as a drop-in replacement for the `recursive`
+ algorithm (as reflected in its acronym -- "Ostensibly
+ Recursive's Twin"), and will likely replace it in the future.
+ It fixes corner cases that the `recursive` strategy handles
+ suboptimally, and is significantly faster in large
+ repositories -- especially when many renames are involved.
++
+The `ort` strategy takes all the same options as `recursive`.
+However, it ignores three of those options: `no-renames`,
+`patience` and `diff-algorithm`. It always runs with rename
+detection (it handles it much faster than `recursive` does), and
+it specifically uses `diff-algorithm=histogram`.
+
+resolve::
+ This can only resolve two heads (i.e. the current branch
+ and another branch you pulled from) using a 3-way merge
+ algorithm. It tries to carefully detect criss-cross
+ merge ambiguities. It does not handle renames.
+
octopus::
This resolves cases with more than two heads, but refuses to do
a complex merge that needs manual resolution. It is
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index 27ddaf8..dc685be 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -33,14 +33,16 @@ people using 80-column terminals.
used together.
--encoding=<encoding>::
- The commit objects record the encoding used for the log message
+ Commit objects record the character encoding used for the log message
in their encoding header; this option can be used to tell the
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
defaults to UTF-8. Note that if an object claims to be encoded
in `X` and we are outputting in `X`, we will output the object
verbatim; this means that invalid sequences in the original
- commit may be copied to the output.
+ commit may be copied to the output. Likewise, if iconv(3) fails
+ to convert the commit, we will quietly output the original
+ object verbatim.
--expand-tabs=<n>::
--expand-tabs::
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 5bf2a85..24569b0 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -897,7 +897,7 @@ which are not of the requested type.
+
The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout
specification contained in the blob (or blob-expression) '<blob-ish>'
-to omit blobs that would not be not required for a sparse checkout on
+to omit blobs that would not be required for a sparse checkout on
the requested refs.
+
The form '--filter=tree:<depth>' omits all blobs and trees whose depth
@@ -1064,6 +1064,14 @@ ifdef::git-rev-list[]
--header::
Print the contents of the commit in raw-format; each record is
separated with a NUL character.
+
+--no-commit-header::
+ Suppress the header line containing "commit" and the object ID printed before
+ the specified format. This has no effect on the built-in formats; only custom
+ formats are affected.
+
+--commit-header::
+ Overrides a previous `--no-commit-header`.
endif::git-rev-list[]
--parents::
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index d9169c0..f5f17b6 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -260,6 +260,9 @@ any of the given commits.
A commit's reachable set is the commit itself and the commits in
its ancestry chain.
+There are several notations to specify a set of connected commits
+(called a "revision range"), illustrated below.
+
Commit Exclusions
~~~~~~~~~~~~~~~~~
@@ -294,6 +297,26 @@ is a shorthand for 'HEAD..origin' and asks "What did the origin do since
I forked from them?" Note that '..' would mean 'HEAD..HEAD' which is an
empty range that is both reachable and unreachable from HEAD.
+Commands that are specifically designed to take two distinct ranges
+(e.g. "git range-diff R1 R2" to compare two ranges) do exist, but
+they are exceptions. Unless otherwise noted, all "git" commands
+that operate on a set of commits work on a single revision range.
+In other words, writing two "two-dot range notation" next to each
+other, e.g.
+
+ $ git log A..B C..D
+
+does *not* specify two revision ranges for most commands. Instead
+it will name a single connected set of commits, i.e. those that are
+reachable from either B or D but are reachable from neither A or C.
+In a linear history like this:
+
+ ---A---B---o---o---C---D
+
+because A and B are reachable from C, the revision range specified
+by these two dotted ranges is a single commit D.
+
+
Other <rev>{caret} Parent Shorthand Notations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Three other shorthands exist, particularly useful for merge commits,
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
index 3f52f98..037a91c 100644
--- a/Documentation/technical/api-trace2.txt
+++ b/Documentation/technical/api-trace2.txt
@@ -396,14 +396,14 @@ only present on the "start" and "atexit" events.
}
------------
-`"discard"`::
+`"too_many_files"`::
This event is written to the git-trace2-discard sentinel file if there
are too many files in the target trace directory (see the
trace2.maxFiles config option).
+
------------
{
- "event":"discard",
+ "event":"too_many_files",
...
}
------------
diff --git a/Documentation/technical/directory-rename-detection.txt b/Documentation/technical/directory-rename-detection.txt
index 49b83ef..029ee2c 100644
--- a/Documentation/technical/directory-rename-detection.txt
+++ b/Documentation/technical/directory-rename-detection.txt
@@ -2,9 +2,9 @@ Directory rename detection
==========================
Rename detection logic in diffcore-rename that checks for renames of
-individual files is aggregated and analyzed in merge-recursive for cases
-where combinations of renames indicate that a full directory has been
-renamed.
+individual files is also aggregated there and then analyzed in either
+merge-ort or merge-recursive for cases where combinations of renames
+indicate that a full directory has been renamed.
Scope of abilities
------------------
@@ -88,9 +88,11 @@ directory rename detection support in:
Folks have requested in the past that `git diff` detect directory
renames and somehow simplify its output. It is not clear whether this
would be desirable or how the output should be simplified, so this was
- simply not implemented. Further, to implement this, directory rename
- detection logic would need to move from merge-recursive to
- diffcore-rename.
+ simply not implemented. Also, while diffcore-rename has most of the
+ logic for detecting directory renames, some of the logic is still found
+ within merge-ort and merge-recursive. Fully supporting directory
+ rename detection in diffs would require copying or moving the remaining
+ bits of logic to the diff machinery.
* am
diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt
index 7c1630b..260224b 100644
--- a/Documentation/technical/hash-function-transition.txt
+++ b/Documentation/technical/hash-function-transition.txt
@@ -599,7 +599,7 @@ supports four different modes of operation:
convert any object names written to output to SHA-1, but store
objects using SHA-256. This allows users to test the code with no
visible behavior change except for performance. This allows
- allows running even tests that assume the SHA-1 hash function, to
+ running even tests that assume the SHA-1 hash function, to
sanity-check the behavior of the new mode.
2. ("early transition") Allow both SHA-1 and SHA-256 object names in
diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt
index f7eabc6..1eb525f 100644
--- a/Documentation/technical/packfile-uri.txt
+++ b/Documentation/technical/packfile-uri.txt
@@ -35,13 +35,14 @@ include some sort of non-trivial implementation in the Minimum Viable Product,
at least so that we can test the client.
This is the implementation: a feature, marked experimental, that allows the
-server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>
-<uri>` entries. Whenever the list of objects to be sent is assembled, all such
-blobs are excluded, replaced with URIs. As noted in "Future work" below, the
-server can evolve in the future to support excluding other objects (or other
-implementations of servers could be made that support excluding other objects)
-without needing a protocol change, so clients should not expect that packfiles
-downloaded in this way only contain single blobs.
+server to be configured by one or more `uploadpack.blobPackfileUri=
+<object-hash> <pack-hash> <uri>` entries. Whenever the list of objects to be
+sent is assembled, all such blobs are excluded, replaced with URIs. As noted
+in "Future work" below, the server can evolve in the future to support
+excluding other objects (or other implementations of servers could be made
+that support excluding other objects) without needing a protocol change, so
+clients should not expect that packfiles downloaded in this way only contain
+single blobs.
Client design
-------------
diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt
index 0780d30..a0dd7c6 100644
--- a/Documentation/technical/partial-clone.txt
+++ b/Documentation/technical/partial-clone.txt
@@ -242,8 +242,7 @@ remote in a specific order.
repository and can satisfy all such requests.
- Repack essentially treats promisor and non-promisor packfiles as 2
- distinct partitions and does not mix them. Repack currently only works
- on non-promisor packfiles and loose objects.
+ distinct partitions and does not mix them.
- Dynamic object fetching invokes fetch-pack once *for each item*
because most algorithms stumble upon a missing object and need to have
@@ -273,9 +272,6 @@ to use those promisor remotes in that order."
The user might want to work in a triangular work flow with multiple
promisor remotes that each have an incomplete view of the repository.
-- Allow repack to work on promisor packfiles (while keeping them distinct
- from non-promisor packfiles).
-
- Allow non-pathname-based filters to make use of packfile bitmaps (when
present). This was just an omission during the initial implementation.
diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt
index a1e3136..1040d85 100644
--- a/Documentation/technical/protocol-v2.txt
+++ b/Documentation/technical/protocol-v2.txt
@@ -540,7 +540,7 @@ An `object-info` request takes the following arguments:
Indicates to the server an object which the client wants to obtain
information for.
-The response of `object-info` is a list of the the requested object ids
+The response of `object-info` is a list of the requested object ids
and associated requested information, each separated by a single space.
output = info flush-pkt
diff --git a/Documentation/technical/remembering-renames.txt b/Documentation/technical/remembering-renames.txt
new file mode 100644
index 0000000..2fd5cc8
--- /dev/null
+++ b/Documentation/technical/remembering-renames.txt
@@ -0,0 +1,671 @@
+Rebases and cherry-picks involve a sequence of merges whose results are
+recorded as new single-parent commits. The first parent side of those
+merges represent the "upstream" side, and often include a far larger set of
+changes than the second parent side. Traditionally, the renames on the
+first-parent side of that sequence of merges were repeatedly re-detected
+for every merge. This file explains why it is safe and effective during
+rebases and cherry-picks to remember renames on the upstream side of
+history as an optimization, assuming all merges are automatic and clean
+(i.e. no conflicts and not interrupted for user input or editing).
+
+Outline:
+
+ 0. Assumptions
+
+ 1. How rebasing and cherry-picking work
+
+ 2. Why the renames on MERGE_SIDE1 in any given pick are *always* a
+ superset of the renames on MERGE_SIDE1 for the next pick.
+
+ 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also
+ a rename on MERGE_SIDE1 for the next pick
+
+ 4. A detailed description of the the counter-examples to #3.
+
+ 5. Why the special cases in #4 are still fully reasonable to use to pair
+ up files for three-way content merging in the merge machinery, and why
+ they do not affect the correctness of the merge.
+
+ 6. Interaction with skipping of "irrelevant" renames
+
+ 7. Additional items that need to be cached
+
+ 8. How directory rename detection interacts with the above and why this
+ optimization is still safe even if merge.directoryRenames is set to
+ "true".
+
+
+=== 0. Assumptions ===
+
+There are two assumptions that will hold throughout this document:
+
+ * The upstream side where commits are transplanted to is treated as the
+ first parent side when rebase/cherry-pick call the merge machinery
+
+ * All merges are fully automatic
+
+and a third that will hold in sections 2-5 for simplicity, that I'll later
+address in section 8:
+
+ * No directory renames occur
+
+
+Let me explain more about each assumption and why I include it:
+
+
+The first assumption is merely for the purposes of making this document
+clearer; the optimization implementation does not actually depend upon it.
+However, the assumption does hold in all cases because it reflects the way
+that both rebase and cherry-pick were implemented; and the implementation
+of cherry-pick and rebase are not readily changeable for backwards
+compatibility reasons (see for example the discussion of the --ours and
+--theirs flag in the documentation of `git checkout`, particularly the
+comments about how they behave with rebase). The optimization avoids
+checking first-parent-ness, though. It checks the conditions that make the
+optimization valid instead, so it would still continue working if someone
+changed the parent ordering that cherry-pick and rebase use. But making
+this assumption does make this document much clearer and prevents me from
+having to repeat every example twice.
+
+If the second assumption is violated, then the optimization simply is
+turned off and thus isn't relevant to consider. The second assumption can
+also be stated as "there is no interruption for a user to resolve conflicts
+or to just further edit or tweak files". While real rebases and
+cherry-picks are often interrupted (either because it's an interactive
+rebase where the user requested to stop and edit, or because there were
+conflicts that the user needs to resolve), the cache of renames is not
+stored on disk, and thus is thrown away as soon as the rebase or cherry
+pick stops for the user to resolve the operation.
+
+The third assumption makes sections 2-5 simpler, and allows people to
+understand the basics of why this optimization is safe and effective, and
+then I can go back and address the specifics in section 8. It is probably
+also worth noting that if directory renames do occur, then the default of
+merge.directoryRenames being set to "conflict" means that the operation
+will stop for users to resolve the conflicts and the cache will be thrown
+away, and thus that there won't be an optimization to apply. So, the only
+reason we need to address directory renames specifically, is that some
+users will have set merge.directoryRenames to "true" to allow the merges to
+continue to proceed automatically. The optimization is still safe with
+this config setting, but we have to discuss a few more cases to show why;
+this discussion is deferred until section 8.
+
+
+=== 1. How rebasing and cherry-picking work ===
+
+Consider the following setup (from the git-rebase manpage):
+
+ A---B---C topic
+ /
+ D---E---F---G main
+
+After rebasing or cherry-picking topic onto main, this will appear as:
+
+ A'--B'--C' topic
+ /
+ D---E---F---G main
+
+The way the commits A', B', and C' are created is through a series of
+merges, where rebase or cherry-pick sequentially uses each of the three
+A-B-C commits in a special merge operation. Let's label the three commits
+in the merge operation as MERGE_BASE, MERGE_SIDE1, and MERGE_SIDE2. For
+this picture, the three commits for each of the three merges would be:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+To create C':
+ MERGE_BASE: B
+ MERGE_SIDE1: B'
+ MERGE_SIDE2: C
+
+Sometimes, folks are surprised that these three-way merges are done. It
+can be useful in understanding these three-way merges to view them in a
+slightly different light. For example, in creating C', you can view it as
+either:
+
+ * Apply the changes between B & C to B'
+ * Apply the changes between B & B' to C
+
+Conceptually the two statements above are the same as a three-way merge of
+B, B', and C, at least the parts before you decide to record a commit.
+
+
+=== 2. Why the renames on MERGE_SIDE1 in any given pick are always a ===
+=== superset of the renames on MERGE_SIDE1 for the next pick. ===
+
+The merge machinery uses the filenames it is fed from MERGE_BASE,
+MERGE_SIDE1, and MERGE_SIDE2. It will only move content to a different
+filename under one of three conditions:
+
+ * To make both pieces of a conflict available to a user during conflict
+ resolution (examples: directory/file conflict, add/add type conflict
+ such as symlink vs. regular file)
+
+ * When MERGE_SIDE1 renames the file.
+
+ * When MERGE_SIDE2 renames the file.
+
+First, let's remember what commits are involved in the first and second
+picks of the cherry-pick or rebase sequence:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+So, in particular, we need to show that the renames between E and G are a
+superset of those between A and A'.
+
+A' is created by the first merge. A' will only have renames for one of the
+three reasons listed above. The first case, a conflict, results in a
+situation where the cache is dropped and thus this optimization doesn't
+take effect, so we need not consider that case. The third case, a rename
+on MERGE_SIDE2 (i.e. from G to A), will show up in A' but it also shows up
+in A -- therefore when diffing A and A' that path does not show up as a
+rename. The only remaining way for renames to show up in A' is for the
+rename to come from MERGE_SIDE1. Therefore, all renames between A and A'
+are a subset of those between E and G. Equivalently, all renames between E
+and G are a superset of those between A and A'.
+
+
+=== 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ ===
+=== always also a rename on MERGE_SIDE1 for the next pick. ===
+
+Let's again look at the first two picks:
+
+To create A':
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+
+To create B':
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+
+Now let's look at any given rename from MERGE_SIDE1 of the first pick, i.e.
+any given rename from E to G. Let's use the filenames 'oldfile' and
+'newfile' for demonstration purposes. That first pick will function as
+follows; when the rename is detected, the merge machinery will do a
+three-way content merge of the following:
+ E:oldfile
+ G:newfile
+ A:oldfile
+and produce a new result:
+ A':newfile
+
+Note above that I've assumed that E->A did not rename oldfile. If that
+side did rename, then we most likely have a rename/rename(1to2) conflict
+that will cause the rebase or cherry-pick operation to halt and drop the
+in-memory cache of renames and thus doesn't need to be considered further.
+In the special case that E->A does rename the file but also renames it to
+newfile, then there is no conflict from the renaming and the merge can
+succeed. In this special case, the rename is not valid to cache because
+the second merge will find A:newfile in the MERGE_BASE (see also the new
+testcases in t6429 with "rename same file identically" in their
+description). So a rename/rename(1to1) needs to be specially handled by
+pruning renames from the cache and decrementing the dir_rename_counts in
+the current and leading directories associated with those renames. Or,
+since these are really rare, one could just take the easy way out and
+disable the remembering renames optimization when a rename/rename(1to1)
+happens.
+
+The previous paragraph handled the cases for E->A renaming oldfile, let's
+continue assuming that oldfile is not renamed in A.
+
+As per the diagram for creating B', MERGE_SIDE1 involves the changes from A
+to A'. So, we are curious whether A:oldfile and A':newfile will be viewed
+as renames. Note that:
+
+ * There will be no A':oldfile (because there could not have been a
+ G:oldfile as we do not do break detection in the merge machinery and
+ G:newfile was detected as a rename, and by the construction of the
+ rename above that merged cleanly, the merge machinery will ensure there
+ is no 'oldfile' in the result).
+
+ * There will be no A:newfile (if there had been, we would have had a
+ rename/add conflict).
+
+ * Clearly A:oldfile and A':newfile are "related" (A':newfile came from a
+ clean three-way content merge involving A:oldfile).
+
+We can also expound on the third point above, by noting that three-way
+content merges can also be viewed as applying the differences between the
+base and one side to the other side. Thus we can view A':newfile as
+having been created by taking the changes between E:oldfile and G:newfile
+(which were detected as being related, i.e. <50% changed) to A:oldfile.
+
+Thus A:oldfile and A':newfile are just as related as E:oldfile and
+G:newfile are -- they have exactly identical differences. Since the latter
+were detected as renames, A:oldfile and A':newfile should also be
+detectable as renames almost always.
+
+
+=== 4. A detailed description of the counter-examples to #3. ===
+
+We already noted in section 3 that rename/rename(1to1) (i.e. both sides
+renaming a file the same way) was one counter-example. The more
+interesting bit, though, is why did we need to use the "almost" qualifier
+when stating that A:oldfile and A':newfile are "almost" always detectable
+as renames?
+
+Let's repeat an earlier point that section 3 made:
+
+ A':newfile was created by applying the changes between E:oldfile and
+ G:newfile to A:oldfile. The changes between E:oldfile and G:newfile were
+ <50% of the size of E:oldfile.
+
+If those changes that were <50% of the size of E:oldfile are also <50% of
+the size of A:oldfile, then A:oldfile and A':newfile will be detectable as
+renames. However, if there is a dramatic size reduction between E:oldfile
+and A:oldfile (but the changes between E:oldfile, G:newfile, and A:oldfile
+still somehow merge cleanly), then traditional rename detection would not
+detect A:oldfile and A':newfile as renames.
+
+Here's an example where that can happen:
+ * E:oldfile had 20 lines
+ * G:newfile added 10 new lines at the beginning of the file
+ * A:oldfile kept the first 3 lines of the file, and deleted all the rest
+then
+ => A':newfile would have 13 lines, 3 of which matches those in A:oldfile.
+E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and
+A':newfile would not be.
+
+
+=== 5. Why the special cases in #4 are still fully reasonable to use to ===
+=== pair up files for three-way content merging in the merge machinery, ===
+=== and why they do not affect the correctness of the merge. ===
+
+In the rename/rename(1to1) case, A:newfile and A':newfile are not renames
+since they use the *same* filename. However, files with the same filename
+are obviously fine to pair up for three-way content merging (the merge
+machinery has never employed break detection). The interesting
+counter-example case is thus not the rename/rename(1to1) case, but the case
+where A did not rename oldfile. That was the case that we spent most of
+the time discussing in sections 3 and 4. The remainder of this section
+will be devoted to that case as well.
+
+So, even if A:oldfile and A':newfile aren't detectable as renames, why is
+it still reasonable to pair them up for three-way content merging in the
+merge machinery? There are multiple reasons:
+
+ * As noted in sections 3 and 4, the diff between A:oldfile and A':newfile
+ is *exactly* the same as the diff between E:oldfile and G:newfile. The
+ latter pair were detected as renames, so it seems unlikely to surprise
+ users for us to treat A:oldfile and A':newfile as renames.
+
+ * In fact, "oldfile" and "newfile" were at one point detected as renames
+ due to how they were constructed in the E..G chain. And we used that
+ information once already in this rebase/cherry-pick. I think users
+ would be unlikely to be surprised at us continuing to treat the files
+ as renames and would quickly understand why we had done so.
+
+ * Marking or declaring files as renames is *not* the end goal for merges.
+ Merges use renames to determine which files make sense to be paired up
+ for three-way content merges.
+
+ * A:oldfile and A':newfile were _already_ paired up in a three-way
+ content merge; that is how A':newfile was created. In fact, that
+ three-way content merge was clean. So using them again in a later
+ three-way content merge seems very reasonable.
+
+However, the above is focusing on the common scenarios. Let's try to look
+at all possible unusual scenarios and compare without the optimization to
+with the optimization. Consider the following theoretical cases; we will
+then dive into each to determine which of them are possible,
+and if so, what they mean:
+
+ 1. Without the optimization, the second merge results in a conflict.
+ With the optimization, the second merge also results in a conflict.
+ Questions: Are the conflicts confusingly different? Better in one case?
+
+ 2. Without the optimization, the second merge results in NO conflict.
+ With the optimization, the second merge also results in NO conflict.
+ Questions: Are the merges the same?
+
+ 3. Without the optimization, the second merge results in a conflict.
+ With the optimization, the second merge results in NO conflict.
+ Questions: Possible? Bug, bugfix, or something else?
+
+ 4. Without the optimization, the second merge results in NO conflict.
+ With the optimization, the second merge results in a conflict.
+ Questions: Possible? Bug, bugfix, or something else?
+
+I'll consider all four cases, but out of order.
+
+The fourth case is impossible. For the code without the remembering
+renames optimization to not get a conflict, B:oldfile would need to exactly
+match A:oldfile -- if it doesn't, there would be a modify/delete conflict.
+If A:oldfile matches B:oldfile exactly, then a three-way content merge
+between A:oldfile, A':newfile, and B:oldfile would have no conflict and
+just give us the version of newfile from A' as the result.
+
+From the same logic as the above paragraph, the second case would indeed
+result in identical merges. When A:oldfile exactly matches B:oldfile, an
+undetected rename would say, "Oh, I see one side didn't modify 'oldfile'
+and the other side deleted it. I'll delete it. And I see you have this
+brand new file named 'newfile' in A', so I'll keep it." That gives the
+same results as three-way content merging A:oldfile, A':newfile, and
+B:oldfile -- a removal of oldfile with the version of newfile from A'
+showing up in the result.
+
+The third case is interesting. It means that A:oldfile and A':newfile were
+not just similar enough, but that the changes between them did not conflict
+with the changes between A:oldfile and B:oldfile. This would validate our
+hunch that the files were similar enough to be used in a three-way content
+merge, and thus seems entirely correct for us to have used them that way.
+(Sidenote: One particular example here may be enlightening. Let's say that
+B was an immediate revert of A. B clearly would have been a clean revert
+of A, since A was B's immediate parent. One would assume that if you can
+pick a commit, you should also be able to cherry-pick its immediate revert.
+However, this is one of those funny corner cases; without this
+optimization, we just successfully picked a commit cleanly, but we are
+unable to cherry-pick its immediate revert due to the size differences
+between E:oldfile and A:oldfile.)
+
+That leaves only the first case to consider -- when we get conflicts both
+with or without the optimization. Without the optimization, we'll have a
+modify/delete conflict, where both A':newfile and B:oldfile are left in the
+tree for the user to deal with and no hints about the potential similarity
+between the two. With the optimization, we'll have a three-way content
+merged A:oldfile, A':newfile, and B:oldfile with conflict markers
+suggesting we thought the files were related but giving the user the chance
+to resolve. As noted above, I don't think users will find us treating
+'oldfile' and 'newfile' as related as a surprise since they were between E
+and G. In any event, though, this case shouldn't be concerning since we
+hit a conflict in both cases, told the user what we know, and asked them to
+resolve it.
+
+So, in summary, case 4 is impossible, case 2 yields the same behavior, and
+cases 1 and 3 seem to provide as good or better behavior with the
+optimization than without.
+
+
+=== 6. Interaction with skipping of "irrelevant" renames ===
+
+Previous optimizations involved skipping rename detection for paths
+considered to be "irrelevant". See for example the following commits:
+
+ * 32a56dfb99 ("merge-ort: precompute subset of sources for which we
+ need rename detection", 2021-03-11)
+ * 2fd9eda462 ("merge-ort: precompute whether directory rename
+ detection is needed", 2021-03-11)
+ * 9bd342137e ("diffcore-rename: determine which relevant_sources are
+ no longer relevant", 2021-03-13)
+
+Relevance is always determined by what the _other_ side of history has
+done, in terms of modifing a file that our side renamed, or adding a
+file to a directory which our side renamed. This means that a path
+that is "irrelevant" when picking the first commit of a series in a
+rebase or cherry-pick, may suddenly become "relevant" when picking the
+next commit.
+
+The upshot of this is that we can only cache rename detection results
+for relevant paths, and need to re-check relevance in subsequent
+commits. If those subsequent commits have additional paths that are
+relevant for rename detection, then we will need to redo rename
+detection -- though we can limit it to the paths for which we have not
+already detected renames.
+
+
+=== 7. Additional items that need to be cached ===
+
+It turns out we have to cache more than just renames; we also cache:
+
+ A) non-renames (i.e. unpaired deletes)
+ B) counts of renames within directories
+ C) sources that were marked as RELEVANT_LOCATION, but which were
+ downgraded to RELEVANT_NO_MORE
+ D) the toplevel trees involved in the merge
+
+These are all stored in struct rename_info, and respectively appear in
+ * cached_pairs (along side actual renames, just with a value of NULL)
+ * dir_rename_counts
+ * cached_irrelevant
+ * merge_trees
+
+The reason for (A) comes from the irrelevant renames skipping
+optimization discussed in section 6. The fact that irrelevant renames
+are skipped means we only get a subset of the potential renames
+detected and subsequent commits may need to run rename detection on
+the upstream side on a subset of the remaining renames (to get the
+renames that are relevant for that later commit). Since unpaired
+deletes are involved in rename detection too, we don't want to
+repeatedly check that those paths remain unpaired on the upstream side
+with every commit we are transplanting.
+
+The reason for (B) is that diffcore_rename_extended() is what
+generates the counts of renames by directory which is needed in
+directory rename detection, and if we don't run
+diffcore_rename_extended() again then we need to have the output from
+it, including dir_rename_counts, from the previous run.
+
+The reason for (C) is that merge-ort's tree traversal will again think
+those paths are relevant (marking them as RELEVANT_LOCATION), but the
+fact that they were downgraded to RELEVANT_NO_MORE means that
+dir_rename_counts already has the information we need for directory
+rename detection. (A path which becomes RELEVANT_CONTENT in a
+subsequent commit will be removed from cached_irrelevant.)
+
+The reason for (D) is that is how we determine whether the remember
+renames optimization can be used. In particular, remembering that our
+sequence of merges looks like:
+
+ Merge 1:
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+ => Creates A'
+
+ Merge 2:
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+ => Creates B'
+
+It is the fact that the trees A and A' appear both in Merge 1 and in
+Merge 2, with A as a parent of A' that allows this optimization. So
+we store the trees to compare with what we are asked to merge next
+time.
+
+
+=== 8. How directory rename detection interacts with the above and ===
+=== why this optimization is still safe even if ===
+=== merge.directoryRenames is set to "true". ===
+
+As noted in the assumptions section:
+
+ """
+ ...if directory renames do occur, then the default of
+ merge.directoryRenames being set to "conflict" means that the operation
+ will stop for users to resolve the conflicts and the cache will be
+ thrown away, and thus that there won't be an optimization to apply.
+ So, the only reason we need to address directory renames specifically,
+ is that some users will have set merge.directoryRenames to "true" to
+ allow the merges to continue to proceed automatically.
+ """
+
+Let's remember that we need to look at how any given pick affects the next
+one. So let's again use the first two picks from the diagram in section
+one:
+
+ First pick does this three-way merge:
+ MERGE_BASE: E
+ MERGE_SIDE1: G
+ MERGE_SIDE2: A
+ => creates A'
+
+ Second pick does this three-way merge:
+ MERGE_BASE: A
+ MERGE_SIDE1: A'
+ MERGE_SIDE2: B
+ => creates B'
+
+Now, directory rename detection exists so that if one side of history
+renames a directory, and the other side adds a new file to the old
+directory, then the merge (with merge.directoryRenames=true) can move the
+file into the new directory. There are two qualitatively different ways to
+add a new file to an old directory: create a new file, or rename a file
+into that directory. Also, directory renames can be done on either side of
+history, so there are four cases to consider:
+
+ * MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
+ * MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir
+ * MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
+ * MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir
+
+One last note before we consider these four cases: There are some
+important properties about how we implement this optimization with
+respect to directory rename detection that we need to bear in mind
+while considering all of these cases:
+
+ * rename caching occurs *after* applying directory renames
+
+ * a rename created by directory rename detection is recorded for the side
+ of history that did the directory rename.
+
+ * dir_rename_counts, the nested map of
+ {oldname => {newname => count}},
+ is cached between runs as well. This basically means that directory
+ rename detection is also cached, though only on the side of history
+ that we cache renames for (MERGE_SIDE1 as far as this document is
+ concerned; see the assumptions section). Two interesting sub-notes
+ about these counts:
+
+ * If we need to perform rename-detection again on the given side (e.g.
+ some paths are relevant for rename detection that weren't before),
+ then we clear dir_rename_counts and recompute it, making use of
+ cached_pairs. The reason it is important to do this is optimizations
+ around RELEVANT_LOCATION exist to prevent us from computing
+ unnecessary renames for directory rename detection and from computing
+ dir_rename_counts for irrelevant directories; but those same renames
+ or directories may become necessary for subsequent merges. The
+ easiest way to "fix up" dir_rename_counts in such cases is to just
+ recompute it.
+
+ * If we prune rename/rename(1to1) entries from the cache, then we also
+ need to update dir_rename_counts to decrement the counts for the
+ involved directory and any relevant parent directories (to undo what
+ update_dir_rename_counts() in diffcore-rename.c incremented when the
+ rename was initially found). If we instead just disable the
+ remembering renames optimization when the exceedingly rare
+ rename/rename(1to1) cases occur, then dir_rename_counts will get
+ re-computed the next time rename detection occurs, as noted above.
+
+ * the side with multiple commits to pick, is the side of history that we
+ do NOT cache renames for. Thus, there are no additional commits to
+ change the number of renames in a directory, except for those done by
+ directory rename detection (which always pad the majority).
+
+ * the "renames" we cache are modified slightly by any directory rename,
+ as noted below.
+
+Now, with those notes out of the way, let's go through the four cases
+in order:
+
+Case 1: MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Renames olddir/ -> newdir/
+ MERGE_SIDE2: A, Adds olddir/newfile
+ => creates A', With newdir/newfile
+
+ MERGE_BASE: A, Has olddir/newfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Modifies olddir/newfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers olddir/ -> newdir/
+ * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Case 2: MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir
+
+ This case looks like this:
+ MERGE_BASE: E oldfile, olddir/
+ MERGE_SIDE1: G oldfile, olddir/ -> newdir/
+ MERGE_SIDE2: A oldfile -> olddir/newfile
+ => creates A', With newdir/newfile representing original oldfile
+
+ MERGE_BASE: A olddir/newfile
+ MERGE_SIDE1: A' newdir/newfile
+ MERGE_SIDE2: B modify olddir/newfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers olddir/ -> newdir/
+ * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile
+ (NOT oldfile -> newdir/newfile; compare to case with
+ (p->status == 'R' && new_path) in possibly_cache_new_pair())
+
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Case 3: MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Adds olddir/newfile
+ MERGE_SIDE2: A, Renames olddir/ -> newdir/
+ => creates A', With newdir/newfile
+
+ MERGE_BASE: A, Has newdir/, but no notion of newdir/newfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Has newdir/, but no notion of newdir/newfile
+ => expected B', with newdir/newfile from A'
+
+ In this case, with the optimization, note that after the first commit there
+ were no renames on MERGE_SIDE1, and any renames on MERGE_SIDE2 are tossed.
+ But the second merge didn't need any renames so this is fine.
+
+Case 4: MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir
+
+ This case looks like this:
+
+ MERGE_BASE: E, Has olddir/
+ MERGE_SIDE1: G, Renames oldfile -> olddir/newfile
+ MERGE_SIDE2: A, Renames olddir/ -> newdir/
+ => creates A', With newdir/newfile representing original oldfile
+
+ MERGE_BASE: A, Has oldfile
+ MERGE_SIDE1: A', Has newdir/newfile
+ MERGE_SIDE2: B, Modifies oldfile
+ => expected B', with threeway-merged newdir/newfile from above
+
+ In this case, with the optimization, note that after the first commit:
+ * MERGE_SIDE1 remembers oldfile -> newdir/newfile
+ (NOT oldfile -> olddir/newfile; compare to case of second
+ block under p->status == 'R' in possibly_cache_new_pair())
+ * MERGE_SIDE2 renames are tossed because only MERGE_SIDE1 is remembered
+
+ Given the cached rename noted above, the second merge can proceed as
+ expected without needing to perform rename detection from A -> A'.
+
+Finally, I'll just note here that interactions with the
+skip-irrelevant-renames optimization means we sometimes don't detect
+renames for any files within a directory that was renamed, in which
+case we will not have been able to detect any rename for the directory
+itself. In such a case, we do not know whether the directory was
+renamed; we want to be careful to avoid cacheing some kind of "this
+directory was not renamed" statement. If we did, then a subsequent
+commit being rebased could add a file to the old directory, and the
+user would expect it to end up in the correct directory -- something
+our erroneous "this directory was not renamed" cache would preclude.
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index f9e54b8..9624059 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -2792,7 +2792,7 @@ A fast-forward looks something like this:
In some cases it is possible that the new head will *not* actually be
a descendant of the old head. For example, the developer may have
-realized she made a serious mistake, and decided to backtrack,
+realized a serious mistake was made and decided to backtrack,
resulting in a situation like:
................................................
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index e7efe58..86a3a28 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.32.2
+DEF_VER=v2.33.3
LF='
'
diff --git a/Makefile b/Makefile
index c3565fc..3df0ab5 100644
--- a/Makefile
+++ b/Makefile
@@ -398,6 +398,10 @@ all::
# with a different indexfile format version. If it isn't set the index
# file format used is index-v[23].
#
+# Define GIT_TEST_UTF8_LOCALE to preferred utf-8 locale for testing.
+# If it isn't set, fallback to $LC_ALL, $LANG or use the first utf-8
+# locale returned by "locale -a".
+#
# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime.
#
# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC.
@@ -711,6 +715,7 @@ TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-fast-rebase.o
TEST_BUILTINS_OBJS += test-genrandom.o
TEST_BUILTINS_OBJS += test-genzeros.o
+TEST_BUILTINS_OBJS += test-getcwd.o
TEST_BUILTINS_OBJS += test-hash-speed.o
TEST_BUILTINS_OBJS += test-hash.o
TEST_BUILTINS_OBJS += test-hashmap.o
@@ -722,9 +727,11 @@ TEST_BUILTINS_OBJS += test-mergesort.o
TEST_BUILTINS_OBJS += test-mktemp.o
TEST_BUILTINS_OBJS += test-oid-array.o
TEST_BUILTINS_OBJS += test-oidmap.o
+TEST_BUILTINS_OBJS += test-oidtree.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-partial-clone.o
TEST_BUILTINS_OBJS += test-path-utils.o
TEST_BUILTINS_OBJS += test-pcre2-config.o
TEST_BUILTINS_OBJS += test-pkt-line.o
@@ -845,6 +852,7 @@ LIB_OBJS += branch.o
LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o
+LIB_OBJS += cbtree.o
LIB_OBJS += chdir-notify.o
LIB_OBJS += checkout.o
LIB_OBJS += chunk-format.o
@@ -940,6 +948,7 @@ LIB_OBJS += object.o
LIB_OBJS += oid-array.o
LIB_OBJS += oidmap.o
LIB_OBJS += oidset.o
+LIB_OBJS += oidtree.o
LIB_OBJS += pack-bitmap-write.o
LIB_OBJS += pack-bitmap.o
LIB_OBJS += pack-check.o
@@ -2160,6 +2169,16 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $^
+### Flags affecting all rules
+
+# A GNU make extension since gmake 3.72 (released in late 1994) to
+# remove the target of rules if commands in those rules fail. The
+# default is to only do that if make itself receives a signal. Affects
+# all targets, see:
+#
+# info make --index-search=.DELETE_ON_ERROR
+.DELETE_ON_ERROR:
+
### Target-specific flags and dependencies
# The generic compilation pattern rule and automatically
@@ -2243,7 +2262,6 @@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
$(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV):\
$(perllibdir_SQ)
define cmd_munge_script
-$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
-e 's|@@DIFF@@|$(DIFF_SQ)|' \
@@ -2313,7 +2331,7 @@ endif
PERL_DEFINES += $(gitexecdir) $(perllibdir) $(localedir)
$(SCRIPT_PERL_GEN): % : %.perl GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE
- $(QUIET_GEN)$(RM) $@ $@+ && \
+ $(QUIET_GEN) \
sed -e '1{' \
-e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-e ' r GIT-PERL-HEADER' \
@@ -2333,7 +2351,7 @@ GIT-PERL-DEFINES: FORCE
fi
GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile
- $(QUIET_GEN)$(RM) $@ && \
+ $(QUIET_GEN) \
INSTLIBDIR='$(perllibdir_SQ)' && \
INSTLIBDIR_EXTRA='$(PERLLIB_EXTRA_SQ)' && \
INSTLIBDIR="$$INSTLIBDIR$${INSTLIBDIR_EXTRA:+:$$INSTLIBDIR_EXTRA}" && \
@@ -2359,7 +2377,7 @@ git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES
mv $@+ $@
else # NO_PERL
$(SCRIPT_PERL_GEN) git-instaweb: % : unimplemented.sh
- $(QUIET_GEN)$(RM) $@ $@+ && \
+ $(QUIET_GEN) \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \
unimplemented.sh >$@+ && \
@@ -2373,14 +2391,14 @@ $(SCRIPT_PYTHON_GEN): GIT-BUILD-OPTIONS
ifndef NO_PYTHON
$(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
$(SCRIPT_PYTHON_GEN): % : %.py
- $(QUIET_GEN)$(RM) $@ $@+ && \
+ $(QUIET_GEN) \
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
$< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
else # NO_PYTHON
$(SCRIPT_PYTHON_GEN): % : unimplemented.sh
- $(QUIET_GEN)$(RM) $@ $@+ && \
+ $(QUIET_GEN) \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
unimplemented.sh >$@+ && \
@@ -2388,8 +2406,7 @@ $(SCRIPT_PYTHON_GEN): % : unimplemented.sh
mv $@+ $@
endif # NO_PYTHON
-CONFIGURE_RECIPE = $(RM) configure configure.ac+ && \
- sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+CONFIGURE_RECIPE = sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
configure.ac >configure.ac+ && \
autoconf -o configure configure.ac+ && \
$(RM) configure.ac+
@@ -2460,7 +2477,6 @@ dep_args = -MF $(dep_file) -MQ $@ -MMD -MP
endif
ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
-dep_dirs =
missing_dep_dirs =
dep_args =
endif
@@ -2514,7 +2530,6 @@ endif
ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
all:: compile_commands.json
compile_commands.json:
- @$(RM) $@
$(QUIET_GEN)sed -e '1s/^/[/' -e '$$s/,$$/]/' $(compdb_dir)/*.o.json > $@+
@if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
endif
@@ -2675,10 +2690,13 @@ po/git.pot: $(GENERATED_H) FORCE
.PHONY: pot
pot: po/git.pot
+ifdef NO_GETTEXT
+POFILES :=
+MOFILES :=
+else
POFILES := $(wildcard po/*.po)
MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
-ifndef NO_GETTEXT
all:: $(MOFILES)
endif
@@ -2802,6 +2820,9 @@ endif
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@+
endif
+ifdef GIT_TEST_UTF8_LOCALE
+ @echo GIT_TEST_UTF8_LOCALE=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_UTF8_LOCALE)))'\' >>$@+
+endif
@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@+
ifdef GIT_PERF_REPEAT_COUNT
@echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@+
@@ -3068,8 +3089,7 @@ endif
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
- done && \
- ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
+ done
.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf
.PHONY: quick-install-doc quick-install-man quick-install-html
diff --git a/RelNotes b/RelNotes
index 4ac6838..899139d 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.32.2.txt \ No newline at end of file
+Documentation/RelNotes/2.33.3.txt \ No newline at end of file
diff --git a/add-patch.c b/add-patch.c
index 2fad92c..8c41cdf 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -280,6 +280,7 @@ static void add_p_state_clear(struct add_p_state *s)
clear_add_i_state(&s->s);
}
+__attribute__((format (printf, 2, 3)))
static void err(struct add_p_state *s, const char *fmt, ...)
{
va_list args;
diff --git a/advice.c b/advice.c
index 0b9c89c..337e8f3 100644
--- a/advice.c
+++ b/advice.c
@@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)
die(_("Exiting because of unfinished merge."));
}
+void NORETURN die_ff_impossible(void)
+{
+ die(_("Not possible to fast-forward, aborting."));
+}
+
void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
{
struct string_list_item *item;
diff --git a/advice.h b/advice.h
index bd26c38..a7227ca 100644
--- a/advice.h
+++ b/advice.h
@@ -90,11 +90,13 @@ int advice_enabled(enum advice_type type);
/**
* Checks the visibility of the advice before printing.
*/
+__attribute__((format (printf, 2, 3)))
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);
+void NORETURN die_ff_impossible(void);
void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
void detach_advice(const char *new_name);
diff --git a/apply.c b/apply.c
index 853d3ed..43a0aeb 100644
--- a/apply.c
+++ b/apply.c
@@ -101,9 +101,9 @@ int init_apply_state(struct apply_state *state,
state->ws_error_action = warn_on_ws_error;
state->ws_ignore_action = ignore_ws_none;
state->linenr = 1;
- string_list_init(&state->fn_table, 0);
- string_list_init(&state->limit_by_name, 0);
- string_list_init(&state->symlink_changes, 0);
+ string_list_init_nodup(&state->fn_table);
+ string_list_init_nodup(&state->limit_by_name);
+ string_list_init_nodup(&state->symlink_changes);
strbuf_init(&state->root, 0);
git_apply_config();
@@ -1917,6 +1917,7 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,
state->linenr++;
buffer += llen;
+ size -= llen;
while (1) {
int byte_length, max_byte_length, newsize;
llen = linelen(buffer, size);
@@ -3467,6 +3468,21 @@ static int load_preimage(struct apply_state *state,
return 0;
}
+static int resolve_to(struct image *image, const struct object_id *result_id)
+{
+ unsigned long size;
+ enum object_type type;
+
+ clear_image(image);
+
+ image->buf = read_object_file(result_id, &type, &size);
+ if (!image->buf || type != OBJ_BLOB)
+ die("unable to read blob object %s", oid_to_hex(result_id));
+ image->len = size;
+
+ return 0;
+}
+
static int three_way_merge(struct apply_state *state,
struct image *image,
char *path,
@@ -3478,6 +3494,12 @@ static int three_way_merge(struct apply_state *state,
mmbuffer_t result = { NULL };
int status;
+ /* resolve trivial cases first */
+ if (oideq(base, ours))
+ return resolve_to(image, theirs);
+ else if (oideq(base, theirs) || oideq(ours, theirs))
+ return resolve_to(image, ours);
+
read_mmblob(&base_file, base);
read_mmblob(&our_file, ours);
read_mmblob(&their_file, theirs);
diff --git a/archive.c b/archive.c
index ff2bb54..a3bbb09 100644
--- a/archive.c
+++ b/archive.c
@@ -191,7 +191,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
return err;
}
-static void queue_directory(const unsigned char *sha1,
+static void queue_directory(const struct object_id *oid,
struct strbuf *base, const char *filename,
unsigned mode, struct archiver_context *c)
{
@@ -203,7 +203,7 @@ static void queue_directory(const unsigned char *sha1,
d->mode = mode;
c->bottom = d;
d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
- oidread(&d->oid, sha1);
+ oidcpy(&d->oid, oid);
}
static int write_directory(struct archiver_context *c)
@@ -250,8 +250,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
if (check_attr_export_ignore(check))
return 0;
- queue_directory(oid->hash, base, filename,
- mode, c);
+ queue_directory(oid, base, filename, mode, c);
return READ_TREE_RECURSIVE;
}
@@ -645,7 +644,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
args.pretty_ctx = &ctx;
args.repo = repo;
args.prefix = prefix;
- string_list_init(&args.extra_files, 1);
+ string_list_init_dup(&args.extra_files);
argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
if (!startup_info->have_repository) {
/*
diff --git a/attr.c b/attr.c
index 9e897e4..d029e68 100644
--- a/attr.c
+++ b/attr.c
@@ -685,7 +685,7 @@ static struct attr_stack *read_attr_from_array(const char **list)
* Callers into the attribute system assume there is a single, system-wide
* global state where attributes are read from and when the state is flipped by
* calling git_attr_set_direction(), the stack frames that have been
- * constructed need to be discarded so so that subsequent calls into the
+ * constructed need to be discarded so that subsequent calls into the
* attribute system will lazily read from the right place. Since changing
* direction causes a global paradigm shift, it should not ever be called while
* another thread could potentially be calling into the attribute system.
diff --git a/builtin/add.c b/builtin/add.c
index b773b5a..c37c95b 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -313,9 +313,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
rev.diffopt.use_color = 0;
rev.diffopt.flags.ignore_dirty_submodules = 1;
- out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (out < 0)
- die(_("Could not open '%s' for writing."), file);
+ out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
@@ -470,7 +468,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
{
int exit_status = 0;
struct pathspec pathspec;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
int flags;
int add_new_files;
int require_pathspec;
@@ -577,7 +575,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
die_in_unpopulated_submodule(&the_index, prefix);
die_path_inside_submodule(&the_index, &pathspec);
- dir_init(&dir);
if (add_new_files) {
int baselen;
diff --git a/builtin/am.c b/builtin/am.c
index 0b2d886..c79e016 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -210,6 +210,7 @@ static void write_state_bool(const struct am_state *state,
* If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
* at the end.
*/
+__attribute__((format (printf, 3, 4)))
static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
{
va_list ap;
@@ -2105,7 +2106,8 @@ static void am_abort(struct am_state *state)
if (!has_orig_head)
oidcpy(&orig_head, the_hash_algo->empty_tree);
- clean_index(&curr_head, &orig_head);
+ if (clean_index(&curr_head, &orig_head))
+ die(_("failed to clean index"));
if (has_orig_head)
update_ref("am --abort", "HEAD", &orig_head,
diff --git a/builtin/archive.c b/builtin/archive.c
index 45d1166..7176b04 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -12,9 +12,7 @@
static void create_output_file(const char *output_file)
{
- int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (output_fd < 0)
- die_errno(_("could not create archive file '%s'"), output_file);
+ int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (output_fd != 1) {
if (dup2(output_fd, 1) < 0)
die_errno(_("could not redirect output"));
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 9d9540a..f184eae 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -117,6 +117,7 @@ static int write_in_file(const char *path, const char *mode, const char *format,
return fclose(fp);
}
+__attribute__((format (printf, 2, 3)))
static int write_to_file(const char *path, const char *format, ...)
{
int res;
@@ -129,6 +130,7 @@ static int write_to_file(const char *path, const char *format, ...)
return res;
}
+__attribute__((format (printf, 2, 3)))
static int append_to_file(const char *path, const char *format, ...)
{
int res;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1..03c7b72 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -168,7 +168,7 @@ static int check_branch_commit(const char *branchname, const char *refname,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(the_repository, oid);
- if (!rev) {
+ if (!force && !rev) {
error(_("Couldn't look up commit object for '%s'"), refname);
return -1;
}
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 9915a58..06ed10d 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -171,10 +171,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
get_populated_hooks(&buffer, !startup_info->have_repository);
/* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
- report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
-
- if (report < 0)
- die(_("couldn't create a new file at '%s'"), report_path.buf);
+ report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (write_in_full(report, buffer.buf, buffer.len) < 0)
die_errno(_("unable to write to %s"), report_path.buf);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index ea69481..053a51b 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -46,7 +46,7 @@ static int parse_options_cmd_bundle(int argc,
const char* prefix,
const char * const usagestr[],
const struct option options[],
- const char **bundle_file) {
+ char **bundle_file) {
int newargc;
newargc = parse_options(argc, argv, NULL, options, usagestr,
PARSE_OPT_STOP_AT_NON_OPTION);
@@ -61,7 +61,7 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) {
int progress = isatty(STDERR_FILENO);
struct strvec pack_opts;
int version = -1;
-
+ int ret;
struct option options[] = {
OPT_SET_INT('q', "quiet", &progress,
N_("do not show progress meter"), 0),
@@ -76,7 +76,7 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) {
N_("specify bundle format version")),
OPT_END()
};
- const char* bundle_file;
+ char *bundle_file;
argc = parse_options_cmd_bundle(argc, argv, prefix,
builtin_bundle_create_usage, options, &bundle_file);
@@ -94,75 +94,95 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) {
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
- return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version);
+ ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version);
+ free(bundle_file);
+ return ret;
}
static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) {
- struct bundle_header header;
+ struct bundle_header header = BUNDLE_HEADER_INIT;
int bundle_fd = -1;
int quiet = 0;
-
+ int ret;
struct option options[] = {
OPT_BOOL('q', "quiet", &quiet,
N_("do not show bundle details")),
OPT_END()
};
- const char* bundle_file;
+ char *bundle_file;
argc = parse_options_cmd_bundle(argc, argv, prefix,
builtin_bundle_verify_usage, options, &bundle_file);
/* bundle internals use argv[1] as further parameters */
- memset(&header, 0, sizeof(header));
- if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
- return 1;
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
+ }
close(bundle_fd);
- if (verify_bundle(the_repository, &header, !quiet))
- return 1;
+ if (verify_bundle(the_repository, &header, !quiet)) {
+ ret = 1;
+ goto cleanup;
+ }
+
fprintf(stderr, _("%s is okay\n"), bundle_file);
- return 0;
+ ret = 0;
+cleanup:
+ free(bundle_file);
+ bundle_header_release(&header);
+ return ret;
}
static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) {
- struct bundle_header header;
+ struct bundle_header header = BUNDLE_HEADER_INIT;
int bundle_fd = -1;
-
+ int ret;
struct option options[] = {
OPT_END()
};
- const char* bundle_file;
+ char *bundle_file;
argc = parse_options_cmd_bundle(argc, argv, prefix,
builtin_bundle_list_heads_usage, options, &bundle_file);
/* bundle internals use argv[1] as further parameters */
- memset(&header, 0, sizeof(header));
- if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
- return 1;
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
+ }
close(bundle_fd);
- return !!list_bundle_refs(&header, argc, argv);
+ ret = !!list_bundle_refs(&header, argc, argv);
+cleanup:
+ free(bundle_file);
+ bundle_header_release(&header);
+ return ret;
}
static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) {
- struct bundle_header header;
+ struct bundle_header header = BUNDLE_HEADER_INIT;
int bundle_fd = -1;
-
+ int ret;
struct option options[] = {
OPT_END()
};
- const char* bundle_file;
+ char *bundle_file;
argc = parse_options_cmd_bundle(argc, argv, prefix,
builtin_bundle_unbundle_usage, options, &bundle_file);
/* bundle internals use argv[1] as further parameters */
- memset(&header, 0, sizeof(header));
- if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
- return 1;
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
+ }
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
- return !!unbundle(the_repository, &header, bundle_fd, 0) ||
+ ret = !!unbundle(the_repository, &header, bundle_fd, 0) ||
list_bundle_refs(&header, argc, argv);
+ bundle_header_release(&header);
+cleanup:
+ free(bundle_file);
+ return ret;
}
int cmd_bundle(int argc, const char **argv, const char *prefix)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 5ebf133..243fe68 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -512,12 +512,6 @@ static int batch_objects(struct batch_options *opt)
if (opt->cmdmode)
data.split_on_whitespace = 1;
- if (opt->all_objects) {
- struct object_info empty = OBJECT_INFO_INIT;
- if (!memcmp(&data.info, &empty, sizeof(empty)))
- data.skip_object_info = 1;
- }
-
/*
* If we are printing out the object, then always fill in the type,
* since we will want to decide whether or not to stream.
@@ -527,6 +521,10 @@ static int batch_objects(struct batch_options *opt)
if (opt->all_objects) {
struct object_cb_data cb;
+ struct object_info empty = OBJECT_INFO_INIT;
+
+ if (!memcmp(&data.info, &empty, sizeof(empty)))
+ data.skip_object_info = 1;
if (has_promisor_remote())
warning("This repository uses promisor remotes. Some objects may not be loaded.");
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 8123455..2191256 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -153,7 +153,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
git_config(git_default_config, NULL);
@@ -182,7 +182,6 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (!no_index && read_cache() < 0)
die(_("index file corrupt"));
- dir_init(&dir);
setup_standard_excludes(&dir);
if (stdin_paths) {
diff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c
index 289a9b8..fb9fd13 100644
--- a/builtin/checkout--worker.c
+++ b/builtin/checkout--worker.c
@@ -53,7 +53,7 @@ static void packet_to_pc_item(const char *buffer, int len,
static void report_result(struct parallel_checkout_item *pc_item)
{
- struct pc_item_result res;
+ struct pc_item_result res = { 0 };
size_t size;
res.id = pc_item->id;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index f4cd774..b23bc14 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -378,9 +378,6 @@ static int checkout_worktree(const struct checkout_opts *opts,
if (pc_workers > 1)
init_parallel_checkout();
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
-
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
@@ -407,7 +404,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
remove_marked_cache_entries(&the_index, 1);
remove_scheduled_dirs();
- errs |= finish_delayed_checkout(&state, &nr_checkouts);
+ errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
if (opts->count_checkout_paths) {
if (nr_unmerged)
@@ -530,8 +527,6 @@ static int checkout_paths(const struct checkout_opts *opts,
* Make sure all pathspecs participated in locating the paths
* to be checked out.
*/
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++)
if (opts->overlay_mode)
mark_ce_for_checkout_overlay(active_cache[pos],
@@ -1593,6 +1588,9 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
git_config(git_checkout_config, opts);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
opts->track = BRANCH_TRACK_UNSPECIFIED;
if (!opts->accept_pathspec && !opts->accept_ref)
diff --git a/builtin/clean.c b/builtin/clean.c
index 4944cf4..98a2860 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -641,7 +641,7 @@ static int clean_cmd(void)
static int filter_by_patterns_cmd(void)
{
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
struct strbuf confirm = STRBUF_INIT;
struct strbuf **ignore_list;
struct string_list_item *item;
@@ -665,7 +665,6 @@ static int filter_by_patterns_cmd(void)
if (!confirm.len)
break;
- dir_init(&dir);
pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude");
ignore_list = strbuf_split_max(&confirm, ' ', 0);
@@ -890,7 +889,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf abs_path = STRBUF_INIT;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
struct pathspec pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
@@ -921,7 +920,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
0);
- dir_init(&dir);
if (!interactive && !dry_run && !force) {
if (config_set)
die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
diff --git a/builtin/clone.c b/builtin/clone.c
index eeb74c0..7743dc0 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1320,9 +1320,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
if (!is_local && !complete_refs_before_fetch) {
- err = transport_fetch_refs(transport, mapped_refs);
- if (err)
- goto cleanup;
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
}
remote_head = find_ref_by_name(refs, "HEAD");
@@ -1341,6 +1340,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
our_head_points_at = remote_head_points_at;
}
else {
+ const char *branch;
+ char *ref;
+
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
option_branch, remote_name);
@@ -1351,24 +1353,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote_head_points_at = NULL;
remote_head = NULL;
option_no_checkout = 1;
- if (!option_bare) {
- const char *branch;
- char *ref;
-
- if (transport_ls_refs_options.unborn_head_target &&
- skip_prefix(transport_ls_refs_options.unborn_head_target,
- "refs/heads/", &branch)) {
- ref = transport_ls_refs_options.unborn_head_target;
- transport_ls_refs_options.unborn_head_target = NULL;
- create_symref("HEAD", ref, reflog_msg.buf);
- } else {
- branch = git_default_branch_name(0);
- ref = xstrfmt("refs/heads/%s", branch);
- }
- install_branch_config(0, branch, remote_name, ref);
- free(ref);
+ if (transport_ls_refs_options.unborn_head_target &&
+ skip_prefix(transport_ls_refs_options.unborn_head_target,
+ "refs/heads/", &branch)) {
+ ref = transport_ls_refs_options.unborn_head_target;
+ transport_ls_refs_options.unborn_head_target = NULL;
+ create_symref("HEAD", ref, reflog_msg.buf);
+ } else {
+ branch = git_default_branch_name(0);
+ ref = xstrfmt("refs/heads/%s", branch);
}
+
+ if (!option_bare)
+ install_branch_config(0, branch, remote_name, ref);
+
+ free(ref);
}
write_refspec_config(src_ref_prefix, our_head_points_at,
@@ -1380,9 +1380,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (is_local)
clone_local(path, git_dir);
else if (refs && complete_refs_before_fetch) {
- err = transport_fetch_refs(transport, mapped_refs);
- if (err)
- goto cleanup;
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
}
update_remote_refs(refs, mapped_refs, remote_head_points_at,
@@ -1410,7 +1409,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
junk_mode = JUNK_LEAVE_REPO;
err = checkout(submodule_progress);
-cleanup:
free(remote_name);
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
diff --git a/builtin/column.c b/builtin/column.c
index 40d4b3b..158fdf5 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -29,7 +29,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "width", &copts.width, N_("maximum width")),
OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")),
- OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")),
+ OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")),
OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
OPT_END()
};
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 1031b9a..63ea322 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -88,9 +88,7 @@ static int parse_file_arg_callback(const struct option *opt,
if (!strcmp(arg, "-"))
fd = 0;
else {
- fd = open(arg, O_RDONLY);
- if (fd < 0)
- die_errno(_("git commit-tree: failed to open '%s'"), arg);
+ fd = xopen(arg, O_RDONLY);
}
if (strbuf_read(buf, fd, 0) < 0)
die_errno(_("git commit-tree: failed to read '%s'"), arg);
diff --git a/builtin/commit.c b/builtin/commit.c
index 190d215..7c9b1e7 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -889,7 +889,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
int ident_shown = 0;
int saved_color_setting;
struct ident_split ci, ai;
-
+ const char *hint_cleanup_all = allow_empty_message ?
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '%c' will be ignored.\n") :
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '%c' will be ignored, and an empty"
+ " message aborts the commit.\n");
+ const char *hint_cleanup_space = allow_empty_message ?
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
+ "with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n") :
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
+ "with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "An empty message aborts the commit.\n");
if (whence != FROM_COMMIT) {
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
!merge_contains_scissors)
@@ -911,20 +926,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fprintf(s->fp, "\n");
if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."
- " Lines starting\nwith '%c' will be ignored, and an empty"
- " message aborts the commit.\n"), comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char);
else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
if (whence == FROM_COMMIT && !merge_contains_scissors)
wt_status_add_cut_line(s->fp);
} else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."
- " Lines starting\n"
- "with '%c' will be kept; you may remove them"
- " yourself if you want to.\n"
- "An empty message aborts the commit.\n"), comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char);
/*
* These should never fail because they come from our own
@@ -1246,8 +1253,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (logfile || have_option_m || use_message)
use_editor = 0;
- if (0 <= edit_flag)
- use_editor = edit_flag;
/* Sanity check options */
if (amend && !current_head)
@@ -1337,6 +1342,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
}
}
+ if (0 <= edit_flag)
+ use_editor = edit_flag;
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
@@ -1510,6 +1518,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_status_usage, builtin_status_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
status_init_config(&s, git_status_config);
argc = parse_options(argc, argv, prefix,
builtin_status_options,
@@ -1679,6 +1690,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
status_init_config(&s, git_commit_config);
s.commit_template = 1;
status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 176fe7f..5fd23ab 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -2,6 +2,7 @@
#include "cache.h"
#include "config.h"
#include "diff.h"
+#include "diff-merges.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
@@ -27,6 +28,12 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
rev.abbrev = 0;
prefix = precompose_argv_prefix(argc, argv, prefix);
+ /*
+ * We need (some of) diff for merges options (e.g., --cc), and we need
+ * to avoid conflict with our own meaning of "-m".
+ */
+ diff_merges_suppress_m_parsing();
+
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -35,6 +42,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
option |= DIFF_INDEX_CACHED;
else if (!strcmp(arg, "--merge-base"))
option |= DIFF_INDEX_MERGE_BASE;
+ else if (!strcmp(arg, "-m"))
+ rev.match_missing = 1;
else
usage(diff_cache_usage);
}
diff --git a/builtin/diff.c b/builtin/diff.c
index 2d87c37..dd8ce68 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -26,8 +26,8 @@
static const char builtin_diff_usage[] =
"git diff [<options>] [<commit>] [--] [<path>...]\n"
-" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n"
-" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n"
+" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n"
+" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
" or: git diff [<options>] <blob> <blob>]\n"
" or: git diff [<options>] --no-index [--] <path> <path>]\n"
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 89334b7..90c0bfc 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -562,11 +562,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
if (*entry->left) {
add_path(&ldir, ldir_len, entry->path);
ensure_leading_directories(ldir.buf);
+ unlink(ldir.buf);
write_file(ldir.buf, "%s", entry->left);
}
if (*entry->right) {
add_path(&rdir, rdir_len, entry->path);
ensure_leading_directories(rdir.buf);
+ unlink(rdir.buf);
write_file(rdir.buf, "%s", entry->right);
}
}
@@ -675,7 +677,7 @@ static int run_file_diff(int prompt, const char *prefix,
"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
NULL
};
- int ret = 0, i;
+ int i;
if (prompt > 0)
env[2] = "GIT_DIFFTOOL_PROMPT=true";
@@ -686,8 +688,7 @@ static int run_file_diff(int prompt, const char *prefix,
strvec_push(&args, "diff");
for (i = 0; i < argc; i++)
strvec_push(&args, argv[i]);
- ret = run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
- exit(ret);
+ return run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
}
int cmd_difftool(int argc, const char **argv, const char *prefix)
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 3c20f16..95e8e89 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -821,6 +821,7 @@ static void handle_tag(const char *name, struct tag *tag)
static struct hashmap tags;
message = anonymize_str(&tags, anonymize_tag,
message, message_size, NULL);
+ message_size = strlen(message);
}
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index dfde96a..e064687 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1126,7 +1126,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
if (want_status == FETCH_HEAD_MERGE)
- warning(_("reject %s because shallow roots are not allowed to be updated"),
+ warning(_("rejected %s because shallow roots are not allowed to be updated"),
rm->peer_ref ? rm->peer_ref->name : rm->name);
continue;
}
@@ -1428,7 +1428,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
if (!has_glob_specials(s)) {
struct object_id oid;
if (get_oid(s, &oid))
- die("%s is not a valid object", s);
+ die(_("%s is not a valid object"), s);
+ if (!has_object(the_repository, &oid, 0))
+ die(_("the object %s does not exist"), s);
oid_array_append(oids, &oid);
continue;
}
@@ -1990,6 +1992,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
fetch_config_from_gitmodules(sfjc, rs);
}
+ if (negotiate_only && !negotiation_tip.nr)
+ die(_("--negotiate-only needs one or more --negotiate-tip=*"));
+
if (deepen_relative) {
if (deepen_relative < 0)
die(_("Negative depth in --deepen is not supported"));
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c
index 52be64a..fd86e5a 100644
--- a/builtin/for-each-repo.c
+++ b/builtin/for-each-repo.c
@@ -10,18 +10,16 @@ static const char * const for_each_repo_usage[] = {
NULL
};
-static int run_command_on_repo(const char *path,
- void *cbdata)
+static int run_command_on_repo(const char *path, int argc, const char ** argv)
{
int i;
struct child_process child = CHILD_PROCESS_INIT;
- struct strvec *args = (struct strvec *)cbdata;
child.git_cmd = 1;
strvec_pushl(&child.args, "-C", path, NULL);
- for (i = 0; i < args->nr; i++)
- strvec_push(&child.args, args->v[i]);
+ for (i = 0; i < argc; i++)
+ strvec_push(&child.args, argv[i]);
return run_command(&child);
}
@@ -31,7 +29,6 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix)
static const char *config_key = NULL;
int i, result = 0;
const struct string_list *values;
- struct strvec args = STRVEC_INIT;
const struct option options[] = {
OPT_STRING(0, "config", &config_key, N_("config"),
@@ -45,9 +42,6 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix)
if (!config_key)
die(_("missing --config=<config>"));
- for (i = 0; i < argc; i++)
- strvec_push(&args, argv[i]);
-
values = repo_config_get_value_multi(the_repository,
config_key);
@@ -59,7 +53,7 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix)
return 0;
for (i = 0; !result && i < values->nr; i++)
- result = run_command_on_repo(values->items[i].string, &args);
+ result = run_command_on_repo(values->items[i].string, argc, argv);
return result;
}
diff --git a/builtin/gc.c b/builtin/gc.c
index f05d2f0..ac60662 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -502,7 +502,7 @@ static int report_last_gc_error(void)
*/
warning(_("The last gc run reported the following. "
"Please correct the root cause\n"
- "and remove %s.\n"
+ "and remove %s\n"
"Automatic cleanup will not be performed "
"until the file is removed.\n\n"
"%s"),
@@ -1600,18 +1600,40 @@ static int launchctl_remove_plists(const char *cmd)
launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
}
+static int launchctl_list_contains_plist(const char *name, const char *cmd)
+{
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ char *uid = launchctl_get_uid();
+
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "list", name, NULL);
+
+ child.no_stderr = 1;
+ child.no_stdout = 1;
+
+ if (start_command(&child))
+ die(_("failed to start launchctl"));
+
+ result = finish_command(&child);
+
+ free(uid);
+
+ /* Returns failure if 'name' doesn't exist. */
+ return !result;
+}
+
static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
{
- FILE *plist;
- int i;
+ int i, fd;
const char *preamble, *repeat;
const char *frequency = get_frequency(schedule);
char *name = launchctl_service_name(frequency);
char *filename = launchctl_service_filename(name);
-
- if (safe_create_leading_directories(filename))
- die(_("failed to create directories for '%s'"), filename);
- plist = xfopen(filename, "w");
+ struct lock_file lk = LOCK_INIT;
+ static unsigned long lock_file_timeout_ms = ULONG_MAX;
+ struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
+ struct stat st;
preamble = "<?xml version=\"1.0\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
@@ -1630,7 +1652,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"</array>\n"
"<key>StartCalendarInterval</key>\n"
"<array>\n";
- fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+ strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
switch (schedule) {
case SCHEDULE_HOURLY:
@@ -1639,7 +1661,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 23; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_DAILY:
@@ -1649,32 +1671,59 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 6; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_WEEKLY:
- fprintf(plist,
- "<dict>\n"
- "<key>Day</key><integer>0</integer>\n"
- "<key>Hour</key><integer>0</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
- "</dict>\n");
+ strbuf_addstr(&plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n");
break;
default:
/* unreachable */
break;
}
- fprintf(plist, "</array>\n</dict>\n</plist>\n");
- fclose(plist);
+ strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
+
+ if (safe_create_leading_directories(filename))
+ die(_("failed to create directories for '%s'"), filename);
+
+ if ((long)lock_file_timeout_ms < 0 &&
+ git_config_get_ulong("gc.launchctlplistlocktimeoutms",
+ &lock_file_timeout_ms))
+ lock_file_timeout_ms = 150;
+
+ fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
+ lock_file_timeout_ms);
- /* bootout might fail if not already running, so ignore */
- launchctl_boot_plist(0, filename, cmd);
- if (launchctl_boot_plist(1, filename, cmd))
- die(_("failed to bootstrap service %s"), filename);
+ /*
+ * Does this file already exist? With the intended contents? Is it
+ * registered already? Then it does not need to be re-registered.
+ */
+ if (!stat(filename, &st) && st.st_size == plist.len &&
+ strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
+ !strbuf_cmp(&plist, &plist2) &&
+ launchctl_list_contains_plist(name, cmd))
+ rollback_lock_file(&lk);
+ else {
+ if (write_in_full(fd, plist.buf, plist.len) < 0 ||
+ commit_lock_file(&lk))
+ die_errno(_("could not write '%s'"), filename);
+
+ /* bootout might fail if not already running, so ignore */
+ launchctl_boot_plist(0, filename, cmd);
+ if (launchctl_boot_plist(1, filename, cmd))
+ die(_("failed to bootstrap service %s"), filename);
+ }
free(filename);
free(name);
+ strbuf_release(&plist);
+ strbuf_release(&plist2);
return 0;
}
diff --git a/builtin/grep.c b/builtin/grep.c
index ab8822e..7d2f8e5 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -704,10 +704,9 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
int exc_std, int use_index)
{
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
int i, hit = 0;
- dir_init(&dir);
if (!use_index)
dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 640ef4d..c7b3ad7 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -53,9 +53,7 @@ static void hash_object(const char *path, const char *type, const char *vpath,
unsigned flags, int literally)
{
int fd;
- fd = open(path, O_RDONLY);
- if (fd < 0)
- die_errno("Cannot open '%s'", path);
+ fd = xopen(path, O_RDONLY);
hash_fd(fd, type, vpath, flags, literally);
}
@@ -117,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
prefix = setup_git_directory_gently(&nongit);
if (vpath && prefix)
- vpath = xstrdup(prefix_filename(prefix, vpath));
+ vpath = prefix_filename(prefix, vpath);
git_config(git_default_config, NULL);
diff --git a/builtin/help.c b/builtin/help.c
index bb339f0..b7eec06 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -436,10 +436,9 @@ static void exec_viewer(const char *name, const char *page)
warning(_("'%s': unknown man viewer."), name);
}
-static void show_man_page(const char *git_cmd)
+static void show_man_page(const char *page)
{
struct man_viewer_list *viewer;
- const char *page = cmd_to_page(git_cmd);
const char *fallback = getenv("GIT_MAN_VIEWER");
setup_man_path();
@@ -453,9 +452,8 @@ static void show_man_page(const char *git_cmd)
die(_("no man viewer handled the request"));
}
-static void show_info_page(const char *git_cmd)
+static void show_info_page(const char *page)
{
- const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
die(_("no info viewer handled the request"));
@@ -486,9 +484,8 @@ static void open_html(const char *path)
execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
}
-static void show_html_page(const char *git_cmd)
+static void show_html_page(const char *page)
{
- const char *page = cmd_to_page(git_cmd);
struct strbuf page_path; /* it leaks but we exec bellow */
get_html_page_path(&page_path, page);
@@ -548,6 +545,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
enum help_format parsed_help_format;
+ const char *page;
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
@@ -606,16 +604,17 @@ int cmd_help(int argc, const char **argv, const char *prefix)
argv[0] = check_git_cmd(argv[0]);
+ page = cmd_to_page(argv[0]);
switch (help_format) {
case HELP_FORMAT_NONE:
case HELP_FORMAT_MAN:
- show_man_page(argv[0]);
+ show_man_page(page);
break;
case HELP_FORMAT_INFO:
- show_info_page(argv[0]);
+ show_info_page(page);
break;
case HELP_FORMAT_WEB:
- show_html_page(argv[0]);
+ show_html_page(page);
break;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 3fbc5d7..f267dce 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -338,15 +338,11 @@ static const char *open_pack_file(const char *pack_name)
"pack/tmp_pack_XXXXXX");
pack_name = strbuf_detach(&tmp_file, NULL);
} else {
- output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
- if (output_fd < 0)
- die_errno(_("unable to create '%s'"), pack_name);
+ output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
}
nothread_data.pack_fd = output_fd;
} else {
- input_fd = open(pack_name, O_RDONLY);
- if (input_fd < 0)
- die_errno(_("cannot open packfile '%s'"), pack_name);
+ input_fd = xopen(pack_name, O_RDONLY);
output_fd = -1;
nothread_data.pack_fd = input_fd;
}
@@ -369,9 +365,7 @@ static void parse_pack_header(void)
use(sizeof(struct pack_header));
}
-static NORETURN void bad_object(off_t offset, const char *format,
- ...) __attribute__((format (printf, 2, 3)));
-
+__attribute__((format (printf, 2, 3)))
static NORETURN void bad_object(off_t offset, const char *format, ...)
{
va_list params;
@@ -1483,6 +1477,22 @@ static void write_special_file(const char *suffix, const char *msg,
strbuf_release(&name_buf);
}
+static void rename_tmp_packfile(const char **final_name,
+ const char *curr_name,
+ struct strbuf *name, unsigned char *hash,
+ const char *ext, int make_read_only_if_same)
+{
+ if (*final_name != curr_name) {
+ if (!*final_name)
+ *final_name = odb_pack_name(name, hash, ext);
+ if (finalize_object_file(curr_name, *final_name))
+ die(_("unable to rename temporary '*.%s' file to '%s"),
+ ext, *final_name);
+ } else if (make_read_only_if_same) {
+ chmod(*final_name, 0444);
+ }
+}
+
static void final(const char *final_pack_name, const char *curr_pack_name,
const char *final_index_name, const char *curr_index_name,
const char *final_rev_index_name, const char *curr_rev_index_name,
@@ -1511,31 +1521,13 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
write_special_file("promisor", promisor_msg, final_pack_name,
hash, NULL);
- if (final_pack_name != curr_pack_name) {
- if (!final_pack_name)
- final_pack_name = odb_pack_name(&pack_name, hash, "pack");
- if (finalize_object_file(curr_pack_name, final_pack_name))
- die(_("cannot store pack file"));
- } else if (from_stdin)
- chmod(final_pack_name, 0444);
-
- if (final_index_name != curr_index_name) {
- if (!final_index_name)
- final_index_name = odb_pack_name(&index_name, hash, "idx");
- if (finalize_object_file(curr_index_name, final_index_name))
- die(_("cannot store index file"));
- } else
- chmod(final_index_name, 0444);
-
- if (curr_rev_index_name) {
- if (final_rev_index_name != curr_rev_index_name) {
- if (!final_rev_index_name)
- final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev");
- if (finalize_object_file(curr_rev_index_name, final_rev_index_name))
- die(_("cannot store reverse index file"));
- } else
- chmod(final_rev_index_name, 0444);
- }
+ rename_tmp_packfile(&final_pack_name, curr_pack_name, &pack_name,
+ hash, "pack", from_stdin);
+ if (curr_rev_index_name)
+ rename_tmp_packfile(&final_rev_index_name, curr_rev_index_name,
+ &rev_index_name, hash, "rev", 1);
+ rename_tmp_packfile(&final_index_name, curr_index_name, &index_name,
+ hash, "idx", 1);
if (do_fsck_object) {
struct packed_git *p;
diff --git a/builtin/log.c b/builtin/log.c
index 6102893..3d7717b 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -245,6 +245,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->abbrev_commit = 0;
}
+ if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate)
+ decoration_style = 0;
+
if (decoration_style) {
const struct string_list *config_exclude =
repo_config_get_value_multi(the_repository,
@@ -1968,8 +1971,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
} else if (rev.diffopt.close_file) {
/*
* The diff code parsed --output; it has already opened the
- * file, but but we must instruct it not to close after each
- * diff.
+ * file, but we must instruct it not to close after each diff.
*/
rev.diffopt.no_free = 1;
} else {
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 45cc3b2..29a26ad 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -608,7 +608,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{
int require_work_tree = 0, show_tag = 0, i;
char *max_prefix;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
struct pattern_list *pl;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct option builtin_ls_files_options[] = {
@@ -678,7 +678,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(ls_files_usage, builtin_ls_files_options);
- dir_init(&dir);
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1794548..f4fd823 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -84,6 +84,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
dest = argv[0];
+ packet_trace_identity("ls-remote");
+
UNLEAK(sorting);
if (argc > 1) {
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 664400b..7baef30 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -75,9 +75,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
fprintf(stderr, "corrupt mailbox\n");
exit(1);
}
- fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0)
- die_errno("cannot open output file '%s'", name);
+ fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
output = xfdopen(fd, "w");
/* Copy it out, while searching for a line that begins with
diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c
index 4594507..3583cff 100644
--- a/builtin/merge-ours.c
+++ b/builtin/merge-ours.c
@@ -28,6 +28,6 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die_errno("read_cache failed");
if (index_differs_from(the_repository, "HEAD", NULL, 0))
- exit(2);
- exit(0);
+ return 2;
+ return 0;
}
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index de85207..5dc94d6 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -107,15 +107,12 @@ static void show_diff(struct merge_list *entry)
mmfile_t src, dst;
xpparam_t xpp;
xdemitconf_t xecfg;
- xdemitcb_t ecb;
+ xdemitcb_t ecb = { .out_line = show_outf };
memset(&xpp, 0, sizeof(xpp));
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- ecb.out_hunk = NULL;
- ecb.out_line = show_outf;
- ecb.priv = NULL;
src.ptr = origin(entry, &size);
if (!src.ptr)
diff --git a/builtin/merge.c b/builtin/merge.c
index eddb8ae..8949a9c 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -56,8 +56,8 @@ struct strategy {
static const char * const builtin_merge_usage[] = {
N_("git merge [<options>] [<commit>...]"),
- N_("git merge --abort"),
- N_("git merge --continue"),
+ "git merge --abort",
+ "git merge --continue",
NULL
};
@@ -503,7 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
struct strbuf bname = STRBUF_INIT;
struct merge_remote_desc *desc;
const char *ptr;
- char *found_ref;
+ char *found_ref = NULL;
int len, early;
strbuf_branchname(&bname, remote, 0);
@@ -586,6 +586,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
oid_to_hex(&remote_head->object.oid), remote);
cleanup:
+ free(found_ref);
strbuf_release(&buf);
strbuf_release(&bname);
}
@@ -738,7 +739,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
for (x = 0; x < xopts_nr; x++)
if (parse_merge_opt(&o, xopts[x]))
- die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
+ die(_("unknown strategy option: -X%s"), xopts[x]);
o.branch1 = head_arg;
o.branch2 = merge_remote_util(remoteheads->item)->name;
@@ -861,9 +862,11 @@ static void prepare_to_commit(struct commit_list *remoteheads)
strbuf_commented_addf(&msg, "\n");
}
strbuf_commented_addf(&msg, _(merge_editor_comment));
- strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
- scissors_editor_comment :
- no_scissors_editor_comment), comment_line_char);
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+ strbuf_commented_addf(&msg, _(scissors_editor_comment));
+ else
+ strbuf_commented_addf(&msg,
+ _(no_scissors_editor_comment), comment_line_char);
}
if (signoff)
append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
@@ -1135,9 +1138,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
merge_names = &fetch_head_file;
filename = git_path_fetch_head(the_repository);
- fd = open(filename, O_RDONLY);
- if (fd < 0)
- die_errno(_("could not open '%s' for reading"), filename);
+ fd = xopen(filename, O_RDONLY);
if (strbuf_read(merge_names, fd, 0) < 0)
die_errno(_("could not read '%s'"), filename);
@@ -1560,6 +1561,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
&head_commit->object.oid,
&commit->object.oid,
overwrite_ignore)) {
+ apply_autostash(git_path_merge_autostash(the_repository));
ret = 1;
goto done;
}
@@ -1620,7 +1622,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (fast_forward == FF_ONLY)
- die(_("Not possible to fast-forward, aborting."));
+ die_ff_impossible();
if (autostash)
create_autostash(the_repository,
@@ -1708,6 +1710,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
+ apply_autostash(git_path_merge_autostash(the_repository));
ret = 2;
goto done;
} else if (best_strategy == wt_strategy)
@@ -1715,7 +1718,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else {
printf(_("Rewinding the tree to pristine...\n"));
restore_state(&head_commit->object.oid, &stash);
- printf(_("Using the %s to prepare resolving by hand.\n"),
+ printf(_("Using the %s strategy to prepare resolving by hand.\n"),
best_strategy);
try_merge_strategy(best_strategy, common, remoteheads,
head_commit);
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 891991b..ae78ca1 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -189,5 +189,5 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
used=0; /* reset tree entry buffer for re-use in batch mode */
}
strbuf_release(&sb);
- exit(0);
+ return 0;
}
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 5d3ea44..8ff0dee 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -176,8 +176,8 @@ int cmd_multi_pack_index(int argc, const char **argv,
else if (!strcmp(argv[0], "expire"))
return cmd_multi_pack_index_expire(argc, argv);
else {
-usage:
error(_("unrecognized subcommand: %s"), argv[0]);
+usage:
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
}
diff --git a/builtin/mv.c b/builtin/mv.c
index 3fccdcb..c2f96c8 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -303,5 +303,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
+ string_list_clear(&src_for_dst, 0);
+ UNLEAK(source);
+ UNLEAK(dest_path);
+ free(submodule_gitfile);
+ free(modes);
return 0;
}
diff --git a/builtin/notes.c b/builtin/notes.c
index 74bba39..71c5958 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -172,9 +172,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
/* write the template message before editing: */
d->edit_path = git_pathdup("NOTES_EDITMSG");
- fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), d->edit_path);
+ fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (d->given)
write_or_die(fd, d->buf.buf, d->buf.len);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index de00adb..a01767a 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1217,6 +1217,7 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
struct stat st;
struct strbuf tmpname = STRBUF_INIT;
+ char *idx_tmp_name = NULL;
/*
* Packs are runtime accessed in their mtime
@@ -1237,7 +1238,8 @@ static void write_pack_file(void)
warning_errno(_("failed utime() on %s"), pack_tmp_name);
}
- strbuf_addf(&tmpname, "%s-", base_name);
+ strbuf_addf(&tmpname, "%s-%s.", base_name,
+ hash_to_hex(hash));
if (write_bitmap_index) {
bitmap_writer_set_checksum(hash);
@@ -1245,13 +1247,14 @@ static void write_pack_file(void)
&to_pack, written_list, nr_written);
}
- finish_tmp_packfile(&tmpname, pack_tmp_name,
+ stage_tmp_packfiles(&tmpname, pack_tmp_name,
written_list, nr_written,
- &pack_idx_opts, hash);
+ &pack_idx_opts, hash, &idx_tmp_name);
if (write_bitmap_index) {
- strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash));
+ size_t tmpname_len = tmpname.len;
+ strbuf_addstr(&tmpname, "bitmap");
stop_progress(&progress_state);
bitmap_writer_show_progress(progress);
@@ -1260,8 +1263,12 @@ static void write_pack_file(void)
bitmap_writer_finish(written_list, nr_written,
tmpname.buf, write_bitmap_options);
write_bitmap_index = 0;
+ strbuf_setlen(&tmpname, tmpname_len);
}
+ rename_tmp_packfile_idx(&tmpname, &idx_tmp_name);
+
+ free(idx_tmp_name);
strbuf_release(&tmpname);
free(pack_tmp_name);
puts(hash_to_hex(hash));
@@ -3311,9 +3318,26 @@ static void read_packs_list_from_stdin(void)
}
/*
- * First handle all of the excluded packs, marking them as kept in-core
- * so that later calls to add_object_entry() discards any objects that
- * are also found in excluded packs.
+ * Arguments we got on stdin may not even be packs. First
+ * check that to avoid segfaulting later on in
+ * e.g. pack_mtime_cmp(), excluded packs are handled below.
+ *
+ * Since we first parsed our STDIN and then sorted the input
+ * lines the pack we error on will be whatever line happens to
+ * sort first. This is lazy, it's enough that we report one
+ * bad case here, we don't need to report the first/last one,
+ * or all of them.
+ */
+ for_each_string_list_item(item, &include_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ }
+
+ /*
+ * Then, handle all of the excluded packs, marking them as
+ * kept in-core so that later calls to add_object_entry()
+ * discards any objects that are also found in excluded packs.
*/
for_each_string_list_item(item, &exclude_packs) {
struct packed_git *p = item->util;
diff --git a/builtin/pull.c b/builtin/pull.c
index e8927fc..b311ea6 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -126,9 +126,9 @@ static struct option pull_options[] = {
/* Options passed to git-merge or git-rebase */
OPT_GROUP(N_("Options related to merging")),
OPT_CALLBACK_F('r', "rebase", &opt_rebase,
- "(false|true|merges|preserve|interactive)",
- N_("incorporate changes by rebasing rather than merging"),
- PARSE_OPT_OPTARG, parse_opt_rebase),
+ "(false|true|merges|preserve|interactive)",
+ N_("incorporate changes by rebasing rather than merging"),
+ PARSE_OPT_OPTARG, parse_opt_rebase),
OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
N_("do not show a diffstat at the end of the merge"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG),
@@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,
strvec_pushv(&args, opt_strategy_opts.v);
if (opt_gpg_sign)
strvec_push(&args, opt_gpg_sign);
+ if (opt_signoff)
+ strvec_push(&args, opt_signoff);
if (opt_autostash == 0)
strvec_push(&args, "--no-autostash");
else if (opt_autostash == 1)
@@ -911,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,
return ret;
}
-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
+static int get_can_ff(struct object_id *orig_head,
+ struct oid_array *merge_heads)
{
int ret;
struct commit_list *list = NULL;
struct commit *merge_head, *head;
+ struct object_id *orig_merge_head;
+
+ if (merge_heads->nr > 1)
+ return 0;
+ orig_merge_head = &merge_heads->oid[0];
head = lookup_commit_reference(the_repository, orig_head);
commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository, orig_merge_head);
@@ -927,9 +935,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_
static void show_advice_pull_non_ff(void)
{
- advise(_("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"
+ advise(_("You have divergent branches and need to specify how to reconcile them.\n"
+ "You can do so by running one of the following commands sometime before\n"
+ "your next pull:\n"
"\n"
" git config pull.rebase false # merge (the default strategy)\n"
" git config pull.rebase true # rebase\n"
@@ -947,7 +955,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
struct oid_array merge_heads = OID_ARRAY_INIT;
struct object_id orig_head, curr_head;
struct object_id rebase_fork_point;
- int autostash;
int rebase_unspecified = 0;
int can_ff;
@@ -967,8 +974,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
parse_repo_refspecs(argc, argv, &repo, &refspecs);
- if (!opt_ff)
+ if (!opt_ff) {
opt_ff = xstrdup_or_null(config_get_ff());
+ /*
+ * A subtle point: opt_ff was set on the line above via
+ * reading from config. opt_rebase, in contrast, is set
+ * before this point via command line options. The setting
+ * of opt_rebase via reading from config (using
+ * config_get_rebase()) does not happen until later. We
+ * are relying on the next if-condition happening before
+ * the config_get_rebase() call so that an explicit
+ * "--rebase" can override a config setting of
+ * pull.ff=only.
+ */
+ if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only"))
+ opt_ff = "--ff";
+ }
if (opt_rebase < 0)
opt_rebase = config_get_rebase(&rebase_unspecified);
@@ -982,8 +1003,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (get_oid("HEAD", &orig_head))
oidclr(&orig_head);
- autostash = config_autostash;
if (opt_rebase) {
+ int autostash = config_autostash;
if (opt_autostash != -1)
autostash = opt_autostash;
@@ -1042,19 +1063,29 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
die(_("Cannot merge multiple branches into empty head."));
return pull_into_void(merge_heads.oid, &curr_head);
}
- if (opt_rebase && merge_heads.nr > 1)
- die(_("Cannot rebase onto multiple branches."));
+ if (merge_heads.nr > 1) {
+ if (opt_rebase)
+ die(_("Cannot rebase onto multiple branches."));
+ if (opt_ff && !strcmp(opt_ff, "--ff-only"))
+ die(_("Cannot fast-forward to multiple branches."));
+ }
- can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
+ can_ff = get_can_ff(&orig_head, &merge_heads);
- if (rebase_unspecified && !opt_ff && !can_ff) {
- if (opt_verbosity >= 0)
- show_advice_pull_non_ff();
+ /* ff-only takes precedence over rebase */
+ if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
+ if (!can_ff)
+ die_ff_impossible();
+ opt_rebase = REBASE_FALSE;
+ }
+ /* If no action specified and we can't fast forward, then warn. */
+ if (!opt_ff && rebase_unspecified && !can_ff) {
+ show_advice_pull_non_ff();
+ die(_("Need to specify how to reconcile divergent branches."));
}
if (opt_rebase) {
int ret = 0;
- int ran_ff = 0;
struct object_id newbase;
struct object_id upstream;
@@ -1065,16 +1096,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
submodule_touches_in_range(the_repository, &upstream, &curr_head))
die(_("cannot rebase with locally recorded submodule modifications"));
- if (!autostash) {
- if (can_ff) {
- /* we can fast-forward this without invoking rebase */
- opt_ff = "--ff-only";
- ran_ff = 1;
- ret = run_merge();
- }
- }
- if (!ran_ff)
+
+ if (can_ff) {
+ /* we can fast-forward this without invoking rebase */
+ opt_ff = "--ff-only";
+ ret = run_merge();
+ } else {
ret = run_rebase(&newbase, &upstream);
+ }
if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
diff --git a/builtin/push.c b/builtin/push.c
index 194967e..e8b10a9 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -185,82 +185,73 @@ static const char message_detached_head_die[] =
"\n"
" git push %s HEAD:<name-of-remote-branch>\n");
-static void setup_push_upstream(struct remote *remote, struct branch *branch,
- int triangular, int simple)
+static const char *get_upstream_ref(struct branch *branch, const char *remote_name)
{
- if (!branch)
- die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
" git push --set-upstream %s %s\n"),
branch->name,
- remote->name,
+ remote_name,
branch->name);
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
- if (triangular)
- die(_("You are pushing to remote '%s', which is not the upstream of\n"
- "your current branch '%s', without telling me what to push\n"
- "to update which remote branch."),
- remote->name, branch->name);
-
- if (simple) {
- /* Additional safety */
- if (strcmp(branch->refname, branch->merge[0]->src))
- die_push_simple(branch, remote);
- }
-
- refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src);
-}
-
-static void setup_push_current(struct remote *remote, struct branch *branch)
-{
- if (!branch)
- die(_(message_detached_head_die), remote->name);
- refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname);
-}
-static int is_workflow_triangular(struct remote *remote)
-{
- struct remote *fetch_remote = remote_get(NULL);
- return (fetch_remote && fetch_remote != remote);
+ return branch->merge[0]->src;
}
static void setup_default_push_refspecs(struct remote *remote)
{
- struct branch *branch = branch_get(NULL);
- int triangular = is_workflow_triangular(remote);
+ struct branch *branch;
+ const char *dst;
+ int same_remote;
switch (push_default) {
- default:
case PUSH_DEFAULT_MATCHING:
refspec_append(&rs, ":");
+ return;
+
+ case PUSH_DEFAULT_NOTHING:
+ die(_("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\"."));
+ return;
+ default:
break;
+ }
+ branch = branch_get(NULL);
+ if (!branch)
+ die(_(message_detached_head_die), remote->name);
+
+ dst = branch->refname;
+ same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));
+
+ switch (push_default) {
+ default:
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
- if (triangular)
- setup_push_current(remote, branch);
- else
- setup_push_upstream(remote, branch, triangular, 1);
+ if (!same_remote)
+ break;
+ if (strcmp(branch->refname, get_upstream_ref(branch, remote->name)))
+ die_push_simple(branch, remote);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, branch, triangular, 0);
+ if (!same_remote)
+ die(_("You are pushing to remote '%s', which is not the upstream of\n"
+ "your current branch '%s', without telling me what to push\n"
+ "to update which remote branch."),
+ remote->name, branch->name);
+ dst = get_upstream_ref(branch, remote->name);
break;
case PUSH_DEFAULT_CURRENT:
- setup_push_current(remote, branch);
- break;
-
- case PUSH_DEFAULT_NOTHING:
- die(_("You didn't specify any refspecs to push, and "
- "push.default is \"nothing\"."));
break;
}
+
+ refspec_appendf(&rs, "%s:%s", branch->refname, dst);
}
static const char message_advice_pull_before_push[] =
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 12f0931..66a0a0f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -139,7 +139,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
replay.ignore_date = opts->ignore_date;
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
if (opts->strategy)
- replay.strategy = opts->strategy;
+ replay.strategy = xstrdup_or_null(opts->strategy);
else if (!replay.strategy && replay.default_strategy) {
replay.strategy = replay.default_strategy;
replay.default_strategy = NULL;
@@ -1918,7 +1918,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
&options.orig_head))
options.head_name = NULL;
else
- die(_("fatal: no such branch/commit '%s'"),
+ die(_("no such branch/commit '%s'"),
branch_name);
} else if (argc == 0) {
/* Do not need to switch branches, we are already on it. */
@@ -2109,6 +2109,7 @@ cleanup:
free(options.head_name);
free(options.gpg_sign_opt);
free(options.cmd);
+ free(options.strategy);
strbuf_release(&options.git_format_patch_opt);
free(squash_onto_name);
return ret;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index a347425..2d1f97e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -425,9 +425,6 @@ static int proc_receive_ref_matches(struct command *cmd)
return 0;
}
-static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
-static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
-
static void report_message(const char *prefix, const char *err, va_list params)
{
int sz;
@@ -445,6 +442,7 @@ static void report_message(const char *prefix, const char *err, va_list params)
xwrite(2, msg, sz);
}
+__attribute__((format (printf, 1, 2)))
static void rp_warning(const char *err, ...)
{
va_list params;
@@ -453,6 +451,7 @@ static void rp_warning(const char *err, ...)
va_end(params);
}
+__attribute__((format (printf, 1, 2)))
static void rp_error(const char *err, ...)
{
va_list params;
diff --git a/builtin/repack.c b/builtin/repack.c
index 5f9bc74..c3e4771 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -208,10 +208,10 @@ static struct {
unsigned optional:1;
} exts[] = {
{".pack"},
- {".idx"},
{".rev", 1},
{".bitmap", 1},
{".promisor", 1},
+ {".idx"},
};
static unsigned populate_pack_exts(char *name)
diff --git a/builtin/rerere.c b/builtin/rerere.c
index fd3be17..83d7a77 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -28,7 +28,7 @@ static int diff_two(const char *file1, const char *label1,
{
xpparam_t xpp;
xdemitconf_t xecfg;
- xdemitcb_t ecb;
+ xdemitcb_t ecb = { .out_line = outf };
mmfile_t minus, plus;
int ret;
@@ -41,8 +41,6 @@ static int diff_two(const char *file1, const char *label1,
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- ecb.out_hunk = NULL;
- ecb.out_line = outf;
ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 7677b1a..36cb909 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -127,13 +127,15 @@ static void show_commit(struct commit *commit, void *data)
if (info->header_prefix)
fputs(info->header_prefix, stdout);
- if (!revs->graph)
- fputs(get_revision_mark(revs, commit), stdout);
- if (revs->abbrev_commit && revs->abbrev)
- fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev),
- stdout);
- else
- fputs(oid_to_hex(&commit->object.oid), stdout);
+ if (revs->include_header) {
+ if (!revs->graph)
+ fputs(get_revision_mark(revs, commit), stdout);
+ if (revs->abbrev_commit && revs->abbrev)
+ fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev),
+ stdout);
+ else
+ fputs(oid_to_hex(&commit->object.oid), stdout);
+ }
if (revs->print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
@@ -153,7 +155,7 @@ static void show_commit(struct commit *commit, void *data)
show_decorations(revs, commit);
if (revs->commit_format == CMIT_FMT_ONELINE)
putchar(' ');
- else
+ else if (revs->include_header)
putchar('\n');
if (revs->verbose_header) {
@@ -512,6 +514,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
+ revs.include_header = 1;
/*
* Scan the argument list before invoking setup_revisions(), so that we
@@ -627,6 +630,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
continue;
}
+ if (!strcmp(arg, ("--commit-header"))) {
+ revs.include_header = 1;
+ continue;
+ }
+
+ if (!strcmp(arg, ("--no-commit-header"))) {
+ revs.include_header = 0;
+ continue;
+ }
+
if (!strcmp(arg, "--disk-usage")) {
show_disk_usage = 1;
info.flags |= REV_LIST_QUIET;
@@ -636,10 +649,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
usage(rev_list_usage);
}
+ if (revs.commit_format != CMIT_FMT_USERFORMAT)
+ revs.include_header = 1;
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
/* The command line has a --pretty */
info.hdr_termination = '\n';
- if (revs.commit_format == CMIT_FMT_ONELINE)
+ if (revs.commit_format == CMIT_FMT_ONELINE || !revs.include_header)
info.header_prefix = "";
else
info.header_prefix = "commit ";
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 7af8dab..22c4e1a 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -435,11 +435,11 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
/* get the usage up to the first line with a -- on it */
for (;;) {
if (strbuf_getline(&sb, stdin) == EOF)
- die("premature end of input");
+ die(_("premature end of input"));
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) {
if (unb < 1)
- die("no usage string given before the `--' separator");
+ die(_("no usage string given before the `--' separator"));
usage[unb] = NULL;
break;
}
@@ -545,7 +545,7 @@ static void die_no_single_rev(int quiet)
if (quiet)
exit(1);
else
- die("Needed a single revision");
+ die(_("Needed a single revision"));
}
static const char builtin_rev_parse_usage[] =
@@ -709,10 +709,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--resolve-git-dir")) {
const char *gitdir = argv[++i];
if (!gitdir)
- die("--resolve-git-dir requires an argument");
+ die(_("--resolve-git-dir requires an argument"));
gitdir = resolve_gitdir(gitdir);
if (!gitdir)
- die("not a gitdir '%s'", argv[i]);
+ die(_("not a gitdir '%s'"), argv[i]);
puts(gitdir);
continue;
}
@@ -736,7 +736,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!seen_end_of_options && *arg == '-') {
if (!strcmp(arg, "--git-path")) {
if (!argv[i + 1])
- die("--git-path requires an argument");
+ die(_("--git-path requires an argument"));
strbuf_reset(&buf);
print_path(git_path("%s", argv[i + 1]), prefix,
format,
@@ -746,7 +746,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
if (!strcmp(arg,"-n")) {
if (++i >= argc)
- die("-n requires an argument");
+ die(_("-n requires an argument"));
if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
show(arg);
show(argv[i]);
@@ -760,26 +760,26 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
if (opt_with_value(arg, "--path-format", &arg)) {
if (!arg)
- die("--path-format requires an argument");
+ die(_("--path-format requires an argument"));
if (!strcmp(arg, "absolute")) {
format = FORMAT_CANONICAL;
} else if (!strcmp(arg, "relative")) {
format = FORMAT_RELATIVE;
} else {
- die("unknown argument to --path-format: %s", arg);
+ die(_("unknown argument to --path-format: %s"), arg);
}
continue;
}
if (!strcmp(arg, "--default")) {
def = argv[++i];
if (!def)
- die("--default requires an argument");
+ die(_("--default requires an argument"));
continue;
}
if (!strcmp(arg, "--prefix")) {
prefix = argv[++i];
if (!prefix)
- die("--prefix requires an argument");
+ die(_("--prefix requires an argument"));
startup_info->prefix = prefix;
output_prefix = 1;
continue;
@@ -848,7 +848,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
else if (!strcmp(arg, "loose"))
abbrev_ref_strict = 0;
else
- die("unknown mode for --abbrev-ref: %s",
+ die(_("unknown mode for --abbrev-ref: %s"),
arg);
}
continue;
@@ -892,7 +892,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (work_tree)
print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
else
- die("this operation must be run in a work tree");
+ die(_("this operation must be run in a work tree"));
continue;
}
if (!strcmp(arg, "--show-superproject-working-tree")) {
@@ -1020,7 +1020,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (strcmp(val, "storage") &&
strcmp(val, "input") &&
strcmp(val, "output"))
- die("unknown mode for --show-object-format: %s",
+ die(_("unknown mode for --show-object-format: %s"),
arg);
puts(the_hash_algo->name);
continue;
@@ -1058,7 +1058,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (verify)
die_no_single_rev(quiet);
if (has_dashdash)
- die("bad revision '%s'", arg);
+ die(_("bad revision '%s'"), arg);
as_is = 1;
if (!show_file(arg, output_prefix))
continue;
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index a7e0166..729dea1 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -230,6 +230,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.atomic = atomic;
args.stateless_rpc = stateless_rpc;
args.push_options = push_options.nr ? &push_options : NULL;
+ args.url = dest;
if (from_stdin) {
if (args.stateless_rpc) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index d6d2dab..bea4bbf 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -482,10 +482,9 @@ static void snarf_refs(int head, int remotes)
}
}
-static int rev_is_head(const char *head, const char *name,
- unsigned char *head_sha1, unsigned char *sha1)
+static int rev_is_head(const char *head, const char *name)
{
- if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
+ if (!head)
return 0;
skip_prefix(head, "refs/heads/", &head);
if (!skip_prefix(name, "refs/heads/", &name))
@@ -806,9 +805,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head,
- ref_name[i],
- head_oid.hash, NULL))
+ if (rev_is_head(head, ref_name[i]))
has_head++;
}
if (!has_head) {
@@ -867,10 +864,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head,
- ref_name[i],
- head_oid.hash,
- rev[i]->object.oid.hash);
+ int is_head = rev_is_head(head, ref_name[i]) &&
+ oideq(&head_oid, &rev[i]->object.oid);
if (extra < 0)
printf("%c [%s] ",
is_head ? '*' : ' ', ref_name[i]);
@@ -939,9 +934,12 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
mark = '*';
else
mark = '+';
- printf("%s%c%s",
- get_color_code(i),
- mark, get_color_reset_code());
+ if (mark == ' ')
+ putchar(mark);
+ else
+ printf("%s%c%s",
+ get_color_code(i),
+ mark, get_color_reset_code());
}
putchar(' ');
}
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index a4bdd7c..8ba9f13 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -380,10 +380,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
struct pattern_entry *e = xmalloc(sizeof(*e));
e->patternlen = path->len;
e->pattern = strbuf_detach(path, NULL);
- hashmap_entry_init(&e->ent,
- ignore_case ?
- strihash(e->pattern) :
- strhash(e->pattern));
+ hashmap_entry_init(&e->ent, fspathhash(e->pattern));
hashmap_add(&pl->recursive_hashmap, &e->ent);
@@ -399,10 +396,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
e = xmalloc(sizeof(struct pattern_entry));
e->patternlen = newlen;
e->pattern = xstrndup(oldpattern, newlen);
- hashmap_entry_init(&e->ent,
- ignore_case ?
- strihash(e->pattern) :
- strhash(e->pattern));
+ hashmap_entry_init(&e->ent, fspathhash(e->pattern));
if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
hashmap_add(&pl->parent_hashmap, &e->ent);
diff --git a/builtin/stash.c b/builtin/stash.c
index 01066d7..5512f49 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -26,7 +26,7 @@ static const char * const git_stash_usage[] = {
N_("git stash drop [-q|--quiet] [<stash>]"),
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
N_("git stash branch <branchname> [<stash>]"),
- N_("git stash clear"),
+ "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"
@@ -67,7 +67,7 @@ static const char * const git_stash_branch_usage[] = {
};
static const char * const git_stash_clear_usage[] = {
- N_("git stash clear"),
+ "git stash clear",
NULL
};
@@ -313,6 +313,17 @@ static int reset_head(void)
return run_command(&cp);
}
+static int is_path_a_directory(const char *path)
+{
+ /*
+ * This function differs from abspath.c:is_directory() in that
+ * here we use lstat() instead of stat(); we do not want to
+ * follow symbolic links here.
+ */
+ struct stat st;
+ return (!lstat(path, &st) && S_ISDIR(st.st_mode));
+}
+
static void add_diff_to_buf(struct diff_queue_struct *q,
struct diff_options *options,
void *data)
@@ -320,6 +331,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
int i;
for (i = 0; i < q->nr; i++) {
+ if (is_path_a_directory(q->queue[i]->one->path))
+ continue;
+
strbuf_addstr(data, q->queue[i]->one->path);
/* NUL-terminate: will be fed to update-index -z */
@@ -521,9 +535,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
}
}
- if (info->has_u && restore_untracked(&info->u_tree))
- return error(_("could not restore untracked files from stash"));
-
init_merge_options(&o, the_repository);
o.branch1 = "Updated upstream";
@@ -558,6 +569,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
unstage_changes_unless_new(&c_tree);
}
+ if (info->has_u && restore_untracked(&info->u_tree))
+ return error(_("could not restore untracked files from stash"));
+
if (!quiet) {
struct child_process cp = CHILD_PROCESS_INIT;
@@ -761,7 +775,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
cp.git_cmd = 1;
strvec_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
- "--first-parent", "-m", NULL);
+ "--first-parent", NULL);
strvec_pushv(&cp.args, argv);
strvec_push(&cp.args, ref_stash);
strvec_push(&cp.args, "--");
@@ -991,9 +1005,8 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
{
int i;
int found = 0;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
- dir_init(&dir);
if (include_untracked != INCLUDE_ALL_FILES)
setup_standard_excludes(&dir);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index d55f626..ef2776a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -19,7 +19,6 @@
#include "diffcore.h"
#include "diff.h"
#include "object-store.h"
-#include "dir.h"
#include "advice.h"
#define OPT_QUIET (1 << 0)
@@ -188,11 +187,13 @@ static char *relative_url(const char *remote_url,
out = xstrdup(sb.buf + 2);
else
out = xstrdup(sb.buf);
- strbuf_reset(&sb);
- if (!up_path || !is_relative)
+ if (!up_path || !is_relative) {
+ strbuf_release(&sb);
return out;
+ }
+ strbuf_reset(&sb);
strbuf_addf(&sb, "%s%s", up_path, out);
free(out);
return strbuf_detach(&sb, NULL);
@@ -1300,7 +1301,7 @@ static int module_summary(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "cached", &cached,
N_("use the commit stored in the index instead of the submodule HEAD")),
OPT_BOOL(0, "files", &files,
- N_("to compare the commit in the index with that in the submodule HEAD")),
+ N_("compare the commit in the index with that in the submodule HEAD")),
OPT_BOOL(0, "for-status", &for_status,
N_("skip submodules with 'ignore_config' value set to 'all'")),
OPT_INTEGER('n', "summary-limit", &summary_limit,
@@ -1658,45 +1659,20 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
return 0;
}
-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 single_branch)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
-
- strvec_push(&cp.args, "clone");
- strvec_push(&cp.args, "--no-checkout");
- if (quiet)
- strvec_push(&cp.args, "--quiet");
- if (progress)
- strvec_push(&cp.args, "--progress");
- if (depth && *depth)
- strvec_pushl(&cp.args, "--depth", depth, NULL);
- if (reference->nr) {
- struct string_list_item *item;
- for_each_string_list_item(item, reference)
- strvec_pushl(&cp.args, "--reference",
- item->string, NULL);
- }
- if (dissociate)
- strvec_push(&cp.args, "--dissociate");
- if (gitdir && *gitdir)
- strvec_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
- if (single_branch >= 0)
- strvec_push(&cp.args, single_branch ?
- "--single-branch" :
- "--no-single-branch");
-
- strvec_push(&cp.args, "--");
- strvec_push(&cp.args, url);
- strvec_push(&cp.args, path);
-
- cp.git_cmd = 1;
- prepare_submodule_repo_env(&cp.env_array);
- cp.no_stdin = 1;
-
- return run_command(&cp);
-}
+struct module_clone_data {
+ const char *prefix;
+ const char *path;
+ const char *name;
+ const char *url;
+ const char *depth;
+ struct string_list reference;
+ unsigned int quiet: 1;
+ unsigned int progress: 1;
+ unsigned int dissociate: 1;
+ unsigned int require_init: 1;
+ int single_branch;
+};
+#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
struct submodule_alternate_setup {
const char *submodule_name;
@@ -1802,37 +1778,128 @@ static void prepare_possible_alternates(const char *sm_name,
free(error_strategy);
}
-static int module_clone(int argc, const char **argv, const char *prefix)
+static int clone_submodule(struct module_clone_data *clone_data)
{
- const char *name = NULL, *url = NULL, *depth = NULL;
- int quiet = 0;
- int progress = 0;
- char *p, *path = NULL, *sm_gitdir;
- struct strbuf sb = STRBUF_INIT;
- struct string_list reference = STRING_LIST_INIT_NODUP;
- int dissociate = 0, require_init = 0;
+ char *p, *sm_gitdir;
char *sm_alternate = NULL, *error_strategy = NULL;
- int single_branch = -1;
+ struct strbuf sb = STRBUF_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name);
+ sm_gitdir = absolute_pathdup(sb.buf);
+ strbuf_reset(&sb);
+
+ if (!is_absolute_path(clone_data->path)) {
+ strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path);
+ clone_data->path = strbuf_detach(&sb, NULL);
+ } else {
+ clone_data->path = xstrdup(clone_data->path);
+ }
+
+ if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0)
+ die(_("refusing to create/use '%s' in another submodule's "
+ "git dir"), sm_gitdir);
+
+ if (!file_exists(sm_gitdir)) {
+ if (safe_create_leading_directories_const(sm_gitdir) < 0)
+ die(_("could not create directory '%s'"), sm_gitdir);
+
+ prepare_possible_alternates(clone_data->name, &clone_data->reference);
+
+ strvec_push(&cp.args, "clone");
+ strvec_push(&cp.args, "--no-checkout");
+ if (clone_data->quiet)
+ strvec_push(&cp.args, "--quiet");
+ if (clone_data->progress)
+ strvec_push(&cp.args, "--progress");
+ if (clone_data->depth && *(clone_data->depth))
+ strvec_pushl(&cp.args, "--depth", clone_data->depth, NULL);
+ if (clone_data->reference.nr) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, &clone_data->reference)
+ strvec_pushl(&cp.args, "--reference",
+ item->string, NULL);
+ }
+ if (clone_data->dissociate)
+ strvec_push(&cp.args, "--dissociate");
+ if (sm_gitdir && *sm_gitdir)
+ strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL);
+ if (clone_data->single_branch >= 0)
+ strvec_push(&cp.args, clone_data->single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
+
+ strvec_push(&cp.args, "--");
+ strvec_push(&cp.args, clone_data->url);
+ strvec_push(&cp.args, clone_data->path);
+
+ cp.git_cmd = 1;
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.no_stdin = 1;
+
+ if(run_command(&cp))
+ die(_("clone of '%s' into submodule path '%s' failed"),
+ clone_data->url, clone_data->path);
+ } else {
+ if (clone_data->require_init && !access(clone_data->path, X_OK) &&
+ !is_empty_dir(clone_data->path))
+ die(_("directory not empty: '%s'"), clone_data->path);
+ if (safe_create_leading_directories_const(clone_data->path) < 0)
+ die(_("could not create directory '%s'"), clone_data->path);
+ strbuf_addf(&sb, "%s/index", sm_gitdir);
+ unlink_or_warn(sb.buf);
+ strbuf_reset(&sb);
+ }
+
+ connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0);
+
+ p = git_pathdup_submodule(clone_data->path, "config");
+ if (!p)
+ die(_("could not get submodule directory for '%s'"), clone_data->path);
+
+ /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
+ git_config_get_string("submodule.alternateLocation", &sm_alternate);
+ if (sm_alternate)
+ git_config_set_in_file(p, "submodule.alternateLocation",
+ sm_alternate);
+ git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
+ if (error_strategy)
+ git_config_set_in_file(p, "submodule.alternateErrorStrategy",
+ error_strategy);
+
+ free(sm_alternate);
+ free(error_strategy);
+
+ strbuf_release(&sb);
+ free(sm_gitdir);
+ free(p);
+ return 0;
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+ int dissociate = 0, quiet = 0, progress = 0, require_init = 0;
+ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
struct option module_clone_options[] = {
- OPT_STRING(0, "prefix", &prefix,
+ OPT_STRING(0, "prefix", &clone_data.prefix,
N_("path"),
N_("alternative anchor for relative paths")),
- OPT_STRING(0, "path", &path,
+ OPT_STRING(0, "path", &clone_data.path,
N_("path"),
N_("where the new submodule will be cloned to")),
- OPT_STRING(0, "name", &name,
+ OPT_STRING(0, "name", &clone_data.name,
N_("string"),
N_("name of the new submodule")),
- OPT_STRING(0, "url", &url,
+ OPT_STRING(0, "url", &clone_data.url,
N_("string"),
N_("url where to clone the submodule from")),
- OPT_STRING_LIST(0, "reference", &reference,
+ OPT_STRING_LIST(0, "reference", &clone_data.reference,
N_("repo"),
N_("reference repository")),
OPT_BOOL(0, "dissociate", &dissociate,
N_("use --reference only while cloning")),
- OPT_STRING(0, "depth", &depth,
+ OPT_STRING(0, "depth", &clone_data.depth,
N_("string"),
N_("depth for shallow clones")),
OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
@@ -1840,7 +1907,7 @@ 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,
+ OPT_BOOL(0, "single-branch", &clone_data.single_branch,
N_("clone only one branch, HEAD or --branch")),
OPT_END()
};
@@ -1856,67 +1923,16 @@ static int module_clone(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
- if (argc || !url || !path || !*path)
+ clone_data.dissociate = !!dissociate;
+ clone_data.quiet = !!quiet;
+ clone_data.progress = !!progress;
+ clone_data.require_init = !!require_init;
+
+ if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))
usage_with_options(git_submodule_helper_usage,
module_clone_options);
- strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
- sm_gitdir = absolute_pathdup(sb.buf);
- strbuf_reset(&sb);
-
- if (!is_absolute_path(path)) {
- strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
- path = strbuf_detach(&sb, NULL);
- } else
- path = xstrdup(path);
-
- if (validate_submodule_git_dir(sm_gitdir, name) < 0)
- die(_("refusing to create/use '%s' in another submodule's "
- "git dir"), sm_gitdir);
-
- if (!file_exists(sm_gitdir)) {
- if (safe_create_leading_directories_const(sm_gitdir) < 0)
- die(_("could not create directory '%s'"), sm_gitdir);
-
- prepare_possible_alternates(name, &reference);
-
- if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
- quiet, progress, single_branch))
- die(_("clone of '%s' into submodule path '%s' failed"),
- url, path);
- } else {
- if (require_init && !access(path, X_OK) && !is_empty_dir(path))
- die(_("directory not empty: '%s'"), path);
- if (safe_create_leading_directories_const(path) < 0)
- die(_("could not create directory '%s'"), path);
- strbuf_addf(&sb, "%s/index", sm_gitdir);
- unlink_or_warn(sb.buf);
- strbuf_reset(&sb);
- }
-
- connect_work_tree_and_git_dir(path, sm_gitdir, 0);
-
- p = git_pathdup_submodule(path, "config");
- if (!p)
- die(_("could not get submodule directory for '%s'"), path);
-
- /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
- git_config_get_string("submodule.alternateLocation", &sm_alternate);
- if (sm_alternate)
- git_config_set_in_file(p, "submodule.alternateLocation",
- sm_alternate);
- git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
- if (error_strategy)
- git_config_set_in_file(p, "submodule.alternateErrorStrategy",
- error_strategy);
-
- free(sm_alternate);
- free(error_strategy);
-
- strbuf_release(&sb);
- free(sm_gitdir);
- free(path);
- free(p);
+ clone_submodule(&clone_data);
return 0;
}
@@ -2745,6 +2761,181 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
return !!ret;
}
+struct add_data {
+ const char *prefix;
+ const char *branch;
+ const char *reference_path;
+ const char *sm_path;
+ const char *sm_name;
+ const char *repo;
+ const char *realrepo;
+ int depth;
+ unsigned int force: 1;
+ unsigned int quiet: 1;
+ unsigned int progress: 1;
+ unsigned int dissociate: 1;
+};
+#define ADD_DATA_INIT { .depth = -1 }
+
+static void show_fetch_remotes(FILE *output, const char *git_dir_path)
+{
+ struct child_process cp_remote = CHILD_PROCESS_INIT;
+ struct strbuf sb_remote_out = STRBUF_INIT;
+
+ cp_remote.git_cmd = 1;
+ strvec_pushf(&cp_remote.env_array,
+ "GIT_DIR=%s", git_dir_path);
+ strvec_push(&cp_remote.env_array, "GIT_WORK_TREE=.");
+ strvec_pushl(&cp_remote.args, "remote", "-v", NULL);
+ if (!capture_command(&cp_remote, &sb_remote_out, 0)) {
+ char *next_line;
+ char *line = sb_remote_out.buf;
+ while ((next_line = strchr(line, '\n')) != NULL) {
+ size_t len = next_line - line;
+ if (strip_suffix_mem(line, &len, " (fetch)"))
+ fprintf(output, " %.*s\n", (int)len, line);
+ line = next_line + 1;
+ }
+ }
+
+ strbuf_release(&sb_remote_out);
+}
+
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
+ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
+
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
+ struct strbuf sm_path = STRBUF_INIT;
+ strbuf_addstr(&sm_path, add_data->sm_path);
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
+ if (is_nonbare_repository_dir(&sm_path))
+ printf(_("Adding existing repo at '%s' to the index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
+ strbuf_release(&sm_path);
+ free(submod_gitdir_path);
+ } else {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
+ fprintf(stderr, _("A git directory for '%s' is found "
+ "locally with remote(s):"),
+ add_data->sm_name);
+ show_fetch_remotes(stderr, submod_gitdir_path);
+ free(submod_gitdir_path);
+ die(_("If you want to reuse this local git "
+ "directory instead of cloning again from\n"
+ " %s\n"
+ "use the '--force' option. If the local git "
+ "directory is not the correct repo\n"
+ "or if you are unsure what this means, choose "
+ "another name with the '--name' option.\n"),
+ add_data->realrepo);
+ } else {
+ printf(_("Reactivating local git directory for "
+ "submodule '%s'\n"), add_data->sm_name);
+ }
+ }
+ free(submod_gitdir_path);
+
+ clone_data.prefix = add_data->prefix;
+ clone_data.path = add_data->sm_path;
+ clone_data.name = add_data->sm_name;
+ clone_data.url = add_data->realrepo;
+ clone_data.quiet = add_data->quiet;
+ clone_data.progress = add_data->progress;
+ if (add_data->reference_path)
+ string_list_append(&clone_data.reference,
+ xstrdup(add_data->reference_path));
+ clone_data.dissociate = add_data->dissociate;
+ if (add_data->depth >= 0)
+ clone_data.depth = xstrfmt("%d", add_data->depth);
+
+ if (clone_submodule(&clone_data))
+ return -1;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.git_cmd = 1;
+ cp.dir = add_data->sm_path;
+ strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
+
+ if (add_data->branch) {
+ strvec_pushl(&cp.args, "-B", add_data->branch, NULL);
+ strvec_pushf(&cp.args, "origin/%s", add_data->branch);
+ }
+
+ if (run_command(&cp))
+ die(_("unable to checkout submodule '%s'"), add_data->sm_path);
+ }
+ return 0;
+}
+
+static int add_clone(int argc, const char **argv, const char *prefix)
+{
+ int force = 0, quiet = 0, dissociate = 0, progress = 0;
+ struct add_data add_data = ADD_DATA_INIT;
+
+ struct option options[] = {
+ OPT_STRING('b', "branch", &add_data.branch,
+ N_("branch"),
+ N_("branch of repository to checkout on cloning")),
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &add_data.sm_path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &add_data.sm_name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &add_data.realrepo,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING(0, "reference", &add_data.reference_path,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate,
+ N_("use --reference only while cloning")),
+ OPT_INTEGER(0, "depth", &add_data.depth,
+ N_("depth for shallow clones")),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
+ PARSE_OPT_NOCOMPLETE),
+ OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
+ OPT_END()
+ };
+
+ const char *const usage[] = {
+ N_("git submodule--helper add-clone [<options>...] "
+ "--url <url> --path <path> --name <name>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 0)
+ usage_with_options(usage, options);
+
+ add_data.prefix = prefix;
+ add_data.progress = !!progress;
+ add_data.dissociate = !!dissociate;
+ add_data.force = !!force;
+ add_data.quiet = !!quiet;
+
+ if (add_submodule(&add_data))
+ return 1;
+
+ return 0;
+}
+
#define SUPPORT_SUPER_PREFIX (1<<0)
struct cmd_struct {
@@ -2757,6 +2948,7 @@ static struct cmd_struct commands[] = {
{"list", module_list, 0},
{"name", module_name, 0},
{"clone", module_clone, 0},
+ {"add-clone", add_clone, 0},
{"update-module-mode", module_update_module_mode, 0},
{"update-clone", update_clone, 0},
{"ensure-core-worktree", ensure_core_worktree, 0},
diff --git a/builtin/tag.c b/builtin/tag.c
index 82fcfc0..87552ae 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -293,9 +293,7 @@ static void create_tag(const struct object_id *object, const char *object_ref,
/* write the template message before editing: */
path = git_pathdup("TAG_EDITMSG");
- fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), path);
+ fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (opt->message_given) {
write_or_die(fd, buf->buf, buf->len);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index f1f16f2..187203e 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -95,9 +95,7 @@ static int create_file(const char *path)
{
int fd;
path = get_mtime_path(path);
- fd = open(path, O_CREAT | O_RDWR, 0644);
- if (fd < 0)
- die_errno(_("failed to create file %s"), path);
+ fd = xopen(path, O_CREAT | O_RDWR, 0644);
return fd;
}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 6029a80..a84e7b4 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -302,6 +302,12 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
strbuf_release(&err);
}
+static void report_ok(const char *command)
+{
+ fprintf(stdout, "%s: ok\n", command);
+ fflush(stdout);
+}
+
static void parse_cmd_option(struct ref_transaction *transaction,
const char *next, const char *end)
{
@@ -317,7 +323,7 @@ static void parse_cmd_start(struct ref_transaction *transaction,
{
if (*next != line_termination)
die("start: extra input: %s", next);
- puts("start: ok");
+ report_ok("start");
}
static void parse_cmd_prepare(struct ref_transaction *transaction,
@@ -328,7 +334,7 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
die("prepare: extra input: %s", next);
if (ref_transaction_prepare(transaction, &error))
die("prepare: %s", error.buf);
- puts("prepare: ok");
+ report_ok("prepare");
}
static void parse_cmd_abort(struct ref_transaction *transaction,
@@ -339,7 +345,7 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
die("abort: extra input: %s", next);
if (ref_transaction_abort(transaction, &error))
die("abort: %s", error.buf);
- puts("abort: ok");
+ report_ok("abort");
}
static void parse_cmd_commit(struct ref_transaction *transaction,
@@ -350,7 +356,7 @@ static void parse_cmd_commit(struct ref_transaction *transaction,
die("commit: extra input: %s", next);
if (ref_transaction_commit(transaction, &error))
die("commit: %s", error.buf);
- puts("commit: ok");
+ report_ok("commit");
ref_transaction_free(transaction);
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 976bf8e..0d0a80d 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -30,7 +30,7 @@ struct add_opts {
int detach;
int quiet;
int checkout;
- int keep_locked;
+ const char *keep_locked;
};
static int show_only;
@@ -302,10 +302,10 @@ static int add_worktree(const char *path, const char *refname,
* after the preparation is over.
*/
strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- if (!opts->keep_locked)
- write_file(sb.buf, "initializing");
+ if (opts->keep_locked)
+ write_file(sb.buf, "%s", opts->keep_locked);
else
- write_file(sb.buf, "added with --lock");
+ write_file(sb.buf, _("initializing"));
strbuf_addf(&sb_git, "%s/.git", path);
if (safe_create_leading_directories_const(sb_git.buf))
@@ -475,6 +475,8 @@ static int add(int ac, const char **av, const char *prefix)
const char *branch;
const char *new_branch = NULL;
const char *opt_track = NULL;
+ const char *lock_reason = NULL;
+ int keep_locked = 0;
struct option options[] = {
OPT__FORCE(&opts.force,
N_("checkout <branch> even if already checked out in other worktree"),
@@ -485,7 +487,9 @@ static int add(int ac, const char **av, const char *prefix)
N_("create or reset a branch")),
OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
- OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
+ OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")),
+ OPT_STRING(0, "reason", &lock_reason, N_("string"),
+ N_("reason for locking")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
OPT_PASSTHRU(0, "track", &opt_track, NULL,
N_("set up tracking mode (see git-branch(1))"),
@@ -500,6 +504,13 @@ static int add(int ac, const char **av, const char *prefix)
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
die(_("-b, -B, and --detach are mutually exclusive"));
+ if (lock_reason && !keep_locked)
+ die(_("--reason requires --lock"));
+ if (lock_reason)
+ opts.keep_locked = lock_reason;
+ else if (keep_locked)
+ opts.keep_locked = _("added with --lock");
+
if (ac < 1 || ac > 2)
usage_with_options(worktree_usage, options);
diff --git a/bulk-checkin.c b/bulk-checkin.c
index 127312a..8785b2a 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -23,9 +23,25 @@ static struct bulk_checkin_state {
uint32_t nr_written;
} state;
+static void finish_tmp_packfile(struct strbuf *basename,
+ const char *pack_tmp_name,
+ struct pack_idx_entry **written_list,
+ uint32_t nr_written,
+ struct pack_idx_option *pack_idx_opts,
+ unsigned char hash[])
+{
+ char *idx_tmp_name = NULL;
+
+ stage_tmp_packfiles(basename, pack_tmp_name, written_list, nr_written,
+ pack_idx_opts, hash, &idx_tmp_name);
+ rename_tmp_packfile_idx(basename, &idx_tmp_name);
+
+ free(idx_tmp_name);
+}
+
static void finish_bulk_checkin(struct bulk_checkin_state *state)
{
- struct object_id oid;
+ unsigned char hash[GIT_MAX_RAWSZ];
struct strbuf packname = STRBUF_INIT;
int i;
@@ -37,19 +53,20 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
unlink(state->pack_tmp_name);
goto clear_exit;
} else if (state->nr_written == 1) {
- finalize_hashfile(state->f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+ finalize_hashfile(state->f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
} else {
- int fd = finalize_hashfile(state->f, oid.hash, 0);
- fixup_pack_header_footer(fd, oid.hash, state->pack_tmp_name,
- state->nr_written, oid.hash,
+ int fd = finalize_hashfile(state->f, hash, 0);
+ fixup_pack_header_footer(fd, hash, state->pack_tmp_name,
+ state->nr_written, hash,
state->offset);
close(fd);
}
- strbuf_addf(&packname, "%s/pack/pack-", get_object_directory());
+ strbuf_addf(&packname, "%s/pack/pack-%s.", get_object_directory(),
+ hash_to_hex(hash));
finish_tmp_packfile(&packname, state->pack_tmp_name,
state->written, state->nr_written,
- &state->pack_idx_opts, oid.hash);
+ &state->pack_idx_opts, hash);
for (i = 0; i < state->nr_written; i++)
free(state->written[i]);
@@ -100,6 +117,7 @@ static int stream_to_pack(struct bulk_checkin_state *state,
const char *path, unsigned flags)
{
git_zstream s;
+ unsigned char ibuf[16384];
unsigned char obuf[16384];
unsigned hdrlen;
int status = Z_OK;
@@ -113,8 +131,6 @@ static int stream_to_pack(struct bulk_checkin_state *state,
s.avail_out = sizeof(obuf) - hdrlen;
while (status != Z_STREAM_END) {
- unsigned char ibuf[16384];
-
if (size && !s.avail_in) {
ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
ssize_t read_result = read_in_full(fd, ibuf, rsize);
diff --git a/bundle.c b/bundle.c
index 693d619..ab63f40 100644
--- a/bundle.c
+++ b/bundle.c
@@ -23,13 +23,16 @@ static struct {
{ 3, v3_bundle_signature },
};
-static void add_to_ref_list(const struct object_id *oid, const char *name,
- struct ref_list *list)
+void bundle_header_init(struct bundle_header *header)
{
- ALLOC_GROW(list->list, list->nr + 1, list->alloc);
- oidcpy(&list->list[list->nr].oid, oid);
- list->list[list->nr].name = xstrdup(name);
- list->nr++;
+ struct bundle_header blank = BUNDLE_HEADER_INIT;
+ memcpy(header, &blank, sizeof(*header));
+}
+
+void bundle_header_release(struct bundle_header *header)
+{
+ string_list_clear(&header->prerequisites, 1);
+ string_list_clear(&header->references, 1);
}
static int parse_capability(struct bundle_header *header, const char *capability)
@@ -112,10 +115,11 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
status = -1;
break;
} else {
+ struct object_id *dup = oiddup(&oid);
if (is_prereq)
- add_to_ref_list(&oid, "", &header->prerequisites);
+ string_list_append(&header->prerequisites, "")->util = dup;
else
- add_to_ref_list(&oid, p + 1, &header->references);
+ string_list_append(&header->references, p + 1)->util = dup;
}
}
@@ -139,33 +143,38 @@ int read_bundle_header(const char *path, struct bundle_header *header)
int is_bundle(const char *path, int quiet)
{
- struct bundle_header header;
+ struct bundle_header header = BUNDLE_HEADER_INIT;
int fd = open(path, O_RDONLY);
if (fd < 0)
return 0;
- memset(&header, 0, sizeof(header));
fd = parse_bundle_header(fd, &header, quiet ? NULL : path);
if (fd >= 0)
close(fd);
+ bundle_header_release(&header);
return (fd >= 0);
}
-static int list_refs(struct ref_list *r, int argc, const char **argv)
+static int list_refs(struct string_list *r, int argc, const char **argv)
{
int i;
for (i = 0; i < r->nr; i++) {
+ struct object_id *oid;
+ const char *name;
+
if (argc > 1) {
int j;
for (j = 1; j < argc; j++)
- if (!strcmp(r->list[i].name, argv[j]))
+ if (!strcmp(r->items[i].string, argv[j]))
break;
if (j == argc)
continue;
}
- printf("%s %s\n", oid_to_hex(&r->list[i].oid),
- r->list[i].name);
+
+ oid = r->items[i].util;
+ name = r->items[i].string;
+ printf("%s %s\n", oid_to_hex(oid), name);
}
return 0;
}
@@ -181,7 +190,7 @@ int verify_bundle(struct repository *r,
* Do fast check, then if any prereqs are missing then go line by line
* to be verbose about the errors
*/
- struct ref_list *p = &header->prerequisites;
+ struct string_list *p = &header->prerequisites;
struct rev_info revs;
const char *argv[] = {NULL, "--all", NULL};
struct commit *commit;
@@ -193,16 +202,18 @@ int verify_bundle(struct repository *r,
repo_init_revisions(r, &revs, NULL);
for (i = 0; i < p->nr; i++) {
- struct ref_list_entry *e = p->list + i;
- struct object *o = parse_object(r, &e->oid);
+ struct string_list_item *e = p->items + i;
+ const char *name = e->string;
+ struct object_id *oid = e->util;
+ struct object *o = parse_object(r, oid);
if (o) {
o->flags |= PREREQ_MARK;
- add_pending_object(&revs, o, e->name);
+ add_pending_object(&revs, o, name);
continue;
}
if (++ret == 1)
error("%s", message);
- error("%s %s", oid_to_hex(&e->oid), e->name);
+ error("%s %s", oid_to_hex(oid), name);
}
if (revs.pending.nr != p->nr)
return ret;
@@ -218,26 +229,29 @@ int verify_bundle(struct repository *r,
i--;
for (i = 0; i < p->nr; i++) {
- struct ref_list_entry *e = p->list + i;
- struct object *o = parse_object(r, &e->oid);
+ struct string_list_item *e = p->items + i;
+ const char *name = e->string;
+ const struct object_id *oid = e->util;
+ struct object *o = parse_object(r, oid);
assert(o); /* otherwise we'd have returned early */
if (o->flags & SHOWN)
continue;
if (++ret == 1)
error("%s", message);
- error("%s %s", oid_to_hex(&e->oid), e->name);
+ error("%s %s", oid_to_hex(oid), name);
}
/* Clean up objects used, as they will be reused. */
for (i = 0; i < p->nr; i++) {
- struct ref_list_entry *e = p->list + i;
- commit = lookup_commit_reference_gently(r, &e->oid, 1);
+ struct string_list_item *e = p->items + i;
+ struct object_id *oid = e->util;
+ commit = lookup_commit_reference_gently(r, oid, 1);
if (commit)
clear_commit_marks(commit, ALL_REV_FLAGS);
}
if (verbose) {
- struct ref_list *r;
+ struct string_list *r;
r = &header->references;
printf_ln(Q_("The bundle contains this ref:",
diff --git a/bundle.h b/bundle.h
index f9e2d1c..1927d8c 100644
--- a/bundle.h
+++ b/bundle.h
@@ -3,22 +3,23 @@
#include "strvec.h"
#include "cache.h"
-
-struct ref_list {
- unsigned int nr, alloc;
- struct ref_list_entry {
- struct object_id oid;
- char *name;
- } *list;
-};
+#include "string-list.h"
struct bundle_header {
unsigned version;
- struct ref_list prerequisites;
- struct ref_list references;
+ struct string_list prerequisites;
+ struct string_list references;
const struct git_hash_algo *hash_algo;
};
+#define BUNDLE_HEADER_INIT \
+{ \
+ .prerequisites = STRING_LIST_INIT_DUP, \
+ .references = STRING_LIST_INIT_DUP, \
+}
+void bundle_header_init(struct bundle_header *header);
+void bundle_header_release(struct bundle_header *header);
+
int is_bundle(const char *path, int quiet);
int read_bundle_header(const char *path, struct bundle_header *header);
int create_bundle(struct repository *r, const char *path,
diff --git a/cache-tree.c b/cache-tree.c
index 45e5866..90919f9 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -237,6 +237,11 @@ int cache_tree_fully_valid(struct cache_tree *it)
return 1;
}
+static int must_check_existence(const struct cache_entry *ce)
+{
+ return !(has_promisor_remote() && ce_skip_worktree(ce));
+}
+
static int update_one(struct cache_tree *it,
struct cache_entry **cache,
int entries,
@@ -378,8 +383,7 @@ static int update_one(struct cache_tree *it,
}
ce_missing_ok = mode == S_IFGITLINK || missing_ok ||
- (has_promisor_remote() &&
- ce_skip_worktree(ce));
+ !must_check_existence(ce);
if (is_null_oid(oid) ||
(!ce_missing_ok && !has_object_file(oid))) {
strbuf_release(&buffer);
@@ -461,11 +465,12 @@ int cache_tree_update(struct index_state *istate, int flags)
if (i)
return i;
- ensure_full_index(istate);
-
if (!istate->cache_tree)
istate->cache_tree = cache_tree();
+ if (!(flags & WRITE_TREE_MISSING_OK) && has_promisor_remote())
+ prefetch_cache_entries(istate, must_check_existence);
+
trace_performance_enter();
trace2_region_enter("cache_tree", "update", the_repository);
i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
diff --git a/cache.h b/cache.h
index ba04ff8..0a8d44c 100644
--- a/cache.h
+++ b/cache.h
@@ -410,6 +410,15 @@ struct cache_entry *dup_cache_entry(const struct cache_entry *ce, struct index_s
*/
void validate_cache_entries(const struct index_state *istate);
+/*
+ * Bulk prefetch all missing cache entries that are not GITLINKs and that match
+ * the given predicate. This function should only be called if
+ * has_promisor_remote() returns true.
+ */
+typedef int (*must_prefetch_predicate)(const struct cache_entry *);
+void prefetch_cache_entries(const struct index_state *istate,
+ must_prefetch_predicate must_prefetch);
+
#ifdef USE_THE_INDEX_COMPATIBILITY_MACROS
extern struct index_state the_index;
@@ -949,7 +958,6 @@ extern char *apply_default_ignorewhitespace;
extern const char *git_attributes_file;
extern const char *git_hooks_path;
extern int zlib_compression_level;
-extern int core_compression_level;
extern int pack_compression_level;
extern size_t packed_git_window_size;
extern size_t packed_git_limit;
@@ -1385,6 +1393,7 @@ enum get_oid_result {
};
int repo_get_oid(struct repository *r, const char *str, struct object_id *oid);
+__attribute__((format (printf, 2, 3)))
int get_oidf(struct object_id *oid, const char *fmt, ...);
int repo_get_oid_commit(struct repository *r, const char *str, struct object_id *oid);
int repo_get_oid_committish(struct repository *r, const char *str, struct object_id *oid);
diff --git a/cbtree.c b/cbtree.c
new file mode 100644
index 0000000..b0c65d8
--- /dev/null
+++ b/cbtree.c
@@ -0,0 +1,167 @@
+/*
+ * crit-bit tree implementation, does no allocations internally
+ * For more information on crit-bit trees: https://cr.yp.to/critbit.html
+ * Based on Adam Langley's adaptation of Dan Bernstein's public domain code
+ * git clone https://github.com/agl/critbit.git
+ */
+#include "cbtree.h"
+
+static struct cb_node *cb_node_of(const void *p)
+{
+ return (struct cb_node *)((uintptr_t)p - 1);
+}
+
+/* locate the best match, does not do a final comparision */
+static struct cb_node *cb_internal_best_match(struct cb_node *p,
+ const uint8_t *k, size_t klen)
+{
+ while (1 & (uintptr_t)p) {
+ struct cb_node *q = cb_node_of(p);
+ uint8_t c = q->byte < klen ? k[q->byte] : 0;
+ size_t direction = (1 + (q->otherbits | c)) >> 8;
+
+ p = q->child[direction];
+ }
+ return p;
+}
+
+/* returns NULL if successful, existing cb_node if duplicate */
+struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen)
+{
+ size_t newbyte, newotherbits;
+ uint8_t c;
+ int newdirection;
+ struct cb_node **wherep, *p;
+
+ assert(!((uintptr_t)node & 1)); /* allocations must be aligned */
+
+ if (!t->root) { /* insert into empty tree */
+ t->root = node;
+ return NULL; /* success */
+ }
+
+ /* see if a node already exists */
+ p = cb_internal_best_match(t->root, node->k, klen);
+
+ /* find first differing byte */
+ for (newbyte = 0; newbyte < klen; newbyte++) {
+ if (p->k[newbyte] != node->k[newbyte])
+ goto different_byte_found;
+ }
+ return p; /* element exists, let user deal with it */
+
+different_byte_found:
+ newotherbits = p->k[newbyte] ^ node->k[newbyte];
+ newotherbits |= newotherbits >> 1;
+ newotherbits |= newotherbits >> 2;
+ newotherbits |= newotherbits >> 4;
+ newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255;
+ c = p->k[newbyte];
+ newdirection = (1 + (newotherbits | c)) >> 8;
+
+ node->byte = newbyte;
+ node->otherbits = newotherbits;
+ node->child[1 - newdirection] = node;
+
+ /* find a place to insert it */
+ wherep = &t->root;
+ for (;;) {
+ struct cb_node *q;
+ size_t direction;
+
+ p = *wherep;
+ if (!(1 & (uintptr_t)p))
+ break;
+ q = cb_node_of(p);
+ if (q->byte > newbyte)
+ break;
+ if (q->byte == newbyte && q->otherbits > newotherbits)
+ break;
+ c = q->byte < klen ? node->k[q->byte] : 0;
+ direction = (1 + (q->otherbits | c)) >> 8;
+ wherep = q->child + direction;
+ }
+
+ node->child[newdirection] = *wherep;
+ *wherep = (struct cb_node *)(1 + (uintptr_t)node);
+
+ return NULL; /* success */
+}
+
+struct cb_node *cb_lookup(struct cb_tree *t, const uint8_t *k, size_t klen)
+{
+ struct cb_node *p = cb_internal_best_match(t->root, k, klen);
+
+ return p && !memcmp(p->k, k, klen) ? p : NULL;
+}
+
+struct cb_node *cb_unlink(struct cb_tree *t, const uint8_t *k, size_t klen)
+{
+ struct cb_node **wherep = &t->root;
+ struct cb_node **whereq = NULL;
+ struct cb_node *q = NULL;
+ size_t direction = 0;
+ uint8_t c;
+ struct cb_node *p = t->root;
+
+ if (!p) return NULL; /* empty tree, nothing to delete */
+
+ /* traverse to find best match, keeping link to parent */
+ while (1 & (uintptr_t)p) {
+ whereq = wherep;
+ q = cb_node_of(p);
+ c = q->byte < klen ? k[q->byte] : 0;
+ direction = (1 + (q->otherbits | c)) >> 8;
+ wherep = q->child + direction;
+ p = *wherep;
+ }
+
+ if (memcmp(p->k, k, klen))
+ return NULL; /* no match, nothing unlinked */
+
+ /* found an exact match */
+ if (whereq) /* update parent */
+ *whereq = q->child[1 - direction];
+ else
+ t->root = NULL;
+ return p;
+}
+
+static enum cb_next cb_descend(struct cb_node *p, cb_iter fn, void *arg)
+{
+ if (1 & (uintptr_t)p) {
+ struct cb_node *q = cb_node_of(p);
+ enum cb_next n = cb_descend(q->child[0], fn, arg);
+
+ return n == CB_BREAK ? n : cb_descend(q->child[1], fn, arg);
+ } else {
+ return fn(p, arg);
+ }
+}
+
+void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
+ cb_iter fn, void *arg)
+{
+ struct cb_node *p = t->root;
+ struct cb_node *top = p;
+ size_t i = 0;
+
+ if (!p) return; /* empty tree */
+
+ /* Walk tree, maintaining top pointer */
+ while (1 & (uintptr_t)p) {
+ struct cb_node *q = cb_node_of(p);
+ uint8_t c = q->byte < klen ? kpfx[q->byte] : 0;
+ size_t direction = (1 + (q->otherbits | c)) >> 8;
+
+ p = q->child[direction];
+ if (q->byte < klen)
+ top = p;
+ }
+
+ for (i = 0; i < klen; i++) {
+ if (p->k[i] != kpfx[i])
+ return; /* "best" match failed */
+ }
+ cb_descend(top, fn, arg);
+}
diff --git a/cbtree.h b/cbtree.h
new file mode 100644
index 0000000..a04a312
--- /dev/null
+++ b/cbtree.h
@@ -0,0 +1,56 @@
+/*
+ * crit-bit tree implementation, does no allocations internally
+ * For more information on crit-bit trees: https://cr.yp.to/critbit.html
+ * Based on Adam Langley's adaptation of Dan Bernstein's public domain code
+ * git clone https://github.com/agl/critbit.git
+ *
+ * This is adapted to store arbitrary data (not just NUL-terminated C strings
+ * and allocates no memory internally. The user needs to allocate
+ * "struct cb_node" and fill cb_node.k[] with arbitrary match data
+ * for memcmp.
+ * If "klen" is variable, then it should be embedded into "c_node.k[]"
+ * Recursion is bound by the maximum value of "klen" used.
+ */
+#ifndef CBTREE_H
+#define CBTREE_H
+
+#include "git-compat-util.h"
+
+struct cb_node;
+struct cb_node {
+ struct cb_node *child[2];
+ /*
+ * n.b. uint32_t for `byte' is excessive for OIDs,
+ * we may consider shorter variants if nothing else gets stored.
+ */
+ uint32_t byte;
+ uint8_t otherbits;
+ uint8_t k[FLEX_ARRAY]; /* arbitrary data, unaligned */
+};
+
+struct cb_tree {
+ struct cb_node *root;
+};
+
+enum cb_next {
+ CB_CONTINUE = 0,
+ CB_BREAK = 1
+};
+
+#define CBTREE_INIT { .root = NULL }
+
+static inline void cb_init(struct cb_tree *t)
+{
+ t->root = NULL;
+}
+
+struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen);
+struct cb_node *cb_insert(struct cb_tree *, struct cb_node *, size_t klen);
+struct cb_node *cb_unlink(struct cb_tree *t, const uint8_t *k, size_t klen);
+
+typedef enum cb_next (*cb_iter)(struct cb_node *, void *arg);
+
+void cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen,
+ cb_iter, void *arg);
+
+#endif /* CBTREE_H */
diff --git a/check_bindir b/check_bindir
deleted file mode 100755
index 623eadc..0000000
--- a/check_bindir
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-bindir="$1"
-gitexecdir="$2"
-gitcmd="$3"
-if test "$bindir" != "$gitexecdir" && test -x "$gitcmd"
-then
- echo
- echo "!! You have installed git-* commands to new gitexecdir."
- echo "!! Old version git-* commands still remain in bindir."
- echo "!! Mixing two versions of Git will lead to problems."
- echo "!! Please remove old version commands in bindir now."
- echo
-fi
diff --git a/chunk-format.c b/chunk-format.c
index da191e5..1c3dca6 100644
--- a/chunk-format.c
+++ b/chunk-format.c
@@ -58,9 +58,11 @@ void add_chunk(struct chunkfile *cf,
int write_chunkfile(struct chunkfile *cf, void *data)
{
- int i;
+ int i, result = 0;
uint64_t cur_offset = hashfile_total(cf->f);
+ trace2_region_enter("chunkfile", "write", the_repository);
+
/* Add the table of contents to the current offset */
cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE;
@@ -77,10 +79,10 @@ int write_chunkfile(struct chunkfile *cf, void *data)
for (i = 0; i < cf->chunks_nr; i++) {
off_t start_offset = hashfile_total(cf->f);
- int result = cf->chunks[i].write_fn(cf->f, data);
+ result = cf->chunks[i].write_fn(cf->f, data);
if (result)
- return result;
+ goto cleanup;
if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size)
BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead",
@@ -88,7 +90,9 @@ int write_chunkfile(struct chunkfile *cf, void *data)
hashfile_total(cf->f) - start_offset);
}
- return 0;
+cleanup:
+ trace2_region_leave("chunkfile", "write", the_repository);
+ return result;
}
int read_table_of_contents(struct chunkfile *cf,
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index 67852d0..5772081 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -65,6 +65,11 @@ StaticAnalysis)
sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
libexpat-dev gettext make
;;
+sparse)
+ sudo apt-get -q update -q
+ sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \
+ libexpat-dev gettext zlib1g-dev
+ ;;
Documentation)
sudo apt-get -q update
sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns make
diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh
index 26a6689..07a8c6b 100755
--- a/ci/install-docker-dependencies.sh
+++ b/ci/install-docker-dependencies.sh
@@ -15,4 +15,8 @@ linux-musl)
apk add --update build-base curl-dev openssl-dev expat-dev gettext \
pcre2-dev python3 musl-libintl perl-utils ncurses >/dev/null
;;
+pedantic)
+ dnf -yq update >/dev/null &&
+ dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+ ;;
esac
diff --git a/ci/lib.sh b/ci/lib.sh
index d848c03..476c3f3 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -229,6 +229,7 @@ linux-musl)
CC=gcc
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/python3 USE_LIBPCRE2=Yes"
MAKEFLAGS="$MAKEFLAGS NO_REGEX=Yes ICONV_OMITS_BOM=Yes"
+ MAKEFLAGS="$MAKEFLAGS GIT_TEST_UTF8_LOCALE=C.UTF-8"
;;
esac
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 3ce81ff..f3aba5d 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -10,6 +10,11 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
*) ln -s "$cache_dir/.prove" t/.prove;;
esac
+if test "$jobname" = "pedantic"
+then
+ export DEVOPTS=pedantic
+fi
+
make
case "$jobname" in
linux-gcc)
@@ -35,10 +40,9 @@ linux-clang)
export GIT_TEST_DEFAULT_HASH=sha256
make test
;;
-linux-gcc-4.8)
+linux-gcc-4.8|pedantic)
# Don't run the tests; we only care about whether Git can be
- # built with GCC 4.8, as it errors out on some undesired (C99)
- # constructs that newer compilers seem to quietly accept.
+ # built with GCC 4.8 or with pedantic
;;
*)
make test
diff --git a/combine-diff.c b/combine-diff.c
index 7d925ce..d93782d 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -403,11 +403,11 @@ static void consume_hunk(void *state_,
state->sline[state->nb-1].p_lno[state->n] = state->ob;
}
-static void consume_line(void *state_, char *line, unsigned long len)
+static int consume_line(void *state_, char *line, unsigned long len)
{
struct combine_diff_state *state = state_;
if (!state->lost_bucket)
- return; /* not in any hunk yet */
+ return 0; /* not in any hunk yet */
switch (line[0]) {
case '-':
append_lost(state->lost_bucket, state->n, line+1, len-1);
@@ -417,6 +417,7 @@ static void consume_line(void *state_, char *line, unsigned long len)
state->lno++;
break;
}
+ return 0;
}
static void combine_diff(struct repository *r,
diff --git a/commit-graph.c b/commit-graph.c
index 2bcb4e0..3860a0d 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -2408,6 +2408,7 @@ cleanup:
#define VERIFY_COMMIT_GRAPH_ERROR_HASH 2
static int verify_commit_graph_error;
+__attribute__((format (printf, 1, 2)))
static void graph_report(const char *fmt, ...)
{
va_list ap;
@@ -2422,14 +2423,16 @@ static void graph_report(const char *fmt, ...)
#define GENERATION_ZERO_EXISTS 1
#define GENERATION_NUMBER_EXISTS 2
+static int commit_graph_checksum_valid(struct commit_graph *g)
+{
+ return hashfile_checksum_valid(g->data, g->data_len);
+}
+
int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
{
uint32_t i, cur_fanout_pos = 0;
struct object_id prev_oid, cur_oid;
- unsigned char checksum[GIT_MAX_HEXSZ];
int generation_zero = 0;
- struct hashfile *f;
- int devnull;
struct progress *progress = NULL;
int local_error = 0;
@@ -2442,11 +2445,7 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags)
if (verify_commit_graph_error)
return verify_commit_graph_error;
- devnull = open("/dev/null", O_WRONLY);
- f = hashfd(devnull, NULL);
- hashwrite(f, g->data, g->data_len - g->hash_len);
- finalize_hashfile(f, checksum, CSUM_CLOSE);
- if (!hasheq(checksum, g->data + g->data_len - g->hash_len)) {
+ if (!commit_graph_checksum_valid(g)) {
graph_report(_("the commit-graph file has incorrect checksum and is likely corrupt"));
verify_commit_graph_error = VERIFY_COMMIT_GRAPH_ERROR_HASH;
}
diff --git a/commit.c b/commit.c
index 8ea55a4..143f472 100644
--- a/commit.c
+++ b/commit.c
@@ -1178,7 +1178,7 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
/*
* We could verify this signature and either omit the tag when
* it does not validate, but the integrator may not have the
- * public key of the signer of the tag he is merging, while a
+ * public key of the signer of the tag being merged, while a
* later auditor may have it while auditing, so let's not run
* verify-signed-buffer here for now...
*
diff --git a/compat/mingw.c b/compat/mingw.c
index af1b896..5e42191 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -342,6 +342,27 @@ int mingw_rmdir(const char *pathname)
{
int ret, tries = 0;
wchar_t wpathname[MAX_PATH];
+ struct stat st;
+
+ /*
+ * Contrary to Linux' `rmdir()`, Windows' _wrmdir() and _rmdir()
+ * (and `RemoveDirectoryW()`) will attempt to remove the target of a
+ * symbolic link (if it points to a directory).
+ *
+ * This behavior breaks the assumption of e.g. `remove_path()` which
+ * upon successful deletion of a file will attempt to remove its parent
+ * directories recursively until failure (which usually happens when
+ * the directory is not empty).
+ *
+ * Therefore, before calling `_wrmdir()`, we first check if the path is
+ * a symbolic link. If it is, we exit and return the same error as
+ * Linux' `rmdir()` would, i.e. `ENOTDIR`.
+ */
+ if (!mingw_lstat(pathname, &st) && S_ISLNK(st.st_mode)) {
+ errno = ENOTDIR;
+ return -1;
+ }
+
if (xutftowcs_path(wpathname, pathname) < 0)
return -1;
diff --git a/compat/mmap.c b/compat/mmap.c
index 14d3101..8d6c02d 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -7,7 +7,12 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
- start = xmalloc(length);
+ if (length == 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = malloc(length);
if (start == NULL) {
errno = ENOMEM;
return MAP_FAILED;
diff --git a/config.c b/config.c
index f9c400a..7a6ff18 100644
--- a/config.c
+++ b/config.c
@@ -76,7 +76,6 @@ static struct key_value_info *current_config_kvi;
*/
static enum config_scope current_parsing_scope;
-static int core_compression_seen;
static int pack_compression_seen;
static int zlib_compression_seen;
@@ -1400,8 +1399,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die(_("bad zlib compression level %d"), level);
- core_compression_level = level;
- core_compression_seen = 1;
if (!zlib_compression_seen)
zlib_compression_level = level;
if (!pack_compression_seen)
@@ -1833,9 +1830,10 @@ static int git_config_from_blob_ref(config_fn_t fn,
char *git_system_config(void)
{
char *system_config = xstrdup_or_null(getenv("GIT_CONFIG_SYSTEM"));
- if (system_config)
- return system_config;
- return system_path(ETC_GITCONFIG);
+ if (!system_config)
+ system_config = system_path(ETC_GITCONFIG);
+ normalize_path_copy(system_config, system_config);
+ return system_config;
}
void git_global_config(char **user_out, char **xdg_out)
@@ -2072,7 +2070,7 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
e = xmalloc(sizeof(*e));
hashmap_entry_init(&e->ent, strhash(key));
e->key = xstrdup(key);
- string_list_init(&e->value_list, 1);
+ string_list_init_dup(&e->value_list);
hashmap_add(&cs->config_hash, &e->ent);
}
si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
@@ -2837,7 +2835,7 @@ static void maybe_remove_section(struct config_store_data *store,
begin = store->parsed[i].begin;
/*
- * Next, make sure that we are removing he last key(s) in the section,
+ * Next, make sure that we are removing the last key(s) in the section,
* and that there are no comments that are possibly about the current
* section.
*/
@@ -3051,7 +3049,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
if (contents == MAP_FAILED) {
if (errno == ENODEV && S_ISDIR(st.st_mode))
errno = EISDIR;
- error_errno(_("unable to mmap '%s'"), config_filename);
+ error_errno(_("unable to mmap '%s'%s"),
+ config_filename, mmap_os_err());
ret = CONFIG_INVALID_FILE;
contents = NULL;
goto out_free;
diff --git a/config.h b/config.h
index 9038538..a2200f3 100644
--- a/config.h
+++ b/config.h
@@ -450,8 +450,8 @@ void git_configset_init(struct config_set *cs);
/**
* Parses the file and adds the variable-value pairs to the `config_set`,
* dies if there is an error in parsing the file. Returns 0 on success, or
- * -1 if the file does not exist or is inaccessible. The user has to decide
- * if he wants to free the incomplete configset or continue using it when
+ * -1 if the file does not exist or is inaccessible. The caller decides
+ * whether to free the incomplete configset or continue using it when
* the function returns -1.
*/
int git_configset_add_file(struct config_set *cs, const char *filename);
diff --git a/config.mak.uname b/config.mak.uname
index cb443b4..69413fb 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -437,6 +437,11 @@ ifeq ($(uname_S),Windows)
NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
DEFAULT_HELP_FORMAT = html
+ifeq (/mingw64,$(subst 32,64,$(prefix)))
+ # Move system config into top-level /etc/
+ ETC_GITCONFIG = ../etc/gitconfig
+ ETC_GITATTRIBUTES = ../etc/gitattributes
+endif
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
@@ -668,9 +673,14 @@ else
HAVE_LIBCHARSET_H = YesPlease
NO_GETTEXT =
USE_GETTEXT_SCHEME = fallthrough
- USE_LIBPCRE= YesPlease
+ USE_LIBPCRE = YesPlease
NO_CURL =
USE_NED_ALLOCATOR = YesPlease
+ ifeq (/mingw64,$(subst 32,64,$(prefix)))
+ # Move system config into top-level /etc/
+ ETC_GITCONFIG = ../etc/gitconfig
+ ETC_GITATTRIBUTES = ../etc/gitattributes
+ endif
else
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO
NO_CURL = YesPlease
diff --git a/connect.c b/connect.c
index 70b1338..eaf7d6d 100644
--- a/connect.c
+++ b/connect.c
@@ -164,6 +164,8 @@ enum protocol_version discover_version(struct packet_reader *reader)
BUG("unknown protocol version");
}
+ trace2_data_intmax("transfer", NULL, "negotiated-version", version);
+
return version;
}
@@ -555,6 +557,8 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
if (!*value || isspace(*value)) {
if (lenp)
*lenp = 0;
+ if (offset)
+ *offset = found + len - feature_list;
return value;
}
/* feature with a value (e.g., "agent=git/1.2.3") */
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index a878413..171b412 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -43,14 +43,27 @@ NOTE: By default CMake uses Makefile as the build tool on Linux and Visual Studi
to use another tool say `ninja` add this to the command line when configuring.
`-G Ninja`
+NOTE: By default CMake will install vcpkg locally to your source tree on configuration,
+to avoid this, add `-DNO_VCPKG=TRUE` to the command line when configuring.
+
]]
cmake_minimum_required(VERSION 3.14)
#set the source directory to root of git
set(CMAKE_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
-if(WIN32)
+
+option(USE_VCPKG "Whether or not to use vcpkg for obtaining dependencies. Only applicable to Windows platforms" ON)
+if(NOT WIN32)
+ set(USE_VCPKG OFF CACHE BOOL FORCE)
+endif()
+
+if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS)
+ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+endif()
+
+if(USE_VCPKG)
set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg")
- if(MSVC AND NOT EXISTS ${VCPKG_DIR})
+ if(NOT EXISTS ${VCPKG_DIR})
message("Initializing vcpkg and building the Git's dependencies (this will take a while...)")
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat)
endif()
@@ -176,12 +189,18 @@ if(WIN32 AND NOT MSVC)#not required for visual studio builds
endif()
endif()
-find_program(MSGFMT_EXE msgfmt)
-if(NOT MSGFMT_EXE)
- set(MSGFMT_EXE ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg/downloads/tools/msys2/msys64/usr/bin/msgfmt.exe)
- if(NOT EXISTS ${MSGFMT_EXE})
- message(WARNING "Text Translations won't be built")
- unset(MSGFMT_EXE)
+if(NO_GETTEXT)
+ message(STATUS "msgfmt not used under NO_GETTEXT")
+else()
+ find_program(MSGFMT_EXE msgfmt)
+ if(NOT MSGFMT_EXE)
+ if(USE_VCPKG)
+ set(MSGFMT_EXE ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg/downloads/tools/msys2/msys64/usr/bin/msgfmt.exe)
+ endif()
+ if(NOT EXISTS ${MSGFMT_EXE})
+ message(WARNING "Text Translations won't be built")
+ unset(MSGFMT_EXE)
+ endif()
endif()
endif()
@@ -204,8 +223,6 @@ list(APPEND compat_SOURCES sha1dc_git.c sha1dc/sha1.c sha1dc/ubc_check.c block-s
add_compile_definitions(PAGER_ENV="LESS=FRX LV=-c"
- ETC_GITATTRIBUTES="etc/gitattributes"
- ETC_GITCONFIG="etc/gitconfig"
GIT_EXEC_PATH="libexec/git-core"
GIT_LOCALE_PATH="share/locale"
GIT_MAN_PATH="share/man"
@@ -220,10 +237,15 @@ add_compile_definitions(PAGER_ENV="LESS=FRX LV=-c"
if(WIN32)
set(FALLBACK_RUNTIME_PREFIX /mingw64)
- add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}")
+ # Move system config into top-level /etc/
+ add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}"
+ ETC_GITATTRIBUTES="../etc/gitattributes"
+ ETC_GITCONFIG="../etc/gitconfig")
else()
set(FALLBACK_RUNTIME_PREFIX /home/$ENV{USER})
- add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}")
+ add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}"
+ ETC_GITATTRIBUTES="etc/gitattributes"
+ ETC_GITCONFIG="etc/gitconfig")
endif()
@@ -982,7 +1004,7 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_GETTEXT='${NO_GETTEXT}'\n"
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PREFIX}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PYTHON='${NO_PYTHON}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SUPPORTS_SIMPLE_IPC='${SUPPORTS_SIMPLE_IPC}'\n")
-if(WIN32)
+if(USE_VCPKG)
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n")
endif()
diff --git a/contrib/coccinelle/xopen.cocci b/contrib/coccinelle/xopen.cocci
new file mode 100644
index 0000000..814d7b8
--- /dev/null
+++ b/contrib/coccinelle/xopen.cocci
@@ -0,0 +1,16 @@
+@@
+identifier fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+(
+ fd =
+- open
++ xopen
+ (...);
+|
+ int fd =
+- open
++ xopen
+ (...);
+)
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index b50c5d0..8108eda 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -356,7 +356,7 @@ __gitcomp ()
local cur_="${3-$cur}"
case "$cur_" in
- --*=)
+ *=)
;;
--no-*)
local c i=0 IFS=$' \t\n'
@@ -421,7 +421,7 @@ __gitcomp_builtin ()
local incl="${2-}"
local excl="${3-}"
- local var=__gitcomp_builtin_"${cmd/-/_}"
+ local var=__gitcomp_builtin_"${cmd//-/_}"
local options
eval "options=\${$var-}"
@@ -1729,6 +1729,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--indent-heuristic --no-indent-heuristic
--textconv --no-textconv
--patch --no-patch
+ --anchored=
"
__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex
@@ -2649,10 +2650,10 @@ __git_complete_config_variable_name ()
return
;;
branch.*)
- local pfx="${cur%.*}."
- cur_="${cur#*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
- __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
return
;;
guitool.*.*)
@@ -2686,7 +2687,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__git_compute_all_commands
- __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
return
;;
remote.*.*)
@@ -2702,7 +2703,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
- __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
return
;;
url.*.*)
@@ -3512,6 +3513,7 @@ fi
__git_func_wrap ()
{
local cur words cword prev
+ local __git_cmd_idx=0
_get_comp_words_by_ref -n =: cur words cword prev
$1
}
diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh
index 4a790d8..ba797e5 100644
--- a/contrib/completion/git-completion.tcsh
+++ b/contrib/completion/git-completion.tcsh
@@ -80,8 +80,9 @@ else
COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
fi
-# Call _git() or _gitk() of the bash script, based on the first argument
-_\${1}
+# Call __git_wrap__git_main() or __git_wrap__gitk_main() of the bash script,
+# based on the first argument
+__git_wrap__\${1}_main
IFS=\$'\n'
if [ \${#COMPREPLY[*]} -eq 0 ]; then
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index bcd3f57..0b44a9b 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -10,6 +10,7 @@ static char *username;
static char *password;
static UInt16 port;
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 5bdad41..5091048 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -11,6 +11,7 @@
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES
deleted file mode 100644
index 35791fd..0000000
--- a/contrib/hooks/multimail/CHANGES
+++ /dev/null
@@ -1,285 +0,0 @@
-Release 1.5.0
-=============
-
-Backward-incompatible change
-----------------------------
-
-The name of classes for environment was misnamed as `*Environement`.
-It is now `*Environment`.
-
-New features
-------------
-
-* A Thread-Index header is now added to each email sent (except for
- combined emails where it would not make sense), so that MS Outlook
- properly groups messages by threads even though they have a
- different subject line. Unfortunately, even adding this header the
- threading still seems to be unreliable, but it is unclear whether
- this is an issue on our side or on MS Outlook's side (see discussion
- here: https://github.com/git-multimail/git-multimail/pull/194).
-
-* A new variable multimailhook.ExcludeMergeRevisions was added to send
- notification emails only for non-merge commits.
-
-* For gitolite environment, it is now possible to specify the mail map
- in a separate file in addition to gitolite.conf, using the variable
- multimailhook.MailaddressMap.
-
-Internal changes
-----------------
-
-* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for
- compatibility with recent Git versions. Only tests are affected.
-
-* We don't try to install pyflakes in the continuous integration job
- for old Python versions where it's no longer available.
-
-* Stop using the deprecated cgi.escape in Python 3.
-
-* New flake8 warnings have been fixed.
-
-* Python 3.6 is now tested against on Travis-CI.
-
-* A bunch of lgtm.com warnings have been fixed.
-
-Bug fixes
----------
-
-* SMTPMailer logs in only once now. It used to re-login for each email
- sent which triggered errors for some SMTP servers.
-
-* migrate-mailhook-config was broken by internal refactoring, it
- should now work again.
-
-This version was tested with Python 2.6 to 3.7. It was tested with Git
-1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0.
-
-Release 1.4.0
-=============
-
-New features to troubleshoot a git-multimail installation
----------------------------------------------------------
-
-* One can now perform a basic check of git-multimail's setup by
- running the hook with the environment variable
- GIT_MULTIMAIL_CHECK_SETUP set to a non-empty string. See
- doc/troubleshooting.rst for details.
-
-* A new log files system was added. See the multimailhook.logFile,
- multimailhook.errorLogFile and multimailhook.debugLogFile variables.
-
-* git_multimail.py can now be made more verbose using
- multimailhook.verbose.
-
-* A new option --check-ref-filter is now available to help debugging
- the refFilter* options.
-
-Formatting emails
------------------
-
-* Formatting of emails was made slightly more compact, to reduce the
- odds of having long subject lines truncated or wrapped in short list
- of commits.
-
-* multimailhook.emailPrefix may now use the '%(repo_shortname)s'
- placeholder for the repository's short name.
-
-* A new option multimailhook.subjectMaxLength is available to truncate
- overly long subject lines.
-
-Bug fixes and minor changes
----------------------------
-
-* Options refFilterDoSendRegex and refFilterDontSendRegex were
- essentially broken. They should work now.
-
-* The behavior when both refFilter{Do,Dont}SendRegex and
- refFilter{Exclusion,Inclusion}Regex are set have been slightly
- changed. Exclusion/Inclusion is now strictly stronger than
- DoSend/DontSend.
-
-* The management of precedence when a setting can be computed in
- multiple ways has been considerably refactored and modified.
- multimailhook.from and multimailhook.reponame now have precedence
- over the environment-specific settings ($GL_REPO/$GL_USER for
- gitolite, --stash-user/repo for Stash, --submitter/--project for
- Gerrit).
-
-* The coverage of the testsuite has been considerably improved. All
- configuration variables now appear at least once in the testsuite.
-
-This version was tested with Python 2.6 to 3.5. It also mostly works
-with Python 2.4, but there is one known breakage in the testsuite
-related to non-ascii characters. It was tested with Git
-1.7.10.406.gdc801, 1.8.5.6, 2.1.4, and 2.10.0.rc0.1.g07c9292.
-
-Release 1.3.1 (bugfix-only release)
-===================================
-
-* Generate links to commits in combined emails (it was done only for
- commit emails in 1.3.0).
-
-* Fix broken links on PyPi.
-
-Release 1.3.0
-=============
-
-* New options multimailhook.htmlInIntro and multimailhook.htmlInFooter
- now allow using HTML in the introduction and footer of emails (e.g.
- for a more pleasant formatting or to insert a link to the commit on
- a web interface).
-
-* A new option multimailhook.commitBrowseURL gives a simpler (and less
- flexible) way to add a link to a web interface for commit emails
- than multimailhook.htmlInIntro and multimailhook.htmlInFooter.
-
-* A new public function config.add_config_parameters was added to
- allow custom hooks to set specific Git configuration variables
- without modifying the configuration files. See an example in
- post-receive.example.
-
-* Error handling for SMTP has been improved (we used to print Python
- backtraces for legitimate errors).
-
-* The SMTP mailer can now check TLS certificates when the newly added
- configuration variable multimailhook.smtpCACerts.
-
-* Python 3 portability has been improved.
-
-* The documentation's formatting has been improved.
-
-* The testsuite has been improved (we now use pyflakes to check for
- errors in the code).
-
-This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
-v1.7.10-406-gdc801e7, 2.1.4 and 2.8.1.339.g3ad15fd.
-
-No change since 1.3 RC1.
-
-Release 1.2.0
-=============
-
-* It is now possible to exclude some refs (e.g. exclude some branches
- or tags). See refFilterDoSendRegex, refFilterDontSendRegex,
- refFilterInclusionRegex and refFilterExclusionRegex.
-
-* New commitEmailFormat option which can be set to "html" to generate
- simple colorized diffs using HTML for the commit emails.
-
-* git-multimail can now be ran as a Gerrit ref-updated hook, or from
- Atlassian BitBucket Server (formerly known as Atlassian Stash).
-
-* The From: field is now more customizeable. It can be set
- independently for refchange emails and commit emails (see
- fromCommit, fromRefChange). The special values pusher and author can
- be used in these configuration variable.
-
-* A new command-line option, --version, was added. The version is also
- available in the X-Git-Multimail-Version header of sent emails.
-
-* Set X-Git-NotificationType header to differentiate the various types
- of notifications. Current values are: diff, ref_changed_plus_diff,
- ref_changed.
-
-* Preliminary support for Python 3. The testsuite passes with Python 3,
- but it has not received as much testing as the Python 2 version yet.
-
-* Several encoding-related fixes. UTF-8 characters work in more
- situations (but non-ascii characters in email address are still not
- supported).
-
-* The testsuite and its documentation has been greatly improved.
-
-Plus all the bugfixes from version 1.1.1.
-
-This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
-v1.7.10-406-gdc801e7, git-1.8.2.3 and 2.6.0. Git versions prior to
-v1.7.10-406-gdc801e7 probably work, but cannot run the testsuite
-properly.
-
-Release 1.1.1 (bugfix-only release)
-===================================
-
-* The SMTP mailer was not working with Python 2.4.
-
-Release 1.1.0
-=============
-
-* When a single commit is pushed, omit the reference changed email.
- Set multimailhook.combineWhenSingleCommit to false to disable this
- new feature.
-
-* In gitolite environments, the pusher's email address can be used as
- the From address by creating a specially formatted comment block in
- gitolite.conf (see multimailhook.from in README).
-
-* Support for SMTP authentication and SSL/TLS encryption was added,
- see smtpUser, smtpPass, smtpEncryption in README.
-
-* A new option scanCommitForCc was added to allow git-multimail to
- search the commit message for 'Cc: ...' lines, and add the
- corresponding emails in Cc.
-
-* If $USER is not set, use the variable $USERNAME. This is needed on
- Windows platform to recognize the pusher.
-
-* The emailPrefix variable can now be set to an empty string to remove
- the prefix.
-
-* A short tutorial was added in doc/gitolite.rst to set up
- git-multimail with gitolite.
-
-* The post-receive file was renamed to post-receive.example. It has
- always been an example (the standard way to call git-multimail is to
- call git_multimail.py), but it was unclear to many users.
-
-* A new refchangeShowGraph option was added to make it possible to
- include both a graph and a log in the summary emails. The options
- to control the graph formatting can be set via the new graphOpts
- option.
-
-* New option --force-send was added to disable new commit detection
- for update hook. One use-case is to run git_multimail.py after
- running "git fetch" to send emails about commits that have just been
- fetched (the detection of new commits was unreliable in this mode).
-
-* The testing infrastructure was considerably improved (continuous
- integration with travis-ci, automatic check of PEP8 and RST syntax,
- many improvements to the test scripts).
-
-This version has been tested with Python 2.4 to 2.7, and Git 1.7.1 to
-2.4.
-
-Release 1.0.0
-=============
-
-* Fix encoding of non-ASCII email addresses in email headers.
-
-* Fix backwards-compatibility bugs for older Python 2.x versions.
-
-* Fix a backwards-compatibility bug for Git 1.7.1.
-
-* Add an option commitDiffOpts to customize logs for revisions.
-
-* Pass "-oi" to sendmail by default to prevent premature termination
- on a line containing only ".".
-
-* Stagger email "Date:" values in an attempt to help mail clients
- thread the emails in the right order.
-
-* If a mailing list setting is missing, just skip sending the
- corresponding email (with a warning) instead of failing.
-
-* Add a X-Git-Host header that can be used for email filtering.
-
-* Allow the sender's fully-qualified domain name to be configured.
-
-* Minor documentation improvements.
-
-* Add this CHANGES file.
-
-
-Release 0.9.0
-=============
-
-* Initial release.
diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst
deleted file mode 100644
index de20a54..0000000
--- a/contrib/hooks/multimail/CONTRIBUTING.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-Contributing
-============
-
-git-multimail is an open-source project, built by volunteers. We would
-welcome your help!
-
-The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and
-`Michael Haggerty <https://github.com/mhagger>`__.
-
-Please note that although a copy of git-multimail is distributed in
-the "contrib" section of the main Git project, development takes place
-in a separate `git-multimail repository on GitHub`_.
-
-Whenever enough changes to git-multimail have accumulated, a new
-code-drop of git-multimail will be submitted for inclusion in the Git
-project.
-
-We use the GitHub issue tracker to keep track of bugs and feature
-requests, and we use GitHub pull requests to exchange patches (though,
-if you prefer, you can send patches via the Git mailing list with CC
-to the maintainers). Please sign off your patches as per the `Git
-project practice
-<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
-
-Please vote for issues you would like to be addressed in priority
-(click "add your reaction" and then the "+1" thumbs-up button on the
-GitHub issue).
-
-General discussion of git-multimail can take place on the main `Git
-mailing list`_.
-
-Please CC emails regarding git-multimail to the maintainers so that we
-don't overlook them.
-
-Help needed: testers/maintainer for specific environments/OS
-------------------------------------------------------------
-
-The current maintainer uses and tests git-multimail on Linux with the
-Generic environment. More testers, or better contributors are needed
-to test git-multimail on other real-life setups:
-
-* Mac OS X, Windows: git-multimail is currently not supported on these
- platforms. But since we have no external dependencies and try to
- write code as portable as possible, it is possible that
- git-multimail already runs there and if not, it is likely that it
- could be ported easily.
-
- Patches to improve support for Windows and OS X are welcome.
- Ideally, there would be a sub-maintainer for each OS who would test
- at least once before each release (around twice a year).
-
-* Gerrit, Stash, Gitolite environments: although the testsuite
- contains tests for these environments, a tester/maintainer for each
- environment would be welcome to test and report failure (or success)
- on real-life environments periodically (here also, feedback before
- each release would be highly appreciated).
-
-
-.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
-.. _`Git mailing list`: git@vger.kernel.org
diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git
index 0444442..c427efc 100644
--- a/contrib/hooks/multimail/README.Git
+++ b/contrib/hooks/multimail/README.Git
@@ -1,15 +1,7 @@
-This copy of git-multimail is distributed as part of the "contrib"
-section of the Git project as a convenience to Git users.
git-multimail is developed as an independent project at the following
website:
https://github.com/git-multimail/git-multimail
-The version in this directory was obtained from the upstream project
-on January 07 2019 and consists of the "git-multimail" subdirectory from
-revision
-
- 04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0
-
-Please see the README file in this directory for information about how
-to report bugs or contribute to git-multimail.
+Please refer to that project page for information about how to report
+bugs or contribute to git-multimail.
diff --git a/contrib/hooks/multimail/README.migrate-from-post-receive-email b/contrib/hooks/multimail/README.migrate-from-post-receive-email
deleted file mode 100644
index 1e6a976..0000000
--- a/contrib/hooks/multimail/README.migrate-from-post-receive-email
+++ /dev/null
@@ -1,145 +0,0 @@
-git-multimail is close to, but not exactly, a plug-in replacement for
-the old Git project script contrib/hooks/post-receive-email. This
-document describes the differences and explains how to configure
-git-multimail to get behavior closest to that of post-receive-email.
-
-If you are in a hurry
-=====================
-
-A script called migrate-mailhook-config is included with
-git-multimail. If you run this script within a Git repository that is
-configured to use post-receive-email, it will convert the
-configuration settings into the approximate equivalent settings for
-git-multimail. For more information, run
-
- migrate-mailhook-config --help
-
-
-Configuration differences
-=========================
-
-* The names of the config options for git-multimail are in namespace
- "multimailhook.*" instead of "hooks.*". (Editorial comment:
- post-receive-email should never have used such a generic top-level
- namespace.)
-
-* In emails about new annotated tags, post-receive-email includes a
- shortlog of all changes since the previous annotated tag. To get
- this behavior with git-multimail, you need to set
- multimailhook.announceshortlog to true:
-
- git config multimailhook.announceshortlog true
-
-* multimailhook.commitlist -- This is a new configuration variable.
- Recipients listed here will receive a separate email for each new
- commit. However, if this variable is *not* set, it defaults to the
- value of multimailhook.mailinglist. Therefore, if you *don't* want
- the members of multimailhook.mailinglist to receive one email per
- commit, then set this value to the empty string:
-
- git config multimailhook.commitlist ''
-
-* multimailhook.emailprefix -- If this value is not set, then the
- subjects of generated emails are prefixed with the short name of the
- repository enclosed in square brackets; e.g., "[myrepo]".
- post-receive-email defaults to prefix "[SCM]" if this option is not
- set. So if you were using the old default and want to retain it
- (for example, to avoid having to change your email filters), set
- this variable explicitly to the old value:
-
- git config multimailhook.emailprefix "[SCM]"
-
-* The "multimailhook.showrev" configuration option is not supported.
- Its main use is obsoleted by the one-email-per-commit feature of
- git-multimail.
-
-
-Other differences
-=================
-
-This section describes other differences in the behavior of
-git-multimail vs. post-receive-email. For full details, please refer
-to the main README file:
-
-* One email per commit. For each reference change, the script first
- outputs one email summarizing the reference change (including
- one-line summaries of the new commits), then it outputs a separate
- email for each new commit that was introduced, including patches.
- These one-email-per-commit emails go to the addresses listed in
- multimailhook.commitlist. post-receive-email sends only one email
- for each *reference* that is changed, no matter how many commits
- were added to the reference.
-
-* Better algorithm for detecting new commits. post-receive-email
- processes one reference change at a time, which causes it to fail to
- describe new commits that were included in multiple branches. For
- example, if a single push adds the "*" commits in the diagram below,
- then post-receive-email would never include the details of the two
- commits that are common to "master" and "branch" in its
- notifications.
-
- o---o---o---*---*---* <-- master
- \
- *---* <-- branch
-
- git-multimail analyzes all reference modifications to determine
- which commits were not present before the change, therefore avoiding
- that error.
-
-* In reference change emails, git-multimail tells which commits have
- been added to the reference vs. are entirely new to the repository,
- and which commits that have been omitted from the reference
- vs. entirely discarded from the repository.
-
-* The environment in which Git is running can be configured via an
- "Environment" abstraction.
-
-* Built-in support for Gitolite-managed repositories.
-
-* Instead of using full SHA1 object names in emails, git-multimail
- mostly uses abbreviated SHA1s, plus one-line log message summaries
- where appropriate.
-
-* In the schematic diagrams that explain non-fast-forward commits,
- git-multimail shows the names of the branches involved.
-
-* The emails generated by git-multimail include the name of the Git
- repository that was modified; this is convenient for recipients who
- are monitoring multiple repositories.
-
-* git-multimail allows the email "From" addresses to be configured.
-
-* The recipients lists (multimailhook.mailinglist,
- multimailhook.refchangelist, multimailhook.announcelist, and
- multimailhook.commitlist) can be comma-separated values and/or
- multivalued settings in the config file; e.g.,
-
- [multimailhook]
- mailinglist = mr.brown@example.com, mr.black@example.com
- announcelist = Him <him@example.com>
- announcelist = Jim <jim@example.com>
- announcelist = pop@example.com
-
- This might make it easier to maintain short recipients lists without
- requiring full-fledged mailing list software.
-
-* By default, git-multimail sets email "Reply-To" headers to reply to
- the pusher (for reference updates) and to the author (for commit
- notifications). By default, the pusher's email address is
- constructed by appending "multimailhook.emaildomain" to the pusher's
- username.
-
-* The generated emails contain a configurable footer. By default, it
- lists the name of the administrator who should be contacted to
- unsubscribe from notification emails.
-
-* New option multimailhook.emailmaxlinelength to limit the length of
- lines in the main part of the email body. The default limit is 500
- characters.
-
-* New option multimailhook.emailstrictutf8 to ensure that the main
- part of the email body is valid UTF-8. Invalid characters are
- turned into the Unicode replacement character, U+FFFD. By default
- this option is turned on.
-
-* Written in Python. Easier to add new features.
diff --git a/contrib/hooks/multimail/README.rst b/contrib/hooks/multimail/README.rst
deleted file mode 100644
index 7c0fc4a..0000000
--- a/contrib/hooks/multimail/README.rst
+++ /dev/null
@@ -1,774 +0,0 @@
-git-multimail version 1.5.0
-===========================
-
-.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
- :target: https://travis-ci.org/git-multimail/git-multimail
-
-git-multimail is a tool for sending notification emails on pushes to a
-Git repository. It includes a Python module called ``git_multimail.py``,
-which can either be used as a hook script directly or can be imported
-as a Python module into another script.
-
-git-multimail is derived from the Git project's old
-contrib/hooks/post-receive-email, and is mostly compatible with that
-script. See README.migrate-from-post-receive-email for details about
-the differences and for how to migrate from post-receive-email to
-git-multimail.
-
-git-multimail, like the rest of the Git project, is licensed under
-GPLv2 (see the COPYING file for details).
-
-Please note: although, as a convenience, git-multimail may be
-distributed along with the main Git project, development of
-git-multimail takes place in its own, separate project. Please, read
-`<CONTRIBUTING.rst>`__ for more information.
-
-
-By default, for each push received by the repository, git-multimail:
-
-1. Outputs one email summarizing each reference that was changed.
- These "reference change" (called "refchange" below) emails describe
- the nature of the change (e.g., was the reference created, deleted,
- fast-forwarded, etc.) and include a one-line summary of each commit
- that was added to the reference.
-
-2. Outputs one email for each new commit that was introduced by the
- reference change. These "commit" emails include a list of the
- files changed by the commit, followed by the diffs of files
- modified by the commit. The commit emails are threaded to the
- corresponding reference change email via "In-Reply-To". This style
- (similar to the "git format-patch" style used on the Git mailing
- list) makes it easy to scan through the emails, jump to patches
- that need further attention, and write comments about specific
- commits. Commits are handled in reverse topological order (i.e.,
- parents shown before children). For example::
-
- [git] branch master updated
- + [git] 01/08: doc: fix xref link from api docs to manual pages
- + [git] 02/08: api-credentials.txt: show the big picture first
- + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
- + [git] 04/08: api-credentials.txt: add "see also" section
- + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
- + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
- + [git] 07/08: Merge branch 'mm/api-credentials-doc'
- + [git] 08/08: Git 1.7.11-rc2
-
- By default, each commit appears in exactly one commit email, the
- first time that it is pushed to the repository. If a commit is later
- merged into another branch, then a one-line summary of the commit
- is included in the reference change email (as usual), but no
- additional commit email is generated. See
- `multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
- below to configure which branches and tags are watched by the hook.
-
- By default, reference change emails have their "Reply-To" field set
- to the person who pushed the change, and commit emails have their
- "Reply-To" field set to the author of the commit.
-
-3. Output one "announce" mail for each new annotated tag, including
- information about the tag and optionally a shortlog describing the
- changes since the previous tag. Such emails might be useful if you
- use annotated tags to mark releases of your project.
-
-
-Requirements
-------------
-
-* Python 2.x, version 2.4 or later. No non-standard Python modules
- are required. git-multimail has preliminary support for Python 3
- (but it has been better tested with Python 2).
-
-* The ``git`` command must be in your PATH. git-multimail is known to
- work with Git versions back to 1.7.1. (Earlier versions have not
- been tested; if you do so, please report your results.)
-
-* To send emails using the default configuration, a standard sendmail
- program must be located at '/usr/sbin/sendmail' or
- '/usr/lib/sendmail' and must be configured correctly to send emails.
- If this is not the case, set multimailhook.sendmailCommand, or see
- the multimailhook.mailer configuration variable below for how to
- configure git-multimail to send emails via an SMTP server.
-
-* git-multimail is currently tested only on Linux. It may or may not
- work on other platforms such as Windows and Mac OS. See
- `<CONTRIBUTING.rst>`__ to improve the situation.
-
-
-Invocation
-----------
-
-``git_multimail.py`` is designed to be used as a ``post-receive`` hook in a
-Git repository (see githooks(5)). Link or copy it to
-$GIT_DIR/hooks/post-receive within the repository for which email
-notifications are desired. Usually it should be installed on the
-central repository for a project, to which all commits are eventually
-pushed.
-
-For use on pre-v1.5.1 Git servers, ``git_multimail.py`` can also work as
-an ``update`` hook, taking its arguments on the command line. To use
-this script in this manner, link or copy it to $GIT_DIR/hooks/update.
-Please note that the script is not completely reliable in this mode
-[1]_.
-
-Alternatively, ``git_multimail.py`` can be imported as a Python module
-into your own Python post-receive script. This method is a bit more
-work, but allows the behavior of the hook to be customized using
-arbitrary Python code. For example, you can use a custom environment
-(perhaps inheriting from GenericEnvironment or GitoliteEnvironment) to
-
-* change how the user who did the push is determined
-
-* read users' email addresses from an LDAP server or from a database
-
-* decide which users should be notified about which commits based on
- the contents of the commits (e.g., for users who want to be notified
- only about changes affecting particular files or subdirectories)
-
-Or you can change how emails are sent by writing your own Mailer
-class. The ``post-receive`` script in this directory demonstrates how
-to use ``git_multimail.py`` as a Python module. (If you make interesting
-changes of this type, please consider sharing them with the
-community.)
-
-
-Troubleshooting/FAQ
--------------------
-
-Please read `<doc/troubleshooting.rst>`__ for frequently asked
-questions and common issues with git-multimail.
-
-
-Configuration
--------------
-
-By default, git-multimail mostly takes its configuration from the
-following ``git config`` settings:
-
-multimailhook.environment
- This describes the general environment of the repository. In most
- cases, you do not need to specify a value for this variable:
- `git-multimail` will autodetect which environment to use.
- Currently supported values:
-
- generic
- the username of the pusher is read from $USER or $USERNAME and
- the repository name is derived from the repository's path.
-
- gitolite
- Environment to use when ``git-multimail`` is ran as a gitolite_
- hook.
-
- The username of the pusher is read from $GL_USER, the repository
- name is read from $GL_REPO, and the From: header value is
- optionally read from gitolite.conf (see multimailhook.from).
-
- For more information about gitolite and git-multimail, read
- `<doc/gitolite.rst>`__
-
- stash
- Environment to use when ``git-multimail`` is ran as an Atlassian
- BitBucket Server (formerly known as Atlassian Stash) hook.
-
- **Warning:** this mode was provided by a third-party contributor
- and never tested by the git-multimail maintainers. It is
- provided as-is and may or may not work for you.
-
- This value is automatically assumed when the stash-specific
- flags (``--stash-user`` and ``--stash-repo``) are specified on
- the command line. When this environment is active, the username
- and repo come from these two command line flags, which must be
- specified.
-
- gerrit
- Environment to use when ``git-multimail`` is ran as a
- ``ref-updated`` Gerrit hook.
-
- This value is used when the gerrit-specific command line flags
- (``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
- gerrit's ref-updated hook are present. When this environment is
- active, the username of the pusher is taken from the
- ``--submitter`` argument if that command line option is passed,
- otherwise 'Gerrit' is used. The repository name is taken from
- the ``--project`` option on the command line, which must be passed.
-
- For more information about gerrit and git-multimail, read
- `<doc/gerrit.rst>`__
-
- If none of these environments is suitable for your setup, then you
- can implement a Python class that inherits from Environment and
- instantiate it via a script that looks like the example
- post-receive script.
-
- The environment value can be specified on the command line using
- the ``--environment`` option. If it is not specified on the
- command line or by ``multimailhook.environment``, the value is
- guessed as follows:
-
- * If stash-specific (respectively gerrit-specific) command flags
- are present on the command-line, then ``stash`` (respectively
- ``gerrit``) is used.
-
- * If the environment variables $GL_USER and $GL_REPO are set, then
- ``gitolite`` is used.
-
- * If none of the above apply, then ``generic`` is used.
-
-multimailhook.repoName
- A short name of this Git repository, to be used in various places
- in the notification email text. The default is to use $GL_REPO
- for gitolite repositories, or otherwise to derive this value from
- the repository path name.
-
-multimailhook.mailingList
- The list of email addresses to which notification emails should be
- sent, as RFC 2822 email addresses separated by commas. This
- configuration option can be multivalued. Leave it unset or set it
- to the empty string to not send emails by default. The next few
- settings can be used to configure specific address lists for
- specific types of notification email.
-
-multimailhook.refchangeList
- The list of email addresses to which summary emails about
- reference changes should be sent, as RFC 2822 email addresses
- separated by commas. This configuration option can be
- multivalued. The default is the value in
- multimailhook.mailingList. Set this value to "none" (or the empty
- string) to prevent reference change emails from being sent even if
- multimailhook.mailingList is set.
-
-multimailhook.announceList
- The list of email addresses to which emails about new annotated
- tags should be sent, as RFC 2822 email addresses separated by
- commas. This configuration option can be multivalued. The
- default is the value in multimailhook.refchangeList or
- multimailhook.mailingList. Set this value to "none" (or the empty
- string) to prevent annotated tag announcement emails from being sent
- even if one of the other values is set.
-
-multimailhook.commitList
- The list of email addresses to which emails about individual new
- commits should be sent, as RFC 2822 email addresses separated by
- commas. This configuration option can be multivalued. The
- default is the value in multimailhook.mailingList. Set this value
- to "none" (or the empty string) to prevent notification emails about
- individual commits from being sent even if
- multimailhook.mailingList is set.
-
-multimailhook.announceShortlog
- If this option is set to true, then emails about changes to
- annotated tags include a shortlog of changes since the previous
- tag. This can be useful if the annotated tags represent releases;
- then the shortlog will be a kind of rough summary of what has
- happened since the last release. But if your tagging policy is
- not so straightforward, then the shortlog might be confusing
- rather than useful. Default is false.
-
-multimailhook.commitEmailFormat
- The format of email messages for the individual commits, can be "text" or
- "html". In the latter case, the emails will include diffs using colorized
- HTML instead of plain text used by default. Note that this currently the
- ref change emails are always sent in plain text.
-
- Note that when using "html", the formatting is done by parsing the
- output of ``git log`` with ``-p``. When using
- ``multimailhook.commitLogOpts`` to specify a ``--format`` for
- ``git log``, one may get false positive (e.g. lines in the body of
- the message starting with ``+++`` or ``---`` colored in red or
- green).
-
- By default, all the message is HTML-escaped. See
- ``multimailhook.htmlInIntro`` to change this behavior.
-
-multimailhook.commitBrowseURL
- Used to generate a link to an online repository browser in commit
- emails. This variable must be a string. Format directives like
- ``%(<variable>)s`` will be expanded the same way as template
- strings. In particular, ``%(id)s`` will be replaced by the full
- Git commit identifier (40-chars hexadecimal).
-
- If the string does not contain any format directive, then
- ``%(id)s`` will be automatically added to the string. If you don't
- want ``%(id)s`` to be automatically added, use the empty format
- directive ``%()s`` anywhere in the string.
-
- For example, a suitable value for the git-multimail project itself
- would be
- ``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
-
-multimailhook.htmlInIntro, multimailhook.htmlInFooter
- When generating an HTML message, git-multimail escapes any HTML
- sequence by default. This means that if a template contains HTML
- like ``<a href="foo">link</a>``, the reader will see the HTML
- source code and not a proper link.
-
- Set ``multimailhook.htmlInIntro`` to true to allow writing HTML
- formatting in introduction templates. Similarly, set
- ``multimailhook.htmlInFooter`` for HTML in the footer.
-
- Variables expanded in the template are still escaped. For example,
- if a repository's path contains a ``<``, it will be rendered as
- such in the message.
-
- Read `<doc/customizing-emails.rst>`__ for more details and
- examples.
-
-multimailhook.refchangeShowGraph
- If this option is set to true, then summary emails about reference
- changes will additionally include:
-
- * a graph of the added commits (if any)
-
- * a graph of the discarded commits (if any)
-
- The log is generated by running ``git log --graph`` with the options
- specified in graphOpts. The default is false.
-
-multimailhook.refchangeShowLog
- If this option is set to true, then summary emails about reference
- changes will include a detailed log of the added commits in
- addition to the one line summary. The log is generated by running
- ``git log`` with the options specified in multimailhook.logOpts.
- Default is false.
-
-multimailhook.mailer
- This option changes the way emails are sent. Accepted values are:
-
- * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
- ``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
- mode can be further customized via the following options:
-
- multimailhook.sendmailCommand
- The command used by mailer ``sendmail`` to send emails. Shell
- quoting is allowed in the value of this setting, but remember that
- Git requires double-quotes to be escaped; e.g.::
-
- git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
-
- Default is '/usr/sbin/sendmail -oi -t' or
- '/usr/lib/sendmail -oi -t' (depending on which file is
- present and executable).
-
- multimailhook.envelopeSender
- If set then pass this value to sendmail via the -f option to set
- the envelope sender address.
-
- * **smtp**: use Python's smtplib. This is useful when the sendmail
- command is not available on the system. This mode can be
- further customized via the following options:
-
- multimailhook.smtpServer
- The name of the SMTP server to connect to. The value can
- also include a colon and a port number; e.g.,
- ``mail.example.com:25``. Default is 'localhost' using port 25.
-
- multimailhook.smtpUser, multimailhook.smtpPass
- Server username and password. Required if smtpEncryption is 'ssl'.
- Note that the username and password currently need to be
- set cleartext in the configuration file, which is not
- recommended. If you need to use this option, be sure your
- configuration file is read-only.
-
- multimailhook.envelopeSender
- The sender address to be passed to the SMTP server. If
- unset, then the value of multimailhook.from is used.
-
- multimailhook.smtpServerTimeout
- Timeout in seconds. Default is 10.
-
- multimailhook.smtpEncryption
- Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
- Default is ``none``.
-
- multimailhook.smtpCACerts
- Set the path to a list of trusted CA certificate to verify the
- server certificate, only supported when ``smtpEncryption`` is
- ``tls``. If unset or empty, the server certificate is not
- verified. If it targets a file containing a list of trusted CA
- certificates (PEM format) these CAs will be used to verify the
- server certificate. For debian, you can set
- ``/etc/ssl/certs/ca-certificates.crt`` for using the system
- trusted CAs. For self-signed server, you can add your server
- certificate to the system store::
-
- cd /usr/local/share/ca-certificates/
- openssl s_client -starttls smtp \
- -connect mail.example.net:587 -showcerts \
- </dev/null 2>/dev/null \
- | openssl x509 -outform PEM >mail.example.net.crt
- update-ca-certificates
-
- and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
- directly use your ``/path/to/mail.example.net.crt``. Default is
- unset.
-
- multimailhook.smtpServerDebugLevel
- Integer number. Set to greater than 0 to activate debugging.
-
-multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
- If set, use this value in the From: field of generated emails.
- ``fromCommit`` is used for commit emails, ``fromRefchange`` is
- used for refchange emails, and ``from`` is used as fall-back in
- all cases.
-
- The value for these variables can be either:
-
- - An email address, which will be used directly.
-
- - The value ``pusher``, in which case the pusher's address (if
- available) will be used.
-
- - The value ``author`` (meaningful only for ``fromCommit``), in which
- case the commit author's address will be used.
-
- If config values are unset, the value of the From: header is
- determined as follows:
-
- 1. (gitolite environment only)
- 1.a) If ``multimailhook.MailaddressMap`` is set, and is a path
- to an existing file (if relative, it is considered relative to
- the place where ``gitolite.conf`` is located), then this file
- should contain lines like::
-
- username Firstname Lastname <email@example.com>
-
- git-multimail will then look for a line where ``$GL_USER``
- matches the ``username`` part, and use the rest of the line for
- the ``From:`` header.
-
- 1.b) Parse gitolite.conf, looking for a block of comments that
- looks like this::
-
- # BEGIN USER EMAILS
- # username Firstname Lastname <email@example.com>
- # END USER EMAILS
-
- If that block exists, and there is a line between the BEGIN
- USER EMAILS and END USER EMAILS lines where the first field
- matches the gitolite username ($GL_USER), use the rest of the
- line for the From: header.
-
- 2. If the user.email configuration setting is set, use its value
- (and the value of user.name, if set).
-
- 3. Use the value of multimailhook.envelopeSender.
-
-multimailhook.MailaddressMap
- (gitolite environment only)
- File to look for a ``From:`` address based on the user doing the
- push. Defaults to unset. See ``multimailhook.from`` for details.
-
-multimailhook.administrator
- The name and/or email address of the administrator of the Git
- repository; used in FOOTER_TEMPLATE. Default is
- multimailhook.envelopesender if it is set; otherwise a generic
- string is used.
-
-multimailhook.emailPrefix
- All emails have this string prepended to their subjects, to aid
- email filtering (though filtering based on the X-Git-* email
- headers is probably more robust). Default is the short name of
- the repository in square brackets; e.g., ``[myrepo]``. Set this
- value to the empty string to suppress the email prefix. You may
- use the placeholder ``%(repo_shortname)s`` for the short name of
- the repository.
-
-multimailhook.emailMaxLines
- The maximum number of lines that should be included in the body of
- a generated email. If not specified, there is no limit. Lines
- beyond the limit are suppressed and counted, and a final line is
- added indicating the number of suppressed lines.
-
-multimailhook.emailMaxLineLength
- The maximum length of a line in the email body. Lines longer than
- this limit are truncated to this length with a trailing ``[...]``
- added to indicate the missing text. The default is 500, because
- (a) diffs with longer lines are probably from binary files, for
- which a diff is useless, and (b) even if a text file has such long
- lines, the diffs are probably unreadable anyway. To disable line
- truncation, set this option to 0.
-
-multimailhook.subjectMaxLength
- The maximum length of the subject line (i.e. the ``oneline`` field
- in templates, not including the prefix). Lines longer than this
- limit are truncated to this length with a trailing ``[...]`` added
- to indicate the missing text. This option The default is to use
- ``multimailhook.emailMaxLineLength``. This option avoids sending
- emails with overly long subject lines, but should not be needed if
- the commit messages follow the Git convention (one short subject
- line, then a blank line, then the message body). To disable line
- truncation, set this option to 0.
-
-multimailhook.maxCommitEmails
- The maximum number of commit emails to send for a given change.
- When the number of patches is larger that this value, only the
- summary refchange email is sent. This can avoid accidental
- mailbombing, for example on an initial push. To disable commit
- emails limit, set this option to 0. The default is 500.
-
-multimailhook.excludeMergeRevisions
- When sending out revision emails, do not consider merge commits (the
- functional equivalent of `rev-list --no-merges`).
- The default is `false` (send merge commit emails).
-
-multimailhook.emailStrictUTF8
- If this boolean option is set to `true`, then the main part of the
- email body is forced to be valid UTF-8. Any characters that are
- not valid UTF-8 are converted to the Unicode replacement
- character, U+FFFD. The default is `true`.
-
- This option is ineffective with Python 3, where non-UTF-8
- characters are unconditionally replaced.
-
-multimailhook.diffOpts
- Options passed to ``git diff-tree`` when generating the summary
- information for ReferenceChange emails. Default is ``--stat
- --summary --find-copies-harder``. Add -p to those options to
- include a unified diff of changes in addition to the usual summary
- output. Shell quoting is allowed; see ``multimailhook.logOpts`` for
- details.
-
-multimailhook.graphOpts
- Options passed to ``git log --graph`` when generating graphs for the
- reference change summary emails (used only if refchangeShowGraph
- is true). The default is '--oneline --decorate'.
-
- Shell quoting is allowed; see logOpts for details.
-
-multimailhook.logOpts
- Options passed to ``git log`` to generate additional info for
- reference change emails (used only if refchangeShowLog is set).
- For example, adding -p will show each commit's complete diff. The
- default is empty.
-
- Shell quoting is allowed; for example, a log format that contains
- spaces can be specified using something like::
-
- git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
-
- If you want to set this by editing your configuration file
- directly, remember that Git requires double-quotes to be escaped
- (see git-config(1) for more information)::
-
- [multimailhook]
- logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
-
-multimailhook.commitLogOpts
- Options passed to ``git log`` to generate additional info for
- revision change emails. For example, adding --ignore-all-spaces
- will suppress whitespace changes. The default options are ``-C
- --stat -p --cc``. Shell quoting is allowed; see
- multimailhook.logOpts for details.
-
-multimailhook.dateSubstitute
- String to use as a substitute for ``Date:`` in the output of ``git
- log`` while formatting commit messages. This is useful to avoid
- emitting a line that can be interpreted by mailers as the start of
- a cited message (Zimbra webmail in particular). Defaults to
- ``CommitDate:``. Set to an empty string or ``none`` to deactivate
- the behavior.
-
-multimailhook.emailDomain
- Domain name appended to the username of the person doing the push
- to convert it into an email address
- (via ``"%s@%s" % (username, emaildomain)``). More complicated
- schemes can be implemented by overriding Environment and
- overriding its get_pusher_email() method.
-
-multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
- Addresses to use in the Reply-To: field for commit emails
- (replyToCommit) and refchange emails (replyToRefchange).
- multimailhook.replyTo is used as default when replyToCommit or
- replyToRefchange is not set. The shortcuts ``pusher`` and
- ``author`` are allowed with the same semantics as for
- ``multimailhook.from``. In addition, the value ``none`` can be
- used to omit the ``Reply-To:`` field.
-
- The default is ``pusher`` for refchange emails, and ``author`` for
- commit emails.
-
-multimailhook.quiet
- Do not output the list of email recipients from the hook
-
-multimailhook.stdout
- For debugging, send emails to stdout rather than to the
- mailer. Equivalent to the --stdout command line option
-
-multimailhook.scanCommitForCc
- If this option is set to true, than recipients from lines in commit body
- that starts with ``CC:`` will be added to CC list.
- Default: false
-
-multimailhook.combineWhenSingleCommit
- If this option is set to true and a single new commit is pushed to
- a branch, combine the summary and commit email messages into a
- single email.
- Default: true
-
-multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
- **Warning:** these options are experimental. They should work, but
- the user-interface is not stable yet (in particular, the option
- names may change). If you want to participate in stabilizing the
- feature, please contact the maintainers and/or send pull-requests.
- If you are happy with the current shape of the feature, please
- report it too.
-
- Regular expressions that can be used to limit refs for which email
- updates will be sent. It is an error to specify both an inclusion
- and an exclusion regex. If a ``refFilterInclusionRegex`` is
- specified, emails will only be sent for refs which match this
- regex. If a ``refFilterExclusionRegex`` regex is specified,
- emails will be sent for all refs except those that match this
- regex (or that match a predefined regex specific to the
- environment, such as "^refs/notes" for most environments and
- "^refs/notes|^refs/changes" for the gerrit environment).
-
- The expressions are matched against the complete refname, and is
- considered to match if any substring matches. For example, to
- filter-out all tags, set ``refFilterExclusionRegex`` to
- ``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
- you set ``refFilterExclusionRegex`` to ``master``, then any ref
- containing ``master`` will be excluded (the ``master`` branch, but
- also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
-
- ``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
- analogous to ``refFilterInclusionRegex`` and
- ``refFilterExclusionRegex`` with one difference: with
- ``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
- introduced by one excluded ref will not be considered as new when
- they reach an included ref. Typically, if you add a branch ``foo``
- to ``refFilterDontSendRegex``, push commits to this branch, and
- later merge branch ``foo`` into ``master``, then the notification
- email for ``master`` will contain a commit email only for the
- merge commit. If you include ``foo`` in
- ``refFilterExclusionRegex``, then at the time of merge, you will
- receive one commit email per commit in the branch.
-
- These variables can be multi-valued, like::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/
- refFilterExclusionRegex = ^refs/heads/master$
-
- You can also provide a whitespace-separated list like::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
-
- Both examples exclude tags and the master branch, and are
- equivalent to::
-
- [multimailhook]
- refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
-
- ``refFilterInclusionRegex`` and ``refFilterExclusionRegex`` are
- strictly stronger than ``refFilterDoSendRegex`` and
- ``refFilterDontSendRegex``. In other words, adding a ref to a
- DoSend/DontSend regex has no effect if it is already excluded by a
- Exclusion/Inclusion regex.
-
-multimailhook.logFile, multimailhook.errorLogFile, multimailhook.debugLogFile
-
- When set, these variable designate path to files where
- git-multimail will log some messages. Normal messages and error
- messages are sent to ``logFile``, and error messages are also sent
- to ``errorLogFile``. Debug messages and all other messages are
- sent to ``debugLogFile``. The recommended way is to set only one
- of these variables, but it is also possible to set several of them
- (part of the information is then duplicated in several log files,
- for example errors are duplicated to all log files).
-
- Relative path are relative to the Git repository where the push is
- done.
-
-multimailhook.verbose
-
- Verbosity level of git-multimail on its standard output. By
- default, show only error and info messages. If set to true, show
- also debug messages.
-
-Email filtering aids
---------------------
-
-All emails include extra headers to enable fine tuned filtering and
-give information for debugging. All emails include the headers
-``X-Git-Host``, ``X-Git-Repo``, ``X-Git-Refname``, and ``X-Git-Reftype``.
-ReferenceChange emails also include headers ``X-Git-Oldrev`` and ``X-Git-Newrev``;
-Revision emails also include header ``X-Git-Rev``.
-
-
-Customizing email contents
---------------------------
-
-git-multimail mostly generates emails by expanding templates. The
-templates can be customized. To avoid the need to edit
-``git_multimail.py`` directly, the preferred way to change the templates
-is to write a separate Python script that imports ``git_multimail.py`` as
-a module, then replaces the templates in place. See the provided
-post-receive script for an example of how this is done.
-
-
-Customizing git-multimail for your environment
-----------------------------------------------
-
-git-multimail is mostly customized via an "environment" that describes
-the local environment in which Git is running. Two types of
-environment are built in:
-
-GenericEnvironment
- a stand-alone Git repository.
-
-GitoliteEnvironment
- a Git repository that is managed by gitolite_. For such
- repositories, the identity of the pusher is read from
- environment variable $GL_USER, the name of the repository is read
- from $GL_REPO (if it is not overridden by multimailhook.reponame),
- and the From: header value is optionally read from gitolite.conf
- (see multimailhook.from).
-
-By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
-$GL_REPO are set, and otherwise assumes GenericEnvironment.
-Alternatively, you can choose one of these two environments explicitly
-by setting a ``multimailhook.environment`` config setting (which can
-have the value `generic` or `gitolite`) or by passing an --environment
-option to the script.
-
-If you need to customize the script in ways that are not supported by
-the existing environments, you can define your own environment class
-class using arbitrary Python code. To do so, you need to import
-``git_multimail.py`` as a Python module, as demonstrated by the example
-post-receive script. Then implement your environment class; it should
-usually inherit from one of the existing Environment classes and
-possibly one or more of the EnvironmentMixin classes. Then set the
-``environment`` variable to an instance of your own environment class
-and pass it to ``run_as_post_receive_hook()``.
-
-The standard environment classes, GenericEnvironment and
-GitoliteEnvironment, are in fact themselves put together out of a
-number of mixin classes, each of which handles one aspect of the
-customization. For the finest control over your configuration, you
-can specify exactly which mixin classes your own environment class
-should inherit from, and override individual methods (or even add your
-own mixin classes) to implement entirely new behaviors. If you
-implement any mixins that might be useful to other people, please
-consider sharing them with the community!
-
-
-Getting involved
-----------------
-
-Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
-contribute to git-multimail.
-
-
-Footnotes
----------
-
-.. [1] Because of the way information is passed to update hooks, the
- script's method of determining whether a commit has already
- been seen does not work when it is used as an ``update`` script.
- In particular, no notification email will be generated for a
- new commit that is added to multiple references in the same
- push. A workaround is to use --force-send to force sending the
- emails.
-
-.. _gitolite: https://github.com/sitaramc/gitolite
diff --git a/contrib/hooks/multimail/doc/customizing-emails.rst b/contrib/hooks/multimail/doc/customizing-emails.rst
deleted file mode 100644
index 3f5b67f..0000000
--- a/contrib/hooks/multimail/doc/customizing-emails.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-Customizing the content and formatting of emails
-================================================
-
-Overloading template strings
-----------------------------
-
-The content of emails is generated based on template strings defined
-in ``git_multimail.py``. You can customize these template strings
-without changing the script itself, by defining a Python wrapper
-around it. The python wrapper should ``import git_multimail`` and then
-override the ``git_multimail.*`` strings like this::
-
- import sys # needed for sys.argv
-
- # Import and customize git_multimail:
- import git_multimail
- git_multimail.REVISION_INTRO_TEMPLATE = """..."""
- git_multimail.COMBINED_INTRO_TEMPLATE = git_multimail.REVISION_INTRO_TEMPLATE
-
- # start git_multimail itself:
- git_multimail.main(sys.argv[1:])
-
-The template strings can use any value already used in the existing
-templates (read the source code).
-
-Using HTML in template strings
-------------------------------
-
-If ``multimailhook.commitEmailFormat`` is set to HTML, then
-git-multimail will generate HTML emails for commit notifications. The
-log and diff will be formatted automatically by git-multimail. By
-default, any HTML special character in the templates will be escaped.
-
-To use HTML formatting in the introduction of the email, set
-``multimailhook.htmlInIntro`` to ``true``. Then, the template can
-contain any HTML tags, that will be sent as-is in the email. For
-example, to add some formatting and a link to the online commit, use
-a format like::
-
- git_multimail.REVISION_INTRO_TEMPLATE = """\
- <span style="color:#808080">This is an automated email from the git hooks/post-receive script.</span><br /><br />
-
- <strong>%(pusher)s</strong> pushed a commit to %(refname_type)s %(short_refname)s
- in repository %(repo_shortname)s.<br />
-
- <a href="https://github.com/git-multimail/git-multimail/commit/%(newrev)s">View on GitHub</a>.
- """
-
-Note that the values expanded from ``%(variable)s`` in the format
-strings will still be escaped.
-
-For a less flexible but easier to set up way to add a link to commit
-emails, see ``multimailhook.commitBrowseURL``.
-
-Similarly, one can set ``multimailhook.htmlInFooter`` and override any
-of the ``*_FOOTER*`` template strings.
diff --git a/contrib/hooks/multimail/doc/gerrit.rst b/contrib/hooks/multimail/doc/gerrit.rst
deleted file mode 100644
index 8011d05..0000000
--- a/contrib/hooks/multimail/doc/gerrit.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-Setting up git-multimail on Gerrit
-==================================
-
-Gerrit has its own email-sending system, but you may prefer using
-``git-multimail`` instead. It supports Gerrit natively as a Gerrit
-``ref-updated`` hook (Warning: `Gerrit hooks
-<https://gerrit-review.googlesource.com/Documentation/config-hooks.html>`__
-are distinct from Git hooks). Setting up ``git-multimail`` on a Gerrit
-installation can be done following the instructions below.
-
-The explanations show an easy way to set up ``git-multimail``,
-but leave ``git-multimail`` installed and unconfigured for a while. If
-you run Gerrit on a production server, it is advised that you
-execute the step "Set up the hook" last to avoid confusing your users
-in the meantime.
-
-Set up the hook
----------------
-
-Create a directory ``$site_path/hooks/`` if it does not exist (if you
-don't know what ``$site_path`` is, run ``gerrit.sh status`` and look
-for a ``GERRIT_SITE`` line). Either copy ``git_multimail.py`` to
-``$site_path/hooks/ref-updated`` or create a wrapper script like
-this::
-
- #! /bin/sh
- exec /path/to/git_multimail.py "$@"
-
-In both cases, make sure the file is named exactly
-``$site_path/hooks/ref-updated`` and is executable.
-
-(Alternatively, you may configure the ``[hooks]`` section of
-gerrit.config)
-
-Configuration
--------------
-
-Log on the gerrit server and edit ``$site_path/git/$project/config``
-to configure ``git-multimail``.
-
-Troubleshooting
----------------
-
-Warning: this will disable ``git-multimail`` during the debug, and
-could confuse your users. Don't run on a production server.
-
-To debug configuration issues with ``git-multimail``, you can add the
-``--stdout`` option when calling ``git_multimail.py`` like this::
-
- #!/bin/sh
- exec /path/to/git-multimail/git-multimail/git_multimail.py \
- --stdout "$@" >> /tmp/log.txt
-
-and try pushing from a test repository. You should see the source of
-the email that would have been sent in the output of ``git push`` in
-the file ``/tmp/log.txt``.
diff --git a/contrib/hooks/multimail/doc/gitolite.rst b/contrib/hooks/multimail/doc/gitolite.rst
deleted file mode 100644
index 5054833..0000000
--- a/contrib/hooks/multimail/doc/gitolite.rst
+++ /dev/null
@@ -1,118 +0,0 @@
-Setting up git-multimail on gitolite
-====================================
-
-``git-multimail`` supports gitolite 3 natively.
-The explanations below show an easy way to set up ``git-multimail``,
-but leave ``git-multimail`` installed and unconfigured for a while. If
-you run gitolite on a production server, it is advised that you
-execute the step "Set up the hook" last to avoid confusing your users
-in the meantime.
-
-Set up the hook
----------------
-
-Log in as your gitolite user.
-
-Create a file ``.gitolite/hooks/common/post-receive`` on your gitolite
-account containing (adapt the path, obviously)::
-
- #!/bin/sh
- exec /path/to/git-multimail/git-multimail/git_multimail.py "$@"
-
-Make sure it's executable (``chmod +x``). Record the hook in
-gitolite::
-
- gitolite setup
-
-Configuration
--------------
-
-First, you have to allow the admin to set Git configuration variables.
-
-As gitolite user, edit the line containing ``GIT_CONFIG_KEYS`` in file
-``.gitolite.rc``, to make it look like::
-
- GIT_CONFIG_KEYS => 'multimailhook\..*',
-
-You can now log out and return to your normal user.
-
-In the ``gitolite-admin`` clone, edit the file ``conf/gitolite.conf``
-and add::
-
- repo @all
- # Not strictly needed as git_multimail.py will chose gitolite if
- # $GL_USER is set.
- config multimailhook.environment = gitolite
- config multimailhook.mailingList = # Where emails should be sent
- config multimailhook.from = # From address to use
-
-Note that by default, gitolite forbids ``<`` and ``>`` in variable
-values (for security/paranoia reasons, see
-`compensating for UNSAFE_PATT
-<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__
-in gitolite's documentation for explanations and a way to disable
-this). As a consequence, you will not be able to use ``First Last
-<First.Last@example.com>`` as recipient email, but specifying
-``First.Last@example.com`` alone works.
-
-Obviously, you can customize all parameters on a per-repository basis by
-adding these ``config multimailhook.*`` lines in the section
-corresponding to a repository or set of repositories.
-
-To activate ``git-multimail`` on a per-repository basis, do not set
-``multimailhook.mailingList`` in the ``@all`` section and set it only
-for repositories for which you want ``git-multimail``.
-
-Alternatively, you can set up the ``From:`` field on a per-user basis
-by adding a ``BEGIN USER EMAILS``/``END USER EMAILS`` section (see
-``../README``).
-
-Specificities of Gitolite for Configuration
--------------------------------------------
-
-Empty configuration variables
-.............................
-
-With gitolite, the syntax ``config multimailhook.commitList = ""``
-unsets the variable instead of setting it to an empty string (see
-`here
-<http://gitolite.com/gitolite/git-config.html#an-important-warning-about-deleting-a-config-line>`__).
-As a result, there is no way to set a variable to the empty string.
-In all most places where an empty value is required, git-multimail
-now allows to specify special ``"none"`` value (case-sensitive) to
-mean the same.
-
-Alternatively, one can use ``" "`` (a single space) instead of ``""``.
-In most cases (in particular ``multimailhook.*List`` variables), this
-will be equivalent to an empty string.
-
-If you have a use-case where ``"none"`` is not an acceptable value and
-you need ``" "`` or ``""`` instead, please report it as a bug to
-git-multimail.
-
-Allowing Regular Expressions in Configuration
-.............................................
-
-gitolite has a mechanism to prevent unsafe configuration variable
-values, which prevent characters like ``|`` commonly used in regular
-expressions. If you do not need the safety feature of gitolite and
-need to use regular expressions in your configuration (e.g. for
-``multimailhook.refFilter*`` variables), set
-`UNSAFE_PATT
-<http://gitolite.com/gitolite/git-config.html#unsafe-patt>`__ to a
-less restrictive value.
-
-Troubleshooting
----------------
-
-Warning: this will disable ``git-multimail`` during the debug, and
-could confuse your users. Don't run on a production server.
-
-To debug configuration issues with ``git-multimail``, you can add the
-``--stdout`` option when calling ``git_multimail.py`` like this::
-
- #!/bin/sh
- exec /path/to/git-multimail/git-multimail/git_multimail.py --stdout "$@"
-
-and try pushing from a test repository. You should see the source of
-the email that would have been sent in the output of ``git push``.
diff --git a/contrib/hooks/multimail/doc/troubleshooting.rst b/contrib/hooks/multimail/doc/troubleshooting.rst
deleted file mode 100644
index 651b509..0000000
--- a/contrib/hooks/multimail/doc/troubleshooting.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-Troubleshooting issues with git-multimail: a FAQ
-================================================
-
-How to check that git-multimail is properly set up?
----------------------------------------------------
-
-Since version 1.4.0, git-multimail allows a simple self-checking of
-its configuration: run it with the environment variable
-``GIT_MULTIMAIL_CHECK_SETUP`` set to a non-empty string. You should
-get something like this::
-
- $ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
- Environment values:
- administrator : 'the administrator of this repository'
- charset : 'utf-8'
- emailprefix : '[git-multimail] '
- fqdn : 'anie'
- projectdesc : 'UNNAMED PROJECT'
- pusher : 'moy'
- repo_path : '/home/moy/dev/git-multimail'
- repo_shortname : 'git-multimail'
-
- Now, checking that git-multimail's standard input is properly set ...
- Please type some text and then press Return
- foo
- You have just entered:
- foo
- git-multimail seems properly set up.
-
-If you forgot to set an important variable, you may get instead::
-
- $ GIT_MULTIMAIL_CHECK_SETUP=true /home/moy/dev/git-multimail/git-multimail/git_multimail.py
- No email recipients configured!
-
-Do not set ``$GIT_MULTIMAIL_CHECK_SETUP`` other than for testing your
-configuration: it would disable the hook completely.
-
-Git is not using the right address in the From/To/Reply-To field
-----------------------------------------------------------------
-
-First, make sure that git-multimail actually uses what you think it is
-using. A lot happens to your email (especially when posting to a
-mailing-list) between the time `git_multimail.py` sends it and the
-time it reaches your inbox.
-
-A simple test (to do on a test repository, do not use in production as
-it would disable email sending): change your post-receive hook to call
-`git_multimail.py` with the `--stdout` option, and try to push to the
-repository. You should see something like::
-
- Counting objects: 3, done.
- Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
- Total 3 (delta 0), reused 0 (delta 0)
- remote: Sending notification emails to: foo.bar@example.com
- remote: ===========================================================================
- remote: Date: Mon, 25 Apr 2016 18:39:59 +0200
- remote: To: foo.bar@example.com
- remote: Subject: [git] branch master updated: foo
- remote: MIME-Version: 1.0
- remote: Content-Type: text/plain; charset=utf-8
- remote: Content-Transfer-Encoding: 8bit
- remote: Message-ID: <20160425163959.2311.20498@anie>
- remote: From: Auth Or <Foo.Bar@example.com>
- remote: Reply-To: Auth Or <Foo.Bar@example.com>
- remote: X-Git-Host: example
- ...
- remote: --
- remote: To stop receiving notification emails like this one, please contact
- remote: the administrator of this repository.
- remote: ===========================================================================
- To /path/to/repo
- 6278f04..e173f20 master -> master
-
-Note: this does not include the sender (Return-Path: header), as it is
-not part of the message content but passed to the mailer. Some mailer
-show the ``Sender:`` field instead of the ``From:`` field (for
-example, Zimbra Webmail shows ``From: <sender-field> on behalf of
-<from-field>``).
diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py
deleted file mode 100755
index f563be8..0000000
--- a/contrib/hooks/multimail/git_multimail.py
+++ /dev/null
@@ -1,4346 +0,0 @@
-#! /usr/bin/env python
-
-__version__ = '1.5.0'
-
-# Copyright (c) 2015-2016 Matthieu Moy and others
-# Copyright (c) 2012-2014 Michael Haggerty and others
-# Derived from contrib/hooks/post-receive-email, which is
-# Copyright (c) 2007 Andy Parkins
-# and also includes contributions by other authors.
-#
-# This file is part of git-multimail.
-#
-# git-multimail is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License version
-# 2 as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-# <http://www.gnu.org/licenses/>.
-
-"""Generate notification emails for pushes to a git repository.
-
-This hook sends emails describing changes introduced by pushes to a
-git repository. For each reference that was changed, it emits one
-ReferenceChange email summarizing how the reference was changed,
-followed by one Revision email for each new commit that was introduced
-by the reference change.
-
-Each commit is announced in exactly one Revision email. If the same
-commit is merged into another branch in the same or a later push, then
-the ReferenceChange email will list the commit's SHA1 and its one-line
-summary, but no new Revision email will be generated.
-
-This script is designed to be used as a "post-receive" hook in a git
-repository (see githooks(5)). It can also be used as an "update"
-script, but this usage is not completely reliable and is deprecated.
-
-To help with debugging, this script accepts a --stdout option, which
-causes the emails to be written to standard output rather than sent
-using sendmail.
-
-See the accompanying README file for the complete documentation.
-
-"""
-
-import sys
-import os
-import re
-import bisect
-import socket
-import subprocess
-import shlex
-import optparse
-import logging
-import smtplib
-try:
- import ssl
-except ImportError:
- # Python < 2.6 do not have ssl, but that's OK if we don't use it.
- pass
-import time
-
-import uuid
-import base64
-
-PYTHON3 = sys.version_info >= (3, 0)
-
-if sys.version_info <= (2, 5):
- def all(iterable):
- for element in iterable:
- if not element:
- return False
- return True
-
-
-def is_ascii(s):
- return all(ord(c) < 128 and ord(c) > 0 for c in s)
-
-
-if PYTHON3:
- def is_string(s):
- return isinstance(s, str)
-
- def str_to_bytes(s):
- return s.encode(ENCODING)
-
- def bytes_to_str(s, errors='strict'):
- return s.decode(ENCODING, errors)
-
- unicode = str
-
- def write_str(f, msg):
- # Try outputting with the default encoding. If it fails,
- # try UTF-8.
- try:
- f.buffer.write(msg.encode(sys.getdefaultencoding()))
- except UnicodeEncodeError:
- f.buffer.write(msg.encode(ENCODING))
-
- def read_line(f):
- # Try reading with the default encoding. If it fails,
- # try UTF-8.
- out = f.buffer.readline()
- try:
- return out.decode(sys.getdefaultencoding())
- except UnicodeEncodeError:
- return out.decode(ENCODING)
-
- import html
-
- def html_escape(s):
- return html.escape(s)
-
-else:
- def is_string(s):
- try:
- return isinstance(s, basestring)
- except NameError: # Silence Pyflakes warning
- raise
-
- def str_to_bytes(s):
- return s
-
- def bytes_to_str(s, errors='strict'):
- return s
-
- def write_str(f, msg):
- f.write(msg)
-
- def read_line(f):
- return f.readline()
-
- def next(it):
- return it.next()
-
- import cgi
-
- def html_escape(s):
- return cgi.escape(s, True)
-
-try:
- from email.charset import Charset
- from email.utils import make_msgid
- from email.utils import getaddresses
- from email.utils import formataddr
- from email.utils import formatdate
- from email.header import Header
-except ImportError:
- # Prior to Python 2.5, the email module used different names:
- from email.Charset import Charset
- from email.Utils import make_msgid
- from email.Utils import getaddresses
- from email.Utils import formataddr
- from email.Utils import formatdate
- from email.Header import Header
-
-
-DEBUG = False
-
-ZEROS = '0' * 40
-LOGBEGIN = '- Log -----------------------------------------------------------------\n'
-LOGEND = '-----------------------------------------------------------------------\n'
-
-ADDR_HEADERS = set(['from', 'to', 'cc', 'bcc', 'reply-to', 'sender'])
-
-# It is assumed in many places that the encoding is uniformly UTF-8,
-# so changing these constants is unsupported. But define them here
-# anyway, to make it easier to find (at least most of) the places
-# where the encoding is important.
-(ENCODING, CHARSET) = ('UTF-8', 'utf-8')
-
-
-REF_CREATED_SUBJECT_TEMPLATE = (
- '%(emailprefix)s%(refname_type)s %(short_refname)s created'
- ' (now %(newrev_short)s)'
- )
-REF_UPDATED_SUBJECT_TEMPLATE = (
- '%(emailprefix)s%(refname_type)s %(short_refname)s updated'
- ' (%(oldrev_short)s -> %(newrev_short)s)'
- )
-REF_DELETED_SUBJECT_TEMPLATE = (
- '%(emailprefix)s%(refname_type)s %(short_refname)s deleted'
- ' (was %(oldrev_short)s)'
- )
-
-COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE = (
- '%(emailprefix)s%(refname_type)s %(short_refname)s updated: %(oneline)s'
- )
-
-REFCHANGE_HEADER_TEMPLATE = """\
-Date: %(send_date)s
-To: %(recipients)s
-Subject: %(subject)s
-MIME-Version: 1.0
-Content-Type: text/%(contenttype)s; charset=%(charset)s
-Content-Transfer-Encoding: 8bit
-Message-ID: %(msgid)s
-From: %(fromaddr)s
-Reply-To: %(reply_to)s
-Thread-Index: %(thread_index)s
-X-Git-Host: %(fqdn)s
-X-Git-Repo: %(repo_shortname)s
-X-Git-Refname: %(refname)s
-X-Git-Reftype: %(refname_type)s
-X-Git-Oldrev: %(oldrev)s
-X-Git-Newrev: %(newrev)s
-X-Git-NotificationType: ref_changed
-X-Git-Multimail-Version: %(multimail_version)s
-Auto-Submitted: auto-generated
-"""
-
-REFCHANGE_INTRO_TEMPLATE = """\
-This is an automated email from the git hooks/post-receive script.
-
-%(pusher)s pushed a change to %(refname_type)s %(short_refname)s
-in repository %(repo_shortname)s.
-
-"""
-
-
-FOOTER_TEMPLATE = """\
-
--- \n\
-To stop receiving notification emails like this one, please contact
-%(administrator)s.
-"""
-
-
-REWIND_ONLY_TEMPLATE = """\
-This update removed existing revisions from the reference, leaving the
-reference pointing at a previous point in the repository history.
-
- * -- * -- N %(refname)s (%(newrev_short)s)
- \\
- O -- O -- O (%(oldrev_short)s)
-
-Any revisions marked "omit" are not gone; other references still
-refer to them. Any revisions marked "discard" are gone forever.
-"""
-
-
-NON_FF_TEMPLATE = """\
-This update added new revisions after undoing existing revisions.
-That is to say, some revisions that were in the old version of the
-%(refname_type)s are not in the new version. This situation occurs
-when a user --force pushes a change and generates a repository
-containing something like this:
-
- * -- * -- B -- O -- O -- O (%(oldrev_short)s)
- \\
- N -- N -- N %(refname)s (%(newrev_short)s)
-
-You should already have received notification emails for all of the O
-revisions, and so the following emails describe only the N revisions
-from the common base, B.
-
-Any revisions marked "omit" are not gone; other references still
-refer to them. Any revisions marked "discard" are gone forever.
-"""
-
-
-NO_NEW_REVISIONS_TEMPLATE = """\
-No new revisions were added by this update.
-"""
-
-
-DISCARDED_REVISIONS_TEMPLATE = """\
-This change permanently discards the following revisions:
-"""
-
-
-NO_DISCARDED_REVISIONS_TEMPLATE = """\
-The revisions that were on this %(refname_type)s are still contained in
-other references; therefore, this change does not discard any commits
-from the repository.
-"""
-
-
-NEW_REVISIONS_TEMPLATE = """\
-The %(tot)s revisions listed above as "new" are entirely new to this
-repository and will be described in separate emails. The revisions
-listed as "add" were already present in the repository and have only
-been added to this reference.
-
-"""
-
-
-TAG_CREATED_TEMPLATE = """\
- at %(newrev_short)-8s (%(newrev_type)s)
-"""
-
-
-TAG_UPDATED_TEMPLATE = """\
-*** WARNING: tag %(short_refname)s was modified! ***
-
- from %(oldrev_short)-8s (%(oldrev_type)s)
- to %(newrev_short)-8s (%(newrev_type)s)
-"""
-
-
-TAG_DELETED_TEMPLATE = """\
-*** WARNING: tag %(short_refname)s was deleted! ***
-
-"""
-
-
-# The template used in summary tables. It looks best if this uses the
-# same alignment as TAG_CREATED_TEMPLATE and TAG_UPDATED_TEMPLATE.
-BRIEF_SUMMARY_TEMPLATE = """\
-%(action)8s %(rev_short)-8s %(text)s
-"""
-
-
-NON_COMMIT_UPDATE_TEMPLATE = """\
-This is an unusual reference change because the reference did not
-refer to a commit either before or after the change. We do not know
-how to provide full information about this reference change.
-"""
-
-
-REVISION_HEADER_TEMPLATE = """\
-Date: %(send_date)s
-To: %(recipients)s
-Cc: %(cc_recipients)s
-Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
-MIME-Version: 1.0
-Content-Type: text/%(contenttype)s; charset=%(charset)s
-Content-Transfer-Encoding: 8bit
-From: %(fromaddr)s
-Reply-To: %(reply_to)s
-In-Reply-To: %(reply_to_msgid)s
-References: %(reply_to_msgid)s
-Thread-Index: %(thread_index)s
-X-Git-Host: %(fqdn)s
-X-Git-Repo: %(repo_shortname)s
-X-Git-Refname: %(refname)s
-X-Git-Reftype: %(refname_type)s
-X-Git-Rev: %(rev)s
-X-Git-NotificationType: diff
-X-Git-Multimail-Version: %(multimail_version)s
-Auto-Submitted: auto-generated
-"""
-
-REVISION_INTRO_TEMPLATE = """\
-This is an automated email from the git hooks/post-receive script.
-
-%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
-in repository %(repo_shortname)s.
-
-"""
-
-LINK_TEXT_TEMPLATE = """\
-View the commit online:
-%(browse_url)s
-
-"""
-
-LINK_HTML_TEMPLATE = """\
-<p><a href="%(browse_url)s">View the commit online</a>.</p>
-"""
-
-
-REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
-
-
-# Combined, meaning refchange+revision email (for single-commit additions)
-COMBINED_HEADER_TEMPLATE = """\
-Date: %(send_date)s
-To: %(recipients)s
-Subject: %(subject)s
-MIME-Version: 1.0
-Content-Type: text/%(contenttype)s; charset=%(charset)s
-Content-Transfer-Encoding: 8bit
-Message-ID: %(msgid)s
-From: %(fromaddr)s
-Reply-To: %(reply_to)s
-X-Git-Host: %(fqdn)s
-X-Git-Repo: %(repo_shortname)s
-X-Git-Refname: %(refname)s
-X-Git-Reftype: %(refname_type)s
-X-Git-Oldrev: %(oldrev)s
-X-Git-Newrev: %(newrev)s
-X-Git-Rev: %(rev)s
-X-Git-NotificationType: ref_changed_plus_diff
-X-Git-Multimail-Version: %(multimail_version)s
-Auto-Submitted: auto-generated
-"""
-
-COMBINED_INTRO_TEMPLATE = """\
-This is an automated email from the git hooks/post-receive script.
-
-%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
-in repository %(repo_shortname)s.
-
-"""
-
-COMBINED_FOOTER_TEMPLATE = FOOTER_TEMPLATE
-
-
-class CommandError(Exception):
- def __init__(self, cmd, retcode):
- self.cmd = cmd
- self.retcode = retcode
- Exception.__init__(
- self,
- 'Command "%s" failed with retcode %s' % (' '.join(cmd), retcode,)
- )
-
-
-class ConfigurationException(Exception):
- pass
-
-
-# The "git" program (this could be changed to include a full path):
-GIT_EXECUTABLE = 'git'
-
-
-# How "git" should be invoked (including global arguments), as a list
-# of words. This variable is usually initialized automatically by
-# read_git_output() via choose_git_command(), but if a value is set
-# here then it will be used unconditionally.
-GIT_CMD = None
-
-
-def choose_git_command():
- """Decide how to invoke git, and record the choice in GIT_CMD."""
-
- global GIT_CMD
-
- if GIT_CMD is None:
- try:
- # Check to see whether the "-c" option is accepted (it was
- # only added in Git 1.7.2). We don't actually use the
- # output of "git --version", though if we needed more
- # specific version information this would be the place to
- # do it.
- cmd = [GIT_EXECUTABLE, '-c', 'foo.bar=baz', '--version']
- read_output(cmd)
- GIT_CMD = [GIT_EXECUTABLE, '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)]
- except CommandError:
- GIT_CMD = [GIT_EXECUTABLE]
-
-
-def read_git_output(args, input=None, keepends=False, **kw):
- """Read the output of a Git command."""
-
- if GIT_CMD is None:
- choose_git_command()
-
- return read_output(GIT_CMD + args, input=input, keepends=keepends, **kw)
-
-
-def read_output(cmd, input=None, keepends=False, **kw):
- if input:
- stdin = subprocess.PIPE
- input = str_to_bytes(input)
- else:
- stdin = None
- errors = 'strict'
- if 'errors' in kw:
- errors = kw['errors']
- del kw['errors']
- p = subprocess.Popen(
- tuple(str_to_bytes(w) for w in cmd),
- stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
- )
- (out, err) = p.communicate(input)
- out = bytes_to_str(out, errors=errors)
- retcode = p.wait()
- if retcode:
- raise CommandError(cmd, retcode)
- if not keepends:
- out = out.rstrip('\n\r')
- return out
-
-
-def read_git_lines(args, keepends=False, **kw):
- """Return the lines output by Git command.
-
- Return as single lines, with newlines stripped off."""
-
- return read_git_output(args, keepends=True, **kw).splitlines(keepends)
-
-
-def git_rev_list_ish(cmd, spec, args=None, **kw):
- """Common functionality for invoking a 'git rev-list'-like command.
-
- Parameters:
- * cmd is the Git command to run, e.g., 'rev-list' or 'log'.
- * spec is a list of revision arguments to pass to the named
- command. If None, this function returns an empty list.
- * args is a list of extra arguments passed to the named command.
- * All other keyword arguments (if any) are passed to the
- underlying read_git_lines() function.
-
- Return the output of the Git command in the form of a list, one
- entry per output line.
- """
- if spec is None:
- return []
- if args is None:
- args = []
- args = [cmd, '--stdin'] + args
- spec_stdin = ''.join(s + '\n' for s in spec)
- return read_git_lines(args, input=spec_stdin, **kw)
-
-
-def git_rev_list(spec, **kw):
- """Run 'git rev-list' with the given list of revision arguments.
-
- See git_rev_list_ish() for parameter and return value
- documentation.
- """
- return git_rev_list_ish('rev-list', spec, **kw)
-
-
-def git_log(spec, **kw):
- """Run 'git log' with the given list of revision arguments.
-
- See git_rev_list_ish() for parameter and return value
- documentation.
- """
- return git_rev_list_ish('log', spec, **kw)
-
-
-def header_encode(text, header_name=None):
- """Encode and line-wrap the value of an email header field."""
-
- # Convert to unicode, if required.
- if not isinstance(text, unicode):
- text = unicode(text, 'utf-8')
-
- if is_ascii(text):
- charset = 'ascii'
- else:
- charset = 'utf-8'
-
- return Header(text, header_name=header_name, charset=Charset(charset)).encode()
-
-
-def addr_header_encode(text, header_name=None):
- """Encode and line-wrap the value of an email header field containing
- email addresses."""
-
- # Convert to unicode, if required.
- if not isinstance(text, unicode):
- text = unicode(text, 'utf-8')
-
- text = ', '.join(
- formataddr((header_encode(name), emailaddr))
- for name, emailaddr in getaddresses([text])
- )
-
- if is_ascii(text):
- charset = 'ascii'
- else:
- charset = 'utf-8'
-
- return Header(text, header_name=header_name, charset=Charset(charset)).encode()
-
-
-class Config(object):
- def __init__(self, section, git_config=None):
- """Represent a section of the git configuration.
-
- If git_config is specified, it is passed to "git config" in
- the GIT_CONFIG environment variable, meaning that "git config"
- will read the specified path rather than the Git default
- config paths."""
-
- self.section = section
- if git_config:
- self.env = os.environ.copy()
- self.env['GIT_CONFIG'] = git_config
- else:
- self.env = None
-
- @staticmethod
- def _split(s):
- """Split NUL-terminated values."""
-
- words = s.split('\0')
- assert words[-1] == ''
- return words[:-1]
-
- @staticmethod
- def add_config_parameters(c):
- """Add configuration parameters to Git.
-
- c is either an str or a list of str, each element being of the
- form 'var=val' or 'var', with the same syntax and meaning as
- the argument of 'git -c var=val'.
- """
- if isinstance(c, str):
- c = (c,)
- parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
- if parameters:
- parameters += ' '
- # git expects GIT_CONFIG_PARAMETERS to be of the form
- # "'name1=value1' 'name2=value2' 'name3=value3'"
- # including everything inside the double quotes (but not the double
- # quotes themselves). Spacing is critical. Also, if a value contains
- # a literal single quote that quote must be represented using the
- # four character sequence: '\''
- parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c)
- os.environ['GIT_CONFIG_PARAMETERS'] = parameters
-
- def get(self, name, default=None):
- try:
- values = self._split(read_git_output(
- ['config', '--get', '--null', '%s.%s' % (self.section, name)],
- env=self.env, keepends=True,
- ))
- assert len(values) == 1
- return values[0]
- except CommandError:
- return default
-
- def get_bool(self, name, default=None):
- try:
- value = read_git_output(
- ['config', '--get', '--bool', '%s.%s' % (self.section, name)],
- env=self.env,
- )
- except CommandError:
- return default
- return value == 'true'
-
- def get_all(self, name, default=None):
- """Read a (possibly multivalued) setting from the configuration.
-
- Return the result as a list of values, or default if the name
- is unset."""
-
- try:
- return self._split(read_git_output(
- ['config', '--get-all', '--null', '%s.%s' % (self.section, name)],
- env=self.env, keepends=True,
- ))
- except CommandError:
- t, e, traceback = sys.exc_info()
- if e.retcode == 1:
- # "the section or key is invalid"; i.e., there is no
- # value for the specified key.
- return default
- else:
- raise
-
- def set(self, name, value):
- read_git_output(
- ['config', '%s.%s' % (self.section, name), value],
- env=self.env,
- )
-
- def add(self, name, value):
- read_git_output(
- ['config', '--add', '%s.%s' % (self.section, name), value],
- env=self.env,
- )
-
- def __contains__(self, name):
- return self.get_all(name, default=None) is not None
-
- # We don't use this method anymore internally, but keep it here in
- # case somebody is calling it from their own code:
- def has_key(self, name):
- return name in self
-
- def unset_all(self, name):
- try:
- read_git_output(
- ['config', '--unset-all', '%s.%s' % (self.section, name)],
- env=self.env,
- )
- except CommandError:
- t, e, traceback = sys.exc_info()
- if e.retcode == 5:
- # The name doesn't exist, which is what we wanted anyway...
- pass
- else:
- raise
-
- def set_recipients(self, name, value):
- self.unset_all(name)
- for pair in getaddresses([value]):
- self.add(name, formataddr(pair))
-
-
-def generate_summaries(*log_args):
- """Generate a brief summary for each revision requested.
-
- log_args are strings that will be passed directly to "git log" as
- revision selectors. Iterate over (sha1_short, subject) for each
- commit specified by log_args (subject is the first line of the
- commit message as a string without EOLs)."""
-
- cmd = [
- 'log', '--abbrev', '--format=%h %s',
- ] + list(log_args) + ['--']
- for line in read_git_lines(cmd):
- yield tuple(line.split(' ', 1))
-
-
-def limit_lines(lines, max_lines):
- for (index, line) in enumerate(lines):
- if index < max_lines:
- yield line
-
- if index >= max_lines:
- yield '... %d lines suppressed ...\n' % (index + 1 - max_lines,)
-
-
-def limit_linelength(lines, max_linelength):
- for line in lines:
- # Don't forget that lines always include a trailing newline.
- if len(line) > max_linelength + 1:
- line = line[:max_linelength - 7] + ' [...]\n'
- yield line
-
-
-class CommitSet(object):
- """A (constant) set of object names.
-
- The set should be initialized with full SHA1 object names. The
- __contains__() method returns True iff its argument is an
- abbreviation of any the names in the set."""
-
- def __init__(self, names):
- self._names = sorted(names)
-
- def __len__(self):
- return len(self._names)
-
- def __contains__(self, sha1_abbrev):
- """Return True iff this set contains sha1_abbrev (which might be abbreviated)."""
-
- i = bisect.bisect_left(self._names, sha1_abbrev)
- return i < len(self) and self._names[i].startswith(sha1_abbrev)
-
-
-class GitObject(object):
- def __init__(self, sha1, type=None):
- if sha1 == ZEROS:
- self.sha1 = self.type = self.commit_sha1 = None
- else:
- self.sha1 = sha1
- self.type = type or read_git_output(['cat-file', '-t', self.sha1])
-
- if self.type == 'commit':
- self.commit_sha1 = self.sha1
- elif self.type == 'tag':
- try:
- self.commit_sha1 = read_git_output(
- ['rev-parse', '--verify', '%s^0' % (self.sha1,)]
- )
- except CommandError:
- # Cannot deref tag to determine commit_sha1
- self.commit_sha1 = None
- else:
- self.commit_sha1 = None
-
- self.short = read_git_output(['rev-parse', '--short', sha1])
-
- def get_summary(self):
- """Return (sha1_short, subject) for this commit."""
-
- if not self.sha1:
- raise ValueError('Empty commit has no summary')
-
- return next(iter(generate_summaries('--no-walk', self.sha1)))
-
- def __eq__(self, other):
- return isinstance(other, GitObject) and self.sha1 == other.sha1
-
- def __ne__(self, other):
- return not self == other
-
- def __hash__(self):
- return hash(self.sha1)
-
- def __nonzero__(self):
- return bool(self.sha1)
-
- def __bool__(self):
- """Python 2 backward compatibility"""
- return self.__nonzero__()
-
- def __str__(self):
- return self.sha1 or ZEROS
-
-
-class Change(object):
- """A Change that has been made to the Git repository.
-
- Abstract class from which both Revisions and ReferenceChanges are
- derived. A Change knows how to generate a notification email
- describing itself."""
-
- def __init__(self, environment):
- self.environment = environment
- self._values = None
- self._contains_html_diff = False
-
- def _contains_diff(self):
- # We do contain a diff, should it be rendered in HTML?
- if self.environment.commit_email_format == "html":
- self._contains_html_diff = True
-
- def _compute_values(self):
- """Return a dictionary {keyword: expansion} for this Change.
-
- Derived classes overload this method to add more entries to
- the return value. This method is used internally by
- get_values(). The return value should always be a new
- dictionary."""
-
- values = self.environment.get_values()
- fromaddr = self.environment.get_fromaddr(change=self)
- if fromaddr is not None:
- values['fromaddr'] = fromaddr
- values['multimail_version'] = get_version()
- return values
-
- # Aliases usable in template strings. Tuple of pairs (destination,
- # source).
- VALUES_ALIAS = (
- ("id", "newrev"),
- )
-
- def get_values(self, **extra_values):
- """Return a dictionary {keyword: expansion} for this Change.
-
- Return a dictionary mapping keywords to the values that they
- should be expanded to for this Change (used when interpolating
- template strings). If any keyword arguments are supplied, add
- those to the return value as well. The return value is always
- a new dictionary."""
-
- if self._values is None:
- self._values = self._compute_values()
-
- values = self._values.copy()
- if extra_values:
- values.update(extra_values)
-
- for alias, val in self.VALUES_ALIAS:
- values[alias] = values[val]
- return values
-
- def expand(self, template, **extra_values):
- """Expand template.
-
- Expand the template (which should be a string) using string
- interpolation of the values for this Change. If any keyword
- arguments are provided, also include those in the keywords
- available for interpolation."""
-
- return template % self.get_values(**extra_values)
-
- def expand_lines(self, template, html_escape_val=False, **extra_values):
- """Break template into lines and expand each line."""
-
- values = self.get_values(**extra_values)
- if html_escape_val:
- for k in values:
- if is_string(values[k]):
- values[k] = html_escape(values[k])
- for line in template.splitlines(True):
- yield line % values
-
- def expand_header_lines(self, template, **extra_values):
- """Break template into lines and expand each line as an RFC 2822 header.
-
- Encode values and split up lines that are too long. Silently
- skip lines that contain references to unknown variables."""
-
- values = self.get_values(**extra_values)
- if self._contains_html_diff:
- self._content_type = 'html'
- else:
- self._content_type = 'plain'
- values['contenttype'] = self._content_type
-
- for line in template.splitlines():
- (name, value) = line.split(': ', 1)
-
- try:
- value = value % values
- except KeyError:
- t, e, traceback = sys.exc_info()
- if DEBUG:
- self.environment.log_warning(
- 'Warning: unknown variable %r in the following line; line skipped:\n'
- ' %s\n'
- % (e.args[0], line,)
- )
- else:
- if name.lower() in ADDR_HEADERS:
- value = addr_header_encode(value, name)
- else:
- value = header_encode(value, name)
- for splitline in ('%s: %s\n' % (name, value)).splitlines(True):
- yield splitline
-
- def generate_email_header(self):
- """Generate the RFC 2822 email headers for this Change, a line at a time.
-
- The output should not include the trailing blank line."""
-
- raise NotImplementedError()
-
- def generate_browse_link(self, base_url):
- """Generate a link to an online repository browser."""
- return iter(())
-
- def generate_email_intro(self, html_escape_val=False):
- """Generate the email intro for this Change, a line at a time.
-
- The output will be used as the standard boilerplate at the top
- of the email body."""
-
- raise NotImplementedError()
-
- def generate_email_body(self, push):
- """Generate the main part of the email body, a line at a time.
-
- The text in the body might be truncated after a specified
- number of lines (see multimailhook.emailmaxlines)."""
-
- raise NotImplementedError()
-
- def generate_email_footer(self, html_escape_val):
- """Generate the footer of the email, a line at a time.
-
- The footer is always included, irrespective of
- multimailhook.emailmaxlines."""
-
- raise NotImplementedError()
-
- def _wrap_for_html(self, lines):
- """Wrap the lines in HTML <pre> tag when using HTML format.
-
- Escape special HTML characters and add <pre> and </pre> tags around
- the given lines if we should be generating HTML as indicated by
- self._contains_html_diff being set to true.
- """
- if self._contains_html_diff:
- yield "<pre style='margin:0'>\n"
-
- for line in lines:
- yield html_escape(line)
-
- yield '</pre>\n'
- else:
- for line in lines:
- yield line
-
- def generate_email(self, push, body_filter=None, extra_header_values={}):
- """Generate an email describing this change.
-
- Iterate over the lines (including the header lines) of an
- email describing this change. If body_filter is not None,
- then use it to filter the lines that are intended for the
- email body.
-
- The extra_header_values field is received as a dict and not as
- **kwargs, to allow passing other keyword arguments in the
- future (e.g. passing extra values to generate_email_intro()"""
-
- for line in self.generate_email_header(**extra_header_values):
- yield line
- yield '\n'
- html_escape_val = (self.environment.html_in_intro and
- self._contains_html_diff)
- intro = self.generate_email_intro(html_escape_val)
- if not self.environment.html_in_intro:
- intro = self._wrap_for_html(intro)
- for line in intro:
- yield line
-
- if self.environment.commitBrowseURL:
- for line in self.generate_browse_link(self.environment.commitBrowseURL):
- yield line
-
- body = self.generate_email_body(push)
- if body_filter is not None:
- body = body_filter(body)
-
- diff_started = False
- if self._contains_html_diff:
- # "white-space: pre" is the default, but we need to
- # specify it again in case the message is viewed in a
- # webmail which wraps it in an element setting white-space
- # to something else (Zimbra does this and sets
- # white-space: pre-line).
- yield '<pre style="white-space: pre; background: #F8F8F8">'
- for line in body:
- if self._contains_html_diff:
- # This is very, very naive. It would be much better to really
- # parse the diff, i.e. look at how many lines do we have in
- # the hunk headers instead of blindly highlighting everything
- # that looks like it might be part of a diff.
- bgcolor = ''
- fgcolor = ''
- if line.startswith('--- a/'):
- diff_started = True
- bgcolor = 'e0e0ff'
- elif line.startswith('diff ') or line.startswith('index '):
- diff_started = True
- fgcolor = '808080'
- elif diff_started:
- if line.startswith('+++ '):
- bgcolor = 'e0e0ff'
- elif line.startswith('@@'):
- bgcolor = 'e0e0e0'
- elif line.startswith('+'):
- bgcolor = 'e0ffe0'
- elif line.startswith('-'):
- bgcolor = 'ffe0e0'
- elif line.startswith('commit '):
- fgcolor = '808000'
- elif line.startswith(' '):
- fgcolor = '404040'
-
- # Chop the trailing LF, we don't want it inside <pre>.
- line = html_escape(line[:-1])
-
- if bgcolor or fgcolor:
- style = 'display:block; white-space:pre;'
- if bgcolor:
- style += 'background:#' + bgcolor + ';'
- if fgcolor:
- style += 'color:#' + fgcolor + ';'
- # Use a <span style='display:block> to color the
- # whole line. The newline must be inside the span
- # to display properly both in Firefox and in
- # text-based browser.
- line = "<span style='%s'>%s\n</span>" % (style, line)
- else:
- line = line + '\n'
-
- yield line
- if self._contains_html_diff:
- yield '</pre>'
- html_escape_val = (self.environment.html_in_footer and
- self._contains_html_diff)
- footer = self.generate_email_footer(html_escape_val)
- if not self.environment.html_in_footer:
- footer = self._wrap_for_html(footer)
- for line in footer:
- yield line
-
- def get_specific_fromaddr(self):
- """For kinds of Changes which specify it, return the kind-specific
- From address to use."""
- return None
-
-
-class Revision(Change):
- """A Change consisting of a single git commit."""
-
- CC_RE = re.compile(r'^\s*C[Cc]:\s*(?P<to>[^#]+@[^\s#]*)\s*(#.*)?$')
-
- def __init__(self, reference_change, rev, num, tot):
- Change.__init__(self, reference_change.environment)
- self.reference_change = reference_change
- self.rev = rev
- self.change_type = self.reference_change.change_type
- self.refname = self.reference_change.refname
- self.num = num
- self.tot = tot
- self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
- self.recipients = self.environment.get_revision_recipients(self)
-
- # -s is short for --no-patch, but -s works on older git's (e.g. 1.7)
- self.parents = read_git_lines(['show', '-s', '--format=%P',
- self.rev.sha1])[0].split()
-
- self.cc_recipients = ''
- if self.environment.get_scancommitforcc():
- self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients())
- if self.cc_recipients:
- self.environment.log_msg(
- 'Add %s to CC for %s' % (self.cc_recipients, self.rev.sha1))
-
- def _cc_recipients(self):
- cc_recipients = []
- message = read_git_output(['log', '--no-walk', '--format=%b', self.rev.sha1])
- lines = message.strip().split('\n')
- for line in lines:
- m = re.match(self.CC_RE, line)
- if m:
- cc_recipients.append(m.group('to'))
-
- return cc_recipients
-
- def _compute_values(self):
- values = Change._compute_values(self)
-
- oneline = read_git_output(
- ['log', '--format=%s', '--no-walk', self.rev.sha1]
- )
-
- max_subject_length = self.environment.get_max_subject_length()
- if max_subject_length > 0 and len(oneline) > max_subject_length:
- oneline = oneline[:max_subject_length - 6] + ' [...]'
-
- values['rev'] = self.rev.sha1
- values['parents'] = ' '.join(self.parents)
- values['rev_short'] = self.rev.short
- values['change_type'] = self.change_type
- values['refname'] = self.refname
- values['newrev'] = self.rev.sha1
- values['short_refname'] = self.reference_change.short_refname
- values['refname_type'] = self.reference_change.refname_type
- values['reply_to_msgid'] = self.reference_change.msgid
- values['thread_index'] = self.reference_change.thread_index
- values['num'] = self.num
- values['tot'] = self.tot
- values['recipients'] = self.recipients
- if self.cc_recipients:
- values['cc_recipients'] = self.cc_recipients
- values['oneline'] = oneline
- values['author'] = self.author
-
- reply_to = self.environment.get_reply_to_commit(self)
- if reply_to:
- values['reply_to'] = reply_to
-
- return values
-
- def generate_email_header(self, **extra_values):
- for line in self.expand_header_lines(
- REVISION_HEADER_TEMPLATE, **extra_values
- ):
- yield line
-
- def generate_browse_link(self, base_url):
- if '%(' not in base_url:
- base_url += '%(id)s'
- url = "".join(self.expand_lines(base_url))
- if self._content_type == 'html':
- for line in self.expand_lines(LINK_HTML_TEMPLATE,
- html_escape_val=True,
- browse_url=url):
- yield line
- elif self._content_type == 'plain':
- for line in self.expand_lines(LINK_TEXT_TEMPLATE,
- html_escape_val=False,
- browse_url=url):
- yield line
- else:
- raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.")
-
- def generate_email_intro(self, html_escape_val=False):
- for line in self.expand_lines(REVISION_INTRO_TEMPLATE,
- html_escape_val=html_escape_val):
- yield line
-
- def generate_email_body(self, push):
- """Show this revision."""
-
- for line in read_git_lines(
- ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
- keepends=True,
- errors='replace'):
- if line.startswith('Date: ') and self.environment.date_substitute:
- yield self.environment.date_substitute + line[len('Date: '):]
- else:
- yield line
-
- def generate_email_footer(self, html_escape_val):
- return self.expand_lines(REVISION_FOOTER_TEMPLATE,
- html_escape_val=html_escape_val)
-
- def generate_email(self, push, body_filter=None, extra_header_values={}):
- self._contains_diff()
- return Change.generate_email(self, push, body_filter, extra_header_values)
-
- def get_specific_fromaddr(self):
- return self.environment.from_commit
-
-
-class ReferenceChange(Change):
- """A Change to a Git reference.
-
- An abstract class representing a create, update, or delete of a
- Git reference. Derived classes handle specific types of reference
- (e.g., tags vs. branches). These classes generate the main
- reference change email summarizing the reference change and
- whether it caused any any commits to be added or removed.
-
- ReferenceChange objects are usually created using the static
- create() method, which has the logic to decide which derived class
- to instantiate."""
-
- REF_RE = re.compile(r'^refs\/(?P<area>[^\/]+)\/(?P<shortname>.*)$')
-
- @staticmethod
- def create(environment, oldrev, newrev, refname):
- """Return a ReferenceChange object representing the change.
-
- Return an object that represents the type of change that is being
- made. oldrev and newrev should be SHA1s or ZEROS."""
-
- old = GitObject(oldrev)
- new = GitObject(newrev)
- rev = new or old
-
- # The revision type tells us what type the commit is, combined with
- # the location of the ref we can decide between
- # - working branch
- # - tracking branch
- # - unannotated tag
- # - annotated tag
- m = ReferenceChange.REF_RE.match(refname)
- if m:
- area = m.group('area')
- short_refname = m.group('shortname')
- else:
- area = ''
- short_refname = refname
-
- if rev.type == 'tag':
- # Annotated tag:
- klass = AnnotatedTagChange
- elif rev.type == 'commit':
- if area == 'tags':
- # Non-annotated tag:
- klass = NonAnnotatedTagChange
- elif area == 'heads':
- # Branch:
- klass = BranchChange
- elif area == 'remotes':
- # Tracking branch:
- environment.log_warning(
- '*** Push-update of tracking branch %r\n'
- '*** - incomplete email generated.'
- % (refname,)
- )
- klass = OtherReferenceChange
- else:
- # Some other reference namespace:
- environment.log_warning(
- '*** Push-update of strange reference %r\n'
- '*** - incomplete email generated.'
- % (refname,)
- )
- klass = OtherReferenceChange
- else:
- # Anything else (is there anything else?)
- environment.log_warning(
- '*** Unknown type of update to %r (%s)\n'
- '*** - incomplete email generated.'
- % (refname, rev.type,)
- )
- klass = OtherReferenceChange
-
- return klass(
- environment,
- refname=refname, short_refname=short_refname,
- old=old, new=new, rev=rev,
- )
-
- @staticmethod
- def make_thread_index():
- """Return a string appropriate for the Thread-Index header,
- needed by MS Outlook to get threading right.
-
- The format is (base64-encoded):
- - 1 byte must be 1
- - 5 bytes encode a date (hardcoded here)
- - 16 bytes for a globally unique identifier
-
- FIXME: Unfortunately, even with the Thread-Index field, MS
- Outlook doesn't seem to do the threading reliably (see
- https://github.com/git-multimail/git-multimail/pull/194).
- """
- thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes
- return base64.standard_b64encode(thread_index).decode('ascii')
-
- def __init__(self, environment, refname, short_refname, old, new, rev):
- Change.__init__(self, environment)
- self.change_type = {
- (False, True): 'create',
- (True, True): 'update',
- (True, False): 'delete',
- }[bool(old), bool(new)]
- self.refname = refname
- self.short_refname = short_refname
- self.old = old
- self.new = new
- self.rev = rev
- self.msgid = make_msgid()
- self.thread_index = self.make_thread_index()
- self.diffopts = environment.diffopts
- self.graphopts = environment.graphopts
- self.logopts = environment.logopts
- self.commitlogopts = environment.commitlogopts
- self.showgraph = environment.refchange_showgraph
- self.showlog = environment.refchange_showlog
-
- self.header_template = REFCHANGE_HEADER_TEMPLATE
- self.intro_template = REFCHANGE_INTRO_TEMPLATE
- self.footer_template = FOOTER_TEMPLATE
-
- def _compute_values(self):
- values = Change._compute_values(self)
-
- values['change_type'] = self.change_type
- values['refname_type'] = self.refname_type
- values['refname'] = self.refname
- values['short_refname'] = self.short_refname
- values['msgid'] = self.msgid
- values['thread_index'] = self.thread_index
- values['recipients'] = self.recipients
- values['oldrev'] = str(self.old)
- values['oldrev_short'] = self.old.short
- values['newrev'] = str(self.new)
- values['newrev_short'] = self.new.short
-
- if self.old:
- values['oldrev_type'] = self.old.type
- if self.new:
- values['newrev_type'] = self.new.type
-
- reply_to = self.environment.get_reply_to_refchange(self)
- if reply_to:
- values['reply_to'] = reply_to
-
- return values
-
- def send_single_combined_email(self, known_added_sha1s):
- """Determine if a combined refchange/revision email should be sent
-
- If there is only a single new (non-merge) commit added by a
- change, it is useful to combine the ReferenceChange and
- Revision emails into one. In such a case, return the single
- revision; otherwise, return None.
-
- This method is overridden in BranchChange."""
-
- return None
-
- def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
- """Generate an email describing this change AND specified revision.
-
- Iterate over the lines (including the header lines) of an
- email describing this change. If body_filter is not None,
- then use it to filter the lines that are intended for the
- email body.
-
- The extra_header_values field is received as a dict and not as
- **kwargs, to allow passing other keyword arguments in the
- future (e.g. passing extra values to generate_email_intro()
-
- This method is overridden in BranchChange."""
-
- raise NotImplementedError
-
- def get_subject(self):
- template = {
- 'create': REF_CREATED_SUBJECT_TEMPLATE,
- 'update': REF_UPDATED_SUBJECT_TEMPLATE,
- 'delete': REF_DELETED_SUBJECT_TEMPLATE,
- }[self.change_type]
- return self.expand(template)
-
- def generate_email_header(self, **extra_values):
- if 'subject' not in extra_values:
- extra_values['subject'] = self.get_subject()
-
- for line in self.expand_header_lines(
- self.header_template, **extra_values
- ):
- yield line
-
- def generate_email_intro(self, html_escape_val=False):
- for line in self.expand_lines(self.intro_template,
- html_escape_val=html_escape_val):
- yield line
-
- def generate_email_body(self, push):
- """Call the appropriate body-generation routine.
-
- Call one of generate_create_summary() /
- generate_update_summary() / generate_delete_summary()."""
-
- change_summary = {
- 'create': self.generate_create_summary,
- 'delete': self.generate_delete_summary,
- 'update': self.generate_update_summary,
- }[self.change_type](push)
- for line in change_summary:
- yield line
-
- for line in self.generate_revision_change_summary(push):
- yield line
-
- def generate_email_footer(self, html_escape_val):
- return self.expand_lines(self.footer_template,
- html_escape_val=html_escape_val)
-
- def generate_revision_change_graph(self, push):
- if self.showgraph:
- args = ['--graph'] + self.graphopts
- for newold in ('new', 'old'):
- has_newold = False
- spec = push.get_commits_spec(newold, self)
- for line in git_log(spec, args=args, keepends=True):
- if not has_newold:
- has_newold = True
- yield '\n'
- yield 'Graph of %s commits:\n\n' % (
- {'new': 'new', 'old': 'discarded'}[newold],)
- yield ' ' + line
- if has_newold:
- yield '\n'
-
- def generate_revision_change_log(self, new_commits_list):
- if self.showlog:
- yield '\n'
- yield 'Detailed log of new commits:\n\n'
- for line in read_git_lines(
- ['log', '--no-walk'] +
- self.logopts +
- new_commits_list +
- ['--'],
- keepends=True,
- ):
- yield line
-
- def generate_new_revision_summary(self, tot, new_commits_list, push):
- for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=tot):
- yield line
- for line in self.generate_revision_change_graph(push):
- yield line
- for line in self.generate_revision_change_log(new_commits_list):
- yield line
-
- def generate_revision_change_summary(self, push):
- """Generate a summary of the revisions added/removed by this change."""
-
- if self.new.commit_sha1 and not self.old.commit_sha1:
- # A new reference was created. List the new revisions
- # brought by the new reference (i.e., those revisions that
- # were not in the repository before this reference
- # change).
- sha1s = list(push.get_new_commits(self))
- sha1s.reverse()
- tot = len(sha1s)
- new_revisions = [
- Revision(self, GitObject(sha1), num=i + 1, tot=tot)
- for (i, sha1) in enumerate(sha1s)
- ]
-
- if new_revisions:
- yield self.expand('This %(refname_type)s includes the following new commits:\n')
- yield '\n'
- for r in new_revisions:
- (sha1, subject) = r.rev.get_summary()
- yield r.expand(
- BRIEF_SUMMARY_TEMPLATE, action='new', text=subject,
- )
- yield '\n'
- for line in self.generate_new_revision_summary(
- tot, [r.rev.sha1 for r in new_revisions], push):
- yield line
- else:
- for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
- yield line
-
- elif self.new.commit_sha1 and self.old.commit_sha1:
- # A reference was changed to point at a different commit.
- # List the revisions that were removed and/or added *from
- # that reference* by this reference change, along with a
- # diff between the trees for its old and new values.
-
- # List of the revisions that were added to the branch by
- # this update. Note this list can include revisions that
- # have already had notification emails; we want such
- # revisions in the summary even though we will not send
- # new notification emails for them.
- adds = list(generate_summaries(
- '--topo-order', '--reverse', '%s..%s'
- % (self.old.commit_sha1, self.new.commit_sha1,)
- ))
-
- # List of the revisions that were removed from the branch
- # by this update. This will be empty except for
- # non-fast-forward updates.
- discards = list(generate_summaries(
- '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,)
- ))
-
- if adds:
- new_commits_list = push.get_new_commits(self)
- else:
- new_commits_list = []
- new_commits = CommitSet(new_commits_list)
-
- if discards:
- discarded_commits = CommitSet(push.get_discarded_commits(self))
- else:
- discarded_commits = CommitSet([])
-
- if discards and adds:
- for (sha1, subject) in discards:
- if sha1 in discarded_commits:
- action = 'discard'
- else:
- action = 'omit'
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action=action,
- rev_short=sha1, text=subject,
- )
- for (sha1, subject) in adds:
- if sha1 in new_commits:
- action = 'new'
- else:
- action = 'add'
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action=action,
- rev_short=sha1, text=subject,
- )
- yield '\n'
- for line in self.expand_lines(NON_FF_TEMPLATE):
- yield line
-
- elif discards:
- for (sha1, subject) in discards:
- if sha1 in discarded_commits:
- action = 'discard'
- else:
- action = 'omit'
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action=action,
- rev_short=sha1, text=subject,
- )
- yield '\n'
- for line in self.expand_lines(REWIND_ONLY_TEMPLATE):
- yield line
-
- elif adds:
- (sha1, subject) = self.old.get_summary()
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action='from',
- rev_short=sha1, text=subject,
- )
- for (sha1, subject) in adds:
- if sha1 in new_commits:
- action = 'new'
- else:
- action = 'add'
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action=action,
- rev_short=sha1, text=subject,
- )
-
- yield '\n'
-
- if new_commits:
- for line in self.generate_new_revision_summary(
- len(new_commits), new_commits_list, push):
- yield line
- else:
- for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
- yield line
- for line in self.generate_revision_change_graph(push):
- yield line
-
- # The diffstat is shown from the old revision to the new
- # revision. This is to show the truth of what happened in
- # this change. There's no point showing the stat from the
- # base to the new revision because the base is effectively a
- # random revision at this point - the user will be interested
- # in what this revision changed - including the undoing of
- # previous revisions in the case of non-fast-forward updates.
- yield '\n'
- yield 'Summary of changes:\n'
- for line in read_git_lines(
- ['diff-tree'] +
- self.diffopts +
- ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
- keepends=True,
- ):
- yield line
-
- elif self.old.commit_sha1 and not self.new.commit_sha1:
- # A reference was deleted. List the revisions that were
- # removed from the repository by this reference change.
-
- sha1s = list(push.get_discarded_commits(self))
- tot = len(sha1s)
- discarded_revisions = [
- Revision(self, GitObject(sha1), num=i + 1, tot=tot)
- for (i, sha1) in enumerate(sha1s)
- ]
-
- if discarded_revisions:
- for line in self.expand_lines(DISCARDED_REVISIONS_TEMPLATE):
- yield line
- yield '\n'
- for r in discarded_revisions:
- (sha1, subject) = r.rev.get_summary()
- yield r.expand(
- BRIEF_SUMMARY_TEMPLATE, action='discard', text=subject,
- )
- for line in self.generate_revision_change_graph(push):
- yield line
- else:
- for line in self.expand_lines(NO_DISCARDED_REVISIONS_TEMPLATE):
- yield line
-
- elif not self.old.commit_sha1 and not self.new.commit_sha1:
- for line in self.expand_lines(NON_COMMIT_UPDATE_TEMPLATE):
- yield line
-
- def generate_create_summary(self, push):
- """Called for the creation of a reference."""
-
- # This is a new reference and so oldrev is not valid
- (sha1, subject) = self.new.get_summary()
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action='at',
- rev_short=sha1, text=subject,
- )
- yield '\n'
-
- def generate_update_summary(self, push):
- """Called for the change of a pre-existing branch."""
-
- return iter([])
-
- def generate_delete_summary(self, push):
- """Called for the deletion of any type of reference."""
-
- (sha1, subject) = self.old.get_summary()
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action='was',
- rev_short=sha1, text=subject,
- )
- yield '\n'
-
- def get_specific_fromaddr(self):
- return self.environment.from_refchange
-
-
-class BranchChange(ReferenceChange):
- refname_type = 'branch'
-
- def __init__(self, environment, refname, short_refname, old, new, rev):
- ReferenceChange.__init__(
- self, environment,
- refname=refname, short_refname=short_refname,
- old=old, new=new, rev=rev,
- )
- self.recipients = environment.get_refchange_recipients(self)
- self._single_revision = None
-
- def send_single_combined_email(self, known_added_sha1s):
- if not self.environment.combine_when_single_commit:
- return None
-
- # In the sadly-all-too-frequent usecase of people pushing only
- # one of their commits at a time to a repository, users feel
- # the reference change summary emails are noise rather than
- # important signal. This is because, in this particular
- # usecase, there is a reference change summary email for each
- # new commit, and all these summaries do is point out that
- # there is one new commit (which can readily be inferred by
- # the existence of the individual revision email that is also
- # sent). In such cases, our users prefer there to be a combined
- # reference change summary/new revision email.
- #
- # So, if the change is an update and it doesn't discard any
- # commits, and it adds exactly one non-merge commit (gerrit
- # forces a workflow where every commit is individually merged
- # and the git-multimail hook fired off for just this one
- # change), then we send a combined refchange/revision email.
- try:
- # If this change is a reference update that doesn't discard
- # any commits...
- if self.change_type != 'update':
- return None
-
- if read_git_lines(
- ['merge-base', self.old.sha1, self.new.sha1]
- ) != [self.old.sha1]:
- return None
-
- # Check if this update introduced exactly one non-merge
- # commit:
-
- def split_line(line):
- """Split line into (sha1, [parent,...])."""
-
- words = line.split()
- return (words[0], words[1:])
-
- # Get the new commits introduced by the push as a list of
- # (sha1, [parent,...])
- new_commits = [
- split_line(line)
- for line in read_git_lines(
- [
- 'log', '-3', '--format=%H %P',
- '%s..%s' % (self.old.sha1, self.new.sha1),
- ]
- )
- ]
-
- if not new_commits:
- return None
-
- # If the newest commit is a merge, save it for a later check
- # but otherwise ignore it
- merge = None
- tot = len(new_commits)
- if len(new_commits[0][1]) > 1:
- merge = new_commits[0][0]
- del new_commits[0]
-
- # Our primary check: we can't combine if more than one commit
- # is introduced. We also currently only combine if the new
- # commit is a non-merge commit, though it may make sense to
- # combine if it is a merge as well.
- if not (
- len(new_commits) == 1 and
- len(new_commits[0][1]) == 1 and
- new_commits[0][0] in known_added_sha1s
- ):
- return None
-
- # We do not want to combine revision and refchange emails if
- # those go to separate locations.
- rev = Revision(self, GitObject(new_commits[0][0]), 1, tot)
- if rev.recipients != self.recipients:
- return None
-
- # We ignored the newest commit if it was just a merge of the one
- # commit being introduced. But we don't want to ignore that
- # merge commit it it involved conflict resolutions. Check that.
- if merge and merge != read_git_output(['diff-tree', '--cc', merge]):
- return None
-
- # We can combine the refchange and one new revision emails
- # into one. Return the Revision that a combined email should
- # be sent about.
- return rev
- except CommandError:
- # Cannot determine number of commits in old..new or new..old;
- # don't combine reference/revision emails:
- return None
-
- def generate_combined_email(self, push, revision, body_filter=None, extra_header_values={}):
- values = revision.get_values()
- if extra_header_values:
- values.update(extra_header_values)
- if 'subject' not in extra_header_values:
- values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values)
-
- self._single_revision = revision
- self._contains_diff()
- self.header_template = COMBINED_HEADER_TEMPLATE
- self.intro_template = COMBINED_INTRO_TEMPLATE
- self.footer_template = COMBINED_FOOTER_TEMPLATE
-
- def revision_gen_link(base_url):
- # revision is used only to generate the body, and
- # _content_type is set while generating headers. Get it
- # from the BranchChange object.
- revision._content_type = self._content_type
- return revision.generate_browse_link(base_url)
- self.generate_browse_link = revision_gen_link
- for line in self.generate_email(push, body_filter, values):
- yield line
-
- def generate_email_body(self, push):
- '''Call the appropriate body generation routine.
-
- If this is a combined refchange/revision email, the special logic
- for handling this combined email comes from this function. For
- other cases, we just use the normal handling.'''
-
- # If self._single_revision isn't set; don't override
- if not self._single_revision:
- for line in super(BranchChange, self).generate_email_body(push):
- yield line
- return
-
- # This is a combined refchange/revision email; we first provide
- # some info from the refchange portion, and then call the revision
- # generate_email_body function to handle the revision portion.
- adds = list(generate_summaries(
- '--topo-order', '--reverse', '%s..%s'
- % (self.old.commit_sha1, self.new.commit_sha1,)
- ))
-
- yield self.expand("The following commit(s) were added to %(refname)s by this push:\n")
- for (sha1, subject) in adds:
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action='new',
- rev_short=sha1, text=subject,
- )
-
- yield self._single_revision.rev.short + " is described below\n"
- yield '\n'
-
- for line in self._single_revision.generate_email_body(push):
- yield line
-
-
-class AnnotatedTagChange(ReferenceChange):
- refname_type = 'annotated tag'
-
- def __init__(self, environment, refname, short_refname, old, new, rev):
- ReferenceChange.__init__(
- self, environment,
- refname=refname, short_refname=short_refname,
- old=old, new=new, rev=rev,
- )
- self.recipients = environment.get_announce_recipients(self)
- self.show_shortlog = environment.announce_show_shortlog
-
- ANNOTATED_TAG_FORMAT = (
- '%(*objectname)\n'
- '%(*objecttype)\n'
- '%(taggername)\n'
- '%(taggerdate)'
- )
-
- def describe_tag(self, push):
- """Describe the new value of an annotated tag."""
-
- # Use git for-each-ref to pull out the individual fields from
- # the tag
- [tagobject, tagtype, tagger, tagged] = read_git_lines(
- ['for-each-ref', '--format=%s' % (self.ANNOTATED_TAG_FORMAT,), self.refname],
- )
-
- yield self.expand(
- BRIEF_SUMMARY_TEMPLATE, action='tagging',
- rev_short=tagobject, text='(%s)' % (tagtype,),
- )
- if tagtype == 'commit':
- # If the tagged object is a commit, then we assume this is a
- # release, and so we calculate which tag this tag is
- # replacing
- try:
- prevtag = read_git_output(['describe', '--abbrev=0', '%s^' % (self.new,)])
- except CommandError:
- prevtag = None
- if prevtag:
- yield ' replaces %s\n' % (prevtag,)
- else:
- prevtag = None
- yield ' length %s bytes\n' % (read_git_output(['cat-file', '-s', tagobject]),)
-
- yield ' by %s\n' % (tagger,)
- yield ' on %s\n' % (tagged,)
- yield '\n'
-
- # Show the content of the tag message; this might contain a
- # change log or release notes so is worth displaying.
- yield LOGBEGIN
- contents = list(read_git_lines(['cat-file', 'tag', self.new.sha1], keepends=True))
- contents = contents[contents.index('\n') + 1:]
- if contents and contents[-1][-1:] != '\n':
- contents.append('\n')
- for line in contents:
- yield line
-
- if self.show_shortlog and tagtype == 'commit':
- # Only commit tags make sense to have rev-list operations
- # performed on them
- yield '\n'
- if prevtag:
- # Show changes since the previous release
- revlist = read_git_output(
- ['rev-list', '--pretty=short', '%s..%s' % (prevtag, self.new,)],
- keepends=True,
- )
- else:
- # No previous tag, show all the changes since time
- # began
- revlist = read_git_output(
- ['rev-list', '--pretty=short', '%s' % (self.new,)],
- keepends=True,
- )
- for line in read_git_lines(['shortlog'], input=revlist, keepends=True):
- yield line
-
- yield LOGEND
- yield '\n'
-
- def generate_create_summary(self, push):
- """Called for the creation of an annotated tag."""
-
- for line in self.expand_lines(TAG_CREATED_TEMPLATE):
- yield line
-
- for line in self.describe_tag(push):
- yield line
-
- def generate_update_summary(self, push):
- """Called for the update of an annotated tag.
-
- This is probably a rare event and may not even be allowed."""
-
- for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
- yield line
-
- for line in self.describe_tag(push):
- yield line
-
- def generate_delete_summary(self, push):
- """Called when a non-annotated reference is updated."""
-
- for line in self.expand_lines(TAG_DELETED_TEMPLATE):
- yield line
-
- yield self.expand(' tag was %(oldrev_short)s\n')
- yield '\n'
-
-
-class NonAnnotatedTagChange(ReferenceChange):
- refname_type = 'tag'
-
- def __init__(self, environment, refname, short_refname, old, new, rev):
- ReferenceChange.__init__(
- self, environment,
- refname=refname, short_refname=short_refname,
- old=old, new=new, rev=rev,
- )
- self.recipients = environment.get_refchange_recipients(self)
-
- def generate_create_summary(self, push):
- """Called for the creation of an annotated tag."""
-
- for line in self.expand_lines(TAG_CREATED_TEMPLATE):
- yield line
-
- def generate_update_summary(self, push):
- """Called when a non-annotated reference is updated."""
-
- for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
- yield line
-
- def generate_delete_summary(self, push):
- """Called when a non-annotated reference is updated."""
-
- for line in self.expand_lines(TAG_DELETED_TEMPLATE):
- yield line
-
- for line in ReferenceChange.generate_delete_summary(self, push):
- yield line
-
-
-class OtherReferenceChange(ReferenceChange):
- refname_type = 'reference'
-
- def __init__(self, environment, refname, short_refname, old, new, rev):
- # We use the full refname as short_refname, because otherwise
- # the full name of the reference would not be obvious from the
- # text of the email.
- ReferenceChange.__init__(
- self, environment,
- refname=refname, short_refname=refname,
- old=old, new=new, rev=rev,
- )
- self.recipients = environment.get_refchange_recipients(self)
-
-
-class Mailer(object):
- """An object that can send emails."""
-
- def __init__(self, environment):
- self.environment = environment
-
- def close(self):
- pass
-
- def send(self, lines, to_addrs):
- """Send an email consisting of lines.
-
- lines must be an iterable over the lines constituting the
- header and body of the email. to_addrs is a list of recipient
- addresses (can be needed even if lines already contains a
- "To:" field). It can be either a string (comma-separated list
- of email addresses) or a Python list of individual email
- addresses.
-
- """
-
- raise NotImplementedError()
-
-
-class SendMailer(Mailer):
- """Send emails using 'sendmail -oi -t'."""
-
- SENDMAIL_CANDIDATES = [
- '/usr/sbin/sendmail',
- '/usr/lib/sendmail',
- ]
-
- @staticmethod
- def find_sendmail():
- for path in SendMailer.SENDMAIL_CANDIDATES:
- if os.access(path, os.X_OK):
- return path
- else:
- raise ConfigurationException(
- 'No sendmail executable found. '
- 'Try setting multimailhook.sendmailCommand.'
- )
-
- def __init__(self, environment, command=None, envelopesender=None):
- """Construct a SendMailer instance.
-
- command should be the command and arguments used to invoke
- sendmail, as a list of strings. If an envelopesender is
- provided, it will also be passed to the command, via '-f
- envelopesender'."""
- super(SendMailer, self).__init__(environment)
- if command:
- self.command = command[:]
- else:
- self.command = [self.find_sendmail(), '-oi', '-t']
-
- if envelopesender:
- self.command.extend(['-f', envelopesender])
-
- def send(self, lines, to_addrs):
- try:
- p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
- except OSError:
- self.environment.get_logger().error(
- '*** Cannot execute command: %s\n' % ' '.join(self.command) +
- '*** %s\n' % sys.exc_info()[1] +
- '*** Try setting multimailhook.mailer to "smtp"\n' +
- '*** to send emails without using the sendmail command.\n'
- )
- sys.exit(1)
- try:
- lines = (str_to_bytes(line) for line in lines)
- p.stdin.writelines(lines)
- except Exception:
- self.environment.get_logger().error(
- '*** Error while generating commit email\n'
- '*** - mail sending aborted.\n'
- )
- if hasattr(p, 'terminate'):
- # subprocess.terminate() is not available in Python 2.4
- p.terminate()
- else:
- import signal
- os.kill(p.pid, signal.SIGTERM)
- raise
- else:
- p.stdin.close()
- retcode = p.wait()
- if retcode:
- raise CommandError(self.command, retcode)
-
-
-class SMTPMailer(Mailer):
- """Send emails using Python's smtplib."""
-
- def __init__(self, environment,
- envelopesender, smtpserver,
- smtpservertimeout=10.0, smtpserverdebuglevel=0,
- smtpencryption='none',
- smtpuser='', smtppass='',
- smtpcacerts=''
- ):
- super(SMTPMailer, self).__init__(environment)
- if not envelopesender:
- self.environment.get_logger().error(
- 'fatal: git_multimail: cannot use SMTPMailer without a sender address.\n'
- 'please set either multimailhook.envelopeSender or user.email\n'
- )
- sys.exit(1)
- if smtpencryption == 'ssl' and not (smtpuser and smtppass):
- raise ConfigurationException(
- 'Cannot use SMTPMailer with security option ssl '
- 'without options username and password.'
- )
- self.envelopesender = envelopesender
- self.smtpserver = smtpserver
- self.smtpservertimeout = smtpservertimeout
- self.smtpserverdebuglevel = smtpserverdebuglevel
- self.security = smtpencryption
- self.username = smtpuser
- self.password = smtppass
- self.smtpcacerts = smtpcacerts
- self.loggedin = False
- try:
- def call(klass, server, timeout):
- try:
- return klass(server, timeout=timeout)
- except TypeError:
- # Old Python versions do not have timeout= argument.
- return klass(server)
- if self.security == 'none':
- self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
- elif self.security == 'ssl':
- if self.smtpcacerts:
- raise smtplib.SMTPException(
- "Checking certificate is not supported for ssl, prefer starttls"
- )
- self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
- elif self.security == 'tls':
- if 'ssl' not in sys.modules:
- self.environment.get_logger().error(
- '*** Your Python version does not have the ssl library installed\n'
- '*** smtpEncryption=tls is not available.\n'
- '*** Either upgrade Python to 2.6 or later\n'
- ' or use git_multimail.py version 1.2.\n')
- if ':' not in self.smtpserver:
- self.smtpserver += ':587' # default port for TLS
- self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
- # start: ehlo + starttls
- # equivalent to
- # self.smtp.ehlo()
- # self.smtp.starttls()
- # with access to the ssl layer
- self.smtp.ehlo()
- if not self.smtp.has_extn("starttls"):
- raise smtplib.SMTPException("STARTTLS extension not supported by server")
- resp, reply = self.smtp.docmd("STARTTLS")
- if resp != 220:
- raise smtplib.SMTPException("Wrong answer to the STARTTLS command")
- if self.smtpcacerts:
- self.smtp.sock = ssl.wrap_socket(
- self.smtp.sock,
- ca_certs=self.smtpcacerts,
- cert_reqs=ssl.CERT_REQUIRED
- )
- else:
- self.smtp.sock = ssl.wrap_socket(
- self.smtp.sock,
- cert_reqs=ssl.CERT_NONE
- )
- self.environment.get_logger().error(
- '*** Warning, the server certificate is not verified (smtp) ***\n'
- '*** set the option smtpCACerts ***\n'
- )
- if not hasattr(self.smtp.sock, "read"):
- # using httplib.FakeSocket with Python 2.5.x or earlier
- self.smtp.sock.read = self.smtp.sock.recv
- self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock)
- self.smtp.helo_resp = None
- self.smtp.ehlo_resp = None
- self.smtp.esmtp_features = {}
- self.smtp.does_esmtp = 0
- # end: ehlo + starttls
- self.smtp.ehlo()
- else:
- sys.stdout.write('*** Error: Control reached an invalid option. ***')
- sys.exit(1)
- if self.smtpserverdebuglevel > 0:
- sys.stdout.write(
- "*** Setting debug on for SMTP server connection (%s) ***\n"
- % self.smtpserverdebuglevel)
- self.smtp.set_debuglevel(self.smtpserverdebuglevel)
- except Exception:
- self.environment.get_logger().error(
- '*** Error establishing SMTP connection to %s ***\n'
- '*** %s\n'
- % (self.smtpserver, sys.exc_info()[1]))
- sys.exit(1)
-
- def close(self):
- if hasattr(self, 'smtp'):
- self.smtp.quit()
- del self.smtp
-
- def __del__(self):
- self.close()
-
- def send(self, lines, to_addrs):
- try:
- if self.username or self.password:
- if not self.loggedin:
- self.smtp.login(self.username, self.password)
- self.loggedin = True
- msg = ''.join(lines)
- # turn comma-separated list into Python list if needed.
- if is_string(to_addrs):
- to_addrs = [email for (name, email) in getaddresses([to_addrs])]
- self.smtp.sendmail(self.envelopesender, to_addrs, msg)
- except socket.timeout:
- self.environment.get_logger().error(
- '*** Error sending email ***\n'
- '*** SMTP server timed out (timeout is %s)\n'
- % self.smtpservertimeout)
- except smtplib.SMTPResponseException:
- err = sys.exc_info()[1]
- self.environment.get_logger().error(
- '*** Error sending email ***\n'
- '*** Error %d: %s\n'
- % (err.smtp_code, bytes_to_str(err.smtp_error)))
- try:
- smtp = self.smtp
- # delete the field before quit() so that in case of
- # error, self.smtp is deleted anyway.
- del self.smtp
- smtp.quit()
- except:
- self.environment.get_logger().error(
- '*** Error closing the SMTP connection ***\n'
- '*** Exiting anyway ... ***\n'
- '*** %s\n' % sys.exc_info()[1])
- sys.exit(1)
-
-
-class OutputMailer(Mailer):
- """Write emails to an output stream, bracketed by lines of '=' characters.
-
- This is intended for debugging purposes."""
-
- SEPARATOR = '=' * 75 + '\n'
-
- def __init__(self, f, environment=None):
- super(OutputMailer, self).__init__(environment=environment)
- self.f = f
-
- def send(self, lines, to_addrs):
- write_str(self.f, self.SEPARATOR)
- for line in lines:
- write_str(self.f, line)
- write_str(self.f, self.SEPARATOR)
-
-
-def get_git_dir():
- """Determine GIT_DIR.
-
- Determine GIT_DIR either from the GIT_DIR environment variable or
- from the working directory, using Git's usual rules."""
-
- try:
- return read_git_output(['rev-parse', '--git-dir'])
- except CommandError:
- sys.stderr.write('fatal: git_multimail: not in a git directory\n')
- sys.exit(1)
-
-
-class Environment(object):
- """Describes the environment in which the push is occurring.
-
- An Environment object encapsulates information about the local
- environment. For example, it knows how to determine:
-
- * the name of the repository to which the push occurred
-
- * what user did the push
-
- * what users want to be informed about various types of changes.
-
- An Environment object is expected to have the following methods:
-
- get_repo_shortname()
-
- Return a short name for the repository, for display
- purposes.
-
- get_repo_path()
-
- Return the absolute path to the Git repository.
-
- get_emailprefix()
-
- Return a string that will be prefixed to every email's
- subject.
-
- get_pusher()
-
- Return the username of the person who pushed the changes.
- This value is used in the email body to indicate who
- pushed the change.
-
- get_pusher_email() (may return None)
-
- Return the email address of the person who pushed the
- changes. The value should be a single RFC 2822 email
- address as a string; e.g., "Joe User <user@example.com>"
- if available, otherwise "user@example.com". If set, the
- value is used as the Reply-To address for refchange
- emails. If it is impossible to determine the pusher's
- email, this attribute should be set to None (in which case
- no Reply-To header will be output).
-
- get_sender()
-
- Return the address to be used as the 'From' email address
- in the email envelope.
-
- get_fromaddr(change=None)
-
- Return the 'From' email address used in the email 'From:'
- headers. If the change is known when this function is
- called, it is passed in as the 'change' parameter. (May
- be a full RFC 2822 email address like 'Joe User
- <user@example.com>'.)
-
- get_administrator()
-
- Return the name and/or email of the repository
- administrator. This value is used in the footer as the
- person to whom requests to be removed from the
- notification list should be sent. Ideally, it should
- include a valid email address.
-
- get_reply_to_refchange()
- get_reply_to_commit()
-
- Return the address to use in the email "Reply-To" header,
- as a string. These can be an RFC 2822 email address, or
- None to omit the "Reply-To" header.
- get_reply_to_refchange() is used for refchange emails;
- get_reply_to_commit() is used for individual commit
- emails.
-
- get_ref_filter_regex()
-
- Return a tuple -- a compiled regex, and a boolean indicating
- whether the regex picks refs to include (if False, the regex
- matches on refs to exclude).
-
- get_default_ref_ignore_regex()
-
- Return a regex that should be ignored for both what emails
- to send and when computing what commits are considered new
- to the repository. Default is "^refs/notes/".
-
- get_max_subject_length()
-
- Return an int giving the maximal length for the subject
- (git log --oneline).
-
- They should also define the following attributes:
-
- announce_show_shortlog (bool)
-
- True iff announce emails should include a shortlog.
-
- commit_email_format (string)
-
- If "html", generate commit emails in HTML instead of plain text
- used by default.
-
- html_in_intro (bool)
- html_in_footer (bool)
-
- When generating HTML emails, the introduction (respectively,
- the footer) will be HTML-escaped iff html_in_intro (respectively,
- the footer) is true. When false, only the values used to expand
- the template are escaped.
-
- refchange_showgraph (bool)
-
- True iff refchanges emails should include a detailed graph.
-
- refchange_showlog (bool)
-
- True iff refchanges emails should include a detailed log.
-
- diffopts (list of strings)
-
- The options that should be passed to 'git diff' for the
- summary email. The value should be a list of strings
- representing words to be passed to the command.
-
- graphopts (list of strings)
-
- Analogous to diffopts, but contains options passed to
- 'git log --graph' when generating the detailed graph for
- a set of commits (see refchange_showgraph)
-
- logopts (list of strings)
-
- Analogous to diffopts, but contains options passed to
- 'git log' when generating the detailed log for a set of
- commits (see refchange_showlog)
-
- commitlogopts (list of strings)
-
- The options that should be passed to 'git log' for each
- commit mail. The value should be a list of strings
- representing words to be passed to the command.
-
- date_substitute (string)
-
- String to be used in substitution for 'Date:' at start of
- line in the output of 'git log'.
-
- quiet (bool)
- On success do not write to stderr
-
- stdout (bool)
- Write email to stdout rather than emailing. Useful for debugging
-
- combine_when_single_commit (bool)
-
- True if a combined email should be produced when a single
- new commit is pushed to a branch, False otherwise.
-
- from_refchange, from_commit (strings)
-
- Addresses to use for the From: field for refchange emails
- and commit emails respectively. Set from
- multimailhook.fromRefchange and multimailhook.fromCommit
- by ConfigEnvironmentMixin.
-
- log_file, error_log_file, debug_log_file (string)
-
- Name of a file to which logs should be sent.
-
- verbose (int)
-
- How verbose the system should be.
- - 0 (default): show info, errors, ...
- - 1 : show basic debug info
- """
-
- REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
-
- def __init__(self, osenv=None):
- self.osenv = osenv or os.environ
- self.announce_show_shortlog = False
- self.commit_email_format = "text"
- self.html_in_intro = False
- self.html_in_footer = False
- self.commitBrowseURL = None
- self.maxcommitemails = 500
- self.excludemergerevisions = False
- self.diffopts = ['--stat', '--summary', '--find-copies-harder']
- self.graphopts = ['--oneline', '--decorate']
- self.logopts = []
- self.refchange_showgraph = False
- self.refchange_showlog = False
- self.commitlogopts = ['-C', '--stat', '-p', '--cc']
- self.date_substitute = 'AuthorDate: '
- self.quiet = False
- self.stdout = False
- self.combine_when_single_commit = True
- self.logger = None
-
- self.COMPUTED_KEYS = [
- 'administrator',
- 'charset',
- 'emailprefix',
- 'pusher',
- 'pusher_email',
- 'repo_path',
- 'repo_shortname',
- 'sender',
- ]
-
- self._values = None
-
- def get_logger(self):
- """Get (possibly creates) the logger associated to this environment."""
- if self.logger is None:
- self.logger = Logger(self)
- return self.logger
-
- def get_repo_shortname(self):
- """Use the last part of the repo path, with ".git" stripped off if present."""
-
- basename = os.path.basename(os.path.abspath(self.get_repo_path()))
- m = self.REPO_NAME_RE.match(basename)
- if m:
- return m.group('name')
- else:
- return basename
-
- def get_pusher(self):
- raise NotImplementedError()
-
- def get_pusher_email(self):
- return None
-
- def get_fromaddr(self, change=None):
- config = Config('user')
- fromname = config.get('name', default='')
- fromemail = config.get('email', default='')
- if fromemail:
- return formataddr([fromname, fromemail])
- return self.get_sender()
-
- def get_administrator(self):
- return 'the administrator of this repository'
-
- def get_emailprefix(self):
- return ''
-
- def get_repo_path(self):
- if read_git_output(['rev-parse', '--is-bare-repository']) == 'true':
- path = get_git_dir()
- else:
- path = read_git_output(['rev-parse', '--show-toplevel'])
- return os.path.abspath(path)
-
- def get_charset(self):
- return CHARSET
-
- def get_values(self):
- """Return a dictionary {keyword: expansion} for this Environment.
-
- This method is called by Change._compute_values(). The keys
- in the returned dictionary are available to be used in any of
- the templates. The dictionary is created by calling
- self.get_NAME() for each of the attributes named in
- COMPUTED_KEYS and recording those that do not return None.
- The return value is always a new dictionary."""
-
- if self._values is None:
- values = {'': ''} # %()s expands to the empty string.
-
- for key in self.COMPUTED_KEYS:
- value = getattr(self, 'get_%s' % (key,))()
- if value is not None:
- values[key] = value
-
- self._values = values
-
- return self._values.copy()
-
- def get_refchange_recipients(self, refchange):
- """Return the recipients for notifications about refchange.
-
- Return the list of email addresses to which notifications
- about the specified ReferenceChange should be sent."""
-
- raise NotImplementedError()
-
- def get_announce_recipients(self, annotated_tag_change):
- """Return the recipients for notifications about annotated_tag_change.
-
- Return the list of email addresses to which notifications
- about the specified AnnotatedTagChange should be sent."""
-
- raise NotImplementedError()
-
- def get_reply_to_refchange(self, refchange):
- return self.get_pusher_email()
-
- def get_revision_recipients(self, revision):
- """Return the recipients for messages about revision.
-
- Return the list of email addresses to which notifications
- about the specified Revision should be sent. This method
- could be overridden, for example, to take into account the
- contents of the revision when deciding whom to notify about
- it. For example, there could be a scheme for users to express
- interest in particular files or subdirectories, and only
- receive notification emails for revisions that affecting those
- files."""
-
- raise NotImplementedError()
-
- def get_reply_to_commit(self, revision):
- return revision.author
-
- def get_default_ref_ignore_regex(self):
- # The commit messages of git notes are essentially meaningless
- # and "filenames" in git notes commits are an implementational
- # detail that might surprise users at first. As such, we
- # would need a completely different method for handling emails
- # of git notes in order for them to be of benefit for users,
- # which we simply do not have right now.
- return "^refs/notes/"
-
- def get_max_subject_length(self):
- """Return the maximal subject line (git log --oneline) length.
- Longer subject lines will be truncated."""
- raise NotImplementedError()
-
- def filter_body(self, lines):
- """Filter the lines intended for an email body.
-
- lines is an iterable over the lines that would go into the
- email body. Filter it (e.g., limit the number of lines, the
- line length, character set, etc.), returning another iterable.
- See FilterLinesEnvironmentMixin and MaxlinesEnvironmentMixin
- for classes implementing this functionality."""
-
- return lines
-
- def log_msg(self, msg):
- """Write the string msg on a log file or on stderr.
-
- Sends the text to stderr by default, override to change the behavior."""
- self.get_logger().info(msg)
-
- def log_warning(self, msg):
- """Write the string msg on a log file or on stderr.
-
- Sends the text to stderr by default, override to change the behavior."""
- self.get_logger().warning(msg)
-
- def log_error(self, msg):
- """Write the string msg on a log file or on stderr.
-
- Sends the text to stderr by default, override to change the behavior."""
- self.get_logger().error(msg)
-
- def check(self):
- pass
-
-
-class ConfigEnvironmentMixin(Environment):
- """A mixin that sets self.config to its constructor's config argument.
-
- This class's constructor consumes the "config" argument.
-
- Mixins that need to inspect the config should inherit from this
- class (1) to make sure that "config" is still in the constructor
- arguments with its own constructor runs and/or (2) to be sure that
- self.config is set after construction."""
-
- def __init__(self, config, **kw):
- super(ConfigEnvironmentMixin, self).__init__(**kw)
- self.config = config
-
-
-class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
- """An Environment that reads most of its information from "git config"."""
-
- @staticmethod
- def forbid_field_values(name, value, forbidden):
- for forbidden_val in forbidden:
- if value is not None and value.lower() == forbidden:
- raise ConfigurationException(
- '"%s" is not an allowed setting for %s' % (value, name)
- )
-
- def __init__(self, config, **kw):
- super(ConfigOptionsEnvironmentMixin, self).__init__(
- config=config, **kw
- )
-
- for var, cfg in (
- ('announce_show_shortlog', 'announceshortlog'),
- ('refchange_showgraph', 'refchangeShowGraph'),
- ('refchange_showlog', 'refchangeshowlog'),
- ('quiet', 'quiet'),
- ('stdout', 'stdout'),
- ):
- val = config.get_bool(cfg)
- if val is not None:
- setattr(self, var, val)
-
- commit_email_format = config.get('commitEmailFormat')
- if commit_email_format is not None:
- if commit_email_format != "html" and commit_email_format != "text":
- self.log_warning(
- '*** Unknown value for multimailhook.commitEmailFormat: %s\n' %
- commit_email_format +
- '*** Expected either "text" or "html". Ignoring.\n'
- )
- else:
- self.commit_email_format = commit_email_format
-
- html_in_intro = config.get_bool('htmlInIntro')
- if html_in_intro is not None:
- self.html_in_intro = html_in_intro
-
- html_in_footer = config.get_bool('htmlInFooter')
- if html_in_footer is not None:
- self.html_in_footer = html_in_footer
-
- self.commitBrowseURL = config.get('commitBrowseURL')
-
- self.excludemergerevisions = config.get('excludeMergeRevisions')
-
- maxcommitemails = config.get('maxcommitemails')
- if maxcommitemails is not None:
- try:
- self.maxcommitemails = int(maxcommitemails)
- except ValueError:
- self.log_warning(
- '*** Malformed value for multimailhook.maxCommitEmails: %s\n'
- % maxcommitemails +
- '*** Expected a number. Ignoring.\n'
- )
-
- diffopts = config.get('diffopts')
- if diffopts is not None:
- self.diffopts = shlex.split(diffopts)
-
- graphopts = config.get('graphOpts')
- if graphopts is not None:
- self.graphopts = shlex.split(graphopts)
-
- logopts = config.get('logopts')
- if logopts is not None:
- self.logopts = shlex.split(logopts)
-
- commitlogopts = config.get('commitlogopts')
- if commitlogopts is not None:
- self.commitlogopts = shlex.split(commitlogopts)
-
- date_substitute = config.get('dateSubstitute')
- if date_substitute == 'none':
- self.date_substitute = None
- elif date_substitute is not None:
- self.date_substitute = date_substitute
-
- reply_to = config.get('replyTo')
- self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
- self.forbid_field_values('replyToRefchange',
- self.__reply_to_refchange,
- ['author'])
- self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
-
- self.from_refchange = config.get('fromRefchange')
- self.forbid_field_values('fromRefchange',
- self.from_refchange,
- ['author', 'none'])
- self.from_commit = config.get('fromCommit')
- self.forbid_field_values('fromCommit',
- self.from_commit,
- ['none'])
-
- combine = config.get_bool('combineWhenSingleCommit')
- if combine is not None:
- self.combine_when_single_commit = combine
-
- self.log_file = config.get('logFile', default=None)
- self.error_log_file = config.get('errorLogFile', default=None)
- self.debug_log_file = config.get('debugLogFile', default=None)
- if config.get_bool('Verbose', default=False):
- self.verbose = 1
- else:
- self.verbose = 0
-
- def get_administrator(self):
- return (
- self.config.get('administrator') or
- self.get_sender() or
- super(ConfigOptionsEnvironmentMixin, self).get_administrator()
- )
-
- def get_repo_shortname(self):
- return (
- self.config.get('reponame') or
- super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
- )
-
- def get_emailprefix(self):
- emailprefix = self.config.get('emailprefix')
- if emailprefix is not None:
- emailprefix = emailprefix.strip()
- if emailprefix:
- emailprefix += ' '
- else:
- emailprefix = '[%(repo_shortname)s] '
- short_name = self.get_repo_shortname()
- try:
- return emailprefix % {'repo_shortname': short_name}
- except:
- self.get_logger().error(
- '*** Invalid multimailhook.emailPrefix: %s\n' % emailprefix +
- '*** %s\n' % sys.exc_info()[1] +
- "*** Only the '%(repo_shortname)s' placeholder is allowed\n"
- )
- raise ConfigurationException(
- '"%s" is not an allowed setting for emailPrefix' % emailprefix
- )
-
- def get_sender(self):
- return self.config.get('envelopesender')
-
- def process_addr(self, addr, change):
- if addr.lower() == 'author':
- if hasattr(change, 'author'):
- return change.author
- else:
- return None
- elif addr.lower() == 'pusher':
- return self.get_pusher_email()
- elif addr.lower() == 'none':
- return None
- else:
- return addr
-
- def get_fromaddr(self, change=None):
- fromaddr = self.config.get('from')
- if change:
- specific_fromaddr = change.get_specific_fromaddr()
- if specific_fromaddr:
- fromaddr = specific_fromaddr
- if fromaddr:
- fromaddr = self.process_addr(fromaddr, change)
- if fromaddr:
- return fromaddr
- return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr(change)
-
- def get_reply_to_refchange(self, refchange):
- if self.__reply_to_refchange is None:
- return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange)
- else:
- return self.process_addr(self.__reply_to_refchange, refchange)
-
- def get_reply_to_commit(self, revision):
- if self.__reply_to_commit is None:
- return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
- else:
- return self.process_addr(self.__reply_to_commit, revision)
-
- def get_scancommitforcc(self):
- return self.config.get('scancommitforcc')
-
-
-class FilterLinesEnvironmentMixin(Environment):
- """Handle encoding and maximum line length of body lines.
-
- email_max_line_length (int or None)
-
- The maximum length of any single line in the email body.
- Longer lines are truncated at that length with ' [...]'
- appended.
-
- strict_utf8 (bool)
-
- If this field is set to True, then the email body text is
- expected to be UTF-8. Any invalid characters are
- converted to U+FFFD, the Unicode replacement character
- (encoded as UTF-8, of course).
-
- """
-
- def __init__(self, strict_utf8=True,
- email_max_line_length=500, max_subject_length=500,
- **kw):
- super(FilterLinesEnvironmentMixin, self).__init__(**kw)
- self.__strict_utf8 = strict_utf8
- self.__email_max_line_length = email_max_line_length
- self.__max_subject_length = max_subject_length
-
- def filter_body(self, lines):
- lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
- if self.__strict_utf8:
- if not PYTHON3:
- lines = (line.decode(ENCODING, 'replace') for line in lines)
- # Limit the line length in Unicode-space to avoid
- # splitting characters:
- if self.__email_max_line_length > 0:
- lines = limit_linelength(lines, self.__email_max_line_length)
- if not PYTHON3:
- lines = (line.encode(ENCODING, 'replace') for line in lines)
- elif self.__email_max_line_length:
- lines = limit_linelength(lines, self.__email_max_line_length)
-
- return lines
-
- def get_max_subject_length(self):
- return self.__max_subject_length
-
-
-class ConfigFilterLinesEnvironmentMixin(
- ConfigEnvironmentMixin,
- FilterLinesEnvironmentMixin,
- ):
- """Handle encoding and maximum line length based on config."""
-
- def __init__(self, config, **kw):
- strict_utf8 = config.get_bool('emailstrictutf8', default=None)
- if strict_utf8 is not None:
- kw['strict_utf8'] = strict_utf8
-
- email_max_line_length = config.get('emailmaxlinelength')
- if email_max_line_length is not None:
- kw['email_max_line_length'] = int(email_max_line_length)
-
- max_subject_length = config.get('subjectMaxLength', default=email_max_line_length)
- if max_subject_length is not None:
- kw['max_subject_length'] = int(max_subject_length)
-
- super(ConfigFilterLinesEnvironmentMixin, self).__init__(
- config=config, **kw
- )
-
-
-class MaxlinesEnvironmentMixin(Environment):
- """Limit the email body to a specified number of lines."""
-
- def __init__(self, emailmaxlines, **kw):
- super(MaxlinesEnvironmentMixin, self).__init__(**kw)
- self.__emailmaxlines = emailmaxlines
-
- def filter_body(self, lines):
- lines = super(MaxlinesEnvironmentMixin, self).filter_body(lines)
- if self.__emailmaxlines > 0:
- lines = limit_lines(lines, self.__emailmaxlines)
- return lines
-
-
-class ConfigMaxlinesEnvironmentMixin(
- ConfigEnvironmentMixin,
- MaxlinesEnvironmentMixin,
- ):
- """Limit the email body to the number of lines specified in config."""
-
- def __init__(self, config, **kw):
- emailmaxlines = int(config.get('emailmaxlines', default='0'))
- super(ConfigMaxlinesEnvironmentMixin, self).__init__(
- config=config,
- emailmaxlines=emailmaxlines,
- **kw
- )
-
-
-class FQDNEnvironmentMixin(Environment):
- """A mixin that sets the host's FQDN to its constructor argument."""
-
- def __init__(self, fqdn, **kw):
- super(FQDNEnvironmentMixin, self).__init__(**kw)
- self.COMPUTED_KEYS += ['fqdn']
- self.__fqdn = fqdn
-
- def get_fqdn(self):
- """Return the fully-qualified domain name for this host.
-
- Return None if it is unavailable or unwanted."""
-
- return self.__fqdn
-
-
-class ConfigFQDNEnvironmentMixin(
- ConfigEnvironmentMixin,
- FQDNEnvironmentMixin,
- ):
- """Read the FQDN from the config."""
-
- def __init__(self, config, **kw):
- fqdn = config.get('fqdn')
- super(ConfigFQDNEnvironmentMixin, self).__init__(
- config=config,
- fqdn=fqdn,
- **kw
- )
-
-
-class ComputeFQDNEnvironmentMixin(FQDNEnvironmentMixin):
- """Get the FQDN by calling socket.getfqdn()."""
-
- def __init__(self, **kw):
- super(ComputeFQDNEnvironmentMixin, self).__init__(
- fqdn=socket.getfqdn(),
- **kw
- )
-
-
-class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin):
- """Deduce pusher_email from pusher by appending an emaildomain."""
-
- def __init__(self, **kw):
- super(PusherDomainEnvironmentMixin, self).__init__(**kw)
- self.__emaildomain = self.config.get('emaildomain')
-
- def get_pusher_email(self):
- if self.__emaildomain:
- # Derive the pusher's full email address in the default way:
- return '%s@%s' % (self.get_pusher(), self.__emaildomain)
- else:
- return super(PusherDomainEnvironmentMixin, self).get_pusher_email()
-
-
-class StaticRecipientsEnvironmentMixin(Environment):
- """Set recipients statically based on constructor parameters."""
-
- def __init__(
- self,
- refchange_recipients, announce_recipients, revision_recipients, scancommitforcc,
- **kw
- ):
- super(StaticRecipientsEnvironmentMixin, self).__init__(**kw)
-
- # The recipients for various types of notification emails, as
- # RFC 2822 email addresses separated by commas (or the empty
- # string if no recipients are configured). Although there is
- # a mechanism to choose the recipient lists based on on the
- # actual *contents* of the change being reported, we only
- # choose based on the *type* of the change. Therefore we can
- # compute them once and for all:
- self.__refchange_recipients = refchange_recipients
- self.__announce_recipients = announce_recipients
- self.__revision_recipients = revision_recipients
-
- def check(self):
- if not (self.get_refchange_recipients(None) or
- self.get_announce_recipients(None) or
- self.get_revision_recipients(None) or
- self.get_scancommitforcc()):
- raise ConfigurationException('No email recipients configured!')
- super(StaticRecipientsEnvironmentMixin, self).check()
-
- def get_refchange_recipients(self, refchange):
- if self.__refchange_recipients is None:
- return super(StaticRecipientsEnvironmentMixin,
- self).get_refchange_recipients(refchange)
- return self.__refchange_recipients
-
- def get_announce_recipients(self, annotated_tag_change):
- if self.__announce_recipients is None:
- return super(StaticRecipientsEnvironmentMixin,
- self).get_refchange_recipients(annotated_tag_change)
- return self.__announce_recipients
-
- def get_revision_recipients(self, revision):
- if self.__revision_recipients is None:
- return super(StaticRecipientsEnvironmentMixin,
- self).get_refchange_recipients(revision)
- return self.__revision_recipients
-
-
-class CLIRecipientsEnvironmentMixin(Environment):
- """Mixin storing recipients information coming from the
- command-line."""
-
- def __init__(self, cli_recipients=None, **kw):
- super(CLIRecipientsEnvironmentMixin, self).__init__(**kw)
- self.__cli_recipients = cli_recipients
-
- def get_refchange_recipients(self, refchange):
- if self.__cli_recipients is None:
- return super(CLIRecipientsEnvironmentMixin,
- self).get_refchange_recipients(refchange)
- return self.__cli_recipients
-
- def get_announce_recipients(self, annotated_tag_change):
- if self.__cli_recipients is None:
- return super(CLIRecipientsEnvironmentMixin,
- self).get_announce_recipients(annotated_tag_change)
- return self.__cli_recipients
-
- def get_revision_recipients(self, revision):
- if self.__cli_recipients is None:
- return super(CLIRecipientsEnvironmentMixin,
- self).get_revision_recipients(revision)
- return self.__cli_recipients
-
-
-class ConfigRecipientsEnvironmentMixin(
- ConfigEnvironmentMixin,
- StaticRecipientsEnvironmentMixin
- ):
- """Determine recipients statically based on config."""
-
- def __init__(self, config, **kw):
- super(ConfigRecipientsEnvironmentMixin, self).__init__(
- config=config,
- refchange_recipients=self._get_recipients(
- config, 'refchangelist', 'mailinglist',
- ),
- announce_recipients=self._get_recipients(
- config, 'announcelist', 'refchangelist', 'mailinglist',
- ),
- revision_recipients=self._get_recipients(
- config, 'commitlist', 'mailinglist',
- ),
- scancommitforcc=config.get('scancommitforcc'),
- **kw
- )
-
- def _get_recipients(self, config, *names):
- """Return the recipients for a particular type of message.
-
- Return the list of email addresses to which a particular type
- of notification email should be sent, by looking at the config
- value for "multimailhook.$name" for each of names. Use the
- value from the first name that is configured. The return
- value is a (possibly empty) string containing RFC 2822 email
- addresses separated by commas. If no configuration could be
- found, raise a ConfigurationException."""
-
- for name in names:
- lines = config.get_all(name)
- if lines is not None:
- lines = [line.strip() for line in lines]
- # Single "none" is a special value equivalen to empty string.
- if lines == ['none']:
- lines = ['']
- return ', '.join(lines)
- else:
- return ''
-
-
-class StaticRefFilterEnvironmentMixin(Environment):
- """Set branch filter statically based on constructor parameters."""
-
- def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex,
- ref_filter_do_send_regex, ref_filter_dont_send_regex,
- **kw):
- super(StaticRefFilterEnvironmentMixin, self).__init__(**kw)
-
- if ref_filter_incl_regex and ref_filter_excl_regex:
- raise ConfigurationException(
- "Cannot specify both a ref inclusion and exclusion regex.")
- self.__is_inclusion_filter = bool(ref_filter_incl_regex)
- default_exclude = self.get_default_ref_ignore_regex()
- if ref_filter_incl_regex:
- ref_filter_regex = ref_filter_incl_regex
- elif ref_filter_excl_regex:
- ref_filter_regex = ref_filter_excl_regex + '|' + default_exclude
- else:
- ref_filter_regex = default_exclude
- try:
- self.__compiled_regex = re.compile(ref_filter_regex)
- except Exception:
- raise ConfigurationException(
- 'Invalid Ref Filter Regex "%s": %s' % (ref_filter_regex, sys.exc_info()[1]))
-
- if ref_filter_do_send_regex and ref_filter_dont_send_regex:
- raise ConfigurationException(
- "Cannot specify both a ref doSend and dontSend regex.")
- self.__is_do_send_filter = bool(ref_filter_do_send_regex)
- if ref_filter_do_send_regex:
- ref_filter_send_regex = ref_filter_do_send_regex
- elif ref_filter_dont_send_regex:
- ref_filter_send_regex = ref_filter_dont_send_regex
- else:
- ref_filter_send_regex = '.*'
- self.__is_do_send_filter = True
- try:
- self.__send_compiled_regex = re.compile(ref_filter_send_regex)
- except Exception:
- raise ConfigurationException(
- 'Invalid Ref Filter Regex "%s": %s' %
- (ref_filter_send_regex, sys.exc_info()[1]))
-
- def get_ref_filter_regex(self, send_filter=False):
- if send_filter:
- return self.__send_compiled_regex, self.__is_do_send_filter
- else:
- return self.__compiled_regex, self.__is_inclusion_filter
-
-
-class ConfigRefFilterEnvironmentMixin(
- ConfigEnvironmentMixin,
- StaticRefFilterEnvironmentMixin
- ):
- """Determine branch filtering statically based on config."""
-
- def _get_regex(self, config, key):
- """Get a list of whitespace-separated regex. The refFilter* config
- variables are multivalued (hence the use of get_all), and we
- allow each entry to be a whitespace-separated list (hence the
- split on each line). The whole thing is glued into a single regex."""
- values = config.get_all(key)
- if values is None:
- return values
- items = []
- for line in values:
- for i in line.split():
- items.append(i)
- if items == []:
- return None
- return '|'.join(items)
-
- def __init__(self, config, **kw):
- super(ConfigRefFilterEnvironmentMixin, self).__init__(
- config=config,
- ref_filter_incl_regex=self._get_regex(config, 'refFilterInclusionRegex'),
- ref_filter_excl_regex=self._get_regex(config, 'refFilterExclusionRegex'),
- ref_filter_do_send_regex=self._get_regex(config, 'refFilterDoSendRegex'),
- ref_filter_dont_send_regex=self._get_regex(config, 'refFilterDontSendRegex'),
- **kw
- )
-
-
-class ProjectdescEnvironmentMixin(Environment):
- """Make a "projectdesc" value available for templates.
-
- By default, it is set to the first line of $GIT_DIR/description
- (if that file is present and appears to be set meaningfully)."""
-
- def __init__(self, **kw):
- super(ProjectdescEnvironmentMixin, self).__init__(**kw)
- self.COMPUTED_KEYS += ['projectdesc']
-
- def get_projectdesc(self):
- """Return a one-line description of the project."""
-
- git_dir = get_git_dir()
- try:
- projectdesc = open(os.path.join(git_dir, 'description')).readline().strip()
- if projectdesc and not projectdesc.startswith('Unnamed repository'):
- return projectdesc
- except IOError:
- pass
-
- return 'UNNAMED PROJECT'
-
-
-class GenericEnvironmentMixin(Environment):
- def get_pusher(self):
- return self.osenv.get('USER', self.osenv.get('USERNAME', 'unknown user'))
-
-
-class GitoliteEnvironmentHighPrecMixin(Environment):
- def get_pusher(self):
- return self.osenv.get('GL_USER', 'unknown user')
-
-
-class GitoliteEnvironmentLowPrecMixin(
- ConfigEnvironmentMixin,
- Environment):
-
- def get_repo_shortname(self):
- # The gitolite environment variable $GL_REPO is a pretty good
- # repo_shortname (though it's probably not as good as a value
- # the user might have explicitly put in his config).
- return (
- self.osenv.get('GL_REPO', None) or
- super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname()
- )
-
- @staticmethod
- def _compile_regex(re_template):
- return (
- re.compile(re_template % x)
- for x in (
- r'BEGIN\s+USER\s+EMAILS',
- r'([^\s]+)\s+(.*)',
- r'END\s+USER\s+EMAILS',
- ))
-
- def get_fromaddr(self, change=None):
- GL_USER = self.osenv.get('GL_USER')
- if GL_USER is not None:
- # Find the path to gitolite.conf. Note that gitolite v3
- # did away with the GL_ADMINDIR and GL_CONF environment
- # variables (they are now hard-coded).
- GL_ADMINDIR = self.osenv.get(
- 'GL_ADMINDIR',
- os.path.expanduser(os.path.join('~', '.gitolite')))
- GL_CONF = self.osenv.get(
- 'GL_CONF',
- os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf'))
-
- mailaddress_map = self.config.get('MailaddressMap')
- # If relative, consider relative to GL_CONF:
- if mailaddress_map:
- mailaddress_map = os.path.join(os.path.dirname(GL_CONF),
- mailaddress_map)
- if os.path.isfile(mailaddress_map):
- f = open(mailaddress_map, 'rU')
- try:
- # Leading '#' is optional
- re_begin, re_user, re_end = self._compile_regex(
- r'^(?:\s*#)?\s*%s\s*$')
- for l in f:
- l = l.rstrip('\n')
- if re_begin.match(l) or re_end.match(l):
- continue # Ignore these lines
- m = re_user.match(l)
- if m:
- if m.group(1) == GL_USER:
- return m.group(2)
- else:
- continue # Not this user, but not an error
- raise ConfigurationException(
- "Syntax error in mail address map.\n"
- "Check file {}.\n"
- "Line: {}".format(mailaddress_map, l))
-
- finally:
- f.close()
-
- if os.path.isfile(GL_CONF):
- f = open(GL_CONF, 'rU')
- try:
- in_user_emails_section = False
- re_begin, re_user, re_end = self._compile_regex(
- r'^\s*#\s*%s\s*$')
- for l in f:
- l = l.rstrip('\n')
- if not in_user_emails_section:
- if re_begin.match(l):
- in_user_emails_section = True
- continue
- if re_end.match(l):
- break
- m = re_user.match(l)
- if m and m.group(1) == GL_USER:
- return m.group(2)
- finally:
- f.close()
- return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change)
-
-
-class IncrementalDateTime(object):
- """Simple wrapper to give incremental date/times.
-
- Each call will result in a date/time a second later than the
- previous call. This can be used to falsify email headers, to
- increase the likelihood that email clients sort the emails
- correctly."""
-
- def __init__(self):
- self.time = time.time()
- self.next = self.__next__ # Python 2 backward compatibility
-
- def __next__(self):
- formatted = formatdate(self.time, True)
- self.time += 1
- return formatted
-
-
-class StashEnvironmentHighPrecMixin(Environment):
- def __init__(self, user=None, repo=None, **kw):
- super(StashEnvironmentHighPrecMixin,
- self).__init__(user=user, repo=repo, **kw)
- self.__user = user
- self.__repo = repo
-
- def get_pusher(self):
- return re.match(r'(.*?)\s*<', self.__user).group(1)
-
- def get_pusher_email(self):
- return self.__user
-
-
-class StashEnvironmentLowPrecMixin(Environment):
- def __init__(self, user=None, repo=None, **kw):
- super(StashEnvironmentLowPrecMixin, self).__init__(**kw)
- self.__repo = repo
- self.__user = user
-
- def get_repo_shortname(self):
- return self.__repo
-
- def get_fromaddr(self, change=None):
- return self.__user
-
-
-class GerritEnvironmentHighPrecMixin(Environment):
- def __init__(self, project=None, submitter=None, update_method=None, **kw):
- super(GerritEnvironmentHighPrecMixin,
- self).__init__(submitter=submitter, project=project, **kw)
- self.__project = project
- self.__submitter = submitter
- self.__update_method = update_method
- "Make an 'update_method' value available for templates."
- self.COMPUTED_KEYS += ['update_method']
-
- def get_pusher(self):
- if self.__submitter:
- if self.__submitter.find('<') != -1:
- # Submitter has a configured email, we transformed
- # __submitter into an RFC 2822 string already.
- return re.match(r'(.*?)\s*<', self.__submitter).group(1)
- else:
- # Submitter has no configured email, it's just his name.
- return self.__submitter
- else:
- # If we arrive here, this means someone pushed "Submit" from
- # the gerrit web UI for the CR (or used one of the programmatic
- # APIs to do the same, such as gerrit review) and the
- # merge/push was done by the Gerrit user. It was technically
- # triggered by someone else, but sadly we have no way of
- # determining who that someone else is at this point.
- return 'Gerrit' # 'unknown user'?
-
- def get_pusher_email(self):
- if self.__submitter:
- return self.__submitter
- else:
- return super(GerritEnvironmentHighPrecMixin, self).get_pusher_email()
-
- def get_default_ref_ignore_regex(self):
- default = super(GerritEnvironmentHighPrecMixin, self).get_default_ref_ignore_regex()
- return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/'
-
- def get_revision_recipients(self, revision):
- # Merge commits created by Gerrit when users hit "Submit this patchset"
- # in the Web UI (or do equivalently with REST APIs or the gerrit review
- # command) are not something users want to see an individual email for.
- # Filter them out.
- committer = read_git_output(['log', '--no-walk', '--format=%cN',
- revision.rev.sha1])
- if committer == 'Gerrit Code Review':
- return []
- else:
- return super(GerritEnvironmentHighPrecMixin, self).get_revision_recipients(revision)
-
- def get_update_method(self):
- return self.__update_method
-
-
-class GerritEnvironmentLowPrecMixin(Environment):
- def __init__(self, project=None, submitter=None, **kw):
- super(GerritEnvironmentLowPrecMixin, self).__init__(**kw)
- self.__project = project
- self.__submitter = submitter
-
- def get_repo_shortname(self):
- return self.__project
-
- def get_fromaddr(self, change=None):
- if self.__submitter and self.__submitter.find('<') != -1:
- return self.__submitter
- else:
- return super(GerritEnvironmentLowPrecMixin, self).get_fromaddr(change)
-
-
-class Push(object):
- """Represent an entire push (i.e., a group of ReferenceChanges).
-
- It is easy to figure out what commits were added to a *branch* by
- a Reference change:
-
- git rev-list change.old..change.new
-
- or removed from a *branch*:
-
- git rev-list change.new..change.old
-
- But it is not quite so trivial to determine which entirely new
- commits were added to the *repository* by a push and which old
- commits were discarded by a push. A big part of the job of this
- class is to figure out these things, and to make sure that new
- commits are only detailed once even if they were added to multiple
- references.
-
- The first step is to determine the "other" references--those
- unaffected by the current push. They are computed by listing all
- references then removing any affected by this push. The results
- are stored in Push._other_ref_sha1s.
-
- The commits contained in the repository before this push were
-
- git rev-list other1 other2 other3 ... change1.old change2.old ...
-
- Where "changeN.old" is the old value of one of the references
- affected by this push.
-
- The commits contained in the repository after this push are
-
- git rev-list other1 other2 other3 ... change1.new change2.new ...
-
- The commits added by this push are the difference between these
- two sets, which can be written
-
- git rev-list \
- ^other1 ^other2 ... \
- ^change1.old ^change2.old ... \
- change1.new change2.new ...
-
- The commits removed by this push can be computed by
-
- git rev-list \
- ^other1 ^other2 ... \
- ^change1.new ^change2.new ... \
- change1.old change2.old ...
-
- The last point is that it is possible that other pushes are
- occurring simultaneously to this one, so reference values can
- change at any time. It is impossible to eliminate all race
- conditions, but we reduce the window of time during which problems
- can occur by translating reference names to SHA1s as soon as
- possible and working with SHA1s thereafter (because SHA1s are
- immutable)."""
-
- # A map {(changeclass, changetype): integer} specifying the order
- # that reference changes will be processed if multiple reference
- # changes are included in a single push. The order is significant
- # mostly because new commit notifications are threaded together
- # with the first reference change that includes the commit. The
- # following order thus causes commits to be grouped with branch
- # changes (as opposed to tag changes) if possible.
- SORT_ORDER = dict(
- (value, i) for (i, value) in enumerate([
- (BranchChange, 'update'),
- (BranchChange, 'create'),
- (AnnotatedTagChange, 'update'),
- (AnnotatedTagChange, 'create'),
- (NonAnnotatedTagChange, 'update'),
- (NonAnnotatedTagChange, 'create'),
- (BranchChange, 'delete'),
- (AnnotatedTagChange, 'delete'),
- (NonAnnotatedTagChange, 'delete'),
- (OtherReferenceChange, 'update'),
- (OtherReferenceChange, 'create'),
- (OtherReferenceChange, 'delete'),
- ])
- )
-
- def __init__(self, environment, changes, ignore_other_refs=False):
- self.changes = sorted(changes, key=self._sort_key)
- self.__other_ref_sha1s = None
- self.__cached_commits_spec = {}
- self.environment = environment
-
- if ignore_other_refs:
- self.__other_ref_sha1s = set()
-
- @classmethod
- def _sort_key(klass, change):
- return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,)
-
- @property
- def _other_ref_sha1s(self):
- """The GitObjects referred to by references unaffected by this push.
- """
- if self.__other_ref_sha1s is None:
- # The refnames being changed by this push:
- updated_refs = set(
- change.refname
- for change in self.changes
- )
-
- # The SHA-1s of commits referred to by all references in this
- # repository *except* updated_refs:
- sha1s = set()
- fmt = (
- '%(objectname) %(objecttype) %(refname)\n'
- '%(*objectname) %(*objecttype) %(refname)'
- )
- ref_filter_regex, is_inclusion_filter = \
- self.environment.get_ref_filter_regex()
- for line in read_git_lines(
- ['for-each-ref', '--format=%s' % (fmt,)]):
- (sha1, type, name) = line.split(' ', 2)
- if (sha1 and type == 'commit' and
- name not in updated_refs and
- include_ref(name, ref_filter_regex, is_inclusion_filter)):
- sha1s.add(sha1)
-
- self.__other_ref_sha1s = sha1s
-
- return self.__other_ref_sha1s
-
- def _get_commits_spec_incl(self, new_or_old, reference_change=None):
- """Get new or old SHA-1 from one or each of the changed refs.
-
- Return a list of SHA-1 commit identifier strings suitable as
- arguments to 'git rev-list' (or 'git log' or ...). The
- returned identifiers are either the old or new values from one
- or all of the changed references, depending on the values of
- new_or_old and reference_change.
-
- new_or_old is either the string 'new' or the string 'old'. If
- 'new', the returned SHA-1 identifiers are the new values from
- each changed reference. If 'old', the SHA-1 identifiers are
- the old values from each changed reference.
-
- If reference_change is specified and not None, only the new or
- old reference from the specified reference is included in the
- return value.
-
- This function returns None if there are no matching revisions
- (e.g., because a branch was deleted and new_or_old is 'new').
- """
-
- if not reference_change:
- incl_spec = sorted(
- getattr(change, new_or_old).sha1
- for change in self.changes
- if getattr(change, new_or_old)
- )
-