summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore26
-rw-r--r--.mailmap2
-rw-r--r--Documentation/CodingGuidelines81
-rw-r--r--Documentation/Makefile27
-rw-r--r--Documentation/RelNotes/1.5.0.1.txt (renamed from Documentation/RelNotes-1.5.0.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.2.txt (renamed from Documentation/RelNotes-1.5.0.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.3.txt (renamed from Documentation/RelNotes-1.5.0.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.4.txt (renamed from Documentation/RelNotes-1.5.0.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.5.txt (renamed from Documentation/RelNotes-1.5.0.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.6.txt (renamed from Documentation/RelNotes-1.5.0.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.7.txt (renamed from Documentation/RelNotes-1.5.0.7.txt)0
-rw-r--r--Documentation/RelNotes/1.5.0.txt (renamed from Documentation/RelNotes-1.5.0.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.1.txt (renamed from Documentation/RelNotes-1.5.1.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.2.txt (renamed from Documentation/RelNotes-1.5.1.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.3.txt (renamed from Documentation/RelNotes-1.5.1.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.4.txt (renamed from Documentation/RelNotes-1.5.1.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.5.txt (renamed from Documentation/RelNotes-1.5.1.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.6.txt (renamed from Documentation/RelNotes-1.5.1.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.1.txt (renamed from Documentation/RelNotes-1.5.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.1.txt (renamed from Documentation/RelNotes-1.5.2.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.2.txt (renamed from Documentation/RelNotes-1.5.2.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.3.txt (renamed from Documentation/RelNotes-1.5.2.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.4.txt (renamed from Documentation/RelNotes-1.5.2.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.5.txt (renamed from Documentation/RelNotes-1.5.2.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.2.txt (renamed from Documentation/RelNotes-1.5.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.1.txt (renamed from Documentation/RelNotes-1.5.3.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.2.txt (renamed from Documentation/RelNotes-1.5.3.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.3.txt (renamed from Documentation/RelNotes-1.5.3.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.4.txt (renamed from Documentation/RelNotes-1.5.3.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.5.txt (renamed from Documentation/RelNotes-1.5.3.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.6.txt (renamed from Documentation/RelNotes-1.5.3.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.7.txt (renamed from Documentation/RelNotes-1.5.3.7.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.8.txt (renamed from Documentation/RelNotes-1.5.3.8.txt)0
-rw-r--r--Documentation/RelNotes/1.5.3.txt (renamed from Documentation/RelNotes-1.5.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.1.txt (renamed from Documentation/RelNotes-1.5.4.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.2.txt (renamed from Documentation/RelNotes-1.5.4.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.3.txt (renamed from Documentation/RelNotes-1.5.4.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.4.txt (renamed from Documentation/RelNotes-1.5.4.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.5.txt (renamed from Documentation/RelNotes-1.5.4.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.6.txt (renamed from Documentation/RelNotes-1.5.4.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.7.txt (renamed from Documentation/RelNotes-1.5.4.7.txt)0
-rw-r--r--Documentation/RelNotes/1.5.4.txt (renamed from Documentation/RelNotes-1.5.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.1.txt (renamed from Documentation/RelNotes-1.5.5.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.2.txt (renamed from Documentation/RelNotes-1.5.5.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.3.txt (renamed from Documentation/RelNotes-1.5.5.3.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.4.txt (renamed from Documentation/RelNotes-1.5.5.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.5.txt (renamed from Documentation/RelNotes-1.5.5.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.6.txt (renamed from Documentation/RelNotes-1.5.5.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.5.txt (renamed from Documentation/RelNotes-1.5.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.1.txt (renamed from Documentation/RelNotes-1.5.6.1.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.2.txt (renamed from Documentation/RelNotes-1.5.6.2.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.3.txt (renamed from Documentation/RelNotes-1.5.6.3.txt)2
-rw-r--r--Documentation/RelNotes/1.5.6.4.txt (renamed from Documentation/RelNotes-1.5.6.4.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.5.txt (renamed from Documentation/RelNotes-1.5.6.5.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.6.txt (renamed from Documentation/RelNotes-1.5.6.6.txt)0
-rw-r--r--Documentation/RelNotes/1.5.6.txt (renamed from Documentation/RelNotes-1.5.6.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.1.txt (renamed from Documentation/RelNotes-1.6.0.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.2.txt (renamed from Documentation/RelNotes-1.6.0.2.txt)2
-rw-r--r--Documentation/RelNotes/1.6.0.3.txt (renamed from Documentation/RelNotes-1.6.0.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.4.txt (renamed from Documentation/RelNotes-1.6.0.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.5.txt (renamed from Documentation/RelNotes-1.6.0.5.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.6.txt (renamed from Documentation/RelNotes-1.6.0.6.txt)0
-rw-r--r--Documentation/RelNotes/1.6.0.txt (renamed from Documentation/RelNotes-1.6.0.txt)0
-rw-r--r--Documentation/RelNotes/1.6.1.1.txt (renamed from Documentation/RelNotes-1.6.1.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.1.2.txt (renamed from Documentation/RelNotes-1.6.1.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.1.3.txt (renamed from Documentation/RelNotes-1.6.1.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.1.4.txt (renamed from Documentation/RelNotes-1.6.1.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.1.txt (renamed from Documentation/RelNotes-1.6.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.1.txt (renamed from Documentation/RelNotes-1.6.2.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.2.txt (renamed from Documentation/RelNotes-1.6.2.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.3.txt (renamed from Documentation/RelNotes-1.6.2.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.4.txt (renamed from Documentation/RelNotes-1.6.2.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.5.txt (renamed from Documentation/RelNotes-1.6.2.5.txt)0
-rw-r--r--Documentation/RelNotes/1.6.2.txt (renamed from Documentation/RelNotes-1.6.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.3.1.txt (renamed from Documentation/RelNotes-1.6.3.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.3.2.txt (renamed from Documentation/RelNotes-1.6.3.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.3.3.txt (renamed from Documentation/RelNotes-1.6.3.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.3.4.txt (renamed from Documentation/RelNotes-1.6.3.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.3.txt (renamed from Documentation/RelNotes-1.6.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.4.1.txt (renamed from Documentation/RelNotes-1.6.4.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.4.2.txt (renamed from Documentation/RelNotes-1.6.4.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.4.3.txt (renamed from Documentation/RelNotes-1.6.4.3.txt)2
-rw-r--r--Documentation/RelNotes/1.6.4.4.txt (renamed from Documentation/RelNotes-1.6.4.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.4.5.txt20
-rw-r--r--Documentation/RelNotes/1.6.4.txt (renamed from Documentation/RelNotes-1.6.4.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.1.txt (renamed from Documentation/RelNotes-1.6.5.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.2.txt (renamed from Documentation/RelNotes-1.6.5.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.3.txt (renamed from Documentation/RelNotes-1.6.5.3.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.4.txt (renamed from Documentation/RelNotes-1.6.5.4.txt)2
-rw-r--r--Documentation/RelNotes/1.6.5.5.txt (renamed from Documentation/RelNotes-1.6.5.5.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.6.txt (renamed from Documentation/RelNotes-1.6.5.6.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.7.txt (renamed from Documentation/RelNotes-1.6.5.7.txt)2
-rw-r--r--Documentation/RelNotes/1.6.5.8.txt (renamed from Documentation/RelNotes-1.6.5.8.txt)0
-rw-r--r--Documentation/RelNotes/1.6.5.9.txt18
-rw-r--r--Documentation/RelNotes/1.6.5.txt (renamed from Documentation/RelNotes-1.6.5.txt)0
-rw-r--r--Documentation/RelNotes/1.6.6.1.txt (renamed from Documentation/RelNotes-1.6.6.1.txt)0
-rw-r--r--Documentation/RelNotes/1.6.6.2.txt (renamed from Documentation/RelNotes-1.6.6.2.txt)0
-rw-r--r--Documentation/RelNotes/1.6.6.3.txt23
-rw-r--r--Documentation/RelNotes/1.6.6.txt (renamed from Documentation/RelNotes-1.6.6.txt)4
-rw-r--r--Documentation/RelNotes/1.7.0.1.txt (renamed from Documentation/RelNotes-1.7.0.1.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.2.txt (renamed from Documentation/RelNotes-1.7.0.2.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.3.txt (renamed from Documentation/RelNotes-1.7.0.3.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.4.txt (renamed from Documentation/RelNotes-1.7.0.4.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.5.txt (renamed from Documentation/RelNotes-1.7.0.5.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.6.txt (renamed from Documentation/RelNotes-1.7.0.6.txt)0
-rw-r--r--Documentation/RelNotes/1.7.0.7.txt16
-rw-r--r--Documentation/RelNotes/1.7.0.8.txt10
-rw-r--r--Documentation/RelNotes/1.7.0.9.txt8
-rw-r--r--Documentation/RelNotes/1.7.0.txt (renamed from Documentation/RelNotes-1.7.0.txt)2
-rw-r--r--Documentation/RelNotes/1.7.1.1.txt (renamed from Documentation/RelNotes-1.7.1.1.txt)0
-rw-r--r--Documentation/RelNotes/1.7.1.2.txt28
-rw-r--r--Documentation/RelNotes/1.7.1.3.txt10
-rw-r--r--Documentation/RelNotes/1.7.1.4.txt8
-rw-r--r--Documentation/RelNotes/1.7.1.txt (renamed from Documentation/RelNotes-1.7.1.txt)0
-rw-r--r--Documentation/RelNotes/1.7.2.1.txt25
-rw-r--r--Documentation/RelNotes/1.7.2.2.txt22
-rw-r--r--Documentation/RelNotes/1.7.2.3.txt39
-rw-r--r--Documentation/RelNotes/1.7.2.4.txt10
-rw-r--r--Documentation/RelNotes/1.7.2.5.txt8
-rw-r--r--Documentation/RelNotes/1.7.2.txt (renamed from Documentation/RelNotes-1.7.2.txt)55
-rw-r--r--Documentation/RelNotes/1.7.3.1.txt14
-rw-r--r--Documentation/RelNotes/1.7.3.2.txt5
-rw-r--r--Documentation/RelNotes/1.7.3.3.txt54
-rw-r--r--Documentation/RelNotes/1.7.3.4.txt45
-rw-r--r--Documentation/RelNotes/1.7.3.5.txt34
-rw-r--r--Documentation/RelNotes/1.7.3.txt76
-rw-r--r--Documentation/RelNotes/1.7.4.1.txt27
-rw-r--r--Documentation/RelNotes/1.7.4.2.txt58
-rw-r--r--Documentation/RelNotes/1.7.4.3.txt32
-rw-r--r--Documentation/RelNotes/1.7.4.4.txt35
-rw-r--r--Documentation/RelNotes/1.7.4.5.txt4
-rw-r--r--Documentation/RelNotes/1.7.4.txt156
-rw-r--r--Documentation/RelNotes/1.7.5.1.txt47
-rw-r--r--Documentation/RelNotes/1.7.5.2.txt57
-rw-r--r--Documentation/RelNotes/1.7.5.3.txt32
-rw-r--r--Documentation/RelNotes/1.7.5.4.txt21
-rw-r--r--Documentation/RelNotes/1.7.5.txt132
-rw-r--r--Documentation/RelNotes/1.7.6.txt136
-rw-r--r--Documentation/RelNotes/1.7.7.txt83
-rw-r--r--Documentation/SubmittingPatches264
-rw-r--r--Documentation/asciidoc.conf3
-rw-r--r--Documentation/blame-options.txt5
-rw-r--r--Documentation/config.txt323
-rw-r--r--Documentation/diff-config.txt136
-rw-r--r--Documentation/diff-generate-patch.txt51
-rw-r--r--Documentation/diff-options.txt191
-rw-r--r--Documentation/docbook.xsl5
-rw-r--r--Documentation/everyday.txt6
-rw-r--r--Documentation/fetch-options.txt41
-rw-r--r--Documentation/git-add.txt114
-rw-r--r--Documentation/git-am.txt19
-rw-r--r--Documentation/git-annotate.txt5
-rw-r--r--Documentation/git-apply.txt22
-rw-r--r--Documentation/git-archimport.txt14
-rw-r--r--Documentation/git-archive.txt50
-rw-r--r--Documentation/git-bisect-lk2009.txt4
-rw-r--r--Documentation/git-bisect.txt77
-rw-r--r--Documentation/git-blame.txt30
-rw-r--r--Documentation/git-branch.txt22
-rw-r--r--Documentation/git-bundle.txt87
-rw-r--r--Documentation/git-cat-file.txt10
-rw-r--r--Documentation/git-check-attr.txt9
-rw-r--r--Documentation/git-check-ref-format.txt11
-rw-r--r--Documentation/git-checkout-index.txt14
-rw-r--r--Documentation/git-checkout.txt220
-rw-r--r--Documentation/git-cherry-pick.txt71
-rw-r--r--Documentation/git-cherry.txt9
-rw-r--r--Documentation/git-citool.txt9
-rw-r--r--Documentation/git-clean.txt14
-rw-r--r--Documentation/git-clone.txt42
-rw-r--r--Documentation/git-commit-tree.txt12
-rw-r--r--Documentation/git-commit.txt68
-rw-r--r--Documentation/git-config.txt27
-rw-r--r--Documentation/git-count-objects.txt10
-rw-r--r--Documentation/git-cvsexportcommit.txt9
-rw-r--r--Documentation/git-cvsimport.txt13
-rw-r--r--Documentation/git-cvsserver.txt22
-rw-r--r--Documentation/git-daemon.txt63
-rw-r--r--Documentation/git-describe.txt13
-rw-r--r--Documentation/git-diff-files.txt10
-rw-r--r--Documentation/git-diff-index.txt14
-rw-r--r--Documentation/git-diff-tree.txt13
-rw-r--r--Documentation/git-diff.txt44
-rw-r--r--Documentation/git-difftool.txt25
-rw-r--r--Documentation/git-fast-export.txt20
-rw-r--r--Documentation/git-fast-import.txt193
-rw-r--r--Documentation/git-fetch-pack.txt10
-rw-r--r--Documentation/git-fetch.txt29
-rw-r--r--Documentation/git-filter-branch.txt47
-rw-r--r--Documentation/git-fmt-merge-msg.txt30
-rw-r--r--Documentation/git-for-each-ref.txt20
-rw-r--r--Documentation/git-format-patch.txt245
-rw-r--r--Documentation/git-fsck-objects.txt5
-rw-r--r--Documentation/git-fsck.txt15
-rw-r--r--Documentation/git-gc.txt20
-rw-r--r--Documentation/git-get-tar-commit-id.txt10
-rw-r--r--Documentation/git-grep.txt40
-rw-r--r--Documentation/git-gui.txt9
-rw-r--r--Documentation/git-hash-object.txt10
-rw-r--r--Documentation/git-help.txt18
-rw-r--r--Documentation/git-http-fetch.txt9
-rw-r--r--Documentation/git-http-push.txt10
-rw-r--r--Documentation/git-imap-send.txt36
-rw-r--r--Documentation/git-index-pack.txt13
-rw-r--r--Documentation/git-init-db.txt7
-rw-r--r--Documentation/git-init.txt72
-rw-r--r--Documentation/git-instaweb.txt25
-rw-r--r--Documentation/git-log.txt39
-rw-r--r--Documentation/git-lost-found.txt10
-rw-r--r--Documentation/git-ls-files.txt44
-rw-r--r--Documentation/git-ls-remote.txt12
-rw-r--r--Documentation/git-ls-tree.txt22
-rw-r--r--Documentation/git-mailinfo.txt12
-rw-r--r--Documentation/git-mailsplit.txt13
-rw-r--r--Documentation/git-merge-base.txt46
-rw-r--r--Documentation/git-merge-file.txt11
-rw-r--r--Documentation/git-merge-index.txt12
-rw-r--r--Documentation/git-merge-one-file.txt10
-rw-r--r--Documentation/git-merge-tree.txt9
-rw-r--r--Documentation/git-merge.txt61
-rw-r--r--Documentation/git-mergetool--lib.txt19
-rw-r--r--Documentation/git-mergetool.txt21
-rw-r--r--Documentation/git-mktag.txt10
-rw-r--r--Documentation/git-mktree.txt9
-rw-r--r--Documentation/git-mv.txt12
-rw-r--r--Documentation/git-name-rev.txt9
-rw-r--r--Documentation/git-notes.txt110
-rw-r--r--Documentation/git-pack-objects.txt38
-rw-r--r--Documentation/git-pack-redundant.txt9
-rw-r--r--Documentation/git-pack-refs.txt6
-rw-r--r--Documentation/git-parse-remote.txt9
-rw-r--r--Documentation/git-patch-id.txt9
-rw-r--r--Documentation/git-peek-remote.txt9
-rw-r--r--Documentation/git-prune-packed.txt9
-rw-r--r--Documentation/git-prune.txt11
-rw-r--r--Documentation/git-pull.txt117
-rw-r--r--Documentation/git-push.txt45
-rw-r--r--Documentation/git-quiltimport.txt8
-rw-r--r--Documentation/git-read-tree.txt21
-rw-r--r--Documentation/git-rebase.txt80
-rw-r--r--Documentation/git-receive-pack.txt10
-rw-r--r--Documentation/git-reflog.txt11
-rw-r--r--Documentation/git-relink.txt11
-rw-r--r--Documentation/git-remote-ext.txt126
-rw-r--r--Documentation/git-remote-fd.txt59
-rw-r--r--Documentation/git-remote-helpers.txt17
-rw-r--r--Documentation/git-remote.txt52
-rw-r--r--Documentation/git-repack.txt26
-rw-r--r--Documentation/git-replace.txt11
-rw-r--r--Documentation/git-repo-config.txt5
-rw-r--r--Documentation/git-request-pull.txt14
-rw-r--r--Documentation/git-rerere.txt13
-rw-r--r--Documentation/git-reset.txt402
-rw-r--r--Documentation/git-rev-list.txt36
-rw-r--r--Documentation/git-rev-parse.txt236
-rw-r--r--Documentation/git-revert.txt25
-rw-r--r--Documentation/git-rm.txt30
-rw-r--r--Documentation/git-send-email.txt78
-rw-r--r--Documentation/git-send-pack.txt10
-rw-r--r--Documentation/git-sh-i18n--envsubst.txt36
-rw-r--r--Documentation/git-sh-i18n.txt43
-rw-r--r--Documentation/git-sh-setup.txt21
-rw-r--r--Documentation/git-shell.txt37
-rw-r--r--Documentation/git-shortlog.txt11
-rw-r--r--Documentation/git-show-branch.txt24
-rw-r--r--Documentation/git-show-index.txt10
-rw-r--r--Documentation/git-show-ref.txt23
-rw-r--r--Documentation/git-show.txt18
-rw-r--r--Documentation/git-stage.txt4
-rw-r--r--Documentation/git-stash.txt29
-rw-r--r--Documentation/git-status.txt78
-rw-r--r--Documentation/git-stripspace.txt9
-rw-r--r--Documentation/git-submodule.txt29
-rw-r--r--Documentation/git-svn.txt112
-rw-r--r--Documentation/git-symbolic-ref.txt5
-rw-r--r--Documentation/git-tag.txt69
-rw-r--r--Documentation/git-tar-tree.txt9
-rw-r--r--Documentation/git-unpack-file.txt9
-rw-r--r--Documentation/git-unpack-objects.txt10
-rw-r--r--Documentation/git-update-index.txt17
-rw-r--r--Documentation/git-update-ref.txt10
-rw-r--r--Documentation/git-update-server-info.txt10
-rw-r--r--Documentation/git-upload-archive.txt9
-rw-r--r--Documentation/git-upload-pack.txt9
-rw-r--r--Documentation/git-var.txt9
-rw-r--r--Documentation/git-verify-pack.txt11
-rw-r--r--Documentation/git-verify-tag.txt13
-rw-r--r--Documentation/git-web--browse.txt43
-rw-r--r--Documentation/git-whatchanged.txt12
-rw-r--r--Documentation/git-write-tree.txt10
-rw-r--r--Documentation/git.txt338
-rw-r--r--Documentation/gitattributes.txt143
-rw-r--r--Documentation/gitcli.txt4
-rw-r--r--Documentation/gitcore-tutorial.txt20
-rw-r--r--Documentation/gitcvs-migration.txt3
-rw-r--r--Documentation/gitdiffcore.txt7
-rw-r--r--Documentation/githooks.txt4
-rw-r--r--Documentation/gitignore.txt37
-rw-r--r--Documentation/gitk.txt12
-rw-r--r--Documentation/gitmodules.txt30
-rw-r--r--Documentation/gitrepository-layout.txt2
-rw-r--r--Documentation/gitrevisions.txt35
-rw-r--r--Documentation/gittutorial-2.txt3
-rw-r--r--Documentation/gittutorial.txt7
-rw-r--r--Documentation/gitworkflows.txt1
-rw-r--r--Documentation/glossary-content.txt104
-rw-r--r--Documentation/howto/revert-a-faulty-merge.txt2
-rw-r--r--Documentation/howto/revert-branch-rebase.txt6
-rw-r--r--Documentation/howto/using-merge-subtree.txt2
-rwxr-xr-xDocumentation/install-webdoc.sh6
-rw-r--r--Documentation/merge-config.txt44
-rw-r--r--Documentation/merge-options.txt16
-rw-r--r--Documentation/merge-strategies.txt40
-rw-r--r--Documentation/pretty-options.txt44
-rw-r--r--Documentation/rev-list-options.txt390
-rw-r--r--Documentation/revisions.txt230
-rw-r--r--Documentation/technical/api-diff.txt4
-rw-r--r--Documentation/technical/api-merge.txt104
-rw-r--r--Documentation/technical/api-parse-options.txt17
-rw-r--r--Documentation/technical/api-sigchain.txt41
-rw-r--r--Documentation/technical/api-tree-walking.txt2
-rw-r--r--Documentation/technical/index-format.txt185
-rw-r--r--Documentation/technical/pack-protocol.txt103
-rw-r--r--Documentation/technical/protocol-capabilities.txt2
-rw-r--r--Documentation/urls.txt2
-rw-r--r--Documentation/user-manual.txt68
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--INSTALL59
-rw-r--r--LGPL-2.1511
-rw-r--r--Makefile461
l---------RelNotes2
-rw-r--r--abspath.c35
-rw-r--r--aclocal.m44
-rw-r--r--alias.c14
-rw-r--r--alloc.c11
-rw-r--r--archive-tar.c135
-rw-r--r--archive-zip.c24
-rw-r--r--archive.c113
-rw-r--r--archive.h23
-rw-r--r--attr.c47
-rw-r--r--base85.c6
-rw-r--r--bisect.c83
-rw-r--r--branch.c14
-rw-r--r--branch.h4
-rw-r--r--builtin.h18
-rw-r--r--builtin/add.c132
-rw-r--r--builtin/apply.c353
-rw-r--r--builtin/archive.c65
-rw-r--r--builtin/blame.c138
-rw-r--r--builtin/branch.c132
-rw-r--r--builtin/bundle.c16
-rw-r--r--builtin/cat-file.c4
-rw-r--r--builtin/check-ref-format.c44
-rw-r--r--builtin/checkout-index.c11
-rw-r--r--builtin/checkout.c571
-rw-r--r--builtin/clean.c60
-rw-r--r--builtin/clone.c116
-rw-r--r--builtin/commit-tree.c14
-rw-r--r--builtin/commit.c566
-rw-r--r--builtin/config.c56
-rw-r--r--builtin/count-objects.c2
-rw-r--r--builtin/describe.c133
-rw-r--r--builtin/diff-files.c4
-rw-r--r--builtin/diff-index.c2
-rw-r--r--builtin/diff-tree.c14
-rw-r--r--builtin/diff.c75
-rw-r--r--builtin/fast-export.c56
-rw-r--r--builtin/fetch-pack.c65
-rw-r--r--builtin/fetch.c259
-rw-r--r--builtin/fmt-merge-msg.c78
-rw-r--r--builtin/for-each-ref.c3
-rw-r--r--builtin/fsck.c36
-rw-r--r--builtin/gc.c29
-rw-r--r--builtin/grep.c372
-rw-r--r--builtin/hash-object.c7
-rw-r--r--builtin/help.c12
-rw-r--r--builtin/index-pack.c298
-rw-r--r--builtin/init-db.c185
-rw-r--r--builtin/log.c300
-rw-r--r--builtin/ls-files.c66
-rw-r--r--builtin/ls-remote.c25
-rw-r--r--builtin/ls-tree.c17
-rw-r--r--builtin/mailinfo.c4
-rw-r--r--builtin/mailsplit.c4
-rw-r--r--builtin/merge-base.c43
-rw-r--r--builtin/merge-file.c13
-rw-r--r--builtin/merge-index.c3
-rw-r--r--builtin/merge-recursive.c18
-rw-r--r--builtin/merge-tree.c8
-rw-r--r--builtin/merge.c407
-rw-r--r--builtin/mktag.c55
-rw-r--r--builtin/mv.c38
-rw-r--r--builtin/name-rev.c2
-rw-r--r--builtin/notes.c466
-rw-r--r--builtin/pack-objects.c82
-rw-r--r--builtin/pack-redundant.c3
-rw-r--r--builtin/pack-refs.c2
-rw-r--r--builtin/patch-id.c7
-rw-r--r--builtin/prune.c6
-rw-r--r--builtin/push.c80
-rw-r--r--builtin/read-tree.c24
-rw-r--r--builtin/receive-pack.c51
-rw-r--r--builtin/remote-ext.c242
-rw-r--r--builtin/remote-fd.c79
-rw-r--r--builtin/remote.c102
-rw-r--r--builtin/rerere.c131
-rw-r--r--builtin/reset.c46
-rw-r--r--builtin/rev-list.c45
-rw-r--r--builtin/rev-parse.c9
-rw-r--r--builtin/revert.c335
-rw-r--r--builtin/rm.c40
-rw-r--r--builtin/send-pack.c18
-rw-r--r--builtin/shortlog.c24
-rw-r--r--builtin/show-branch.c27
-rw-r--r--builtin/show-ref.c5
-rw-r--r--builtin/symbolic-ref.c3
-rw-r--r--builtin/tag.c134
-rw-r--r--builtin/unpack-file.c4
-rw-r--r--builtin/unpack-objects.c4
-rw-r--r--builtin/update-index.c408
-rw-r--r--builtin/update-server-info.c3
-rw-r--r--builtin/upload-archive.c2
-rw-r--r--builtin/var.c12
-rw-r--r--builtin/verify-pack.c132
-rw-r--r--builtin/verify-tag.c12
-rw-r--r--bundle.c11
-rw-r--r--cache-tree.c8
-rw-r--r--cache.h304
-rw-r--r--color.c36
-rw-r--r--color.h11
-rw-r--r--combine-diff.c214
-rw-r--r--commit.c75
-rw-r--r--commit.h50
-rw-r--r--compat/bswap.h18
-rw-r--r--compat/cygwin.c5
-rw-r--r--compat/fnmatch/fnmatch.c4
-rw-r--r--compat/inet_ntop.c22
-rw-r--r--compat/inet_pton.c8
-rw-r--r--compat/mingw.c570
-rw-r--r--compat/mingw.h141
-rw-r--r--compat/msvc.c29
-rw-r--r--compat/msvc.h1
-rw-r--r--compat/nedmalloc/malloc.c.h12
-rw-r--r--compat/regex/regcomp.c3884
-rw-r--r--compat/regex/regex.c5001
-rw-r--r--compat/regex/regex.h462
-rw-r--r--compat/regex/regex_internal.c1744
-rw-r--r--compat/regex/regex_internal.h810
-rw-r--r--compat/regex/regexec.c4369
-rw-r--r--compat/strtok_r.c61
-rw-r--r--compat/vcbuild/include/dirent.h128
-rw-r--r--compat/vcbuild/include/unistd.h4
-rw-r--r--compat/win32/dirent.c108
-rw-r--r--compat/win32/dirent.h24
-rw-r--r--compat/win32/sys/poll.c599
-rw-r--r--compat/win32/sys/poll.h53
-rw-r--r--compat/win32/syslog.c72
-rw-r--r--compat/win32/syslog.h20
-rw-r--r--config.c389
-rw-r--r--config.mak.in9
-rw-r--r--configure.ac209
-rw-r--r--connect.c75
-rwxr-xr-xcontrib/ciabot/ciabot.py2
-rwxr-xr-xcontrib/completion/git-completion.bash677
-rw-r--r--contrib/convert-objects/git-convert-objects.txt1
-rw-r--r--contrib/emacs/git-blame.el1
-rw-r--r--contrib/emacs/git.el13
-rw-r--r--contrib/examples/builtin-fetch--tool.c2
-rwxr-xr-xcontrib/examples/git-commit.sh2
-rwxr-xr-xcontrib/examples/git-merge.sh115
-rwxr-xr-xcontrib/examples/git-revert.sh14
-rwxr-xr-xcontrib/examples/git-svnimport.perl4
-rwxr-xr-xcontrib/fast-import/git-p4469
-rw-r--r--contrib/fast-import/git-p4.txt41
-rwxr-xr-xcontrib/fast-import/import-directories.perl3
-rw-r--r--contrib/git-shell-commands/README18
-rwxr-xr-xcontrib/git-shell-commands/help18
-rwxr-xr-xcontrib/git-shell-commands/list10
-rw-r--r--contrib/gitview/gitview.txt1
-rwxr-xr-xcontrib/hooks/post-receive-email85
-rw-r--r--contrib/svn-fe/.gitignore4
-rw-r--r--contrib/svn-fe/Makefile63
-rw-r--r--contrib/svn-fe/svn-fe.c17
-rw-r--r--contrib/svn-fe/svn-fe.txt71
-rwxr-xr-xcontrib/thunderbird-patch-inline/appp.sh4
-rwxr-xr-xcontrib/workdir/git-new-workdir6
-rw-r--r--convert.c609
-rw-r--r--convert.h72
-rw-r--r--csum-file.c48
-rw-r--r--csum-file.h2
-rw-r--r--ctype.c15
-rw-r--r--daemon.c357
-rw-r--r--date.c21
-rw-r--r--diff-delta.c9
-rw-r--r--diff-lib.c44
-rw-r--r--diff-no-index.c16
-rw-r--r--diff.c751
-rw-r--r--diff.h37
-rw-r--r--diffcore-pickaxe.c155
-rw-r--r--diffcore-rename.c190
-rw-r--r--diffcore.h14
-rw-r--r--dir.c345
-rw-r--r--dir.h12
-rw-r--r--entry.c132
-rw-r--r--environment.c94
-rw-r--r--exec_cmd.c3
-rw-r--r--fast-import.c538
-rw-r--r--fetch-pack.h3
-rw-r--r--fsck.c16
-rwxr-xr-xgenerate-cmdlist.sh3
-rw-r--r--gettext.c14
-rw-r--r--gettext.h40
-rwxr-xr-xgit-add--interactive.perl61
-rwxr-xr-xgit-am.sh123
-rwxr-xr-xgit-archimport.perl3
-rwxr-xr-xgit-bisect.sh129
-rw-r--r--git-compat-util.h74
-rwxr-xr-xgit-cvsexportcommit.perl4
-rwxr-xr-xgit-cvsimport.perl97
-rwxr-xr-xgit-cvsserver.perl7
-rwxr-xr-xgit-difftool--helper.sh1
-rwxr-xr-xgit-difftool.perl13
-rwxr-xr-xgit-filter-branch.sh10
-rwxr-xr-xgit-gui/GIT-VERSION-GEN2
-rw-r--r--git-gui/Makefile1
-rwxr-xr-xgit-gui/git-gui--askpass19
-rwxr-xr-xgit-gui/git-gui.sh255
-rw-r--r--git-gui/lib/blame.tcl28
-rw-r--r--git-gui/lib/branch_rename.tcl2
-rw-r--r--git-gui/lib/browser.tcl2
-rw-r--r--git-gui/lib/choose_repository.tcl25
-rw-r--r--git-gui/lib/commit.tcl9
-rw-r--r--git-gui/lib/diff.tcl150
-rw-r--r--git-gui/lib/index.tcl10
-rw-r--r--git-gui/lib/merge.tcl2
-rw-r--r--git-gui/lib/mergetool.tcl101
-rw-r--r--git-gui/lib/option.tcl1
-rw-r--r--git-gui/lib/remote.tcl91
-rw-r--r--git-gui/lib/remote_branch_delete.tcl9
-rw-r--r--git-gui/lib/shortcut.tcl4
-rw-r--r--git-gui/lib/status_bar.tcl1
-rw-r--r--git-gui/lib/transport.tcl34
-rw-r--r--git-gui/lib/win32.tcl4
-rw-r--r--git-gui/po/glossary/pt_br.po169
-rw-r--r--git-gui/po/pt_br.po2568
-rw-r--r--git-gui/po/ru.po623
-rw-r--r--git-gui/po/sv.po802
-rw-r--r--git-gui/windows/git-gui.sh5
-rwxr-xr-xgit-instaweb.sh184
-rwxr-xr-xgit-merge-octopus.sh5
-rwxr-xr-xgit-merge-one-file.sh7
-rw-r--r--git-mergetool--lib.sh249
-rwxr-xr-xgit-mergetool.sh132
-rw-r--r--git-parse-remote.sh103
-rwxr-xr-xgit-pull.sh103
-rw-r--r--git-rebase--am.sh30
-rw-r--r--[-rwxr-xr-x]git-rebase--interactive.sh846
-rw-r--r--git-rebase--merge.sh151
-rwxr-xr-xgit-rebase.sh603
-rwxr-xr-xgit-relink.perl4
-rwxr-xr-xgit-repack.sh11
-rwxr-xr-xgit-request-pull.sh3
-rwxr-xr-xgit-send-email.perl180
-rw-r--r--git-sh-i18n.sh29
-rw-r--r--git-sh-setup.sh66
-rwxr-xr-xgit-stash.sh310
-rwxr-xr-xgit-submodule.sh323
-rwxr-xr-xgit-svn.perl121
-rwxr-xr-xgit-web--browse.sh177
-rw-r--r--git.c157
-rw-r--r--git_remote_helpers/setup.cfg3
-rwxr-xr-x[-rw-r--r--]gitk-git/gitk107
-rw-r--r--gitk-git/po/pt_br.po1277
-rw-r--r--gitk-git/po/ru.po24
-rw-r--r--gitk-git/po/sv.po601
-rw-r--r--gitweb/INSTALL146
-rw-r--r--gitweb/Makefile35
-rw-r--r--gitweb/README147
-rwxr-xr-xgitweb/gitweb.perl1825
-rw-r--r--gitweb/static/gitweb.css46
-rw-r--r--gitweb/static/js/README20
-rw-r--r--gitweb/static/js/adjust-timezone.js330
-rw-r--r--gitweb/static/js/blame_incremental.js (renamed from gitweb/static/gitweb.js)293
-rw-r--r--gitweb/static/js/javascript-detection.js43
-rw-r--r--gitweb/static/js/lib/common-lib.js224
-rw-r--r--gitweb/static/js/lib/cookies.js114
-rw-r--r--gitweb/static/js/lib/datetime.js176
-rw-r--r--graph.c80
-rw-r--r--graph.h28
-rw-r--r--grep.c253
-rw-r--r--grep.h13
-rw-r--r--hash.c4
-rw-r--r--hash.h2
-rw-r--r--help.c64
-rw-r--r--help.h19
-rw-r--r--http-backend.c15
-rw-r--r--http-fetch.c19
-rw-r--r--http-push.c214
-rw-r--r--http-walker.c10
-rw-r--r--http.c51
-rw-r--r--http.h29
-rw-r--r--ident.c15
-rw-r--r--imap-send.c18
-rw-r--r--list-objects.c33
-rw-r--r--ll-merge.c60
-rw-r--r--ll-merge.h9
-rw-r--r--lockfile.c4
-rw-r--r--log-tree.c35
-rw-r--r--mailmap.c10
-rw-r--r--merge-file.c3
-rw-r--r--merge-file.h7
-rw-r--r--merge-recursive.c848
-rw-r--r--merge-recursive.h14
-rw-r--r--name-hash.c72
-rw-r--r--notes-cache.c3
-rw-r--r--notes-merge.c737
-rw-r--r--notes-merge.h98
-rw-r--r--notes.c345
-rw-r--r--notes.h60
-rw-r--r--object.c14
-rw-r--r--object.h7
-rw-r--r--pack-check.c11
-rw-r--r--pack-refs.c32
-rw-r--r--pack-write.c82
-rw-r--r--pack.h23
-rw-r--r--parse-options.c104
-rw-r--r--parse-options.h26
-rw-r--r--patch-delta.c2
-rw-r--r--path.c137
-rw-r--r--perl/Git.pm26
-rw-r--r--perl/Makefile2
-rw-r--r--pkt-line.c55
-rw-r--r--po/.gitignore1
-rw-r--r--preload-index.c5
-rw-r--r--pretty.c255
-rw-r--r--quote.h3
-rw-r--r--reachable.c7
-rw-r--r--read-cache.c111
-rw-r--r--reflog-walk.c1
-rw-r--r--refs.c178
-rw-r--r--refs.h9
-rw-r--r--remote-curl.c99
-rw-r--r--remote.c6
-rw-r--r--replace_object.c5
-rw-r--r--rerere.c202
-rw-r--r--rerere.h10
-rw-r--r--resolve-undo.c34
-rw-r--r--revision.c504
-rw-r--r--revision.h23
-rw-r--r--run-command.c17
-rw-r--r--send-pack.h1
-rw-r--r--server-info.c3
-rw-r--r--setup.c530
-rw-r--r--sh-i18n--envsubst.c444
-rw-r--r--sha1-array.c59
-rw-r--r--sha1-array.h24
-rw-r--r--sha1_file.c680
-rw-r--r--sha1_name.c204
-rw-r--r--shallow.c2
-rw-r--r--shell.c135
-rw-r--r--strbuf.c65
-rw-r--r--strbuf.h63
-rw-r--r--streaming.c489
-rw-r--r--streaming.h15
-rw-r--r--string-list.c1
-rw-r--r--string-list.h10
-rw-r--r--submodule.c597
-rw-r--r--submodule.h20
-rw-r--r--symlinks.c64
-rw-r--r--t/.gitignore1
-rw-r--r--t/Makefile73
-rw-r--r--t/README482
-rw-r--r--t/annotate-tests.sh39
-rw-r--r--t/gitweb-lib.sh47
-rwxr-xr-xt/harness21
-rw-r--r--t/lib-cvs.sh15
-rw-r--r--t/lib-git-svn.sh93
-rw-r--r--t/lib-httpd.sh19
-rw-r--r--t/lib-httpd/apache.conf36
-rw-r--r--t/lib-httpd/passwd1
-rw-r--r--t/lib-patch-mode.sh5
-rw-r--r--t/lib-prereq-FILEMODE.sh11
-rw-r--r--t/lib-read-tree.sh43
-rw-r--r--t/lib-rebase.sh2
-rw-r--r--t/lib-terminal.sh35
-rwxr-xr-xt/t0000-basic.sh117
-rwxr-xr-xt/t0001-init.sh143
-rwxr-xr-xt/t0003-attributes.sh25
-rwxr-xr-xt/t0004-unwritable.sh60
-rwxr-xr-xt/t0005-signals.sh1
-rwxr-xr-xt/t0006-date.sh7
-rwxr-xr-xt/t0020-crlf.sh2
-rwxr-xr-xt/t0021-conversion.sh94
-rwxr-xr-xt/t0024-crlf-archive.sh4
-rwxr-xr-xt/t0026-eol-config.sh2
-rwxr-xr-xt/t0040-parse-options.sh19
-rwxr-xr-xt/t0050-filesystem.sh16
-rwxr-xr-xt/t0061-run-command.sh23
-rwxr-xr-xt/t0070-fundamental.sh13
-rwxr-xr-xt/t0080-vcs-svn.sh117
-rwxr-xr-xt/t0081-line-buffer.sh90
-rwxr-xr-xt/t0201-gettext-fallbacks.sh51
-rwxr-xr-xt/t1000-read-tree-m-3way.sh83
-rwxr-xr-xt/t1001-read-tree-m-2way.sh77
-rwxr-xr-xt/t1002-read-tree-m-u-2way.sh91
-rwxr-xr-xt/t1004-read-tree-m-u-wf.sh25
-rwxr-xr-xt/t1005-read-tree-reset.sh13
-rwxr-xr-xt/t1007-hash-object.sh13
-rwxr-xr-xt/t1008-read-tree-overlay.sh3
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh207
-rwxr-xr-xt/t1012-read-tree-df.sh9
-rwxr-xr-xt/t1020-subdirectory.sh160
-rwxr-xr-xt/t1021-rerere-in-workdir.sh55
-rwxr-xr-xt/t1050-large.sh27
-rwxr-xr-xt/t1200-tutorial.sh9
-rwxr-xr-xt/t1300-repo-config.sh102
-rwxr-xr-xt/t1302-repo-version.sh77
-rwxr-xr-xt/t1303-wacky-config.sh2
-rwxr-xr-xt/t1304-default-acl.sh17
-rwxr-xr-xt/t1400-update-ref.sh27
-rwxr-xr-xt/t1401-symbolic-ref.sh2
-rwxr-xr-xt/t1402-check-ref-format.sh19
-rwxr-xr-xt/t1410-reflog.sh8
-rwxr-xr-xt/t1411-reflog-show.sh18
-rwxr-xr-xt/t1412-reflog-loop.sh34
-rwxr-xr-xt/t1450-fsck.sh142
-rwxr-xr-xt/t1501-worktree.sh489
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh20
-rwxr-xr-xt/t1503-rev-parse-verify.sh11
-rwxr-xr-xt/t1504-ceiling-dirs.sh5
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh115
-rwxr-xr-xt/t1507-rev-parse-upstream.sh2
-rwxr-xr-xt/t1509-root-worktree.sh34
-rwxr-xr-xt/t1510-repo-setup.sh776
-rwxr-xr-xt/t1511-rev-parse-caret.sh73
-rwxr-xr-xt/t2006-checkout-index-basic.sh24
-rwxr-xr-xt/t2007-checkout-symlink.sh16
-rwxr-xr-xt/t2011-checkout-invalid-head.sh2
-rwxr-xr-xt/t2013-checkout-submodule.sh23
-rwxr-xr-xt/t2016-checkout-patch.sh32
-rwxr-xr-xt/t2017-checkout-orphan.sh25
-rwxr-xr-xt/t2018-checkout-branch.sh183
-rwxr-xr-xt/t2019-checkout-ambiguous-ref.sh59
-rwxr-xr-xt/t2020-checkout-detach.sh151
-rwxr-xr-xt/t2021-checkout-overwrite.sh50
-rwxr-xr-xt/t2050-git-dir-relative.sh4
-rwxr-xr-xt/t2101-update-index-reupdate.sh8
-rwxr-xr-xt/t2102-update-index-symlinks.sh2
-rwxr-xr-xt/t2105-update-index-gitfile.sh2
-rwxr-xr-xt/t2107-update-index-basic.sh32
-rwxr-xr-xt/t2200-add-update.sh28
-rwxr-xr-xt/t2201-add-update-typechange.sh2
-rwxr-xr-xt/t2204-add-ignored.sh37
-rwxr-xr-xt/t3000-ls-files-others.sh89
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh2
-rwxr-xr-xt/t3004-ls-files-basic.sh39
-rwxr-xr-xt/t3020-ls-files-error-unmatch.sh1
-rwxr-xr-xt/t3030-merge-recursive.sh78
-rwxr-xr-xt/t3032-merge-recursive-options.sh203
-rwxr-xr-xt/t3050-subprojects-fetch.sh4
-rwxr-xr-xt/t3060-ls-files-with-tree.sh14
-rwxr-xr-xt/t3100-ls-tree-restrict.sh9
-rwxr-xr-xt/t3101-ls-tree-dirname.sh126
-rwxr-xr-xt/t3102-ls-tree-wildcards.sh22
-rwxr-xr-xt/t3200-branch.sh42
-rwxr-xr-xt/t3203-branch-output.sh8
-rwxr-xr-xt/t3210-pack-refs.sh6
-rwxr-xr-xt/t3300-funny-names.sh90
-rwxr-xr-xt/t3301-notes.sh192
-rwxr-xr-xt/t3302-notes-index-expensive.sh34
-rwxr-xr-xt/t3303-notes-subtrees.sh19
-rwxr-xr-xt/t3306-notes-prune.sh11
-rwxr-xr-xt/t3307-notes-man.sh2
-rwxr-xr-xt/t3308-notes-merge.sh368
-rwxr-xr-xt/t3309-notes-merge-auto-resolve.sh647
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh556
-rwxr-xr-xt/t3311-notes-merge-fanout.sh436
-rwxr-xr-xt/t3400-rebase.sh216
-rwxr-xr-xt/t3402-rebase-merge.sh30
-rwxr-xr-xt/t3403-rebase-skip.sh5
-rwxr-xr-xt/t3404-rebase-interactive.sh156
-rwxr-xr-xt/t3406-rebase-message.sh6
-rwxr-xr-xt/t3407-rebase-abort.sh28
-rwxr-xr-xt/t3408-rebase-multi-line.sh2
-rwxr-xr-xt/t3409-rebase-preserve-merges.sh81
-rwxr-xr-xt/t3410-rebase-preserve-dropped-merges.sh8
-rwxr-xr-xt/t3411-rebase-preserve-around-merges.sh2
-rwxr-xr-xt/t3412-rebase-root.sh4
-rwxr-xr-xt/t3415-rebase-autosquash.sh145
-rwxr-xr-xt/t3417-rebase-whitespace-fix.sh2
-rwxr-xr-xt/t3418-rebase-continue.sh98
-rwxr-xr-xt/t3419-rebase-patch-id.sh109
-rwxr-xr-xt/t3501-revert-cherry-pick.sh12
-rwxr-xr-xt/t3503-cherry-pick-root.sh27
-rwxr-xr-xt/t3504-cherry-pick-rerere.sh4
-rwxr-xr-xt/t3505-cherry-pick-empty.sh20
-rwxr-xr-xt/t3506-cherry-pick-ff.sh10
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh156
-rwxr-xr-xt/t3508-cherry-pick-many-commits.sh64
-rwxr-xr-xt/t3509-cherry-pick-merge-df.sh103
-rwxr-xr-xt/t3600-rm.sh21
-rwxr-xr-xt/t3700-add.sh57
-rwxr-xr-xt/t3701-add-interactive.sh147
-rwxr-xr-xt/t3703-add-magic-pathspec.sh58
-rwxr-xr-xt/t3900-i18n-commit.sh29
-rwxr-xr-xt/t3902-quoted.sh44
-rwxr-xr-xt/t3903-stash.sh243
-rwxr-xr-xt/t3904-stash-patch.sh24
-rwxr-xr-xt/t4001-diff-rename.sh31
-rwxr-xr-xt/t4002-diff-basic.sh17
-rwxr-xr-xt/t4003-diff-rename-1.sh2
-rwxr-xr-xt/t4004-diff-rename-symlink.sh18
-rwxr-xr-xt/t4005-diff-rename-2.sh2
-rwxr-xr-xt/t4008-diff-break-rewrite.sh6
-rwxr-xr-xt/t4009-diff-rename-4.sh2
-rwxr-xr-xt/t4010-diff-pathspec.sh32
-rwxr-xr-xt/t4011-diff-symlink.sh44
-rwxr-xr-xt/t4012-diff-binary.sh4
-rwxr-xr-xt/t4013-diff-various.sh49
-rw-r--r--t/t4013/diff.diff_--cached38
-rw-r--r--t/t4013/diff.diff_--cached_--_file015
-rw-r--r--t/t4013/diff.diff_--dirstat-by-file_initial_rearrange3
-rw-r--r--t/t4013/diff.diff_--dirstat_initial_rearrange3
-rw-r--r--t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^2
-rw-r--r--t/t4013/diff.log_--decorate=full_--all6
-rw-r--r--t/t4013/diff.log_--decorate_--all6
-rw-r--r--t/t4013/diff.log_-GF_-p_--pickaxe-all_master27
-rw-r--r--t/t4013/diff.log_-GF_-p_master18
-rw-r--r--t/t4013/diff.log_-GF_master7
-rw-r--r--t/t4013/diff.log_-SF_master_--max-count=02
-rw-r--r--t/t4013/diff.log_-SF_master_--max-count=17
-rw-r--r--t/t4013/diff.log_-SF_master_--max-count=27
-rw-r--r--t/t4013/diff.log_-S_F_master7
-rwxr-xr-xt/t4014-format-patch.sh210
-rwxr-xr-xt/t4015-diff-whitespace.sh67
-rwxr-xr-xt/t4016-diff-quote.sh22
-rwxr-xr-xt/t4017-diff-retval.sh69
-rwxr-xr-xt/t4018-diff-funcname.sh160
-rwxr-xr-xt/t4019-diff-wserror.sh147
-rwxr-xr-xt/t4020-diff-external.sh2
-rwxr-xr-xt/t4021-format-patch-numbered.sh2
-rwxr-xr-xt/t4022-diff-rewrite.sh43
-rwxr-xr-xt/t4023-diff-rename-typechange.sh14
-rwxr-xr-xt/t4026-color.sh1
-rwxr-xr-xt/t4027-diff-submodule.sh169
-rwxr-xr-xt/t4031-diff-rewrite-binary.sh7
-rwxr-xr-xt/t4034-diff-words.sh462
-rw-r--r--t/t4034/bibtex/expect15
-rw-r--r--t/t4034/bibtex/post10
-rw-r--r--t/t4034/bibtex/pre9
-rw-r--r--t/t4034/cpp/expect36
-rw-r--r--t/t4034/cpp/post19
-rw-r--r--t/t4034/cpp/pre19
-rw-r--r--t/t4034/csharp/expect35
-rw-r--r--t/t4034/csharp/post18
-rw-r--r--t/t4034/csharp/pre18
-rw-r--r--t/t4034/fortran/expect10
-rw-r--r--t/t4034/fortran/post5
-rw-r--r--t/t4034/fortran/pre5
-rw-r--r--t/t4034/html/expect8
-rw-r--r--t/t4034/html/post3
-rw-r--r--t/t4034/html/pre3
-rw-r--r--t/t4034/java/expect36
-rw-r--r--t/t4034/java/post19
-rw-r--r--t/t4034/java/pre19
-rw-r--r--t/t4034/objc/expect35
-rw-r--r--t/t4034/objc/post18
-rw-r--r--t/t4034/objc/pre18
-rw-r--r--t/t4034/pascal/expect35
-rw-r--r--t/t4034/pascal/post18
-rw-r--r--t/t4034/pascal/pre18
-rw-r--r--t/t4034/perl/expect13
-rw-r--r--t/t4034/perl/post22
-rw-r--r--t/t4034/perl/pre22
-rw-r--r--t/t4034/php/expect35
-rw-r--r--t/t4034/php/post18
-rw-r--r--t/t4034/php/pre18
-rw-r--r--t/t4034/python/expect34
-rw-r--r--t/t4034/python/post17
-rw-r--r--t/t4034/python/pre17
-rw-r--r--t/t4034/ruby/expect34
-rw-r--r--t/t4034/ruby/post17
-rw-r--r--t/t4034/ruby/pre17
-rw-r--r--t/t4034/tex/expect9
-rw-r--r--t/t4034/tex/post4
-rw-r--r--t/t4034/tex/pre4
-rwxr-xr-xt/t4040-whitespace-status.sh12
-rwxr-xr-xt/t4041-diff-submodule-option.sh105
-rwxr-xr-xt/t4045-diff-relative.sh61
-rwxr-xr-xt/t4046-diff-unmerged.sh87
-rwxr-xr-xt/t4047-diff-dirstat.sh979
-rwxr-xr-xt/t4048-diff-combined-binary.sh212
-rwxr-xr-xt/t4049-diff-stat-count.sh25
-rwxr-xr-xt/t4102-apply-rename.sh8
-rwxr-xr-xt/t4103-apply-binary.sh35
-rwxr-xr-xt/t4111-apply-subdir.sh142
-rwxr-xr-xt/t4114-apply-typechange.sh28
-rwxr-xr-xt/t4115-apply-symlink.sh12
-rwxr-xr-xt/t4119-apply-config.sh2
-rwxr-xr-xt/t4120-apply-popt.sh66
-rwxr-xr-xt/t4122-apply-symlink-inside.sh12
-rwxr-xr-xt/t4124-apply-ws-rule.sh58
-rwxr-xr-xt/t4127-apply-same-fn.sh18
-rwxr-xr-xt/t4129-apply-samemode.sh8
-rwxr-xr-xt/t4130-apply-criss-cross-rename.sh2
-rwxr-xr-xt/t4132-apply-removal.sh2
-rwxr-xr-xt/t4133-apply-filenames.sh8
-rwxr-xr-xt/t4134-apply-submodule.sh2
-rwxr-xr-xt/t4135-apply-weird-filenames.sh91
-rw-r--r--t/t4135/.gitignore3
-rw-r--r--t/t4135/add-plain.diff5
-rw-r--r--t/t4135/add-with backslash.diff5
-rw-r--r--t/t4135/add-with quote.diff5
-rw-r--r--t/t4135/add-with spaces.diff5
-rw-r--r--t/t4135/add-with tab.diff5
-rw-r--r--t/t4135/damaged-tz.diff5
-rw-r--r--t/t4135/damaged.diff5
-rw-r--r--t/t4135/diff-plain.diff5
-rw-r--r--t/t4135/diff-with backslash.diff5
-rw-r--r--t/t4135/diff-with quote.diff5
-rw-r--r--t/t4135/diff-with spaces.diff5
-rw-r--r--t/t4135/diff-with tab.diff5
-rw-r--r--t/t4135/funny-tz.diff5
-rw-r--r--t/t4135/git-plain.diff7
-rw-r--r--t/t4135/git-with backslash.diff7
-rw-r--r--t/t4135/git-with quote.diff7
-rw-r--r--t/t4135/git-with spaces.diff7
-rw-r--r--t/t4135/git-with tab.diff7
-rwxr-xr-xt/t4135/make-patches45
-rwxr-xr-xt/t4150-am.sh338
-rwxr-xr-xt/t4151-am-abort.sh18
-rwxr-xr-xt/t4152-am-subjects.sh77
-rwxr-xr-xt/t4200-rerere.sh400
-rwxr-xr-xt/t4201-shortlog.sh2
-rwxr-xr-xt/t4202-log.sh101
-rwxr-xr-xt/t4203-mailmap.sh69
-rwxr-xr-xt/t4204-patch-id.sh36
-rwxr-xr-xt/t4205-log-pretty-formats.sh2
-rwxr-xr-xt/t4208-log-magic-pathspec.sh36
-rwxr-xr-xt/t4252-am-options.sh2
-rwxr-xr-xt/t4300-merge-tree.sh257
-rwxr-xr-xt/t5000-tar-tree.sh102
-rwxr-xr-xt/t5001-archive-attr.sh2
-rwxr-xr-xt/t5300-pack-object.sh2
-rwxr-xr-xt/t5301-sliding-window.sh4
-rwxr-xr-xt/t5302-pack-index.sh27
-rwxr-xr-xt/t5304-prune.sh3
-rwxr-xr-xt/t5400-send-pack.sh29
-rwxr-xr-xt/t5407-post-rewrite-hook.sh18
-rwxr-xr-xt/t5500-fetch-pack.sh2
-rwxr-xr-xt/t5501-fetch-push-alternates.sh66
-rwxr-xr-xt/t5502-quickfetch.sh2
-rwxr-xr-xt/t5503-tagfollow.sh38
-rwxr-xr-xt/t5505-remote.sh110
-rwxr-xr-xt/t5506-remote-groups.sh6
-rwxr-xr-xt/t5510-fetch.sh75
-rwxr-xr-xt/t5512-ls-remote.sh24
-rwxr-xr-xt/t5513-fetch-track.sh2
-rwxr-xr-xt/t5514-fetch-multiple.sh2
-rwxr-xr-xt/t5516-fetch-push.sh18
-rwxr-xr-xt/t5519-push-alternates.sh2
-rwxr-xr-xt/t5520-pull.sh80
-rwxr-xr-xt/t5522-pull-symlink.sh14
-rwxr-xr-xt/t5523-push-upstream.sh44
-rwxr-xr-xt/t5525-fetch-tagopt.sh41
-rwxr-xr-xt/t5526-fetch-submodules.sh453
-rwxr-xr-xt/t5530-upload-pack-error.sh9
-rwxr-xr-xt/t5531-deep-submodule-push.sh2
-rwxr-xr-xt/t5532-fetch-proxy.sh43
-rwxr-xr-xt/t5540-http-push.sh2
-rwxr-xr-xt/t5541-http-push.sh29
-rwxr-xr-xt/t5550-http-fetch.sh25
-rwxr-xr-xt/t5551-http-fetch.sh10
-rwxr-xr-xt/t5560-http-backend-noserver.sh13
-rwxr-xr-xt/t5561-http-backend.sh2
-rwxr-xr-xt/t556x_common28
-rwxr-xr-xt/t5601-clone.sh31
-rwxr-xr-xt/t5602-clone-remote-exec.sh22
-rwxr-xr-xt/t5701-clone-local.sh19
-rwxr-xr-xt/t5704-bundle.sh7
-rwxr-xr-xt/t5705-clone-2gb.sh16
-rwxr-xr-xt/t5708-clone-config.sh40
-rwxr-xr-xt/t5800-remote-helpers.sh24
-rwxr-xr-xt/t6000-rev-list-misc.sh51
-rwxr-xr-xt/t6001-rev-list-graft.sh8
-rwxr-xr-xt/t6004-rev-list-path-optim.sh69
-rwxr-xr-xt/t6006-rev-list-format.sh8
-rwxr-xr-xt/t6007-rev-list-cherry-pick-file.sh140
-rwxr-xr-xt/t6009-rev-list-parent.sh120
-rwxr-xr-xt/t6010-merge-base.sh310
-rwxr-xr-xt/t6016-rev-list-graph-simplify-history.sh29
-rwxr-xr-xt/t6017-rev-list-stdin.sh17
-rwxr-xr-xt/t6018-rev-list-glob.sh56
-rwxr-xr-xt/t6020-merge-df.sh86
-rwxr-xr-xt/t6022-merge-rename.sh648
-rwxr-xr-xt/t6023-merge-file.sh8
-rwxr-xr-xt/t6024-recursive-merge.sh2
-rwxr-xr-xt/t6029-merge-subtree.sh2
-rwxr-xr-xt/t6030-bisect-porcelain.sh8
-rwxr-xr-xt/t6031-merge-recursive.sh37
-rwxr-xr-xt/t6032-merge-large-rename.sh30
-rwxr-xr-xt/t6035-merge-dir-to-symlink.sh92
-rwxr-xr-xt/t6036-recursive-corner-cases.sh185
-rwxr-xr-xt/t6037-merge-ours-theirs.sh2
-rwxr-xr-xt/t6038-merge-text-auto.sh191
-rwxr-xr-xt/t6040-tracking-info.sh20
-rwxr-xr-xt/t6050-replace.sh89
-rwxr-xr-xt/t6060-merge-index.sh100
-rwxr-xr-xt/t6110-rev-list-sparse.sh20
-rwxr-xr-xt/t6120-describe.sh2
-rwxr-xr-xt/t6200-fmt-merge-msg.sh387
-rwxr-xr-xt/t6500-gc.sh28
-rwxr-xr-xt/t7001-mv.sh4
-rwxr-xr-xt/t7003-filter-branch.sh102
-rwxr-xr-xt/t7004-tag.sh122
-rwxr-xr-xt/t7005-editor.sh10
-rwxr-xr-xt/t7006-pager.sh368
-rwxr-xr-xt/t7008-grep-binary.sh16
-rwxr-xr-xt/t7011-skip-worktree-reading.sh4
-rwxr-xr-xt/t7012-skip-worktree-writing.sh6
-rwxr-xr-xt/t7060-wtstatus.sh66
-rwxr-xr-xt/t7102-reset.sh2
-rwxr-xr-xt/t7105-reset-patch.sh22
-rwxr-xr-xt/t7110-reset-merge.sh6
-rwxr-xr-xt/t7201-co.sh20
-rwxr-xr-xt/t7300-clean.sh40
-rwxr-xr-xt/t7400-submodule-basic.sh118
-rwxr-xr-xt/t7401-submodule-summary.sh56
-rwxr-xr-xt/t7403-submodule-sync.sh32
-rwxr-xr-xt/t7405-submodule-merge.sh173
-rwxr-xr-xt/t7406-submodule-update.sh241
-rwxr-xr-xt/t7407-submodule-foreach.sh80
-rwxr-xr-xt/t7500-commit.sh115
-rwxr-xr-xt/t7500/add-whitespaced-content8
-rwxr-xr-xt/t7500/edit-content4
-rwxr-xr-xt/t7501-commit.sh33
-rwxr-xr-xt/t7502-commit.sh62
-rwxr-xr-xt/t7503-pre-commit-hook.sh33
-rwxr-xr-xt/t7505-prepare-commit-msg-hook.sh12
-rwxr-xr-xt/t7506-status-submodule.sh120
-rwxr-xr-xt/t7508-status.sh491
-rwxr-xr-xt/t7509-commit.sh35
-rwxr-xr-xt/t7600-merge.sh463
-rwxr-xr-xt/t7601-merge-pull-config.sh12
-rwxr-xr-xt/t7602-merge-octopus-many.sh2
-rwxr-xr-xt/t7606-merge-custom.sh96
-rwxr-xr-xt/t7607-merge-overwrite.sh154
-rwxr-xr-xt/t7608-merge-messages.sh4
-rwxr-xr-xt/t7609-merge-co-error-msgs.sh133
-rwxr-xr-xt/t7610-mergetool.sh377
-rwxr-xr-xt/t7611-merge-abort.sh319
-rwxr-xr-xt/t7700-repack.sh6
-rwxr-xr-xt/t7800-difftool.sh61
-rwxr-xr-xt/t7810-grep.sh294
-rwxr-xr-xt/t7811-grep-open.sh17
-rwxr-xr-xt/t8001-annotate.sh11
-rwxr-xr-xt/t8002-blame.sh5
-rwxr-xr-xt/t8003-blame-corner-cases.sh (renamed from t/t8003-blame.sh)0
-rwxr-xr-xt/t8004-blame-with-conflicts.sh (renamed from t/t8004-blame.sh)0
-rwxr-xr-xt/t8006-blame-textconv.sh84
-rwxr-xr-xt/t8007-cat-file-textconv.sh38
-rwxr-xr-xt/t8008-blame-formats.sh90
-rwxr-xr-xt/t9001-send-email.sh357
-rwxr-xr-xt/t9010-svn-fe.sh863
-rwxr-xr-xt/t9100-git-svn-basic.sh42
-rwxr-xr-xt/t9101-git-svn-props.sh39
-rwxr-xr-xt/t9102-git-svn-deep-rmdir.sh15
-rwxr-xr-xt/t9104-git-svn-follow-parent.sh82
-rwxr-xr-xt/t9105-git-svn-commit-diff.sh9
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh40
-rwxr-xr-xt/t9107-git-svn-migrate.sh16
-rwxr-xr-xt/t9114-git-svn-dcommit-merge.sh9
-rwxr-xr-xt/t9115-git-svn-dcommit-funky-renames.sh9
-rwxr-xr-xt/t9116-git-svn-log.sh31
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh48
-rwxr-xr-xt/t9119-git-svn-info.sh259
-rwxr-xr-xt/t9120-git-svn-clone-with-percent-escapes.sh7
-rwxr-xr-xt/t9123-git-svn-rebuild-with-rewriteroot.sh6
-rwxr-xr-xt/t9124-git-svn-dcommit-auto-props.sh2
-rwxr-xr-xt/t9125-git-svn-multi-glob-branch-names.sh12
-rwxr-xr-xt/t9127-git-svn-partial-rebuild.sh30
-rwxr-xr-xt/t9128-git-svn-cmd-branch.sh18
-rwxr-xr-xt/t9129-git-svn-i18n-commitencoding.sh2
-rwxr-xr-xt/t9130-git-svn-authors-file.sh7
-rwxr-xr-xt/t9131-git-svn-empty-symlink.sh2
-rwxr-xr-xt/t9137-git-svn-dcommit-clobber-series.sh12
-rwxr-xr-xt/t9139-git-svn-non-utf8-commitencoding.sh2
-rwxr-xr-xt/t9140-git-svn-reset.sh2
-rwxr-xr-xt/t9142-git-svn-shallow-clone.sh5
-rwxr-xr-xt/t9143-git-svn-gc.sh4
-rwxr-xr-xt/t9146-git-svn-empty-dirs.sh19
-rwxr-xr-xt/t9151-svn-mergeinfo.sh22
-rwxr-xr-xt/t9155-git-svn-fetch-deleted-tag.sh42
-rwxr-xr-xt/t9156-git-svn-fetch-deleted-tag-2.sh44
-rwxr-xr-xt/t9157-git-svn-fetch-merge.sh58
-rwxr-xr-xt/t9158-git-svn-mergeinfo.sh41
-rwxr-xr-xt/t9159-git-svn-no-parent-mergeinfo.sh33
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh10
-rwxr-xr-xt/t9300-fast-import.sh705
-rwxr-xr-xt/t9301-fast-import-notes.sh6
-rwxr-xr-xt/t9350-fast-export.sh40
-rwxr-xr-xt/t9400-git-cvsserver-server.sh8
-rwxr-xr-xt/t9401-git-cvsserver-crlf.sh61
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh168
-rwxr-xr-xt/t9501-gitweb-standalone-http-status.sh3
-rwxr-xr-xt/t9502-gitweb-standalone-parse-output.sh74
-rwxr-xr-xt/t9600-cvsimport.sh82
-rwxr-xr-xt/t9601-cvsimport-vendor-branch.sh21
-rwxr-xr-xt/t9602-cvsimport-branches-tags.sh25
-rwxr-xr-xt/t9603-cvsimport-patchsets.sh9
-rwxr-xr-xt/t9700-perl-git.sh7
-rwxr-xr-xt/t9700/test.pl24
-rwxr-xr-xt/t9800-git-p4.sh264
-rw-r--r--t/test-lib.sh532
-rwxr-xr-xt/test-terminal.perl (renamed from t/t7006/test-terminal.perl)32
-rw-r--r--t/valgrind/default.supp6
-rw-r--r--tag.c35
-rw-r--r--tag.h3
-rw-r--r--test-date.c11
-rw-r--r--test-line-buffer.c92
-rw-r--r--test-mktemp.c14
-rw-r--r--test-obj-pool.c116
-rw-r--r--test-parse-options.c14
-rw-r--r--test-path-utils.c4
-rw-r--r--test-run-command.c2
-rw-r--r--test-string-pool.c31
-rw-r--r--test-subprocess.c20
-rw-r--r--test-svn-fe.c18
-rw-r--r--test-treap.c70
-rw-r--r--thread-utils.c2
-rw-r--r--thread-utils.h4
-rw-r--r--trace.c145
-rw-r--r--transport-helper.c322
-rw-r--r--transport.c53
-rw-r--r--transport.h4
-rw-r--r--tree-diff.c333
-rw-r--r--tree-walk.c195
-rw-r--r--tree-walk.h8
-rw-r--r--tree.c149
-rw-r--r--tree.h4
-rw-r--r--unpack-trees.c636
-rw-r--r--unpack-trees.h40
-rw-r--r--upload-pack.c94
-rw-r--r--url.c26
-rw-r--r--url.h3
-rw-r--r--usage.c8
-rw-r--r--userdiff.c109
-rw-r--r--userdiff.h2
-rw-r--r--utf8.c9
-rw-r--r--utf8.h2
-rw-r--r--vcs-svn/LICENSE33
-rw-r--r--vcs-svn/fast_export.c88
-rw-r--r--vcs-svn/fast_export.h16
-rw-r--r--vcs-svn/line_buffer.c130
-rw-r--r--vcs-svn/line_buffer.h31
-rw-r--r--vcs-svn/line_buffer.txt77
-rw-r--r--vcs-svn/obj_pool.h61
-rw-r--r--vcs-svn/repo_tree.c326
-rw-r--r--vcs-svn/repo_tree.h27
-rw-r--r--vcs-svn/string_pool.c102
-rw-r--r--vcs-svn/string_pool.h11
-rw-r--r--vcs-svn/string_pool.txt43
-rw-r--r--vcs-svn/svndump.c454
-rw-r--r--vcs-svn/svndump.h9
-rw-r--r--vcs-svn/trp.h237
-rw-r--r--vcs-svn/trp.txt109
-rw-r--r--walker.c2
-rw-r--r--wrapper.c247
-rw-r--r--ws.c30
-rw-r--r--wt-status.c287
-rw-r--r--wt-status.h22
-rw-r--r--xdiff-interface.c11
-rw-r--r--xdiff/xemit.c38
-rw-r--r--xdiff/xmacros.h1
-rw-r--r--xdiff/xmerge.c2
-rw-r--r--xdiff/xutils.c22
-rw-r--r--zlib.c251
1177 files changed, 76098 insertions, 24549 deletions
diff --git a/.gitignore b/.gitignore
index 14e2b6b..8572c8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
/GIT-BUILD-OPTIONS
/GIT-CFLAGS
+/GIT-LDFLAGS
/GIT-GUI-VARS
/GIT-VERSION-FILE
/bin-wrappers/
@@ -43,7 +44,6 @@
/git-fast-export
/git-fast-import
/git-fetch
-/git-fetch--tool
/git-fetch-pack
/git-filter-branch
/git-fmt-merge-msg
@@ -102,16 +102,19 @@
/git-quiltimport
/git-read-tree
/git-rebase
+/git-rebase--am
/git-rebase--interactive
+/git-rebase--merge
/git-receive-pack
/git-reflog
/git-relink
/git-remote
-/git-remote-curl
/git-remote-http
/git-remote-https
/git-remote-ftp
/git-remote-ftps
+/git-remote-fd
+/git-remote-ext
/git-remote-testgit
/git-repack
/git-replace
@@ -125,7 +128,10 @@
/git-rm
/git-send-email
/git-send-pack
+/git-sh-i18n
+/git-sh-i18n--envsubst
/git-sh-setup
+/git-sh-i18n
/git-shell
/git-shortlog
/git-show
@@ -158,7 +164,8 @@
/gitk-git/gitk-wish
/gitweb/GITWEB-BUILD-OPTIONS
/gitweb/gitweb.cgi
-/gitweb/gitweb.min.*
+/gitweb/static/gitweb.js
+/gitweb/static/gitweb.min.*
/test-chmtime
/test-ctype
/test-date
@@ -166,12 +173,19 @@
/test-dump-cache-tree
/test-genrandom
/test-index-version
+/test-line-buffer
/test-match-trees
+/test-mktemp
+/test-obj-pool
/test-parse-options
/test-path-utils
/test-run-command
/test-sha1
/test-sigchain
+/test-string-pool
+/test-subprocess
+/test-svn-fe
+/test-treap
/common-cmds.h
*.tar.gz
*.dsc
@@ -181,6 +195,12 @@
*.[aos]
*.py[co]
.depend/
+*.gcda
+*.gcno
+*.gcov
+/coverage-untested-functions
+/cover_db/
+/cover_db_html/
*+
/config.mak
/autom4te.cache
diff --git a/.mailmap b/.mailmap
index a8091eb..19c8726 100644
--- a/.mailmap
+++ b/.mailmap
@@ -36,7 +36,7 @@ Lars Doelle <lars.doelle@on-line ! de>
Lars Doelle <lars.doelle@on-line.de>
Li Hong <leehong@pku.edu.cn>
Lukas Sandström <lukass@etek.chalmers.se>
-Martin Langhoff <martin@catalyst.net.nz>
+Martin Langhoff <martin@laptop.org>
Michael Coleman <tutufan@gmail.com>
Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
Michael W. Olson <mwolson@gnu.org>
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index b8bf618..fe1c1e5 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -31,25 +31,36 @@ But if you must have a list of rules, here they are.
For shell scripts specifically (not exhaustive):
+ - We use tabs for indentation.
+
+ - Case arms are indented at the same depth as case and esac lines.
+
- We prefer $( ... ) for command substitution; unlike ``, it
properly nests. It should have been the way Bourne spelled
it from day one, but unfortunately isn't.
- - We use ${parameter-word} and its [-=?+] siblings, and their
- colon'ed "unset or null" form.
+ - We use POSIX compliant parameter substitutions and avoid bashisms;
+ namely:
- - We use ${parameter#word} and its [#%] siblings, and their
- doubled "longest matching" form.
+ - We use ${parameter-word} and its [-=?+] siblings, and their
+ colon'ed "unset or null" form.
- - We use Arithmetic Expansion $(( ... )).
+ - We use ${parameter#word} and its [#%] siblings, and their
+ doubled "longest matching" form.
- - No "Substring Expansion" ${parameter:offset:length}.
+ - No "Substring Expansion" ${parameter:offset:length}.
- - No shell arrays.
+ - No shell arrays.
- - No strlen ${#parameter}.
+ - No strlen ${#parameter}.
- - No regexp ${parameter/pattern/string}.
+ - No pattern replacement ${parameter/pattern/string}.
+
+ - We use Arithmetic Expansion $(( ... )).
+
+ - Inside Arithmetic Expansion, spell shell variables with $ in front
+ of them, as some shells do not grok $((x)) while accepting $(($x))
+ just fine (e.g. dash older than 0.5.4).
- We do not use Process Substitution <(list) or >(list).
@@ -132,3 +143,55 @@ For C programs:
- When we pass <string, length> pair to functions, we should try to
pass them in that order.
+
+Writing Documentation:
+
+ Every user-visible change should be reflected in the documentation.
+ The same general rule as for code applies -- imitate the existing
+ conventions. A few commented examples follow to provide reference
+ when writing or modifying command usage strings and synopsis sections
+ in the manual pages:
+
+ Placeholders are spelled in lowercase and enclosed in angle brackets:
+ <file>
+ --sort=<key>
+ --abbrev[=<n>]
+
+ Possibility of multiple occurrences is indicated by three dots:
+ <file>...
+ (One or more of <file>.)
+
+ Optional parts are enclosed in square brackets:
+ [<extra>]
+ (Zero or one <extra>.)
+
+ --exec-path[=<path>]
+ (Option with an optional argument. Note that the "=" is inside the
+ brackets.)
+
+ [<patch>...]
+ (Zero or more of <patch>. Note that the dots are inside, not
+ outside the brackets.)
+
+ Multiple alternatives are indicated with vertical bar:
+ [-q | --quiet]
+ [--utf8 | --no-utf8]
+
+ Parentheses are used for grouping:
+ [(<rev>|<range>)...]
+ (Any number of either <rev> or <range>. Parens are needed to make
+ it clear that "..." pertains to both <rev> and <range>.)
+
+ [(-p <parent>)...]
+ (Any number of option -p, each with one <parent> argument.)
+
+ git remote set-head <name> (-a | -d | <branch>)
+ (One and only one of "-a", "-d" or "<branch>" _must_ (no square
+ brackets) be provided.)
+
+ And a somewhat more contrived example:
+ --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]
+ Here "=" is outside the brackets, because "--diff-filter=" is a
+ valid usage. "*" has its own pair of brackets, because it can
+ (optionally) be specified only when one or more of the letters is
+ also provided.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 04f69cf..36989b7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -6,7 +6,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
gitrepository-layout.txt
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
- gitdiffcore.txt gitworkflows.txt
+ gitdiffcore.txt gitrevisions.txt gitworkflows.txt
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -63,35 +63,28 @@ endif
#
# For asciidoc ...
-# -7.1.2, no extra settings are needed.
-# 8.0-, set ASCIIDOC8.
+# -7.1.2, set ASCIIDOC7
+# 8.0-, no extra settings are needed
#
#
# For docbook-xsl ...
-# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
-# 1.69.0, no extra settings are needed?
+# -1.68.1, no extra settings are needed?
+# 1.69.0, set ASCIIDOC_ROFF?
# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP?
-# 1.71.1, no extra settings are needed?
+# 1.71.1, set ASCIIDOC_ROFF?
# 1.72.0, set DOCBOOK_XSL_172.
-# 1.73.0-, set ASCIIDOC_NO_ROFF
+# 1.73.0-, no extra settings are needed
#
-#
-# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
-# of 'the ".ft C" problem' in your generated manpages, and you
-# instead ended up with weird characters around callouts, try
-# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
-#
-
-ifdef ASCIIDOC8
+ifndef ASCIIDOC7
ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
endif
ifdef DOCBOOK_XSL_172
ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
MANPAGE_XSL = manpage-1.72.xsl
else
- ifdef ASCIIDOC_NO_ROFF
+ ifndef ASCIIDOC_ROFF
# docbook-xsl after 1.72 needs the regular XSL, but will not
# pass-thru raw roff codes from asciidoc.conf, so turn them off.
ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
@@ -279,7 +272,7 @@ $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
XSLT = docbook.xsl
XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
-user-manual.html: user-manual.xml
+user-manual.html: user-manual.xml $(XSLT)
$(QUIET_XSLTPROC)$(RM) $@+ $@ && \
xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \
mv $@+ $@
diff --git a/Documentation/RelNotes-1.5.0.1.txt b/Documentation/RelNotes/1.5.0.1.txt
index fea3f99..fea3f99 100644
--- a/Documentation/RelNotes-1.5.0.1.txt
+++ b/Documentation/RelNotes/1.5.0.1.txt
diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes/1.5.0.2.txt
index b061e50..b061e50 100644
--- a/Documentation/RelNotes-1.5.0.2.txt
+++ b/Documentation/RelNotes/1.5.0.2.txt
diff --git a/Documentation/RelNotes-1.5.0.3.txt b/Documentation/RelNotes/1.5.0.3.txt
index cd500f9..cd500f9 100644
--- a/Documentation/RelNotes-1.5.0.3.txt
+++ b/Documentation/RelNotes/1.5.0.3.txt
diff --git a/Documentation/RelNotes-1.5.0.4.txt b/Documentation/RelNotes/1.5.0.4.txt
index feefa5d..feefa5d 100644
--- a/Documentation/RelNotes-1.5.0.4.txt
+++ b/Documentation/RelNotes/1.5.0.4.txt
diff --git a/Documentation/RelNotes-1.5.0.5.txt b/Documentation/RelNotes/1.5.0.5.txt
index eeec3d7..eeec3d7 100644
--- a/Documentation/RelNotes-1.5.0.5.txt
+++ b/Documentation/RelNotes/1.5.0.5.txt
diff --git a/Documentation/RelNotes-1.5.0.6.txt b/Documentation/RelNotes/1.5.0.6.txt
index c02015a..c02015a 100644
--- a/Documentation/RelNotes-1.5.0.6.txt
+++ b/Documentation/RelNotes/1.5.0.6.txt
diff --git a/Documentation/RelNotes-1.5.0.7.txt b/Documentation/RelNotes/1.5.0.7.txt
index 670ad32..670ad32 100644
--- a/Documentation/RelNotes-1.5.0.7.txt
+++ b/Documentation/RelNotes/1.5.0.7.txt
diff --git a/Documentation/RelNotes-1.5.0.txt b/Documentation/RelNotes/1.5.0.txt
index daf4bdb..daf4bdb 100644
--- a/Documentation/RelNotes-1.5.0.txt
+++ b/Documentation/RelNotes/1.5.0.txt
diff --git a/Documentation/RelNotes-1.5.1.1.txt b/Documentation/RelNotes/1.5.1.1.txt
index 9147121..9147121 100644
--- a/Documentation/RelNotes-1.5.1.1.txt
+++ b/Documentation/RelNotes/1.5.1.1.txt
diff --git a/Documentation/RelNotes-1.5.1.2.txt b/Documentation/RelNotes/1.5.1.2.txt
index d884563..d884563 100644
--- a/Documentation/RelNotes-1.5.1.2.txt
+++ b/Documentation/RelNotes/1.5.1.2.txt
diff --git a/Documentation/RelNotes-1.5.1.3.txt b/Documentation/RelNotes/1.5.1.3.txt
index 876408b..876408b 100644
--- a/Documentation/RelNotes-1.5.1.3.txt
+++ b/Documentation/RelNotes/1.5.1.3.txt
diff --git a/Documentation/RelNotes-1.5.1.4.txt b/Documentation/RelNotes/1.5.1.4.txt
index df2f66c..df2f66c 100644
--- a/Documentation/RelNotes-1.5.1.4.txt
+++ b/Documentation/RelNotes/1.5.1.4.txt
diff --git a/Documentation/RelNotes-1.5.1.5.txt b/Documentation/RelNotes/1.5.1.5.txt
index b0ab8eb..b0ab8eb 100644
--- a/Documentation/RelNotes-1.5.1.5.txt
+++ b/Documentation/RelNotes/1.5.1.5.txt
diff --git a/Documentation/RelNotes-1.5.1.6.txt b/Documentation/RelNotes/1.5.1.6.txt
index 55f3ac1..55f3ac1 100644
--- a/Documentation/RelNotes-1.5.1.6.txt
+++ b/Documentation/RelNotes/1.5.1.6.txt
diff --git a/Documentation/RelNotes-1.5.1.txt b/Documentation/RelNotes/1.5.1.txt
index daed367..daed367 100644
--- a/Documentation/RelNotes-1.5.1.txt
+++ b/Documentation/RelNotes/1.5.1.txt
diff --git a/Documentation/RelNotes-1.5.2.1.txt b/Documentation/RelNotes/1.5.2.1.txt
index ebf20e2..ebf20e2 100644
--- a/Documentation/RelNotes-1.5.2.1.txt
+++ b/Documentation/RelNotes/1.5.2.1.txt
diff --git a/Documentation/RelNotes-1.5.2.2.txt b/Documentation/RelNotes/1.5.2.2.txt
index 7bfa341..7bfa341 100644
--- a/Documentation/RelNotes-1.5.2.2.txt
+++ b/Documentation/RelNotes/1.5.2.2.txt
diff --git a/Documentation/RelNotes-1.5.2.3.txt b/Documentation/RelNotes/1.5.2.3.txt
index addb229..addb229 100644
--- a/Documentation/RelNotes-1.5.2.3.txt
+++ b/Documentation/RelNotes/1.5.2.3.txt
diff --git a/Documentation/RelNotes-1.5.2.4.txt b/Documentation/RelNotes/1.5.2.4.txt
index 75cff47..75cff47 100644
--- a/Documentation/RelNotes-1.5.2.4.txt
+++ b/Documentation/RelNotes/1.5.2.4.txt
diff --git a/Documentation/RelNotes-1.5.2.5.txt b/Documentation/RelNotes/1.5.2.5.txt
index e8281c7..e8281c7 100644
--- a/Documentation/RelNotes-1.5.2.5.txt
+++ b/Documentation/RelNotes/1.5.2.5.txt
diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes/1.5.2.txt
index e8328d0..e8328d0 100644
--- a/Documentation/RelNotes-1.5.2.txt
+++ b/Documentation/RelNotes/1.5.2.txt
diff --git a/Documentation/RelNotes-1.5.3.1.txt b/Documentation/RelNotes/1.5.3.1.txt
index 7ff546c..7ff546c 100644
--- a/Documentation/RelNotes-1.5.3.1.txt
+++ b/Documentation/RelNotes/1.5.3.1.txt
diff --git a/Documentation/RelNotes-1.5.3.2.txt b/Documentation/RelNotes/1.5.3.2.txt
index 4bbde3c..4bbde3c 100644
--- a/Documentation/RelNotes-1.5.3.2.txt
+++ b/Documentation/RelNotes/1.5.3.2.txt
diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes/1.5.3.3.txt
index d213846..d213846 100644
--- a/Documentation/RelNotes-1.5.3.3.txt
+++ b/Documentation/RelNotes/1.5.3.3.txt
diff --git a/Documentation/RelNotes-1.5.3.4.txt b/Documentation/RelNotes/1.5.3.4.txt
index b04b3a4..b04b3a4 100644
--- a/Documentation/RelNotes-1.5.3.4.txt
+++ b/Documentation/RelNotes/1.5.3.4.txt
diff --git a/Documentation/RelNotes-1.5.3.5.txt b/Documentation/RelNotes/1.5.3.5.txt
index 7ff1d5d..7ff1d5d 100644
--- a/Documentation/RelNotes-1.5.3.5.txt
+++ b/Documentation/RelNotes/1.5.3.5.txt
diff --git a/Documentation/RelNotes-1.5.3.6.txt b/Documentation/RelNotes/1.5.3.6.txt
index 069a2b2..069a2b2 100644
--- a/Documentation/RelNotes-1.5.3.6.txt
+++ b/Documentation/RelNotes/1.5.3.6.txt
diff --git a/Documentation/RelNotes-1.5.3.7.txt b/Documentation/RelNotes/1.5.3.7.txt
index 2f69061..2f69061 100644
--- a/Documentation/RelNotes-1.5.3.7.txt
+++ b/Documentation/RelNotes/1.5.3.7.txt
diff --git a/Documentation/RelNotes-1.5.3.8.txt b/Documentation/RelNotes/1.5.3.8.txt
index 0e3ff58..0e3ff58 100644
--- a/Documentation/RelNotes-1.5.3.8.txt
+++ b/Documentation/RelNotes/1.5.3.8.txt
diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes/1.5.3.txt
index 0668d3c..0668d3c 100644
--- a/Documentation/RelNotes-1.5.3.txt
+++ b/Documentation/RelNotes/1.5.3.txt
diff --git a/Documentation/RelNotes-1.5.4.1.txt b/Documentation/RelNotes/1.5.4.1.txt
index d4e44b8..d4e44b8 100644
--- a/Documentation/RelNotes-1.5.4.1.txt
+++ b/Documentation/RelNotes/1.5.4.1.txt
diff --git a/Documentation/RelNotes-1.5.4.2.txt b/Documentation/RelNotes/1.5.4.2.txt
index 21d0df5..21d0df5 100644
--- a/Documentation/RelNotes-1.5.4.2.txt
+++ b/Documentation/RelNotes/1.5.4.2.txt
diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes/1.5.4.3.txt
index b0fc67f..b0fc67f 100644
--- a/Documentation/RelNotes-1.5.4.3.txt
+++ b/Documentation/RelNotes/1.5.4.3.txt
diff --git a/Documentation/RelNotes-1.5.4.4.txt b/Documentation/RelNotes/1.5.4.4.txt
index 323c1a8..323c1a8 100644
--- a/Documentation/RelNotes-1.5.4.4.txt
+++ b/Documentation/RelNotes/1.5.4.4.txt
diff --git a/Documentation/RelNotes-1.5.4.5.txt b/Documentation/RelNotes/1.5.4.5.txt
index bbd130e..bbd130e 100644
--- a/Documentation/RelNotes-1.5.4.5.txt
+++ b/Documentation/RelNotes/1.5.4.5.txt
diff --git a/Documentation/RelNotes-1.5.4.6.txt b/Documentation/RelNotes/1.5.4.6.txt
index 3e3c3e5..3e3c3e5 100644
--- a/Documentation/RelNotes-1.5.4.6.txt
+++ b/Documentation/RelNotes/1.5.4.6.txt
diff --git a/Documentation/RelNotes-1.5.4.7.txt b/Documentation/RelNotes/1.5.4.7.txt
index 9065a0e..9065a0e 100644
--- a/Documentation/RelNotes-1.5.4.7.txt
+++ b/Documentation/RelNotes/1.5.4.7.txt
diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes/1.5.4.txt
index f1323b6..f1323b6 100644
--- a/Documentation/RelNotes-1.5.4.txt
+++ b/Documentation/RelNotes/1.5.4.txt
diff --git a/Documentation/RelNotes-1.5.5.1.txt b/Documentation/RelNotes/1.5.5.1.txt
index 7de4197..7de4197 100644
--- a/Documentation/RelNotes-1.5.5.1.txt
+++ b/Documentation/RelNotes/1.5.5.1.txt
diff --git a/Documentation/RelNotes-1.5.5.2.txt b/Documentation/RelNotes/1.5.5.2.txt
index 391a7b0..391a7b0 100644
--- a/Documentation/RelNotes-1.5.5.2.txt
+++ b/Documentation/RelNotes/1.5.5.2.txt
diff --git a/Documentation/RelNotes-1.5.5.3.txt b/Documentation/RelNotes/1.5.5.3.txt
index f22f98b..f22f98b 100644
--- a/Documentation/RelNotes-1.5.5.3.txt
+++ b/Documentation/RelNotes/1.5.5.3.txt
diff --git a/Documentation/RelNotes-1.5.5.4.txt b/Documentation/RelNotes/1.5.5.4.txt
index 2d0279e..2d0279e 100644
--- a/Documentation/RelNotes-1.5.5.4.txt
+++ b/Documentation/RelNotes/1.5.5.4.txt
diff --git a/Documentation/RelNotes-1.5.5.5.txt b/Documentation/RelNotes/1.5.5.5.txt
index 30fa361..30fa361 100644
--- a/Documentation/RelNotes-1.5.5.5.txt
+++ b/Documentation/RelNotes/1.5.5.5.txt
diff --git a/Documentation/RelNotes-1.5.5.6.txt b/Documentation/RelNotes/1.5.5.6.txt
index d5e85cb..d5e85cb 100644
--- a/Documentation/RelNotes-1.5.5.6.txt
+++ b/Documentation/RelNotes/1.5.5.6.txt
diff --git a/Documentation/RelNotes-1.5.5.txt b/Documentation/RelNotes/1.5.5.txt
index 2932212..2932212 100644
--- a/Documentation/RelNotes-1.5.5.txt
+++ b/Documentation/RelNotes/1.5.5.txt
diff --git a/Documentation/RelNotes-1.5.6.1.txt b/Documentation/RelNotes/1.5.6.1.txt
index 4864b16..4864b16 100644
--- a/Documentation/RelNotes-1.5.6.1.txt
+++ b/Documentation/RelNotes/1.5.6.1.txt
diff --git a/Documentation/RelNotes-1.5.6.2.txt b/Documentation/RelNotes/1.5.6.2.txt
index 5902a85..5902a85 100644
--- a/Documentation/RelNotes-1.5.6.2.txt
+++ b/Documentation/RelNotes/1.5.6.2.txt
diff --git a/Documentation/RelNotes-1.5.6.3.txt b/Documentation/RelNotes/1.5.6.3.txt
index 9426112..f61dd35 100644
--- a/Documentation/RelNotes-1.5.6.3.txt
+++ b/Documentation/RelNotes/1.5.6.3.txt
@@ -4,7 +4,7 @@ GIT v1.5.6.3 Release Notes
Fixes since v1.5.6.2
--------------------
-* Setting core.sharerepository to traditional "true" value was supposed to make
+* Setting core.sharedrepository to traditional "true" value was supposed to make
the repository group writable but should not affect permission for others.
However, since 1.5.6, it was broken to drop permission for others when umask is
022, making the repository unreadable by others.
diff --git a/Documentation/RelNotes-1.5.6.4.txt b/Documentation/RelNotes/1.5.6.4.txt
index d8968f1..d8968f1 100644
--- a/Documentation/RelNotes-1.5.6.4.txt
+++ b/Documentation/RelNotes/1.5.6.4.txt
diff --git a/Documentation/RelNotes-1.5.6.5.txt b/Documentation/RelNotes/1.5.6.5.txt
index 47ca172..47ca172 100644
--- a/Documentation/RelNotes-1.5.6.5.txt
+++ b/Documentation/RelNotes/1.5.6.5.txt
diff --git a/Documentation/RelNotes-1.5.6.6.txt b/Documentation/RelNotes/1.5.6.6.txt
index 79da23d..79da23d 100644
--- a/Documentation/RelNotes-1.5.6.6.txt
+++ b/Documentation/RelNotes/1.5.6.6.txt
diff --git a/Documentation/RelNotes-1.5.6.txt b/Documentation/RelNotes/1.5.6.txt
index e143d8d..e143d8d 100644
--- a/Documentation/RelNotes-1.5.6.txt
+++ b/Documentation/RelNotes/1.5.6.txt
diff --git a/Documentation/RelNotes-1.6.0.1.txt b/Documentation/RelNotes/1.6.0.1.txt
index 49d7a1c..49d7a1c 100644
--- a/Documentation/RelNotes-1.6.0.1.txt
+++ b/Documentation/RelNotes/1.6.0.1.txt
diff --git a/Documentation/RelNotes-1.6.0.2.txt b/Documentation/RelNotes/1.6.0.2.txt
index 51b32f5..e1e24b3 100644
--- a/Documentation/RelNotes-1.6.0.2.txt
+++ b/Documentation/RelNotes/1.6.0.2.txt
@@ -17,7 +17,7 @@ Fixes since v1.6.0.1
* Many commands did not use the correct working tree location when used
with GIT_WORK_TREE environment settings.
-* Some systems needs to use compatibility fnmach and regex libraries
+* Some systems need to use compatibility fnmatch and regex libraries
independent from each other; the compat/ area has been reorganized to
allow this.
diff --git a/Documentation/RelNotes-1.6.0.3.txt b/Documentation/RelNotes/1.6.0.3.txt
index ae05778..ae05778 100644
--- a/Documentation/RelNotes-1.6.0.3.txt
+++ b/Documentation/RelNotes/1.6.0.3.txt
diff --git a/Documentation/RelNotes-1.6.0.4.txt b/Documentation/RelNotes/1.6.0.4.txt
index d522661..d522661 100644
--- a/Documentation/RelNotes-1.6.0.4.txt
+++ b/Documentation/RelNotes/1.6.0.4.txt
diff --git a/Documentation/RelNotes-1.6.0.5.txt b/Documentation/RelNotes/1.6.0.5.txt
index a08bb96..a08bb96 100644
--- a/Documentation/RelNotes-1.6.0.5.txt
+++ b/Documentation/RelNotes/1.6.0.5.txt
diff --git a/Documentation/RelNotes-1.6.0.6.txt b/Documentation/RelNotes/1.6.0.6.txt
index 64ece1f..64ece1f 100644
--- a/Documentation/RelNotes-1.6.0.6.txt
+++ b/Documentation/RelNotes/1.6.0.6.txt
diff --git a/Documentation/RelNotes-1.6.0.txt b/Documentation/RelNotes/1.6.0.txt
index de7ef16..de7ef16 100644
--- a/Documentation/RelNotes-1.6.0.txt
+++ b/Documentation/RelNotes/1.6.0.txt
diff --git a/Documentation/RelNotes-1.6.1.1.txt b/Documentation/RelNotes/1.6.1.1.txt
index 8c594ba..8c594ba 100644
--- a/Documentation/RelNotes-1.6.1.1.txt
+++ b/Documentation/RelNotes/1.6.1.1.txt
diff --git a/Documentation/RelNotes-1.6.1.2.txt b/Documentation/RelNotes/1.6.1.2.txt
index be37cbb..be37cbb 100644
--- a/Documentation/RelNotes-1.6.1.2.txt
+++ b/Documentation/RelNotes/1.6.1.2.txt
diff --git a/Documentation/RelNotes-1.6.1.3.txt b/Documentation/RelNotes/1.6.1.3.txt
index 6f0bde1..6f0bde1 100644
--- a/Documentation/RelNotes-1.6.1.3.txt
+++ b/Documentation/RelNotes/1.6.1.3.txt
diff --git a/Documentation/RelNotes-1.6.1.4.txt b/Documentation/RelNotes/1.6.1.4.txt
index 0ce6316..0ce6316 100644
--- a/Documentation/RelNotes-1.6.1.4.txt
+++ b/Documentation/RelNotes/1.6.1.4.txt
diff --git a/Documentation/RelNotes-1.6.1.txt b/Documentation/RelNotes/1.6.1.txt
index adb7cca..adb7cca 100644
--- a/Documentation/RelNotes-1.6.1.txt
+++ b/Documentation/RelNotes/1.6.1.txt
diff --git a/Documentation/RelNotes-1.6.2.1.txt b/Documentation/RelNotes/1.6.2.1.txt
index dfa3641..dfa3641 100644
--- a/Documentation/RelNotes-1.6.2.1.txt
+++ b/Documentation/RelNotes/1.6.2.1.txt
diff --git a/Documentation/RelNotes-1.6.2.2.txt b/Documentation/RelNotes/1.6.2.2.txt
index fafa998..fafa998 100644
--- a/Documentation/RelNotes-1.6.2.2.txt
+++ b/Documentation/RelNotes/1.6.2.2.txt
diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes/1.6.2.3.txt
index 4d3c1ac..4d3c1ac 100644
--- a/Documentation/RelNotes-1.6.2.3.txt
+++ b/Documentation/RelNotes/1.6.2.3.txt
diff --git a/Documentation/RelNotes-1.6.2.4.txt b/Documentation/RelNotes/1.6.2.4.txt
index f4bf1d0..f4bf1d0 100644
--- a/Documentation/RelNotes-1.6.2.4.txt
+++ b/Documentation/RelNotes/1.6.2.4.txt
diff --git a/Documentation/RelNotes-1.6.2.5.txt b/Documentation/RelNotes/1.6.2.5.txt
index b23f9e9..b23f9e9 100644
--- a/Documentation/RelNotes-1.6.2.5.txt
+++ b/Documentation/RelNotes/1.6.2.5.txt
diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes/1.6.2.txt
index ad060f4..ad060f4 100644
--- a/Documentation/RelNotes-1.6.2.txt
+++ b/Documentation/RelNotes/1.6.2.txt
diff --git a/Documentation/RelNotes-1.6.3.1.txt b/Documentation/RelNotes/1.6.3.1.txt
index 2400b72..2400b72 100644
--- a/Documentation/RelNotes-1.6.3.1.txt
+++ b/Documentation/RelNotes/1.6.3.1.txt
diff --git a/Documentation/RelNotes-1.6.3.2.txt b/Documentation/RelNotes/1.6.3.2.txt
index b2f3f02..b2f3f02 100644
--- a/Documentation/RelNotes-1.6.3.2.txt
+++ b/Documentation/RelNotes/1.6.3.2.txt
diff --git a/Documentation/RelNotes-1.6.3.3.txt b/Documentation/RelNotes/1.6.3.3.txt
index 1c28398..1c28398 100644
--- a/Documentation/RelNotes-1.6.3.3.txt
+++ b/Documentation/RelNotes/1.6.3.3.txt
diff --git a/Documentation/RelNotes-1.6.3.4.txt b/Documentation/RelNotes/1.6.3.4.txt
index cad461b..cad461b 100644
--- a/Documentation/RelNotes-1.6.3.4.txt
+++ b/Documentation/RelNotes/1.6.3.4.txt
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes/1.6.3.txt
index 418c685..418c685 100644
--- a/Documentation/RelNotes-1.6.3.txt
+++ b/Documentation/RelNotes/1.6.3.txt
diff --git a/Documentation/RelNotes-1.6.4.1.txt b/Documentation/RelNotes/1.6.4.1.txt
index e439e45..e439e45 100644
--- a/Documentation/RelNotes-1.6.4.1.txt
+++ b/Documentation/RelNotes/1.6.4.1.txt
diff --git a/Documentation/RelNotes-1.6.4.2.txt b/Documentation/RelNotes/1.6.4.2.txt
index c11ec01..c11ec01 100644
--- a/Documentation/RelNotes-1.6.4.2.txt
+++ b/Documentation/RelNotes/1.6.4.2.txt
diff --git a/Documentation/RelNotes-1.6.4.3.txt b/Documentation/RelNotes/1.6.4.3.txt
index 4f29bab..5643e65 100644
--- a/Documentation/RelNotes-1.6.4.3.txt
+++ b/Documentation/RelNotes/1.6.4.3.txt
@@ -11,7 +11,7 @@ Fixes since v1.6.4.2
been deprecated.
* "git fetch" and "git clone" had an extra sanity check to verify the
- presense of the corresponding *.pack file before downloading *.idx
+ presence of the corresponding *.pack file before downloading *.idx
file by issuing a HEAD request. Github server however sometimes
gave 500 (Internal server error) response to HEAD even if a GET
request for *.pack file to the same URL would have succeeded, and broke
diff --git a/Documentation/RelNotes-1.6.4.4.txt b/Documentation/RelNotes/1.6.4.4.txt
index 0ead45f..0ead45f 100644
--- a/Documentation/RelNotes-1.6.4.4.txt
+++ b/Documentation/RelNotes/1.6.4.4.txt
diff --git a/Documentation/RelNotes/1.6.4.5.txt b/Documentation/RelNotes/1.6.4.5.txt
new file mode 100644
index 0000000..eb6307d
--- /dev/null
+++ b/Documentation/RelNotes/1.6.4.5.txt
@@ -0,0 +1,20 @@
+Git v1.6.4.5 Release Notes
+==========================
+
+Fixes since v1.6.4.4
+--------------------
+
+ * Simplified base85 implementation.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git count-objects" did not handle packs larger than 4G.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+ when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.4.txt b/Documentation/RelNotes/1.6.4.txt
index 7a90441..7a90441 100644
--- a/Documentation/RelNotes-1.6.4.txt
+++ b/Documentation/RelNotes/1.6.4.txt
diff --git a/Documentation/RelNotes-1.6.5.1.txt b/Documentation/RelNotes/1.6.5.1.txt
index 309ba18..309ba18 100644
--- a/Documentation/RelNotes-1.6.5.1.txt
+++ b/Documentation/RelNotes/1.6.5.1.txt
diff --git a/Documentation/RelNotes-1.6.5.2.txt b/Documentation/RelNotes/1.6.5.2.txt
index aa7ccce..aa7ccce 100644
--- a/Documentation/RelNotes-1.6.5.2.txt
+++ b/Documentation/RelNotes/1.6.5.2.txt
diff --git a/Documentation/RelNotes-1.6.5.3.txt b/Documentation/RelNotes/1.6.5.3.txt
index b2fad1b..b2fad1b 100644
--- a/Documentation/RelNotes-1.6.5.3.txt
+++ b/Documentation/RelNotes/1.6.5.3.txt
diff --git a/Documentation/RelNotes-1.6.5.4.txt b/Documentation/RelNotes/1.6.5.4.txt
index e42f8b2..d3a2a3e 100644
--- a/Documentation/RelNotes-1.6.5.4.txt
+++ b/Documentation/RelNotes/1.6.5.4.txt
@@ -26,7 +26,7 @@ Fixes since v1.6.5.3
future versions, but not in this release,
* "git merge -m <message> <branch>..." added the standard merge message
- on its own after user-supplied message, which should have overrided the
+ on its own after user-supplied message, which should have overridden the
standard one.
Other minor documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.5.5.txt b/Documentation/RelNotes/1.6.5.5.txt
index ecfc57d..ecfc57d 100644
--- a/Documentation/RelNotes-1.6.5.5.txt
+++ b/Documentation/RelNotes/1.6.5.5.txt
diff --git a/Documentation/RelNotes-1.6.5.6.txt b/Documentation/RelNotes/1.6.5.6.txt
index a9eaf76..a9eaf76 100644
--- a/Documentation/RelNotes-1.6.5.6.txt
+++ b/Documentation/RelNotes/1.6.5.6.txt
diff --git a/Documentation/RelNotes-1.6.5.7.txt b/Documentation/RelNotes/1.6.5.7.txt
index 5b49ea5..dc5302c 100644
--- a/Documentation/RelNotes-1.6.5.7.txt
+++ b/Documentation/RelNotes/1.6.5.7.txt
@@ -10,7 +10,7 @@ Fixes since v1.6.5.6
an older version of git should just ignore them. Instead we diagnosed
it as an error.
-* With help.autocorrect set to non-zero value, the logic to guess typoes
+* With help.autocorrect set to non-zero value, the logic to guess typos
in the subcommand name misfired and ran a random nonsense command.
* If a command is run with an absolute path as a pathspec inside a bare
diff --git a/Documentation/RelNotes-1.6.5.8.txt b/Documentation/RelNotes/1.6.5.8.txt
index 8b24beb..8b24beb 100644
--- a/Documentation/RelNotes-1.6.5.8.txt
+++ b/Documentation/RelNotes/1.6.5.8.txt
diff --git a/Documentation/RelNotes/1.6.5.9.txt b/Documentation/RelNotes/1.6.5.9.txt
new file mode 100644
index 0000000..bb469dd
--- /dev/null
+++ b/Documentation/RelNotes/1.6.5.9.txt
@@ -0,0 +1,18 @@
+Git v1.6.5.9 Release Notes
+==========================
+
+Fixes since v1.6.5.8
+--------------------
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git blame -L $start,$end" segfaulted when too large $start was given.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+ when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.5.txt b/Documentation/RelNotes/1.6.5.txt
index ee141c1..ee141c1 100644
--- a/Documentation/RelNotes-1.6.5.txt
+++ b/Documentation/RelNotes/1.6.5.txt
diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes/1.6.6.1.txt
index f1d0a4a..f1d0a4a 100644
--- a/Documentation/RelNotes-1.6.6.1.txt
+++ b/Documentation/RelNotes/1.6.6.1.txt
diff --git a/Documentation/RelNotes-1.6.6.2.txt b/Documentation/RelNotes/1.6.6.2.txt
index 4eaddc0..4eaddc0 100644
--- a/Documentation/RelNotes-1.6.6.2.txt
+++ b/Documentation/RelNotes/1.6.6.2.txt
diff --git a/Documentation/RelNotes/1.6.6.3.txt b/Documentation/RelNotes/1.6.6.3.txt
new file mode 100644
index 0000000..11483ac
--- /dev/null
+++ b/Documentation/RelNotes/1.6.6.3.txt
@@ -0,0 +1,23 @@
+Git v1.6.6.3 Release Notes
+==========================
+
+Fixes since v1.6.6.2
+--------------------
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git bisect $path" did not correctly diagnose an error when given a
+ non-existent path.
+
+ * "git blame -L $start,$end" segfaulted when too large $start was given.
+
+ * "git imap-send" did not write draft box with CRLF line endings per RFC.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+ when --keep-dashdash was in effect.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
+
+Other minor fixes and documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes/1.6.6.txt
index 04e205c..c50b59c 100644
--- a/Documentation/RelNotes-1.6.6.txt
+++ b/Documentation/RelNotes/1.6.6.txt
@@ -29,7 +29,7 @@ or adjust to the new behaviour, on the day their sysadmin decides to install
the new version of git. When we switched from "git-foo" to "git foo" in
1.6.0, even though the change had been advertised and the transition
guide had been provided for a very long time, the users procrastinated
-during the entire transtion period, and ended up panicking on the day
+during the entire transition period, and ended up panicking on the day
their sysadmins updated their git installation. We are trying to avoid
repeating that unpleasantness in the 1.7.0 release.
@@ -94,7 +94,7 @@ users will fare this time.
* "git diff" traditionally treated various "ignore whitespace" options
only as a way to filter the patch output. "git diff --exit-code -b"
exited with non-zero status even if all changes were about changing the
- ammount of whitespace and nothing else. and "git diff -b" showed the
+ amount of whitespace and nothing else. and "git diff -b" showed the
"diff --git" header line for such a change without patch text.
In 1.7.0, the "ignore whitespaces" will affect the semantics of the
diff --git a/Documentation/RelNotes-1.7.0.1.txt b/Documentation/RelNotes/1.7.0.1.txt
index 8ff5bca..8ff5bca 100644
--- a/Documentation/RelNotes-1.7.0.1.txt
+++ b/Documentation/RelNotes/1.7.0.1.txt
diff --git a/Documentation/RelNotes-1.7.0.2.txt b/Documentation/RelNotes/1.7.0.2.txt
index fcb46ca..fcb46ca 100644
--- a/Documentation/RelNotes-1.7.0.2.txt
+++ b/Documentation/RelNotes/1.7.0.2.txt
diff --git a/Documentation/RelNotes-1.7.0.3.txt b/Documentation/RelNotes/1.7.0.3.txt
index 3b35573..3b35573 100644
--- a/Documentation/RelNotes-1.7.0.3.txt
+++ b/Documentation/RelNotes/1.7.0.3.txt
diff --git a/Documentation/RelNotes-1.7.0.4.txt b/Documentation/RelNotes/1.7.0.4.txt
index cf7f60e..cf7f60e 100644
--- a/Documentation/RelNotes-1.7.0.4.txt
+++ b/Documentation/RelNotes/1.7.0.4.txt
diff --git a/Documentation/RelNotes-1.7.0.5.txt b/Documentation/RelNotes/1.7.0.5.txt
index 3149c91..3149c91 100644
--- a/Documentation/RelNotes-1.7.0.5.txt
+++ b/Documentation/RelNotes/1.7.0.5.txt
diff --git a/Documentation/RelNotes-1.7.0.6.txt b/Documentation/RelNotes/1.7.0.6.txt
index b2852b6..b2852b6 100644
--- a/Documentation/RelNotes-1.7.0.6.txt
+++ b/Documentation/RelNotes/1.7.0.6.txt
diff --git a/Documentation/RelNotes/1.7.0.7.txt b/Documentation/RelNotes/1.7.0.7.txt
new file mode 100644
index 0000000..d0cb7ca
--- /dev/null
+++ b/Documentation/RelNotes/1.7.0.7.txt
@@ -0,0 +1,16 @@
+Git v1.7.0.7 Release Notes
+==========================
+
+Fixes since v1.7.0.6
+--------------------
+
+ * "make NO_CURL=NoThanks install" was broken.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+ that uses "~/" short-hand segfaulted when $HOME environment variable
+ was not set.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.0.8.txt b/Documentation/RelNotes/1.7.0.8.txt
new file mode 100644
index 0000000..7f05b48
--- /dev/null
+++ b/Documentation/RelNotes/1.7.0.8.txt
@@ -0,0 +1,10 @@
+Git v1.7.0.8 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.0.9.txt b/Documentation/RelNotes/1.7.0.9.txt
new file mode 100644
index 0000000..bfb3166
--- /dev/null
+++ b/Documentation/RelNotes/1.7.0.9.txt
@@ -0,0 +1,8 @@
+Git v1.7.0.9 Release Notes
+==========================
+
+Fixes since v1.7.0.8
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes/1.7.0.txt
index 43e3f33..0bb8c0b 100644
--- a/Documentation/RelNotes-1.7.0.txt
+++ b/Documentation/RelNotes/1.7.0.txt
@@ -202,7 +202,7 @@ release, unless otherwise noted.
the branch is fully merged to its upstream branch if it is not merged
to the current branch. It now deletes it in such a case.
- * "fiter-branch" command incorrectly said --prune-empty and --filter-commit
+ * "filter-branch" command incorrectly said --prune-empty and --filter-commit
were incompatible; the latter should be read as --commit-filter.
* When using "git status" or asking "git diff" to compare the work tree
diff --git a/Documentation/RelNotes-1.7.1.1.txt b/Documentation/RelNotes/1.7.1.1.txt
index 3f6b314..3f6b314 100644
--- a/Documentation/RelNotes-1.7.1.1.txt
+++ b/Documentation/RelNotes/1.7.1.1.txt
diff --git a/Documentation/RelNotes/1.7.1.2.txt b/Documentation/RelNotes/1.7.1.2.txt
new file mode 100644
index 0000000..61ba14e
--- /dev/null
+++ b/Documentation/RelNotes/1.7.1.2.txt
@@ -0,0 +1,28 @@
+Git v1.7.1.2 Release Notes
+==========================
+
+Fixes since v1.7.1.1
+--------------------
+
+ * "git commit" did not honor GIT_REFLOG_ACTION environment variable, resulting
+ reflog messages for cherry-pick and revert actions to be recorded as "commit".
+
+ * "git clone/fetch/pull" issued an incorrect error message when a ref and
+ a symref that points to the ref were updated at the same time. This
+ obviously would update them to the same value, and should not result in
+ an error condition.
+
+ * "git diff" inside a tree with many pathnames that have certain
+ characters has become very slow in 1.7.0 by mistake.
+
+ * "git rev-parse --parseopt --stop-at-non-option" did not stop at non option
+ when --keep-dashdash was in effect.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+ that uses "~/" short-hand segfaulted when $HOME environment variable
+ was not set.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.1.3.txt b/Documentation/RelNotes/1.7.1.3.txt
new file mode 100644
index 0000000..5b18518
--- /dev/null
+++ b/Documentation/RelNotes/1.7.1.3.txt
@@ -0,0 +1,10 @@
+Git v1.7.1.3 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.1.4.txt b/Documentation/RelNotes/1.7.1.4.txt
new file mode 100644
index 0000000..7c734b4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.1.4.txt
@@ -0,0 +1,8 @@
+Git v1.7.1.4 Release Notes
+==========================
+
+Fixes since v1.7.1.3
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes/1.7.1.txt
index 9d89fed..9d89fed 100644
--- a/Documentation/RelNotes-1.7.1.txt
+++ b/Documentation/RelNotes/1.7.1.txt
diff --git a/Documentation/RelNotes/1.7.2.1.txt b/Documentation/RelNotes/1.7.2.1.txt
new file mode 100644
index 0000000..1103c47
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.1.txt
@@ -0,0 +1,25 @@
+Git v1.7.2.1 Release Notes
+==========================
+
+Fixes since v1.7.2
+------------------
+
+ * "git instaweb" wasn't useful when your Apache was installed under a
+ name other than apache2 (e.g. "httpd").
+
+ * Similarly, "git web--browse" (invoked by "git help -w") learned that
+ chrome browser is sometimes called google-chrome.
+
+ * An overlong line after ".gitdir: " in a git file caused out of bounds
+ access to an array on the stack.
+
+ * "git config --path conf.var" to attempt to expand a variable conf.var
+ that uses "~/" short-hand segfaulted when $HOME environment variable
+ was not set.
+
+ * Documentation on Cygwin failed to build.
+
+ * The error message from "git pull blarg" when 'blarg' is an unknown
+ remote name has been improved.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.2.2.txt b/Documentation/RelNotes/1.7.2.2.txt
new file mode 100644
index 0000000..71eb6a8
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.2.txt
@@ -0,0 +1,22 @@
+Git v1.7.2.2 Release Notes
+==========================
+
+Fixes since v1.7.2.1
+--------------------
+
+ * Object transfer over smart http transport deadlocked the client when
+ the remote HTTP server returned a failure, instead of erroring it out.
+
+ * git-gui honors custom textconv filters when showing diff and blame;
+
+ * git diff --relative=subdir (without the necessary trailing /) did not
+ work well;
+
+ * "git diff-files -p --submodule" was recently broken;
+
+ * "git checkout -b n ':/token'" did not work;
+
+ * "git index-pack" (hence "git fetch/clone/pull/push") enabled the object
+ replacement machinery by mistake (it never should have);
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.2.3.txt b/Documentation/RelNotes/1.7.2.3.txt
new file mode 100644
index 0000000..610960c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.3.txt
@@ -0,0 +1,39 @@
+Git v1.7.2.3 Release Notes
+==========================
+
+Fixes since v1.7.2.2
+--------------------
+
+ * When people try insane things such as delta-compressing 4GiB files, we
+ threw an assertion failure.
+
+ * "git archive" gave the full commit ID for "$Format:%h$".
+
+ * "git fetch --tags" did not fetch tags when remote.<nick>.tagopt was set
+ to --no-tags. The command line option now overrides the configuration
+ setting.
+
+ * "git for-each-ref --format='%(objectname:short)'" has been completely
+ broken for a long time.
+
+ * "git gc" incorrectly pruned a rerere record that was created long
+ time ago but still is actively and repeatedly used.
+
+ * "git log --follow -M -p" was seriously broken in 1.7.2, reporting
+ assertion failure.
+
+ * Running "git log" with an incorrect option started pager nevertheless,
+ forcing the user to dismiss it.
+
+ * "git rebase" did not work well when the user has diff.renames
+ configuration variable set.
+
+ * An earlier (and rather old) fix to "git rebase" against a rebased
+ upstream broke a more normal, non rebased upstream case rather badly,
+ attempting to re-apply patches that are already accepted upstream.
+
+ * "git submodule sync" forgot to update the superproject's config file
+ when submodule URL changed.
+
+ * "git pack-refs --all --prune" did not remove a directory that has
+ become empty.
diff --git a/Documentation/RelNotes/1.7.2.4.txt b/Documentation/RelNotes/1.7.2.4.txt
new file mode 100644
index 0000000..f7950a4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.4.txt
@@ -0,0 +1,10 @@
+Git v1.7.2.4 Release Notes
+==========================
+
+This is primarily to backport support for the new "add.ignoreErrors"
+name given to the existing "add.ignore-errors" configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
diff --git a/Documentation/RelNotes/1.7.2.5.txt b/Documentation/RelNotes/1.7.2.5.txt
new file mode 100644
index 0000000..bf976c4
--- /dev/null
+++ b/Documentation/RelNotes/1.7.2.5.txt
@@ -0,0 +1,8 @@
+Git v1.7.2.5 Release Notes
+==========================
+
+Fixes since v1.7.2.4
+--------------------
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
diff --git a/Documentation/RelNotes-1.7.2.txt b/Documentation/RelNotes/1.7.2.txt
index aa79f06..15cf011 100644
--- a/Documentation/RelNotes-1.7.2.txt
+++ b/Documentation/RelNotes/1.7.2.txt
@@ -1,13 +1,16 @@
-Git v1.7.2 Release Notes (draft)
-================================
+Git v1.7.2 Release Notes
+========================
Updates since v1.7.1
--------------------
- * core.eol configuration and eol attribute are the new way to control
- the end of line conventions for files in the working tree;
- core.autocrlf overrides it, keeping the traditional behaviour by
- default.
+ * core.eol configuration and text/eol attributes are the new way to control
+ the end of line conventions for files in the working tree.
+
+ * core.autocrlf has been made safer - it will now only handle line
+ endings for new files and files that are LF-only in the
+ repository. To normalize content that has been checked in with
+ CRLF, use the new eol/text attributes.
* The whitespace rules used in "git apply --whitespace" and "git diff"
gained a new member in the family (tab-in-indent) to help projects with
@@ -45,8 +48,12 @@ Updates since v1.7.1
commit.
* "git cherry-pick" learned to pick a range of commits
- (e.g. "cherry-pick A..B" and "cherry-pick --stdin"); this does not
- have nicer sequencing control "rebase [-i]" has, though.
+ (e.g. "cherry-pick A..B" and "cherry-pick --stdin"), so did "git
+ revert"; these do not support the nicer sequencing control "rebase
+ [-i]" has, though.
+
+ * "git cherry-pick" and "git revert" learned --strategy option to specify
+ the merge strategy to be used when performing three-way merges.
* "git cvsserver" can be told to use pserver; its password file can be
stored outside the repository.
@@ -74,7 +81,7 @@ Updates since v1.7.1
* Various options to "git grep" (e.g. --count, --name-only) work better
with binary files.
- * "git grep" learned "-Ovi" to open the files with hits in yoru editor.
+ * "git grep" learned "-Ovi" to open the files with hits in your editor.
* "git help -w" learned "chrome" and "chromium" browsers.
@@ -96,8 +103,6 @@ Updates since v1.7.1
* "git remote" learned "set-branches" subcommand.
- * "git revert" learned --strategy option to specify the merge strategy.
-
* "git rev-list A..B" learned --ancestry-path option to further limit
the result to the commits that are on the ancestry chain between A and
B (i.e. commits that are not descendants of A are excluded).
@@ -116,6 +121,10 @@ Updates since v1.7.1
highlighting, "plackup" support for instaweb, .fcgi suffix to run
it as FastCGI script, etc.
+ * The test harness has been updated to produce TAP-friendly output.
+
+ * Many documentation improvement patches are also included.
+
Fixes since v1.7.1
------------------
@@ -124,37 +133,19 @@ All of the fixes in v1.7.1.X maintenance series are included in this
release, unless otherwise noted.
* We didn't URL decode "file:///path/to/repo" correctly when path/to/repo
- had percent-encoded characters (638794c, 9d2e942).
-
- * "git commit" did not honor GIT_REFLOG_ACTION environment variable, resulting
- reflog messages for cherry-pick and revert actions to be recorded as "commit".
-
- * "git clone/fetch/pull" issued an incorrect error message when a ref and
- a symref that points to the ref were updated at the same time. This
- obviously would update them to the same value, and should not result in
- an error condition (0e71bc3).
+ had percent-encoded characters (638794c, 9d2e942, ce83eda, 3c73a1d).
* "git clone" did not configure remote.origin.url correctly for bare
clones (df61c889).
- * "git diff" inside a tree with many pathnames that have certain
- characters has become very slow in 1.7.0 by mistake (will merge
- e53e6b443 to 'maint').
-
* "git diff --graph" works better with "--color-words" and other options
(81fa024..4297c0a).
* "git diff" could show ambiguous abbreviation of blob object names on
its "index" line (3e5a188).
- * "git rebase" did not faithfully reproduce a malformed author ident, that
- is often seen in a repository converted from foreign SCMs (43c23251).
-
* "git reset --hard" started from a wrong directory and a working tree in
a nonstandard location is in use got confused (560fb6a1).
---
-exec >/var/tmp/1
-O=v1.7.2-rc0-60-g2927a50
-echo O=$(git describe HEAD)
-git shortlog --no-merges HEAD ^maint ^$O
+ * "git read-tree -m A B" used to switch to branch B while retaining
+ local changes added an incorrect cache-tree information (b1f47514).
diff --git a/Documentation/RelNotes/1.7.3.1.txt b/Documentation/RelNotes/1.7.3.1.txt
new file mode 100644
index 0000000..002c93b
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.1.txt
@@ -0,0 +1,14 @@
+Git v1.7.3.1 Release Notes
+==========================
+
+Fixes since v1.7.3
+------------------
+
+ * "git stash show stash@{$n}" was accidentally broken in 1.7.3 ("git
+ stash show" without any argument still worked, though).
+
+ * "git stash branch $branch stash@{$n}" was accidentally broken in
+ 1.7.3 and started dropping the named stash even when branch creation
+ failed.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.3.2.txt b/Documentation/RelNotes/1.7.3.2.txt
new file mode 100644
index 0000000..5c93b85
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.2.txt
@@ -0,0 +1,5 @@
+Git v1.7.3.2 Release Notes
+==========================
+
+This is primarily to push out many documentation fixes accumulated since
+the 1.7.3.1 release.
diff --git a/Documentation/RelNotes/1.7.3.3.txt b/Documentation/RelNotes/1.7.3.3.txt
new file mode 100644
index 0000000..9b2b244
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.3.txt
@@ -0,0 +1,54 @@
+Git v1.7.3.3 Release Notes
+==========================
+
+In addition to the usual fixes, this release also includes support for
+the new "add.ignoreErrors" name given to the existing "add.ignore-errors"
+configuration variable.
+
+The next version, Git 1.7.4, and future versions, will support both
+old and incorrect name and the new corrected name, but without this
+backport, users who want to use the new name "add.ignoreErrors" in
+their repositories cannot use older versions of Git.
+
+Fixes since v1.7.3.2
+--------------------
+
+ * "git apply" segfaulted when a bogus input is fed to it.
+
+ * Running "git cherry-pick --ff" on a root commit segfaulted.
+
+ * "diff", "blame" and friends incorrectly applied textconv filters to
+ symlinks.
+
+ * Highlighting of whitespace breakage in "diff" output was showing
+ incorrect amount of whitespaces when blank-at-eol is set and the line
+ consisted only of whitespaces and a TAB.
+
+ * "diff" was overly inefficient when trying to find the line to use for
+ the function header (i.e. equivalent to --show-c-function of GNU diff).
+
+ * "git imap-send" depends on libcrypto but our build rule relied on the
+ linker to implicitly link it via libssl, which was wrong.
+
+ * "git merge-file" can be called from within a subdirectory now.
+
+ * "git repack -f" expanded and recompressed non-delta objects in the
+ existing pack, which was wasteful. Use new "-F" option if you really
+ want to (e.g. when changing the pack.compression level).
+
+ * "git rev-list --format="...%x00..." incorrectly chopped its output
+ at NUL.
+
+ * "git send-email" did not correctly remove duplicate mail addresses from
+ the Cc: header that appear on the To: header.
+
+ * The completion script (in contrib/completion) ignored lightweight tags
+ in __git_ps1().
+
+ * "git-blame" mode (in contrib/emacs) didn't say (require 'format-spec)
+ even though it depends on it; it didn't work with Emacs 22 or older
+ unless Gnus is used.
+
+ * "git-p4" (in contrib/) did not correctly handle deleted files.
+
+Other minor fixes and documentation updates are also included.
diff --git a/Documentation/RelNotes/1.7.3.4.txt b/Documentation/RelNotes/1.7.3.4.txt
new file mode 100644
index 0000000..e57f7c1
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.4.txt
@@ -0,0 +1,45 @@
+Git v1.7.3.4 Release Notes
+==========================
+
+Fixes since v1.7.3.3
+--------------------
+
+ * Smart HTTP transport used to incorrectly retry redirected POST
+ request with GET request.
+
+ * "git apply" did not correctly handle patches that only change modes
+ if told to apply while stripping leading paths with -p option.
+
+ * "git apply" can deal with patches with timezone formatted with a
+ colon between the hours and minutes part (e.g. "-08:00" instead of
+ "-0800").
+
+ * "git checkout" removed an untracked file "foo" from the working
+ tree when switching to a branch that contains a tracked path
+ "foo/bar". Prevent this, just like the case where the conflicting
+ path were "foo" (c752e7f..7980872d).
+
+ * "git cherry-pick" or "git revert" refused to work when a path that
+ would be modified by the operation was stat-dirty without a real
+ difference in the contents of the file.
+
+ * "git diff --check" reported an incorrect line number for added
+ blank lines at the end of file.
+
+ * "git imap-send" failed to build under NO_OPENSSL.
+
+ * Setting log.decorate configuration variable to "0" or "1" to mean
+ "false" or "true" did not work.
+
+ * "git push" over dumb HTTP protocol did not work against WebDAV
+ servers that did not terminate a collection name with a slash.
+
+ * "git tag -v" did not work with GPG signatures in rfc1991 mode.
+
+ * The post-receive-email sample hook was accidentally broken in 1.7.3.3
+ update.
+
+ * "gitweb" can sometimes be tricked into parrotting a filename argument
+ given in a request without properly quoting.
+
+Other minor fixes and documentation updates are also included.
diff --git a/Documentation/RelNotes/1.7.3.5.txt b/Documentation/RelNotes/1.7.3.5.txt
new file mode 100644
index 0000000..40f3ba5
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.5.txt
@@ -0,0 +1,34 @@
+Git 1.7.3.5 Release Notes
+=========================
+
+ * The xfuncname pattern used by "git diff" and "git grep" to show the
+ last notable line in context were broken for python and ruby for a long
+ time.
+
+ * "git merge" into an unborn branch removed an untracked file "foo" from
+ the working tree when merged branch had "foo" (this fix was already in
+ 1.7.3.3 but was omitted from the release notes by mistake).
+
+ * "git status -s" did not quote unprintable characters in paths as
+ documented.
+
+ * "git am --abort" used to always reset to the commit at the beginning of
+ the last "am" invocation that has stopped, losing any unrelated commits
+ that may have been made since then. Now it refrains from doing so and
+ instead issues a warning.
+
+ * "git blame" incorrectly reused bogusly cached result of textconv
+ filter for files from the working tree.
+
+ * "git commit" used to abort after the user edited the log message
+ when the committer information was not correctly set up. It now
+ aborts before starting the editor.
+
+ * "git commit --date=invalid" used to silently ignore the incorrectly
+ specified date; it is now diagnosed as an error.
+
+ * "git rebase --skip" to skip the last commit in a series used to fail
+ to run post-rewrite hook and to copy notes from old commits that have
+ successfully been rebased so far. Now it do (backmerge ef88ad2).
+
+ * "gitweb" tried to show a wrong feed logo when none was specified.
diff --git a/Documentation/RelNotes/1.7.3.txt b/Documentation/RelNotes/1.7.3.txt
new file mode 100644
index 0000000..309c331
--- /dev/null
+++ b/Documentation/RelNotes/1.7.3.txt
@@ -0,0 +1,76 @@
+Git v1.7.3 Release Notes
+========================
+
+Updates since v1.7.2
+--------------------
+
+ * git-gui, now at version 0.13.0, got various updates and a new
+ maintainer, Pat Thoyts.
+
+ * Gitweb allows its configuration to change per each request; it used to
+ read the configuration once upon startup.
+
+ * When git finds a corrupt object, it now reports the file that contains
+ it.
+
+ * "git checkout -B <it>" is a shorter way to say "git branch -f <it>"
+ followed by "git checkout <it>".
+
+ * When "git checkout" or "git merge" refuse to proceed in order to
+ protect local modification to your working tree, they used to stop
+ after showing just one path that might be lost. They now show all,
+ in a format that is easier to read.
+
+ * "git clean" learned "-e" ("--exclude") option.
+
+ * Hunk headers produced for C# files by "git diff" and friends show more
+ relevant context than before.
+
+ * diff.ignoresubmodules configuration variable can be used to squelch the
+ differences in submodules reported when running commands (e.g. "diff",
+ "status", etc.) at the superproject level.
+
+ * http.useragent configuration can be used to lie who you are to your
+ restrictive firewall.
+
+ * "git rebase --strategy <s>" learned "-X" option to pass extra options
+ that are understood by the chosen merge strategy.
+
+ * "git rebase -i" learned "exec" that you can insert into the insn sheet
+ to run a command between its steps.
+
+ * "git rebase" between branches that have many binary changes that do
+ not conflict should be faster.
+
+ * "git rebase -i" peeks into rebase.autosquash configuration and acts as
+ if you gave --autosquash from the command line.
+
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.2
+------------------
+
+All of the fixes in v1.7.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git merge -s recursive" (which is the default) did not handle cases
+ where a directory becomes a file (or vice versa) very well.
+
+ * "git fetch" and friends were accidentally broken for url with "+" in
+ its path, e.g. "git://git.gnome.org/gtk+".
+
+ * "git fetch $url" (i.e. without refspecs) was broken for quite some
+ time, if the current branch happen to be tracking some remote.
+
+ * "git ls-tree dir dirgarbage", when "dir" was a directory,
+ incorrectly recursed into "dir".
+
+ * "git note remove" created unnecessary extra commit when named object
+ did not have any note to begin with.
+
+ * "git rebase" did not work well if you had diff.noprefix configured.
+
+ * "git -c foo=bar subcmd" did not work well for subcmd that is not
+ implemented as a built-in command.
diff --git a/Documentation/RelNotes/1.7.4.1.txt b/Documentation/RelNotes/1.7.4.1.txt
new file mode 100644
index 0000000..79923a6
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.1.txt
@@ -0,0 +1,27 @@
+Git v1.7.4.1 Release Notes
+==========================
+
+Fixes since v1.7.4
+------------------
+
+ * On Windows platform, the codepath to spawn a new child process forgot
+ to first flush the output buffer.
+
+ * "git bundle" did not use OFS_DELTA encoding, making its output a few
+ per-cent larger than necessarily.
+
+ * The option to tell "git clone" to recurse into the submodules was
+ misspelled with an underscore "--recurse_submodules".
+
+ * "git diff --cached HEAD" before the first commit does what an end user
+ would expect (namely, show what would be committed without further "git
+ add").
+
+ * "git fast-import" didn't accept the command to ask for "notes" feature
+ to be present in its input stream, even though it was capable of the
+ feature.
+
+ * "git fsck" gave up scanning loose object files in directories with
+ garbage files.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.4.2.txt b/Documentation/RelNotes/1.7.4.2.txt
new file mode 100644
index 0000000..ef4ce1f
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.2.txt
@@ -0,0 +1,58 @@
+Git v1.7.4.2 Release Notes
+==========================
+
+Fixes since v1.7.4.1
+--------------------
+
+ * Many documentation updates to match "git cmd -h" output and the
+ git-cmd manual page.
+
+ * We used to keep one file descriptor open for each and every packfile
+ that we have a mmap window on it (read: "in use"), even when for very
+ tiny packfiles. We now close the file descriptor early when the entire
+ packfile fits inside one mmap window.
+
+ * "git bisect visualize" tried to run "gitk" in windowing
+ environments even when "gitk" is not installed, resulting in a
+ strange error message.
+
+ * "git clone /no/such/path" did not fail correctly.
+
+ * "git commit" did not correctly error out when the user asked to use a
+ non existent file as the commit message template.
+
+ * "git diff --stat -B" ran on binary files counted the changes in lines,
+ which was nonsensical.
+
+ * "git diff -M" opportunistically detected copies, which was not
+ necessarily a good thing, especially when it is internally run by
+ recursive merge.
+
+ * "git difftool" didn't tell (g)vimdiff that the files it is reading are
+ to be opened read-only.
+
+ * "git merge" didn't pay attention to prepare-commit-msg hook, even
+ though if a merge is conflicted and manually resolved, the subsequent
+ "git commit" would have triggered the hook, which was inconsistent.
+
+ * "git patch-id" (and commands like "format-patch --ignore-in-upstream"
+ that use it as their internal logic) handled changes to files that end
+ with incomplete lines incorrectly.
+
+ * The official value to tell "git push" to push the current branch back
+ to update the upstream branch it forked from is now called "upstream".
+ The old name "tracking" is and will be supported.
+
+ * "git submodule update" used to honor the --merge/--rebase option (or
+ corresponding configuration variables) even for a newly cloned
+ subproject, which made no sense (so/submodule-no-update-first-time).
+
+ * gitweb's "highlight" interface mishandled tabs.
+
+ * gitweb didn't understand timezones with GMT offset that is not
+ multiple of a whole hour.
+
+ * gitweb had a few forward-incompatible syntactic constructs and
+ also used incorrect variable when showing the file mode in a diff.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.4.3.txt b/Documentation/RelNotes/1.7.4.3.txt
new file mode 100644
index 0000000..02a3d5b
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.3.txt
@@ -0,0 +1,32 @@
+Git v1.7.4.3 Release Notes
+==========================
+
+Fixes since v1.7.4.2
+--------------------
+
+ * "git apply" used to confuse lines updated by previous hunks as lines
+ that existed before when applying a hunk, contributing misapplication
+ of patches with offsets.
+
+ * "git branch --track" (and "git checkout --track --branch") used to
+ allow setting up a random non-branch that does not make sense to follow
+ as the "upstream". The command correctly diagnoses it as an error.
+
+ * "git checkout $other_branch" silently removed untracked symbolic links
+ in the working tree that are in the way in order to check out paths
+ under it from the named branch.
+
+ * "git cvsimport" did not bail out immediately when the cvs server cannot
+ be reached, spewing unnecessary error messages that complain about the
+ server response that it never got.
+
+ * "git diff --quiet" did not work very well with the "--diff-filter"
+ option.
+
+ * "git grep -n" lacked a long-hand synonym --line-number.
+
+ * "git stash apply" reported the result of its operation by running
+ "git status" from the top-level of the working tree; it should (and
+ now does) run it from the user's working directory.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.4.4.txt b/Documentation/RelNotes/1.7.4.4.txt
new file mode 100644
index 0000000..ff06e04
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.4.txt
@@ -0,0 +1,35 @@
+Git v1.7.4.4 Release Notes
+==========================
+
+Fixes since v1.7.4.3
+--------------------
+
+ * Compilation of sha1_file.c on BSD platforms were broken due to our
+ recent use of getrlimit() without including <sys/resource.h>.
+
+ * "git config" did not diagnose incorrect configuration variable names.
+
+ * "git format-patch" did not wrap a long subject line that resulted from
+ rfc2047 encoding.
+
+ * "git instaweb" should work better again with plackup.
+
+ * "git log --max-count=4 -Sfoobar" now shows 4 commits that changes the
+ number of occurrences of string "foobar"; it used to scan only for 4
+ commits and then emitted only matching ones.
+
+ * "git log --first-parent --boundary $c^..$c" segfaulted on a merge.
+
+ * "git pull" into an empty branch should have behaved as if
+ fast-forwarding from emptiness to the version being pulled, with
+ the usual protection against overwriting untracked files.
+
+ * "git submodule" that is run while a merge in the superproject is in
+ conflicted state tried to process each conflicted submodule up to
+ three times.
+
+ * "git status" spent all the effort to notice racily-clean index entries
+ but didn't update the index file to help later operations go faster in
+ some cases.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.4.5.txt b/Documentation/RelNotes/1.7.4.5.txt
new file mode 100644
index 0000000..b7a0eeb
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.5.txt
@@ -0,0 +1,4 @@
+Git v1.7.4.5 Release Notes
+==========================
+
+This contains only minor documentation fixes accumulated since 1.7.4.4.
diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt
new file mode 100644
index 0000000..d5bca73
--- /dev/null
+++ b/Documentation/RelNotes/1.7.4.txt
@@ -0,0 +1,156 @@
+Git v1.7.4 Release Notes
+========================
+
+Updates since v1.7.3
+--------------------
+
+ * The documentation Makefile now assumes by default asciidoc 8 and
+ docbook-xsl >= 1.73. If you have older versions, you can set
+ ASCIIDOC7 and ASCIIDOC_ROFF, respectively.
+
+ * The option parsers of various commands that create new branches (or
+ rename existing ones to a new name) were too loose and users were
+ allowed to give a branch a name that begins with a dash by creative
+ abuse of their command line options, which only led to burning
+ themselves. The name of a branch cannot begin with a dash now.
+
+ * System-wide fallback default attributes can be stored in
+ /etc/gitattributes; the core.attributesfile configuration variable can
+ be used to customize the path to this file.
+
+ * The thread structure generated by "git send-email" has changed
+ slightly. Setting the cover letter of the latest series as a reply
+ to the cover letter of the previous series with --in-reply-to used
+ to make the new cover letter and all the patches replies to the
+ cover letter of the previous series; this has been changed to make
+ the patches in the new series replies to the new cover letter.
+
+ * The Bash completion script in contrib/ has been adjusted to be usable with
+ Bash 4 (options with '=value' didn't complete). It has been also made
+ usable with zsh.
+
+ * Different pagers can be chosen depending on which subcommand is
+ being run under the pager, using the "pager.<subcommand>" variable.
+
+ * The hardcoded tab-width of 8 that is used in whitespace breakage checks is now
+ configurable via the attributes mechanism.
+
+ * Support of case insensitive filesystems (i.e. "core.ignorecase") has
+ been improved. For example, the gitignore mechanism didn't pay attention
+ to case insensitivity.
+
+ * The <tree>:<path> syntax for naming a blob in a tree, and the :<path>
+ syntax for naming a blob in the index (e.g. "master:Makefile",
+ ":hello.c") have been extended. You can start <path> with "./" to
+ implicitly have the (sub)directory you are in prefixed to the
+ lookup. Similarly, ":../Makefile" from a subdirectory would mean
+ "the Makefile of the parent directory in the index".
+
+ * "git blame" learned the --show-email option to display the e-mail
+ addresses instead of the names of authors.
+
+ * "git commit" learned the --fixup and --squash options to help later invocation
+ of interactive rebase.
+
+ * Command line options to "git cvsimport" whose names are in capital
+ letters (-A, -M, -R and -S) can now be specified as the default in
+ the .git/config file by their longer names (cvsimport.authorsFile,
+ cvsimport.mergeRegex, cvsimport.trackRevisions, cvsimport.ignorePaths).
+
+ * "git daemon" can be built in the MinGW environment.
+
+ * "git daemon" can take more than one --listen option to listen to
+ multiple addresses.
+
+ * "git describe --exact-match" was optimized not to read commit
+ objects unnecessarily.
+
+ * "git diff" and "git grep" learned what functions and subroutines
+ in Fortran, Pascal and Perl look like.
+
+ * "git fetch" learned the "--recurse-submodules" option.
+
+ * "git mergetool" tells vim/gvim to show a three-way diff by default
+ (use vimdiff2/gvimdiff2 as the tool name for old behavior).
+
+ * "git log -G<pattern>" limits the output to commits whose change has
+ added or deleted lines that match the given pattern.
+
+ * "git read-tree" with no argument as a way to empty the index is
+ deprecated; we might want to remove it in the future. Users can
+ use the new --empty option to be more explicit instead.
+
+ * "git repack -f" does not spend cycles to recompress objects in the
+ non-delta representation anymore (use -F if you really mean it
+ e.g. after you changed the core.compression variable setting).
+
+ * "git merge --log" used to limit the resulting merge log to 20
+ entries; this is now customizable by giving e.g. "--log=47".
+
+ * "git merge" may work better when all files were moved out of a
+ directory in one branch while a new file is created in place of that
+ directory in the other branch.
+
+ * "git merge" learned the "--abort" option, synonymous to
+ "git reset --merge" when a merge is in progress.
+
+ * "git notes" learned the "merge" subcommand to merge notes refs.
+ In addition to the default manual conflict resolution, there are
+ also several notes merge strategies for automatically resolving
+ notes merge conflicts.
+
+ * "git rebase --autosquash" can use SHA-1 object names to name the
+ commit which is to be fixed up (e.g. "fixup! e83c5163").
+
+ * The default "recursive" merge strategy learned the --rename-threshold
+ option to influence the rename detection, similar to the -M option
+ of "git diff". From the "git merge" frontend, the "-X<strategy option>"
+ interface, e.g. "git merge -Xrename-threshold=50% ...", can be used
+ to trigger this.
+
+ * The "recursive" strategy also learned to ignore various whitespace
+ changes; the most notable is -Xignore-space-at-eol.
+
+ * "git send-email" learned "--to-cmd", similar to "--cc-cmd", to read
+ the recipient list from a command output.
+
+ * "git send-email" learned to read and use "To:" from its input files.
+
+ * you can extend "git shell", which is often used on boxes that allow
+ git-only login over ssh as login shell, with a custom set of
+ commands.
+
+ * The current branch name in "git status" output can be colored differently
+ from the generic header color by setting the "color.status.branch" variable.
+
+ * "git submodule sync" updates metainformation for all submodules,
+ not just the ones that have been checked out.
+
+ * gitweb can use a custom 'highlight' command with its configuration file.
+
+ * other gitweb updates.
+
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.3
+------------------
+
+All of the fixes in the v1.7.3.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git log --author=me --author=her" did not find commits written by
+ me or by her; instead it looked for commits written by me and by
+ her, which is impossible.
+
+ * "git push --progress" shows progress indicators now.
+
+ * "git rebase -i" showed a confusing error message when given a
+ branch name that does not exist.
+
+ * "git repack" places its temporary packs under $GIT_OBJECT_DIRECTORY/pack
+ instead of $GIT_OBJECT_DIRECTORY/ to avoid cross directory renames.
+
+ * "git submodule update --recursive --other-flags" passes flags down
+ to its subinvocations.
diff --git a/Documentation/RelNotes/1.7.5.1.txt b/Documentation/RelNotes/1.7.5.1.txt
new file mode 100644
index 0000000..c6ebd76
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.1.txt
@@ -0,0 +1,47 @@
+Git v1.7.5.1 Release Notes
+==========================
+
+Fixes since v1.7.5
+------------------
+
+ * When an object "$tree:$path" does not exist, if $path does exist in the
+ subtree of $tree that corresponds to the subdirectory the user is in,
+ git now suggests using "$tree:./$path" in addition to the advice to use
+ the full path from the root of the working tree.
+
+ * The "--date=relative" output format used to say "X years, 12 months"
+ when it should have said "X+1 years".
+
+ * The smart-HTTP transfer was broken in 1.7.5 when the client needs
+ to issue a small POST (which uses content-length) and then a large
+ POST (which uses chunked) back to back.
+
+ * "git clean" used to fail on an empty directory that is not readable,
+ even though rmdir(2) could remove such a directory. Now we attempt it
+ as the last resort.
+
+ * The "--dirstat" option of "diff" family of commands used to totally
+ ignore a change that only rearranged lines within a file. Such a
+ change now counts as at least a minimum but non zero change.
+
+ * The "--dirstat" option of "diff" family of commands used to use the
+ pathname in the original, instead of the pathname in the result,
+ when renames are involved.
+
+ * "git pack-object" did not take core.bigfilethreashold into account
+ (unlike fast-import); now it does.
+
+ * "git reflog" ignored options like "--format=.." on the command line.
+
+ * "git stash apply" used to refuse to work if there was any change in
+ the working tree, even when the change did not overlap with the change
+ the stash recorded.
+
+ * "git stash apply @{99999}" was not diagnosed as an error, even when you
+ did not have that many stash entries.
+
+ * An error message from "git send-email" to diagnose a broken SMTP
+ connection configuration lacked a space between "hello=<smtp-domain>"
+ and "port=<smtp-server-port>".
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.5.2.txt b/Documentation/RelNotes/1.7.5.2.txt
new file mode 100644
index 0000000..951eb7c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.2.txt
@@ -0,0 +1,57 @@
+Git v1.7.5.2 Release Notes
+==========================
+
+The release notes to 1.7.5.1 forgot to mention:
+
+ * "git stash -p --no-keep-index" and "git stash --no-keep-index -p" now
+ mean the same thing.
+
+ * "git upload-pack" (hence "git push" over git native protocol) had a
+ subtle race condition that could lead to a deadlock.
+
+Fixes since v1.7.5.1
+--------------------
+
+ * "git add -p" did not work correctly when a hunk is split and then
+ one of them was given to the editor.
+
+ * "git add -u" did not resolve a conflict where our history deleted and
+ their history modified the same file, and the working tree resolved to
+ keep a file.
+
+ * "git cvsimport" did not know that CVSNT stores its password file in a
+ location different from the traditional CVS.
+
+ * "git diff-files" did not show the mode information from the working
+ tree side of an unmerged path correctly.
+
+ * "git diff -M --cached" used to use unmerged path as a possible rename
+ source candidate, which made no sense.
+
+ * The option name parser in "git fast-import" used prefix matches for
+ some options where it shouldn't, and accepted non-existent options,
+ e.g. "--relative-marksmith" or "--forceps".
+
+ * "git format-patch" did not quote RFC822 special characters in the
+ email address (e.g From: Junio C. Hamano <jch@example.com>, not
+ From: "Junio C. Hamano" <jch@example.com>).
+
+ * "git format-patch" when run with "--quiet" option used to produce a
+ nonsense result that consists of alternating empty output.
+
+ * In "git merge", per-branch branch.<name>.mergeoptions configuration
+ variables did not override the fallback default merge.<option>
+ configuration variables such as merge.ff, merge.log, etc.
+
+ * "git merge-one-file" did not honor GIT_WORK_TREE settings when
+ handling a "both sides added, differently" conflict.
+
+ * "git mergetool" did not handle conflicted submoudules gracefully.
+
+ * "git-p4" (in contrib) used a wrong base image while merge a file that
+ was added on both branches differently.
+
+ * "git rebase -i -p" failed to preserve the history when there is a
+ redundant merge created with the --no-ff option.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.5.3.txt b/Documentation/RelNotes/1.7.5.3.txt
new file mode 100644
index 0000000..9c03353
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.3.txt
@@ -0,0 +1,32 @@
+Git v1.7.5.3 Release Notes
+==========================
+
+Fixes since v1.7.5.2
+--------------------
+
+ * The bash completion scripts should correctly work using zsh's bash
+ completion emulation layer now.
+
+ * Setting $(prefix) in config.mak did not affect where etc/gitconfig
+ file is read from, even though passing it from the command line of
+ $(MAKE) did.
+
+ * The logic to handle "&" (expand to UNIX username) in GECOS field
+ miscounted the length of the name it formatted.
+
+ * "git cherry-pick -s resolve" failed to cherry-pick a root commit.
+
+ * "git diff --word-diff" misbehaved when diff.suppress-blank-empty was
+ in effect.
+
+ * "git log --stdin path" with an input that has additional pathspec
+ used to corrupt memory.
+
+ * "git send-pack" (hence "git push") over smalt-HTTP protocol could
+ deadlock when the client side pack-object died early.
+
+ * Compressed tarball gitweb generates used to be made with the timestamp
+ of the tarball generation; this was bad because snapshot from the same
+ tree should result in a same tarball.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.5.4.txt b/Documentation/RelNotes/1.7.5.4.txt
new file mode 100644
index 0000000..cf3f455
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.4.txt
@@ -0,0 +1,21 @@
+Git v1.7.5.4 Release Notes
+==========================
+
+Fixes since v1.7.5.3
+--------------------
+
+ * The single-key mode of "git add -p" was easily fooled into thinking
+ that it was told to add everthing ('a') when up-arrow was pressed by
+ mistake.
+
+ * Setting a git command that uses custom configuration via "-c var=val"
+ as an alias caused a crash due to a realloc(3) failure.
+
+ * "git diff -C -C" used to disable the rename detection entirely when
+ there are too many copy candidate paths in the tree; now it falls
+ back to "-C" when doing so would keep the copy candidate paths
+ under the rename detection limit.
+
+ * "git rerere" did not diagnose a corrupt MERGE_RR file in some cases.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.5.txt b/Documentation/RelNotes/1.7.5.txt
new file mode 100644
index 0000000..987919c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.txt
@@ -0,0 +1,132 @@
+Git v1.7.5 Release Notes
+========================
+
+Updates since v1.7.4
+--------------------
+
+ * Various MinGW portability fixes.
+
+ * Various git-p4 enhancements (in contrib).
+
+ * Various vcs-svn, git-svn and gitk enhancements and fixes.
+
+ * Various git-gui updates (0.14.0).
+
+ * Update to more modern HP-UX port.
+
+ * The codebase is getting prepared for i18n/l10n; no translated
+ strings nor translation mechanism in the code yet, but the strings
+ are being marked for l10n.
+
+ * The bash completion script can now complete symmetric difference
+ for "git diff" command, e.g. "git diff ...bra<TAB>".
+
+ * The default minimum length of abbreviated and unique object names
+ can now be configured by setting the core.abbrev configuration
+ variable.
+
+ * "git apply -v" reports offset lines when the patch does not apply at
+ the exact location recorded in the diff output.
+
+ * "git config" used to be also known as "git repo-config", but the old
+ name is now officially deprecated.
+
+ * "git checkout --detach <commit>" is a more user friendly synonym for
+ "git checkout <commit>^0".
+
+ * "git checkout" performed on detached HEAD gives a warning and
+ advice when the commit being left behind will become unreachable from
+ any branch or tag.
+
+ * "git cherry-pick" and "git revert" can be told to use a custom merge
+ strategy, similar to "git rebase".
+
+ * "git cherry-pick" remembers which commit failed to apply when it is
+ stopped by conflicts, making it unnecessary to use "commit -c $commit"
+ to conclude it.
+
+ * "git cvsimport" bails out immediately when the cvs server cannot be
+ reached, without spewing unnecessary error messages that complain about
+ the server response it never got.
+
+ * "git fetch" vs "git upload-pack" transfer learned 'no-done'
+ protocol extension to save one round-trip after the content
+ negotiation is done. This saves one HTTP RPC, reducing the overall
+ latency for a trivial fetch.
+
+ * "git fetch" can be told to recursively fetch submodules on-demand.
+
+ * "git grep -f <filename>" learned to treat "-" as "read from the
+ standard input stream".
+
+ * "git grep --no-index" did not honor pathspecs correctly, returning
+ paths outside the specified area.
+
+ * "git init" learned the --separate-git-dir option to allow the git
+ directory for a new repository created elsewhere and linked via the
+ gitdir mechanism. This is primarily to help submodule support later
+ to switch between a branch of superproject that has the submodule
+ and another that does not.
+
+ * "git log" type commands now understand globbing pathspecs. You
+ can say "git log -- '*.txt'" for example.
+
+ * "git log" family of commands learned --cherry and --cherry-mark
+ options that can be used to view two diverged branches while omitting
+ or highlighting equivalent changes that appear on both sides of a
+ symmetric difference (e.g. "log --cherry A...B").
+
+ * A lazy "git merge" that didn't say what to merge used to be an error.
+ When run on a branch that has an upstream defined, however, the command
+ now merges from the configured upstream.
+
+ * "git mergetool" learned how to drive "beyond compare 3" as well.
+
+ * "git rerere forget" without pathspec used to forget all the saved
+ conflicts that relate to the current merge; it now requires you to
+ give it pathspecs.
+
+ * "git rev-list --objects $revs -- $pathspec" now limits the objects listed
+ in its output properly with the pathspec, in preparation for narrow
+ clones.
+
+ * "git push" with no parameters gives better advice messages when
+ "tracking" is used as the push.default semantics or there is no remote
+ configured yet.
+
+ * A possible value to the "push.default" configuration variable,
+ 'tracking', gained a synonym that more naturally describes what it
+ does, 'upstream'.
+
+ * "git rerere" learned a new subcommand "remaining" that is similar to
+ "status" and lists the paths that had conflicts which are known to
+ rerere, but excludes the paths that have already been marked as
+ resolved in the index from its output. "git mergetool" has been
+ updated to use this facility.
+
+Also contains various documentation updates.
+
+
+Fixes since v1.7.4
+------------------
+
+All of the fixes in the v1.7.4.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git fetch" from a client that is mostly following the remote
+ needlessly told all of its refs to the server for both sides to
+ compute the set of objects that need to be transferred efficiently,
+ instead of stopping when the server heard enough. In a project with
+ many tags, this turns out to be extremely wasteful, especially over
+ the smart HTTP transport (sp/maint-{upload,fetch}-pack-stop-early~1).
+
+ * "git fetch" run from a repository that uses the same repository as
+ its alternate object store as the repository it is fetching from
+ did not tell the server that it already has access to objects
+ reachable from the refs in their common alternate object store,
+ causing it to fetch unnecessary objects (jc/maint-fetch-alt).
+
+ * "git remote add --mirror" created a configuration that is suitable for
+ doing both a mirror fetch and a mirror push at the same time, which
+ made little sense. We now warn and require the command line to specify
+ either --mirror=fetch or --mirror=push.
diff --git a/Documentation/RelNotes/1.7.6.txt b/Documentation/RelNotes/1.7.6.txt
new file mode 100644
index 0000000..9ec498e
--- /dev/null
+++ b/Documentation/RelNotes/1.7.6.txt
@@ -0,0 +1,136 @@
+Git v1.7.6 Release Notes
+========================
+
+Updates since v1.7.5
+--------------------
+
+ * Various git-svn updates.
+
+ * Updates the way content tags are handled in gitweb. Also adds
+ a UI to choose common timezone for displaying the dates.
+
+ * Similar to branch names, tagnames that begin with "-" are now
+ disallowed.
+
+ * Clean-up of the C part of i18n (but not l10n---please wait)
+ continues.
+
+ * The scripting part of the codebase is getting prepared for i18n/l10n.
+
+ * Pushing and pulling from a repository with large number of refs that
+ point to identical commits are optimized by not listing the same commit
+ during the common ancestor negotiation exchange with the other side.
+
+ * Adding a file larger than core.bigfilethreshold (defaults to 1/2 Gig)
+ using "git add" will send the contents straight to a packfile without
+ having to hold it and its compressed representation both at the same
+ time in memory.
+
+ * Processes spawned by "[alias] <name> = !process" in the configuration
+ can inspect GIT_PREFIX environment variable to learn where in the
+ working tree the original command was invoked.
+
+ * A magic pathspec ":/" tells a command that limits its operation to
+ the current directory when ran from a subdirectory to work on the
+ entire working tree. In general, ":/path/to/file" would be relative
+ to the root of the working tree hierarchy.
+
+ After "git reset --hard; edit Makefile; cd t/", "git add -u" would
+ be a no-op, but "git add -u :/" would add the updated contents of
+ the Makefile at the top level. If you want to name a path in the
+ current subdirectory whose unusual name begins with ":/", you can
+ name it by "./:/that/path" or by "\:/that/path".
+
+ * "git blame" learned "--abbrev[=<n>]" option to control the minimum
+ number of hexdigits shown for commit object names.
+
+ * "git blame" learned "--line-porcelain" that is less efficient but is
+ easier to parse.
+
+ * Aborting "git commit --interactive" discards updates to the index
+ made during the interactive session.
+
+ * "git commit" learned a "--patch" option to directly jump to the
+ per-hunk selection UI of the interactive mode.
+
+ * "git diff" and its family of commands learned --dirstat=0 to show
+ directories that contribute less than 0.1% of changes.
+
+ * "git diff" and its family of commands learned --dirstat=lines mode to
+ assess damage to the directory based on number of lines in the patch
+ output, not based on the similarity numbers.
+
+ * "git format-patch" learned "--quiet" option to suppress the output of
+ the names of generated files.
+
+ * "git format-patch" quotes people's names when it has RFC822 special
+ characters in it, e.g. "Junio C. Hamano" <jch@example.com>. Earlier
+ it was up to the user to do this when using its output.
+
+ * "git format-patch" can take an empty --subject-prefix now.
+
+ * "git grep" learned the "-P" option to take pcre regular expressions.
+
+ * "git log" and friends learned a new "--notes" option to replace the
+ "--show-notes" option. Unlike "--show-notes", "--notes=<ref>" does
+ not imply showing the default notes.
+
+ * They also learned a log.abbrevCommit configuration variable to augment
+ the --abbrev-commit command line option.
+
+ * "git ls-remote" learned "--exit-code" option to consider it a
+ different kind of error when no remote ref to be shown.
+
+ * "git merge" learned "-" as a short-hand for "the previous branch", just
+ like the way "git checkout -" works.
+
+ * "git merge" uses "merge.ff" configuration variable to decide to always
+ create a merge commit (i.e. --no-ff, aka merge.ff=no), refuse to create
+ a merge commit (i.e. --ff-only, aka merge.ff=only). Setting merge.ff=yes
+ (or not setting it at all) restores the default behaviour of allowing
+ fast-forward to happen when possible.
+
+ * p4-import (from contrib) learned a new option --preserve-user.
+
+ * "git read-tree -m" learned "--dry-run" option that reports if a merge
+ would fail without touching the index nor the working tree.
+
+ * "git rebase" that does not specify on top of which branch to rebase
+ the current branch now uses @{upstream} of the current branch.
+
+ * "git rebase" finished either normally or with --abort did not
+ update the reflog for HEAD to record the event to come back to
+ where it started from.
+
+ * "git remote add -t only-this-branch --mirror=fetch" is now allowed. Earlier
+ a fetch-mode mirror meant mirror everything, but now it only means refs are
+ not renamed.
+
+ * "git rev-list --count" used with "--cherry-mark" counts the cherry-picked
+ commits separately, producing more a useful output.
+
+ * "git submodule update" learned "--force" option to get rid of local
+ changes in submodules and replace them with the up-to-date version.
+
+ * "git status" and friends ignore .gitmodules file while the file is
+ still in a conflicted state during a merge, to avoid using information
+ that is not final and possibly corrupt with conflict markers.
+
+Also contains various documentation updates and minor miscellaneous
+changes.
+
+
+Fixes since v1.7.5
+------------------
+
+Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are
+included in this release.
+
+ * "git config" used to choke with an insanely long line.
+ (merge ef/maint-strbuf-init later)
+
+ * "git diff --quiet" did not work well with --diff-filter.
+ (merge jk/diff-not-so-quick later)
+
+ * "git status -z" did not default to --porcelain output format.
+ (merge bc/maint-status-z-to-use-porcelain later)
diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt
new file mode 100644
index 0000000..42982e5
--- /dev/null
+++ b/Documentation/RelNotes/1.7.7.txt
@@ -0,0 +1,83 @@
+Git v1.7.7 Release Notes
+========================
+
+Updates since v1.7.6
+--------------------
+
+ * The scripting part of the codebase is getting prepared for i18n/l10n.
+
+ * Interix and Cygwin ports got updated.
+
+ * Various codepaths that invoked zlib deflate/inflate assumed that these
+ functions can compress or uncompress more than 4GB data in one call on
+ platforms with 64-bit long, which has been corrected.
+
+ * "git archive" can be told to pass the output to gzip compression and
+ produce "archive.tar.gz".
+
+ * "git checkout" (both the code to update the files upon checking out a
+ different branch, the code to checkout specific set of files) learned
+ to stream the data from object store when possible, without having to
+ read the entire contents of a file in memory first.
+
+ * "git clone" can now take "--config key=value" option to set the
+ repository configuration options that affect the initial checkout.
+
+ * "git diff --stat" learned --stat-count option to limit the output of
+ diffstat report.
+
+ * "git grep" learned --break and --heading options, to let users mimic
+ output format of "ack".
+
+ * "git rebase master topci" no longer spews usage hints after giving
+ "fatal: no such branch: topci" error message.
+
+ * "git submodule update" used to stop at the first error updating a
+ submodule; it now goes on to update other submodules that can be
+ updated, and reports the ones with errors at the end.
+
+ * "git verify-pack" has been rewritten to use the "index-pack" machinery
+ that is more efficient in reading objects in packfiles.
+
+ * test scripts for gitweb tried to run even when CGI-related perl modules
+ are not installed; it now exits early when they are unavailable.
+
+Also contains various documentation updates and minor miscellaneous
+changes.
+
+
+Fixes since v1.7.6
+------------------
+
+Unless otherwise noted, all the fixes in 1.7.6.X maintenance track are
+included in this release.
+
+ * "git diff --cc" learned to correctly ignore binary files.
+ (merge 0508fe5 jk/combine-diff-binary-etc later)
+
+ * "git fetch" did not recurse into submodules in subdirectories.
+ (merge ea2d325 jl/maint-fetch-recursive-fix later)
+
+ * "git rebase -i -p" incorrectly dropped commits from side branches.
+ (merge 12bf828 aw/rebase-i-p later)
+
+ * "git submodule foreach" failed to correctly give the standard input to
+ the user-supplied command it invoked.
+ (merge 4dca1aa bc/submodule-foreach-stdin-fix-1.7.4 later)
+
+ * submodules that the user has never showed interest in by running
+ "git submodule init" was incorrectly marked as interesting by "git
+ submodule sync".
+ (merge 2cd9de3 jc/submodule-sync-no-auto-vivify later)
+
+ * "git tag -l <glob>..." did not take multiple glob patterns from the
+ command line.
+ (merge 588d0e8 jk/tag-list-multiple-patterns later)
+
+--
+exec >/var/tmp/1
+echo O=$(git describe master)
+O=v1.7.6-232-gd907bf8
+git log --first-parent --oneline $O..master
+echo
+git shortlog --no-merges ^maint ^$O master
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index eb53e06..938eccf 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -7,17 +7,24 @@ Checklist (and a short version for the impatient):
before committing
- do not check in commented out code or unneeded files
- the first line of the commit message should be a short
- description and should skip the full stop
+ description (50 characters is the soft limit, see DISCUSSION
+ in git-commit(1)), and should skip the full stop
- the body should provide a meaningful commit message, which:
- - uses the imperative, present tense: "change",
- not "changed" or "changes".
- - includes motivation for the change, and contrasts
- its implementation with previous behaviour
- - if you want your work included in git.git, add a
- "Signed-off-by: Your Name <you@example.com>" line to the
- commit message (or just use the option "-s" when
- committing) to confirm that you agree to the Developer's
- Certificate of Origin
+ . explains the problem the change tries to solve, iow, what
+ is wrong with the current code without the change.
+ . justifies the way the change solves the problem, iow, why
+ the result with the change is better.
+ . alternate solutions considered but discarded, if any.
+ - describe changes in imperative mood, e.g. "make xyzzy do frotz"
+ instead of "[This patch] makes xyzzy do frotz" or "[I] changed
+ xyzzy to do frotz", as if you are giving orders to the codebase
+ to change its behaviour.
+ - try to make sure your explanation can be understood without
+ external resources. Instead of giving a URL to a mailing list
+ archive, summarize the relevant points of the discussion.
+ - add a "Signed-off-by: Your Name <you@example.com>" line to the
+ commit message (or just use the option "-s" when committing)
+ to confirm that you agree to the Developer's Certificate of Origin
- make sure that you have tests for the bug you are fixing
- make sure that the test suite passes after your commit
@@ -91,7 +98,10 @@ your commit head. Instead, always make a commit with complete
commit message and generate a series of patches from your
repository. It is a good discipline.
-Describe the technical detail of the change(s).
+Give an explanation for the change(s) that is detailed enough so
+that people can judge if it is good thing to do, without reading
+the actual patch text to determine how well the code does what
+the explanation promises to do.
If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
@@ -100,9 +110,8 @@ help reviewers check the patch, and future maintainers understand
the code, are the most beautiful patches. Descriptions that summarise
the point in the subject well, and describe the motivation for the
change, the approach taken by the change, and if relevant how this
-differs substantially from the prior version, can be found on Usenet
-archives back into the late 80's. Consider it like good Netiquette,
-but for code.
+differs substantially from the prior version, are all good things
+to have.
Oh, another thing. I am picky about whitespaces. Make sure your
changes do not trigger errors with the sample pre-commit hook shipped
@@ -265,12 +274,21 @@ the change to its true author (see (2) above).
Also notice that a real name is used in the Signed-off-by: line. Please
don't hide your real name.
-Some people also put extra tags at the end.
+If you like, you can put extra tags at the end:
-"Acked-by:" says that the patch was reviewed by the person who
-is more familiar with the issues and the area the patch attempts
-to modify. "Tested-by:" says the patch was tested by the person
-and found to have the desired effect.
+1. "Reported-by:" is used to credit someone who found the bug that
+ the patch attempts to fix.
+2. "Acked-by:" says that the person who is more familiar with the area
+ the patch attempts to modify liked the patch.
+3. "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.
+4. "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:".
------------------------------------------------
An ideal patch flow
@@ -326,50 +344,20 @@ MUA specific hints
Some of patches I receive or pick up from the list share common
patterns of breakage. Please make sure your MUA is set up
-properly not to corrupt whitespaces. Here are two common ones
-I have seen:
-
-* Empty context lines that do not have _any_ whitespace.
-
-* Non empty context lines that have one extra whitespace at the
- beginning.
-
-One test you could do yourself if your MUA is set up correctly is:
-
-* Send the patch to yourself, exactly the way you would, except
- To: and Cc: lines, which would not contain the list and
- maintainer address.
-
-* Save that patch to a file in UNIX mailbox format. Call it say
- a.patch.
-
-* Try to apply to the tip of the "master" branch from the
- git.git public repository:
+properly not to corrupt whitespaces.
- $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply
- $ git checkout test-apply
- $ git reset --hard
- $ git am a.patch
+See the DISCUSSION section of git-format-patch(1) for hints on
+checking your patch by mailing it to yourself and applying with
+git-am(1).
-If it does not apply correctly, there can be various reasons.
-
-* Your patch itself does not apply cleanly. That is _bad_ but
- does not have much to do with your MUA. Please rebase the
- patch appropriately.
-
-* Your MUA corrupted your patch; "am" would complain that
- the patch does not apply. Look at .git/rebase-apply/ subdirectory and
- see what 'patch' file contains and check for the common
- corruption patterns mentioned above.
-
-* While you are at it, check what are in 'info' and
- 'final-commit' files as well. If what is in 'final-commit' is
- not exactly what you would want to see in the commit log
- message, it is very likely that your maintainer would end up
- hand editing the log message when he applies your patch.
- Things like "Hi, this is my first patch.\n", if you really
- want to put in the patch e-mail, should come after the
- three-dash line that signals the end of the commit message.
+While you are at it, check the resulting commit log message from
+a trial run of applying the patch. If what is in the resulting
+commit is not exactly what you would want to see, it is very
+likely that your maintainer would end up hand editing the log
+message when he applies your patch. Things like "Hi, this is my
+first patch.\n", if you really want to put in the patch e-mail,
+should come after the three-dash line that signals the end of the
+commit message.
Pine
@@ -425,89 +413,10 @@ that or Gentoo did it.) So you need to set the
it.
-Thunderbird
------------
-
-(A Large Angry SCM)
-
-By default, Thunderbird will both wrap emails as well as flag them as
-being 'format=flowed', both of which will make the resulting email unusable
-by git.
-
-Here are some hints on how to successfully submit patches inline using
-Thunderbird.
-
-There are two different approaches. One approach is to configure
-Thunderbird to not mangle patches. The second approach is to use
-an external editor to keep Thunderbird from mangling the patches.
-
-Approach #1 (configuration):
-
-This recipe is current as of Thunderbird 2.0.0.19. Three steps:
- 1. Configure your mail server composition as plain text
- Edit...Account Settings...Composition & Addressing,
- uncheck 'Compose Messages in HTML'.
- 2. Configure your general composition window to not wrap
- Edit..Preferences..Composition, wrap plain text messages at 0
- 3. Disable the use of format=flowed
- Edit..Preferences..Advanced..Config Editor. Search for:
- mailnews.send_plaintext_flowed
- toggle it to make sure it is set to 'false'.
-
-After that is done, you should be able to compose email as you
-otherwise would (cut + paste, git-format-patch | git-imap-send, etc),
-and the patches should not be mangled.
-
-Approach #2 (external editor):
-
-This recipe appears to work with the current [*1*] Thunderbird from Suse.
-
-The following Thunderbird extensions are needed:
- AboutConfig 0.5
- http://aboutconfig.mozdev.org/
- External Editor 0.7.2
- http://globs.org/articles.php?lng=en&pg=8
-
-1) Prepare the patch as a text file using your method of choice.
-
-2) Before opening a compose window, use Edit->Account Settings to
-uncheck the "Compose messages in HTML format" setting in the
-"Composition & Addressing" panel of the account to be used to send the
-patch. [*2*]
-
-3) In the main Thunderbird window, _before_ you open the compose window
-for the patch, use Tools->about:config to set the following to the
-indicated values:
- mailnews.send_plaintext_flowed => false
- mailnews.wraplength => 0
-
-4) Open a compose window and click the external editor icon.
-
-5) In the external editor window, read in the patch file and exit the
-editor normally.
-
-6) Back in the compose window: Add whatever other text you wish to the
-message, complete the addressing and subject fields, and press send.
-
-7) Optionally, undo the about:config/account settings changes made in
-steps 2 & 3.
+Thunderbird, KMail, GMail
+-------------------------
-
-[Footnotes]
-*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse
-9.3 professional updates.
-
-*2* It may be possible to do this with about:config and the following
-settings but I haven't tried, yet.
- mail.html_compose => false
- mail.identity.default.compose_html => false
- mail.identity.id?.compose_html => false
-
-(Lukas Sandström)
-
-There is a script in contrib/thunderbird-patch-inline which can help
-you include patches with Thunderbird in an easy way. To use it, do the
-steps above and then use the script as the external editor.
+See the MUA-SPECIFIC HINTS section of git-format-patch(1).
Gnus
----
@@ -522,72 +431,3 @@ characters (most notably in people's names), and also
whitespaces (fatal in patches). Running 'C-u g' to display the
message in raw form before using '|' to run the pipe can work
this problem around.
-
-
-KMail
------
-
-This should help you to submit patches inline using KMail.
-
-1) Prepare the patch as a text file.
-
-2) Click on New Mail.
-
-3) Go under "Options" in the Composer window and be sure that
-"Word wrap" is not set.
-
-4) Use Message -> Insert file... and insert the patch.
-
-5) Back in the compose window: add whatever other text you wish to the
-message, complete the addressing and subject fields, and press send.
-
-
-Gmail
------
-
-GMail does not appear to have any way to turn off line wrapping in the web
-interface, so this will mangle any emails that you send. You can however
-use "git send-email" and send your patches through the GMail SMTP server, or
-use any IMAP email client to connect to the google IMAP server and forward
-the emails through that.
-
-To use "git send-email" and send your patches through the GMail SMTP server,
-edit ~/.gitconfig to specify your account settings:
-
-[sendemail]
- smtpencryption = tls
- smtpserver = smtp.gmail.com
- smtpuser = user@gmail.com
- smtppass = p4ssw0rd
- smtpserverport = 587
-
-Once your commits are ready to be sent to the mailing list, run the
-following commands:
-
- $ git format-patch --cover-letter -M origin/master -o outgoing/
- $ edit outgoing/0000-*
- $ git send-email outgoing/*
-
-To submit using the IMAP interface, first, edit your ~/.gitconfig to specify your
-account settings:
-
-[imap]
- folder = "[Gmail]/Drafts"
- host = imaps://imap.gmail.com
- user = user@gmail.com
- pass = p4ssw0rd
- port = 993
- sslverify = false
-
-You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
-that the "Folder doesn't exist".
-
-Once your commits are ready to be sent to the mailing list, run the
-following commands:
-
- $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
-
-Just make sure to disable line wrapping in the email client (GMail web
-interface will line wrap no matter what, so you need to use a real
-IMAP client).
-
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index 87a90f2..aea8627 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -16,8 +16,11 @@ plus=&#43;
caret=&#94;
startsb=&#91;
endsb=&#93;
+backslash=&#92;
tilde=&#126;
+apostrophe=&#39;
backtick=&#96;
+litdd=&#45;&#45;
ifdef::backend-docbook[]
[linkgit-inlinemacro]
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 16e3c68..e76195a 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -52,6 +52,11 @@ of lines before or after the line given by <start>.
--porcelain::
Show in a format designed for machine consumption.
+--line-porcelain::
+ Show the porcelain format, but output commit information for
+ each line, not just the first time a commit is referenced.
+ Implies --porcelain.
+
--incremental::
Show the result incrementally in a format designed for
machine consumption.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 72949e7..0658ffb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -62,7 +62,7 @@ Internal whitespace within a variable value is retained verbatim.
The values following the equals sign in variable assign are all either
a string, an integer, or a boolean. Boolean values may be given as yes/no,
-0/1, true/false or on/off. Case is not significant in boolean values, when
+1/0, true/false or on/off. Case is not significant in boolean values, when
converting value to the canonical form using '--bool' type specifier;
'git config' will ensure that the output is "true" or "false".
@@ -128,7 +128,7 @@ advice.*::
when writing commit messages. Default: true.
commitBeforeMerge::
Advice shown when linkgit:git-merge[1] refuses to
- merge to avoid overwritting local changes.
+ merge to avoid overwriting local changes.
Default: true.
resolveConflict::
Advices shown by various commands when conflicts
@@ -317,24 +317,26 @@ false), while all other repositories are assumed to be bare (bare
= true).
core.worktree::
- Set the path to the root of the work tree.
+ Set the path to the root of the working tree.
This can be overridden by the GIT_WORK_TREE environment
- variable and the '--work-tree' command line option. It can be
- an absolute path or a relative path to the .git directory,
- either specified by --git-dir or GIT_DIR, or automatically
- discovered.
- If --git-dir or GIT_DIR are specified but none of
+ variable and the '--work-tree' command line option.
+ The value can be an absolute path or relative to the path to
+ the .git directory, which is either specified by --git-dir
+ or GIT_DIR, or automatically discovered.
+ If --git-dir or GIT_DIR is specified but none of
--work-tree, GIT_WORK_TREE and core.worktree is specified,
- the current working directory is regarded as the root of the
- work tree.
+ the current working directory is regarded as the top level
+ of your working tree.
+
Note that this variable is honored even when set in a configuration
-file in a ".git" subdirectory of a directory, and its value differs
+file in a ".git" subdirectory of a directory and its value differs
from the latter directory (e.g. "/path/to/.git/config" has
core.worktree set to "/different/path"), which is most likely a
-misconfiguration. Running git commands in "/path/to" directory will
+misconfiguration. Running git commands in the "/path/to" directory will
still use "/different/path" as the root of the work tree and can cause
-great confusion to the users.
+confusion unless you know what you are doing (e.g. you are creating a
+read-only snapshot of the same index to a location different from the
+repository's usual working tree).
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
@@ -342,7 +344,9 @@ core.logAllRefUpdates::
SHA1, the date/time and the reason of the update, but
only when the file exists. If this configuration
variable is set to true, missing "$GIT_DIR/logs/<ref>"
- file is automatically created for branch heads.
+ file is automatically created for branch heads (i.e. under
+ refs/heads/), remote refs (i.e. under refs/remotes/),
+ note refs (i.e. under refs/notes/), and the symbolic ref HEAD.
+
This information can be used to determine what commit
was the tip of a branch "2 days ago".
@@ -418,7 +422,7 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
core.deltaBaseCacheLimit::
Maximum number of bytes to reserve for caching base objects
- that multiple deltafied objects reference. By storing the
+ that may be referenced by multiple deltified objects. By storing the
entire decompressed base objects in a cache Git is able
to avoid unpacking and decompressing frequently used base
objects multiple times.
@@ -440,8 +444,6 @@ for most projects as source code and other text files can still
be delta compressed, but larger binary media files won't be.
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
-+
-Currently only linkgit:git-fast-import[1] honors this setting.
core.excludesfile::
In addition to '.gitignore' (per-directory) and
@@ -450,6 +452,21 @@ core.excludesfile::
to the value of `$HOME` and "{tilde}user/" to the specified user's
home directory. See linkgit:gitignore[5].
+core.askpass::
+ Some commands (e.g. svn and http interfaces) that interactively
+ ask for a password can be told to use an external program given
+ via the value of this variable. Can be overridden by the 'GIT_ASKPASS'
+ environment variable. If not set, fall back to the value of the
+ 'SSH_ASKPASS' environment variable or, failing that, a simple password
+ prompt. The external program shall be given a suitable prompt as
+ command line argument and write the password on its STDOUT.
+
+core.attributesfile::
+ In addition to '.gitattributes' (per-directory) and
+ '.git/info/attributes', git looks into this file for attributes
+ (see linkgit:gitattributes[5]). Path expansions are made the same
+ way as for `core.excludesfile`.
+
core.editor::
Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this
@@ -498,6 +515,9 @@ core.whitespace::
part of the line terminator, i.e. with it, `trailing-space`
does not trigger if the character before such a carriage-return
is not a whitespace (not enabled by default).
+* `tabwidth=<n>` tells how many character positions a tab occupies; this
+ is relevant for `indent-with-non-tab` and when git fixes `tab-in-indent`
+ errors. The default tab width is 8. Allowed values are 1 to 63.
core.fsyncobjectfiles::
This boolean will enable 'fsync()' when writing object files.
@@ -538,10 +558,20 @@ core.sparseCheckout::
Enable "sparse checkout" feature. See section "Sparse checkout" in
linkgit:git-read-tree[1] for more information.
+core.abbrev::
+ Set the length object names are abbreviated to. If unspecified,
+ many commands abbreviate to 7 hexdigits, which may not be enough
+ for abbreviated object names to stay unique for sufficiently long
+ time.
+
add.ignore-errors::
+add.ignoreErrors::
Tells 'git add' to continue adding files when some files cannot be
added due to indexing errors. Equivalent to the '--ignore-errors'
- option of linkgit:git-add[1].
+ option of linkgit:git-add[1]. Older versions of git accept only
+ `add.ignore-errors`, which does not follow the usual naming
+ convention for configuration variables. Newer versions of git
+ honor `add.ignoreErrors` as well.
alias.*::
Command aliases for the linkgit:git[1] command wrapper - e.g.
@@ -559,11 +589,13 @@ it will be treated as a shell command. For example, defining
"gitk --all --not ORIG_HEAD". Note that shell commands will be
executed from the top-level directory of a repository, which may
not necessarily be the current directory.
+'GIT_PREFIX' is set as returned by running 'git rev-parse --show-prefix'
+from the original current directory. See linkgit:git-rev-parse[1].
am.keepcr::
If true, git-am will call git-mailsplit for patches in mbox format
with parameter '--keep-cr'. In this case git-mailsplit will
- not remove `\r` from lines ending with `\r\n`. Can be overrriden
+ not remove `\r` from lines ending with `\r\n`. Can be overridden
by giving '--no-keep-cr' from the command line.
See linkgit:git-am[1], linkgit:git-mailsplit[1].
@@ -586,8 +618,9 @@ branch.autosetupmerge::
this behavior can be chosen per-branch using the `--track`
and `--no-track` options. The valid settings are: `false` -- no
automatic setup is done; `true` -- automatic setup is done when the
- starting point is a remote branch; `always` -- automatic setup is
- done when the starting point is either a local branch or remote
+ starting point is a remote-tracking branch; `always` --
+ automatic setup is done when the starting point is either a
+ local branch or remote-tracking
branch. This option defaults to true.
branch.autosetuprebase::
@@ -598,7 +631,7 @@ branch.autosetuprebase::
When `local`, rebase is set to true for tracked branches of
other local branches.
When `remote`, rebase is set to true for tracked branches of
- remote branches.
+ remote-tracking branches.
When `always`, rebase will be set to true for all tracking
branches.
See "branch.autosetupmerge" for details on how to set up a
@@ -612,7 +645,7 @@ branch.<name>.remote::
branch.<name>.merge::
Defines, together with branch.<name>.remote, the upstream branch
- for the given branch. It tells 'git fetch'/'git pull' which
+ for the given branch. It tells 'git fetch'/'git pull'/'git rebase' which
branch to merge and can also affect 'git push' (see push.default).
When in branch <name>, it tells 'git fetch' the default
refspec to be marked for merging in FETCH_HEAD. The value is
@@ -645,7 +678,7 @@ branch.<name>.rebase::
browser.<tool>.cmd::
Specify the command to invoke the specified browser. The
specified command is evaluated in shell with the URLs passed
- as arguments. (See linkgit:git-web--browse[1].)
+ as arguments. (See linkgit:git-web{litdd}browse[1].)
browser.<tool>.path::
Override the path for the given tool that may be used to
@@ -665,7 +698,7 @@ color.branch::
color.branch.<slot>::
Use customized color for branch coloration. `<slot>` is one of
`current` (the current branch), `local` (a local branch),
- `remote` (a tracking branch in refs/remotes/), `plain` (other
+ `remote` (a remote-tracking branch in refs/remotes/), `plain` (other
refs).
+
The value for these configuration variables is a list of colors (at most
@@ -677,9 +710,16 @@ second is the background. The position of the attribute, if any,
doesn't matter.
color.diff::
- When set to `always`, always use colors in patch.
- When false (or `never`), never. When set to `true` or `auto`, use
- colors only when the output is to the terminal. Defaults to false.
+ Whether to use ANSI escape sequences to add color to patches.
+ If this is set to `always`, linkgit:git-diff[1],
+ linkgit:git-log[1], and linkgit:git-show[1] will use color
+ for all patches. If it is set to `true` or `auto`, those
+ commands will only use color when output is to the terminal.
+ Defaults to false.
++
+This does not affect linkgit:git-format-patch[1] nor the
+'git-diff-{asterisk}' plumbing commands. Can be overridden on the
+command line with the `--color[=<when>]` option.
color.diff.<slot>::
Use customized color for diff colorization. `<slot>` specifies
@@ -693,7 +733,7 @@ color.diff.<slot>::
color.decorate.<slot>::
Use customized color for 'git log --decorate' output. `<slot>` is one
of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
- branches, remote tracking branches, tags, stash and HEAD, respectively.
+ branches, remote-tracking branches, tags, stash and HEAD, respectively.
color.grep::
When set to `always`, always highlight matches. When `false` (or
@@ -758,17 +798,22 @@ color.status.<slot>::
one of `header` (the header text of the status message),
`added` or `updated` (files which are added but not committed),
`changed` (files which are changed but not added in the index),
- `untracked` (files which are not tracked by git), or
+ `untracked` (files which are not tracked by git),
+ `branch` (the current branch), or
`nobranch` (the color the 'no branch' warning is shown in, defaulting
to red). The values of these variables may be specified as in
color.branch.<slot>.
color.ui::
- When set to `always`, always use colors in all git commands which
- are capable of colored output. When false (or `never`), never. When
- set to `true` or `auto`, use colors only when the output is to the
- terminal. When more specific variables of color.* are set, they always
- take precedence over this setting. Defaults to false.
+ This variable determines the default value for variables such
+ as `color.diff` and `color.grep` that control the use of color
+ per command family. Its scope will expand as more commands learn
+ configuration to set a default for the `--color` option. Set it
+ to `always` if you want all output not intended for machine
+ consumption to use color, to `true` or `auto` if you want such
+ output to use color when written to the terminal, or to `false` or
+ `never` if you prefer git commands not to use color unless enabled
+ explicitly with some other configuration or the `--color` option.
commit.status::
A boolean to enable/disable inclusion of status information in the
@@ -780,61 +825,7 @@ commit.template::
"{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
specified user's home directory.
-diff.autorefreshindex::
- When using 'git diff' to compare with work tree
- files, do not consider stat-only change as changed.
- Instead, silently run `git update-index --refresh` to
- update the cached stat information for paths whose
- contents in the work tree match the contents in the
- index. This option defaults to true. Note that this
- affects only 'git diff' Porcelain, and not lower level
- 'diff' commands such as 'git diff-files'.
-
-diff.external::
- If this config variable is set, diff generation is not
- performed using the internal diff machinery, but using the
- given command. Can be overridden with the `GIT_EXTERNAL_DIFF'
- environment variable. The command is called with parameters
- as described under "git Diffs" in linkgit:git[1]. Note: if
- you want to use an external diff program only on a subset of
- your files, you might want to use linkgit:gitattributes[5] instead.
-
-diff.mnemonicprefix::
- If set, 'git diff' uses a prefix pair that is different from the
- standard "a/" and "b/" depending on what is being compared. When
- this configuration is in effect, reverse diff output also swaps
- the order of the prefixes:
-diff.noprefix::
- If set, 'git diff' does not show any source or destination prefix.
-`git diff`;;
- compares the (i)ndex and the (w)ork tree;
-`git diff HEAD`;;
- compares a (c)ommit and the (w)ork tree;
-`git diff --cached`;;
- compares a (c)ommit and the (i)ndex;
-`git diff HEAD:file1 file2`;;
- compares an (o)bject and a (w)ork tree entity;
-`git diff --no-index a b`;;
- compares two non-git things (1) and (2).
-
-diff.renameLimit::
- The number of files to consider when performing the copy/rename
- detection; equivalent to the 'git diff' option '-l'.
-
-diff.renames::
- Tells git to detect renames. If set to any boolean value, it
- will enable basic rename detection. If set to "copies" or
- "copy", it will detect copies, as well.
-
-diff.suppressBlankEmpty::
- A boolean to inhibit the standard behavior of printing a space
- before each empty output line. Defaults to false.
-
-diff.tool::
- Controls which diff tool is used. `diff.tool` overrides
- `merge.tool` when used by linkgit:git-difftool[1] and has
- the same valid values as `merge.tool` minus "tortoisemerge"
- and plus "kompare".
+include::diff-config.txt[]
difftool.<tool>.path::
Override the path for the given tool. This is useful in case
@@ -857,6 +848,15 @@ diff.wordRegex::
sequences that match the regular expression are "words", all other
characters are *ignorable* whitespace.
+fetch.recurseSubmodules::
+ This option can be either set to a boolean value or to 'on-demand'.
+ Setting it to a boolean changes the behavior of fetch and pull to
+ unconditionally recurse into submodules when set to true or to not
+ recurse at all when set to false. When set to 'on-demand' (the default
+ value), fetch and pull will only recurse into a populated submodule
+ when its superproject retrieves a commit that updates the submodule's
+ reference.
+
fetch.unpackLimit::
If the number of objects fetched over the git native
transfer is below this
@@ -886,9 +886,11 @@ format.headers::
Additional email headers to include in a patch to be submitted
by mail. See linkgit:git-format-patch[1].
+format.to::
format.cc::
- Additional "Cc:" headers to include in a patch to be submitted
- by mail. See the --cc option in linkgit:git-format-patch[1].
+ Additional recipients to include in a patch to be submitted
+ by mail. See the --to and --cc options in
+ linkgit:git-format-patch[1].
format.subjectprefix::
The default for format-patch is to output files with the '[PATCH]'
@@ -927,6 +929,16 @@ format.signoff::
the rights to submit this work under the same open source license.
Please see the 'SubmittingPatches' document for further discussion.
+filter.<driver>.clean::
+ The command which is used to convert the content of a worktree
+ file to a blob upon checkin. See linkgit:gitattributes[5] for
+ details.
+
+filter.<driver>.smudge::
+ The command which is used to convert the content of a blob
+ object to a worktree file upon checkout. See
+ linkgit:gitattributes[5] for details.
+
gc.aggressiveWindow::
The window size parameter used in the delta compression
algorithm used by 'git gc --aggressive'. This defaults
@@ -949,7 +961,7 @@ gc.packrefs::
Running `git pack-refs` in a repository renders it
unclonable by Git versions prior to 1.5.1.2 over dumb
transports such as HTTP. This variable determines whether
- 'git gc' runs `git pack-refs`. This can be set to `nobare`
+ 'git gc' runs `git pack-refs`. This can be set to `notbare`
to enable it within all non-bare repos or it can be set to a
boolean value. The default is `true`.
@@ -1000,7 +1012,7 @@ gitcvs.usecrlfattr::
If true, the server will look up the end-of-line conversion
attributes for files to determine the '-k' modes to use. If
the attributes force git to treat a file as text,
- the '-k' mode will be left blank so cvs clients will
+ the '-k' mode will be left blank so CVS clients will
treat it as text. If they suppress text conversion, the file
will be set with '-kb' mode, which suppresses any newline munging
the client might otherwise do. If the attributes do not allow
@@ -1052,6 +1064,12 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and
is one of "ext" and "pserver") to make them apply only for the given
access method.
+grep.lineNumber::
+ If set to true, enable '-n' option by default.
+
+grep.extendedRegexp::
+ If set to true, enable '--extended-regexp' option by default.
+
gui.commitmsgwidth::
Defines how wide the commit message window is in the
linkgit:git-gui[1]. "75" is the default.
@@ -1078,7 +1096,7 @@ gui.newbranchtemplate::
linkgit:git-gui[1].
gui.pruneduringfetch::
- "true" if linkgit:git-gui[1] should prune tracking branches when
+ "true" if linkgit:git-gui[1] should prune remote-tracking branches when
performing a fetch. The default value is "false".
gui.trustmtime::
@@ -1180,6 +1198,14 @@ http.proxy::
environment variable (see linkgit:curl[1]). This can be overridden
on a per-remote basis; see remote.<name>.proxy
+http.cookiefile::
+ File containing previously stored cookie lines which should be used
+ in the git http session, if they match the server. The file format
+ of the file to read cookies from should be plain HTTP headers or
+ the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
+ NOTE that the file specified with http.cookiefile is only used as
+ input. No cookies will be stored in the file.
+
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@ -1241,6 +1267,15 @@ http.noEPSV::
support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
environment variable. Default is false (curl will use EPSV).
+http.useragent::
+ The HTTP USER_AGENT string presented to an HTTP server. The default
+ value represents the version of the client git such as git/1.7.1.
+ This option allows you to override this value to a more common value
+ such as Mozilla/4.0. This may be necessary, for instance, if
+ connecting through a firewall that restricts HTTP connections to a set
+ of common USER_AGENT strings (but not including those like git/1.7.1).
+ Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
+
i18n.commitEncoding::
Character encoding the commit messages are stored in; git itself
does not care per se, but this information is necessary e.g. when
@@ -1273,7 +1308,9 @@ instaweb.local::
be bound to the local IP (127.0.0.1).
instaweb.modulepath::
- The module path for an apache httpd used by linkgit:git-instaweb[1].
+ The default module path for linkgit:git-instaweb[1] to use
+ instead of /usr/lib/apache2/modules. Only used if httpd
+ is Apache.
instaweb.port::
The port number to bind the gitweb httpd to. See
@@ -1282,15 +1319,23 @@ instaweb.port::
interactive.singlekey::
In interactive commands, allow the user to provide one-letter
input with a single key (i.e., without hitting enter).
- Currently this is used only by the `\--patch` mode of
- linkgit:git-add[1]. Note that this setting is silently
- ignored if portable keystroke input is not available.
+ Currently this is used by the `\--patch` mode of
+ linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1],
+ linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this
+ setting is silently ignored if portable keystroke input
+ is not available.
+
+log.abbrevCommit::
+ If true, makes linkgit:git-log[1], linkgit:git-show[1], and
+ linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may
+ override this option with `\--no-abbrev-commit`.
log.date::
- Set default date-time mode for the log command. Setting log.date
- value is similar to using 'git log'\'s --date option. The value is one of the
- following alternatives: {relative,local,default,iso,rfc,short}.
- See linkgit:git-log[1].
+ Set the default date-time mode for the 'log' command.
+ Setting a value for log.date is similar to using 'git log''s
+ `\--date` option. Possible values are `relative`, `local`,
+ `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
+ for details.
log.decorate::
Print out the ref names of any commits that are shown by the log
@@ -1436,6 +1481,10 @@ pack.compression::
not set, defaults to -1, the zlib default, which is "a default
compromise between speed and compression (currently equivalent
to level 6)."
++
+Note that changing the compression level will not automatically recompress
+all existing objects. You can force recompression by passing the -F option
+to linkgit:git-repack[1].
pack.deltaCacheSize::
The maximum memory in bytes used for caching deltas in
@@ -1491,11 +1540,13 @@ pack.packSizeLimit::
supported.
pager.<cmd>::
- Allows turning on or off pagination of the output of a
- particular git subcommand when writing to a tty. If
- `\--paginate` or `\--no-pager` is specified on the command line,
- it takes precedence over this option. To disable pagination for
- all commands, set `core.pager` or `GIT_PAGER` to `cat`.
+ If the value is boolean, turns on or off pagination of the
+ output of a particular git subcommand when writing to a tty.
+ Otherwise, turns on pagination for the subcommand using the
+ pager specified by the value of `pager.<cmd>`. If `\--paginate`
+ or `\--no-pager` is specified on the command line, it takes
+ precedence over this option. To disable pagination for all
+ commands, set `core.pager` or `GIT_PAGER` to `cat`.
pretty.<name>::
Alias for a --pretty= format string, as specified in
@@ -1520,17 +1571,21 @@ push.default::
no refspec is implied by any of the options given on the command
line. Possible values are:
+
-* `nothing` do not push anything.
-* `matching` push all matching branches.
+* `nothing` - do not push anything.
+* `matching` - push all matching branches.
All branches having the same name in both ends are considered to be
matching. This is the default.
-* `tracking` push the current branch to its upstream branch.
-* `current` push the current branch to a branch of the same name.
+* `upstream` - push the current branch to its upstream branch.
+* `tracking` - deprecated synonym for `upstream`.
+* `current` - push the current branch to a branch of the same name.
rebase.stat::
Whether to show a diffstat of what changed upstream since the last
rebase. False by default.
+rebase.autosquash::
+ If set to true enable '--autosquash' option by default.
+
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
receiving data from git-push and updating refs. You can stop
@@ -1556,6 +1611,10 @@ receive.denyDeletes::
If set to true, git-receive-pack will deny a ref update that deletes
the ref. Use this to prevent such a ref deletion via a push.
+receive.denyDeleteCurrent::
+ If set to true, git-receive-pack will deny a ref update that
+ deletes the currently checked out branch of a non-bare repository.
+
receive.denyCurrentBranch::
If set to true or "refuse", git-receive-pack will deny a ref update
to the currently checked out branch of a non-bare repository.
@@ -1621,7 +1680,9 @@ remote.<name>.tagopt::
Setting this value to \--no-tags disables automatic tag following when
fetching from remote <name>. Setting it to \--tags will fetch every
tag from remote <name>, even if they are not reachable from remote
- branch heads.
+ branch heads. Passing these flags directly to linkgit:git-fetch[1] can
+ override this setting. See options \--tags and \--no-tags of
+ linkgit:git-fetch[1].
remote.<name>.vcs::
Setting this to a value <vcs> will cause git to interact with
@@ -1688,6 +1749,7 @@ sendemail.to::
sendemail.smtpdomain::
sendemail.smtpserver::
sendemail.smtpserverport::
+sendemail.smtpserveroption::
sendemail.smtpuser::
sendemail.thread::
sendemail.validate::
@@ -1716,9 +1778,9 @@ status.showUntrackedFiles::
the untracked files. Possible values are:
+
--
- - 'no' - Show no untracked files
- - 'normal' - Shows untracked files and directories
- - 'all' - Shows also individual files in untracked directories.
+* `no` - Show no untracked files.
+* `normal` - Show untracked files and directories.
+* `all` - Show also individual files in untracked directories.
--
+
If this variable is not specified, it defaults to 'normal'.
@@ -1732,6 +1794,35 @@ status.submodulesummary::
summary of commits for modified submodules will be shown (see
--summary-limit option of linkgit:git-submodule[1]).
+submodule.<name>.path::
+submodule.<name>.url::
+submodule.<name>.update::
+ The path within this project, URL, and the updating strategy
+ for a submodule. These variables are initially populated
+ by 'git submodule init'; edit them to override the
+ URL and other values found in the `.gitmodules` file. See
+ linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
+
+submodule.<name>.fetchRecurseSubmodules::
+ This option can be used to control recursive fetching of this
+ submodule. It can be overridden by using the --[no-]recurse-submodules
+ command line option to "git fetch" and "git pull".
+ This setting will override that from in the linkgit:gitmodules[5]
+ file.
+
+submodule.<name>.ignore::
+ Defines under what circumstances "git status" and the diff family show
+ a submodule as modified. When set to "all", it will never be considered
+ modified, "dirty" will ignore all changes to the submodules work tree and
+ takes only differences between the HEAD of the submodule and the commit
+ recorded in the superproject into account. "untracked" will additionally
+ let submodules with modified tracked files in their work tree show up.
+ Using "none" (the default when this option is not set) also shows
+ submodules that have untracked files in their work tree as changed.
+ This setting overrides any setting made in .gitmodules for this submodule,
+ both settings can be overridden on the command line by using the
+ "--ignore-submodules" option.
+
tar.umask::
This variable can be used to restrict the permission bits of
tar archive entries. The default is 0002, which turns off the
diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
new file mode 100644
index 0000000..1aed79e
--- /dev/null
+++ b/Documentation/diff-config.txt
@@ -0,0 +1,136 @@
+diff.autorefreshindex::
+ When using 'git diff' to compare with work tree
+ files, do not consider stat-only change as changed.
+ Instead, silently run `git update-index --refresh` to
+ update the cached stat information for paths whose
+ contents in the work tree match the contents in the
+ index. This option defaults to true. Note that this
+ affects only 'git diff' Porcelain, and not lower level
+ 'diff' commands such as 'git diff-files'.
+
+diff.dirstat::
+ A comma separated list of `--dirstat` parameters specifying the
+ default behavior of the `--dirstat` option to linkgit:git-diff[1]`
+ and friends. The defaults can be overridden on the command line
+ (using `--dirstat=<param1,param2,...>`). The fallback defaults
+ (when not changed by `diff.dirstat`) are `changes,noncumulative,3`.
+ The following parameters are available:
++
+--
+`changes`;;
+ Compute the dirstat numbers by counting the lines that have been
+ removed from the source, or added to the destination. This ignores
+ the amount of pure code movements within a file. In other words,
+ rearranging lines in a file is not counted as much as other changes.
+ This is the default behavior when no parameter is given.
+`lines`;;
+ Compute the dirstat numbers by doing the regular line-based diff
+ analysis, and summing the removed/added line counts. (For binary
+ files, count 64-byte chunks instead, since binary files have no
+ natural concept of lines). This is a more expensive `--dirstat`
+ behavior than the `changes` behavior, but it does count rearranged
+ lines within a file as much as other changes. The resulting output
+ is consistent with what you get from the other `--*stat` options.
+`files`;;
+ Compute the dirstat numbers by counting the number of files changed.
+ Each changed file counts equally in the dirstat analysis. This is
+ the computationally cheapest `--dirstat` behavior, since it does
+ not have to look at the file contents at all.
+`cumulative`;;
+ Count changes in a child directory for the parent directory as well.
+ Note that when using `cumulative`, the sum of the percentages
+ reported may exceed 100%. The default (non-cumulative) behavior can
+ be specified with the `noncumulative` parameter.
+<limit>;;
+ An integer parameter specifies a cut-off percent (3% by default).
+ Directories contributing less than this percentage of the changes
+ are not shown in the output.
+--
++
+Example: The following will count changed files, while ignoring
+directories with less than 10% of the total amount of changed files,
+and accumulating child directory counts in the parent directories:
+`files,10,cumulative`.
+
+diff.external::
+ If this config variable is set, diff generation is not
+ performed using the internal diff machinery, but using the
+ given command. Can be overridden with the `GIT_EXTERNAL_DIFF'
+ environment variable. The command is called with parameters
+ as described under "git Diffs" in linkgit:git[1]. Note: if
+ you want to use an external diff program only on a subset of
+ your files, you might want to use linkgit:gitattributes[5] instead.
+
+diff.ignoreSubmodules::
+ Sets the default value of --ignore-submodules. Note that this
+ affects only 'git diff' Porcelain, and not lower level 'diff'
+ commands such as 'git diff-files'. 'git checkout' also honors
+ this setting when reporting uncommitted changes.
+
+diff.mnemonicprefix::
+ If set, 'git diff' uses a prefix pair that is different from the
+ standard "a/" and "b/" depending on what is being compared. When
+ this configuration is in effect, reverse diff output also swaps
+ the order of the prefixes:
+`git diff`;;
+ compares the (i)ndex and the (w)ork tree;
+`git diff HEAD`;;
+ compares a (c)ommit and the (w)ork tree;
+`git diff --cached`;;
+ compares a (c)ommit and the (i)ndex;
+`git diff HEAD:file1 file2`;;
+ compares an (o)bject and a (w)ork tree entity;
+`git diff --no-index a b`;;
+ compares two non-git things (1) and (2).
+
+diff.noprefix::
+ If set, 'git diff' does not show any source or destination prefix.
+
+diff.renameLimit::
+ The number of files to consider when performing the copy/rename
+ detection; equivalent to the 'git diff' option '-l'.
+
+diff.renames::
+ Tells git to detect renames. If set to any boolean value, it
+ will enable basic rename detection. If set to "copies" or
+ "copy", it will detect copies, as well.
+
+diff.suppressBlankEmpty::
+ A boolean to inhibit the standard behavior of printing a space
+ before each empty output line. Defaults to false.
+
+diff.<driver>.command::
+ The custom diff driver command. See linkgit:gitattributes[5]
+ for details.
+
+diff.<driver>.xfuncname::
+ The regular expression that the diff driver should use to
+ recognize the hunk header. A built-in pattern may also be used.
+ See linkgit:gitattributes[5] for details.
+
+diff.<driver>.binary::
+ Set this option to true to make the diff driver treat files as
+ binary. See linkgit:gitattributes[5] for details.
+
+diff.<driver>.textconv::
+ The command that the diff driver should call to generate the
+ text-converted version of a file. The result of the
+ conversion is used to generate a human-readable diff. See
+ linkgit:gitattributes[5] for details.
+
+diff.<driver>.wordregex::
+ The regular expression that the diff driver should use to
+ split words in a line. See linkgit:gitattributes[5] for
+ details.
+
+diff.<driver>.cachetextconv::
+ Set this option to true to make the diff driver cache the text
+ conversion outputs. See linkgit:gitattributes[5] for details.
+
+diff.tool::
+ The diff tool to be used by linkgit:git-difftool[1]. This
+ option overrides `merge.tool`, and has the same valid built-in
+ values as `merge.tool` minus "tortoisemerge" and plus
+ "kompare". Any other value is treated as a custom diff tool,
+ and there must be a corresponding `difftool.<tool>.cmd`
+ option.
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
index 8f9a241..c57460c 100644
--- a/Documentation/diff-generate-patch.txt
+++ b/Documentation/diff-generate-patch.txt
@@ -9,16 +9,15 @@ patch file. You can customize the creation of such patches via the
GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS environment variables.
What the -p option produces is slightly different from the traditional
-diff format.
+diff format:
-1. It is preceded with a "git diff" header, that looks like
- this:
+1. It is preceded with a "git diff" header that looks like this:
diff --git a/file1 b/file2
+
The `a/` and `b/` filenames are the same unless rename/copy is
involved. Especially, even for a creation or a deletion,
-`/dev/null` is _not_ used in place of `a/` or `b/` filenames.
+`/dev/null` is _not_ used in place of the `a/` or `b/` filenames.
+
When rename/copy is involved, `file1` and `file2` show the
name of the source file of the rename/copy and the name of
@@ -37,27 +36,51 @@ the file that rename/copy produces, respectively.
similarity index <number>
dissimilarity index <number>
index <hash>..<hash> <mode>
-
-3. TAB, LF, double quote and backslash characters in pathnames
- are represented as `\t`, `\n`, `\"` and `\\`, respectively.
- If there is need for such substitution then the whole
- pathname is put in double quotes.
-
++
+File modes are printed as 6-digit octal numbers including the file type
+and file permission bits.
++
+Path names in extended headers do not include the `a/` and `b/` prefixes.
++
The similarity index is the percentage of unchanged lines, and
the dissimilarity index is the percentage of changed lines. It
is a rounded down integer, followed by a percent sign. The
similarity index value of 100% is thus reserved for two equal
files, while 100% dissimilarity means that no line from the old
file made it into the new one.
++
+The index line includes the SHA-1 checksum before and after the change.
+The <mode> is included if the file mode does not change; otherwise,
+separate lines indicate the old and the new mode.
+
+3. TAB, LF, double quote and backslash characters in pathnames
+ are represented as `\t`, `\n`, `\"` and `\\`, respectively.
+ If there is need for such substitution then the whole
+ pathname is put in double quotes.
+
+4. All the `file1` files in the output refer to files before the
+ commit, and all the `file2` files refer to files after the commit.
+ It is incorrect to apply each change to each file sequentially. For
+ example, this patch will swap a and b:
+
+ diff --git a/a b/b
+ rename from a
+ rename to b
+ diff --git a/b b/a
+ rename from b
+ rename to a
combined diff format
--------------------
-"git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or
-'--cc' option to produce 'combined diff'. For showing a merge commit
-with "git log -p", this is the default format; you can force showing
-full diff with the '-m' option.
+Any diff-generating command can take the `-c` or `--cc` option to
+produce a 'combined diff' when showing a merge. This is the default
+format when showing merges with linkgit:git-diff[1] or
+linkgit:git-show[1]. Note also that you can give the `-m' option to any
+of these commands to force generation of diffs with individual parents
+of a merge.
+
A 'combined diff' format looks like this:
------------
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 2371262..b620b3a 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -48,11 +48,17 @@ endif::git-format-patch[]
--patience::
Generate a diff using the "patience diff" algorithm.
---stat[=width[,name-width]]::
+--stat[=<width>[,<name-width>[,<count>]]]::
Generate a diffstat. You can override the default
- output width for 80-column terminal by `--stat=width`.
+ output width for 80-column terminal by `--stat=<width>`.
The width of the filename part can be controlled by
giving another width to it separated by a comma.
+ By giving a third parameter `<count>`, you can limit the
+ output to the first `<count>` lines, followed by
+ `...` if there are more.
++
+These parameters can also be set individually with `--stat-width=<width>`,
+`--stat-name-width=<name-width>` and `--stat-count=<count>`.
--numstat::
Similar to `\--stat`, but shows number of added and
@@ -66,15 +72,49 @@ endif::git-format-patch[]
number of modified files, as well as number of added and deleted
lines.
---dirstat[=limit]::
- Output the distribution of relative amount of changes (number of lines added or
- removed) for each sub-directory. Directories with changes below
- a cut-off percent (3% by default) are not shown. The cut-off percent
- can be set with `--dirstat=limit`. Changes in a child directory is not
- counted for the parent directory, unless `--cumulative` is used.
-
---dirstat-by-file[=limit]::
- Same as `--dirstat`, but counts changed files instead of lines.
+--dirstat[=<param1,param2,...>]::
+ Output the distribution of relative amount of changes for each
+ sub-directory. The behavior of `--dirstat` can be customized by
+ passing it a comma separated list of parameters.
+ The defaults are controlled by the `diff.dirstat` configuration
+ variable (see linkgit:git-config[1]).
+ The following parameters are available:
++
+--
+`changes`;;
+ Compute the dirstat numbers by counting the lines that have been
+ removed from the source, or added to the destination. This ignores
+ the amount of pure code movements within a file. In other words,
+ rearranging lines in a file is not counted as much as other changes.
+ This is the default behavior when no parameter is given.
+`lines`;;
+ Compute the dirstat numbers by doing the regular line-based diff
+ analysis, and summing the removed/added line counts. (For binary
+ files, count 64-byte chunks instead, since binary files have no
+ natural concept of lines). This is a more expensive `--dirstat`
+ behavior than the `changes` behavior, but it does count rearranged
+ lines within a file as much as other changes. The resulting output
+ is consistent with what you get from the other `--*stat` options.
+`files`;;
+ Compute the dirstat numbers by counting the number of files changed.
+ Each changed file counts equally in the dirstat analysis. This is
+ the computationally cheapest `--dirstat` behavior, since it does
+ not have to look at the file contents at all.
+`cumulative`;;
+ Count changes in a child directory for the parent directory as well.
+ Note that when using `cumulative`, the sum of the percentages
+ reported may exceed 100%. The default (non-cumulative) behavior can
+ be specified with the `noncumulative` parameter.
+<limit>;;
+ An integer parameter specifies a cut-off percent (3% by default).
+ Directories contributing less than this percentage of the changes
+ are not shown in the output.
+--
++
+Example: The following will count changed files, while ignoring
+directories with less than 10% of the total amount of changed files,
+and accumulating child directory counts in the parent directories:
+`--dirstat=files,10,cumulative`.
--summary::
Output a condensed summary of extended header information
@@ -120,12 +160,19 @@ any of those replacements occurred.
--color[=<when>]::
Show colored diff.
- The value must be always (the default), never, or auto.
+ The value must be `always` (the default for `<when>`), `never`, or `auto`.
+ The default value is `never`.
+ifdef::git-diff[]
+ It can be changed by the `color.ui` and `color.diff`
+ configuration settings.
+endif::git-diff[]
--no-color::
- Turn off colored diff, even when the configuration file
- gives the default to color output.
- Same as `--color=never`.
+ Turn off colored diff.
+ifdef::git-diff[]
+ This can be used to override configuration settings.
+endif::git-diff[]
+ It is the same as `--color=never`.
--word-diff[=<mode>]::
Show a word diff, using the <mode> to delimit changed words.
@@ -183,10 +230,14 @@ endif::git-format-patch[]
ifndef::git-format-patch[]
--check::
- Warn if changes introduce trailing whitespace
- or an indent that uses a space before a tab. Exits with
- non-zero status if problems are found. Not compatible with
- --exit-code.
+ Warn if changes introduce whitespace errors. What are
+ considered whitespace errors is controlled by `core.whitespace`
+ configuration. By default, trailing whitespaces (including
+ lines that solely consist of whitespaces) and a space character
+ that is immediately followed by a tab character inside the
+ initial indent of the line are considered whitespace errors.
+ Exits with non-zero status if problems are found. Not compatible
+ with --exit-code.
endif::git-format-patch[]
--full-index::
@@ -206,10 +257,31 @@ endif::git-format-patch[]
the diff-patch output format. Non default number of
digits can be specified with `--abbrev=<n>`.
--B::
- Break complete rewrite changes into pairs of delete and create.
-
--M::
+-B[<n>][/<m>]::
+--break-rewrites[=[<n>][/<m>]]::
+ Break complete rewrite changes into pairs of delete and
+ create. This serves two purposes:
++
+It affects the way a change that amounts to a total rewrite of a file
+not as a series of deletion and insertion mixed together with a very
+few lines that happen to match textually as the context, but as a
+single deletion of everything old followed by a single insertion of
+everything new, and the number `m` controls this aspect of the -B
+option (defaults to 60%). `-B/70%` specifies that less than 30% of the
+original should remain in the result for git to consider it a total
+rewrite (i.e. otherwise the resulting patch will be a series of
+deletion and insertion mixed together with context lines).
++
+When used with -M, a totally-rewritten file is also considered as the
+source of a rename (usually -M only considers a file that disappeared
+as the source of a rename), and the number `n` controls this aspect of
+the -B option (defaults to 50%). `-B20%` specifies that a change with
+addition and deletion compared to 20% or more of the file's size are
+eligible for being picked up as a possible source of a rename to
+another file.
+
+-M[<n>]::
+--find-renames[=<n>]::
ifndef::git-log[]
Detect renames.
endif::git-log[]
@@ -218,23 +290,16 @@ ifdef::git-log[]
For following files across renames while traversing history, see
`--follow`.
endif::git-log[]
-
--C::
+ If `n` is specified, it is a threshold on the similarity
+ index (i.e. amount of addition/deletions compared to the
+ file's size). For example, `-M90%` means git should consider a
+ delete/add pair to be a rename if more than 90% of the file
+ hasn't changed.
+
+-C[<n>]::
+--find-copies[=<n>]::
Detect copies as well as renames. See also `--find-copies-harder`.
-
-ifndef::git-format-patch[]
---diff-filter=[ACDMRTUXB*]::
- Select only files that are Added (`A`), Copied (`C`),
- Deleted (`D`), Modified (`M`), Renamed (`R`), have their
- type (i.e. regular file, symlink, submodule, ...) changed (`T`),
- are Unmerged (`U`), are
- Unknown (`X`), or have had their pairing Broken (`B`).
- Any combination of the filter characters may be used.
- When `*` (All-or-none) is added to the combination, all
- paths are selected if there is any file that matches
- other criteria in the comparison; if there is no file
- that matches other criteria, nothing is selected.
-endif::git-format-patch[]
+ If `n` is specified, it has the same meaning as for `-M<n>`.
--find-copies-harder::
For performance reasons, by default, `-C` option finds copies only
@@ -245,6 +310,19 @@ endif::git-format-patch[]
projects, so use it with caution. Giving more than one
`-C` option has the same effect.
+-D::
+--irreversible-delete::
+ Omit the preimage for deletes, i.e. print only the header but not
+ the diff between the preimage and `/dev/null`. The resulting patch
+ is not meant to be applied with `patch` nor `git apply`; this is
+ solely for people who want to just concentrate on reviewing the
+ text after the change. In addition, the output obviously lack
+ enough information to apply such a patch in reverse, even manually,
+ hence the name of the option.
++
+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
@@ -253,14 +331,30 @@ endif::git-format-patch[]
number.
ifndef::git-format-patch[]
+--diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]::
+ Select only files that are Added (`A`), Copied (`C`),
+ Deleted (`D`), Modified (`M`), Renamed (`R`), have their
+ type (i.e. regular file, symlink, submodule, ...) changed (`T`),
+ are Unmerged (`U`), are
+ Unknown (`X`), or have had their pairing Broken (`B`).
+ Any combination of the filter characters (including none) can be used.
+ When `*` (All-or-none) is added to the combination, all
+ paths are selected if there is any file that matches
+ other criteria in the comparison; if there is no file
+ that matches other criteria, nothing is selected.
+
-S<string>::
Look for differences that introduce or remove an instance of
<string>. Note that this is different than the string simply
appearing in diff output; see the 'pickaxe' entry in
linkgit:gitdiffcore[7] for more details.
+-G<regex>::
+ Look for differences whose added or removed line matches
+ the given <regex>.
+
--pickaxe-all::
- When `-S` finds a change, show all the changes in that
+ When `-S` or `-G` finds a change, show all the changes in that
changeset, not just the files that contain the change
in <string>.
@@ -328,9 +422,24 @@ endif::git-format-patch[]
--no-ext-diff::
Disallow external diff drivers.
+--textconv::
+--no-textconv::
+ Allow (or disallow) external text conversion filters to be run
+ when comparing binary files. See linkgit:gitattributes[5] for
+ details. Because textconv filters are typically a one-way
+ conversion, the resulting diff is suitable for human
+ consumption, but cannot be applied. For this reason, textconv
+ filters are enabled by default only for linkgit:git-diff[1] and
+ linkgit:git-log[1], but not for linkgit:git-format-patch[1] or
+ diff plumbing commands.
+
--ignore-submodules[=<when>]::
Ignore changes to submodules in the diff generation. <when> can be
- either "untracked", "dirty" or "all", which is the default. When
+ either "none", "untracked", "dirty" or "all", which is the default
+ Using "none" will consider the submodule modified when it either contains
+ untracked or modified files or its HEAD differs from the commit recorded
+ in the superproject and can be used to override any settings of the
+ 'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
"untracked" is used submodules are not considered dirty when they only
contain untracked content (but they are still scanned for modified
content). Using "dirty" ignores all changes to the work tree of submodules,
diff --git a/Documentation/docbook.xsl b/Documentation/docbook.xsl
index 9a6912c..da8b05b 100644
--- a/Documentation/docbook.xsl
+++ b/Documentation/docbook.xsl
@@ -1,5 +1,8 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version='1.0'>
<xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
- <xsl:output method="html" encoding="UTF-8" indent="no" />
+ <xsl:output method="html"
+ encoding="UTF-8" indent="no"
+ doctype-public="-//W3C//DTD HTML 4.01//EN"
+ doctype-system="http://www.w3.org/TR/html4/strict.dtd" />
</xsl:stylesheet>
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index e0ba8cc..ae413e5 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -180,12 +180,12 @@ directory; clone from it to start a repository on the satellite
machine.
<2> clone sets these configuration variables by default.
It arranges `git pull` to fetch and store the branches of mothership
-machine to local `remotes/origin/*` tracking branches.
+machine to local `remotes/origin/*` remote-tracking branches.
<3> arrange `git push` to push local `master` branch to
`remotes/satellite/master` branch of the mothership machine.
<4> push will stash our work away on `remotes/satellite/master`
-tracking branch on the mothership machine. You could use this as
-a back-up method.
+remote-tracking branch on the mothership machine. You could use this
+as a back-up method.
<5> on mothership machine, merge the work done on the satellite
machine into the master branch.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 044ec88..39d326a 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -34,8 +34,9 @@ ifndef::git-pull[]
Allow several <repository> and <group> arguments to be
specified. No <refspec>s may be specified.
+-p::
--prune::
- After fetching, remove any remote tracking branches which
+ After fetching, remove any remote-tracking branches which
no longer exist on the remote.
endif::git-pull[]
@@ -48,8 +49,11 @@ ifndef::git-pull[]
endif::git-pull[]
By default, tags that point at objects that are downloaded
from the remote repository are fetched and stored locally.
- This option disables this automatic tag following.
+ This option disables this automatic tag following. The default
+ behavior for a remote may be specified with the remote.<name>.tagopt
+ setting. See linkgit:git-config[1].
+ifndef::git-pull[]
-t::
--tags::
Most of the tags are fetched automatically as branch
@@ -57,7 +61,38 @@ endif::git-pull[]
objects reachable from the branch heads that are being
tracked will not be fetched by this mechanism. This
flag lets all tags and their associated objects be
- downloaded.
+ downloaded. The default behavior for a remote may be
+ specified with the remote.<name>.tagopt setting. See
+ linkgit:git-config[1].
+
+--recurse-submodules[=yes|on-demand|no]::
+ This option controls if and under what conditions new commits of
+ populated submodules should be fetched too. It can be used as a
+ boolean option to completely disable recursion when set to 'no' or to
+ unconditionally recurse into all populated submodules when set to
+ 'yes', which is the default when this option is used without any
+ value. Use 'on-demand' to only recurse into a populated submodule
+ when the superproject retrieves a commit that updates the submodule's
+ reference to a commit that isn't already in the local submodule
+ clone.
+
+--no-recurse-submodules::
+ Disable recursive fetching of submodules (this has the same effect as
+ using the '--recurse-submodules=no' option).
+
+--submodule-prefix=<path>::
+ Prepend <path> to paths printed in informative messages
+ such as "Fetching submodule foo". This option is used
+ internally when recursing over submodules.
+
+--recurse-submodules-default=[yes|on-demand]::
+ This option is used internally to temporarily provide a
+ non-negative default value for the --recurse-submodules
+ option. All other methods of configuring fetch's submodule
+ recursion (such as settings in linkgit:gitmodules[5] and
+ linkgit:git-config[1]) override this option, as does
+ specifying --[no-]recurse-submodules directly.
+endif::git-pull[]
-u::
--update-head-ok::
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 74741a4..9c1d395 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -10,7 +10,8 @@ SYNOPSIS
[verse]
'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p]
[--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N]
- [--refresh] [--ignore-errors] [--] [<filepattern>...]
+ [--refresh] [--ignore-errors] [--ignore-missing] [--]
+ [<filepattern>...]
DESCRIPTION
-----------
@@ -57,7 +58,8 @@ OPTIONS
-n::
--dry-run::
- Don't actually add the file(s), just show if they exist.
+ Don't actually add the file(s), just show if they exist and/or will
+ be ignored.
-v::
--verbose::
@@ -90,9 +92,11 @@ See ``Interactive mode'' for details.
edit it. After the editor was closed, adjust the hunk headers
and apply the patch to the index.
+
-*NOTE*: Obviously, if you change anything else than the first character
-on lines beginning with a space or a minus, the patch will no longer
-apply.
+The intent of this option is to pick and choose lines of the patch to
+apply, or even to modify the contents of lines to be staged. This can be
+quicker and more flexible than using the interactive hunk selector.
+However, it is easy to confuse oneself and create a patch that does not
+apply to the index. See EDITING PATCHES below.
-u::
--update::
@@ -130,6 +134,14 @@ subdirectories.
If some files could not be added because of errors indexing
them, do not abort the operation, but continue adding the
others. The command shall still exit with non-zero status.
+ The configuration variable `add.ignoreErrors` can be set to
+ true to make this the default behaviour.
+
+--ignore-missing::
+ This option can only be used together with --dry-run. By using
+ this option the user can check if any of the given files would
+ be ignored, no matter if they are already present in the work
+ tree or not.
\--::
This option can be used to separate command-line options from
@@ -149,14 +161,14 @@ those in info/exclude. See linkgit:gitrepository-layout[5].
EXAMPLES
--------
-* Adds content from all `\*.txt` files under `Documentation` directory
+* Adds content from all `*.txt` files under `Documentation` directory
and its subdirectories:
+
------------
$ git add Documentation/\*.txt
------------
+
-Note that the asterisk `\*` is quoted from the shell in this
+Note that the asterisk `*` is quoted from the shell in this
example; this lets the command include the files from
subdirectories of `Documentation/` directory.
@@ -212,7 +224,7 @@ binary so line count cannot be shown) and there is no
difference between indexed copy and the working tree
version (if the working tree version were also different,
'binary' would have been shown in place of 'nothing'). The
-other file, git-add--interactive.perl, has 403 lines added
+other file, git-add{litdd}interactive.perl, has 403 lines added
and 35 lines deleted if you commit what is in the index, but
working tree file has further modifications (one addition and
one deletion).
@@ -262,7 +274,8 @@ patch::
This lets you choose one path out of a 'status' like selection.
After choosing the path, it presents the diff between the index
and the working tree file and asks you if you want to stage
- the change of each hunk. You can say:
+ the change of each hunk. You can select one of the following
+ options and type return:
y - stage this hunk
n - do not stage this hunk
@@ -281,12 +294,87 @@ patch::
+
After deciding the fate for all hunks, if there is any hunk
that was chosen, the index is updated with the selected hunks.
++
+You can omit having to type return here, by setting the configuration
+variable `interactive.singlekey` to `true`.
diff::
This lets you review what will be committed (i.e. between
HEAD and index).
+
+EDITING PATCHES
+---------------
+
+Invoking `git add -e` or selecting `e` from the interactive hunk
+selector will open a patch in your editor; after the editor exits, the
+result is applied to the index. You are free to make arbitrary changes
+to the patch, but note that some changes may have confusing results, or
+even result in a patch that cannot be applied. If you want to abort the
+operation entirely (i.e., stage nothing new in the index), simply delete
+all lines of the patch. The list below describes some common things you
+may see in a patch, and which editing operations make sense on them.
+
+--
+added content::
+
+Added content is represented by lines beginning with "{plus}". You can
+prevent staging any addition lines by deleting them.
+
+removed content::
+
+Removed content is represented by lines beginning with "-". You can
+prevent staging their removal by converting the "-" to a " " (space).
+
+modified content::
+
+Modified content is represented by "-" lines (removing the old content)
+followed by "{plus}" lines (adding the replacement content). You can
+prevent staging the modification by converting "-" lines to " ", and
+removing "{plus}" lines. Beware that modifying only half of the pair is
+likely to introduce confusing changes to the index.
+--
+
+There are also more complex operations that can be performed. But beware
+that because the patch is applied only to the index and not the working
+tree, the working tree will appear to "undo" the change in the index.
+For example, introducing a new line into the index that is in neither
+the HEAD nor the working tree will stage the new line for commit, but
+the line will appear to be reverted in the working tree.
+
+Avoid using these constructs, or do so with extreme caution.
+
+--
+removing untouched content::
+
+Content which does not differ between the index and working tree may be
+shown on context lines, beginning with a " " (space). You can stage
+context lines for removal by converting the space to a "-". The
+resulting working tree file will appear to re-add the content.
+
+modifying existing content::
+
+One can also modify context lines by staging them for removal (by
+converting " " to "-") and adding a "{plus}" line with the new content.
+Similarly, one can modify "{plus}" lines for existing additions or
+modifications. In all cases, the new modification will appear reverted
+in the working tree.
+
+new content::
+
+You may also add new content that does not exist in the patch; simply
+add new lines, each starting with "{plus}". The addition will appear
+reverted in the working tree.
+--
+
+There are also several operations which should be avoided entirely, as
+they will make the patch impossible to apply:
+
+* adding context (" ") or removal ("-") lines
+* deleting context or removal lines
+* modifying the contents of context or removal lines
+
SEE ALSO
--------
linkgit:git-status[1]
@@ -296,14 +384,6 @@ linkgit:git-mv[1]
linkgit:git-commit[1]
linkgit:git-update-index[1]
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 9e62f87..6b1b5af 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -14,7 +14,7 @@ SYNOPSIS
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--reject] [-q | --quiet] [--scissors | --no-scissors]
- [<mbox> | <Maildir>...]
+ [(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort)
DESCRIPTION
@@ -25,7 +25,7 @@ current branch.
OPTIONS
-------
-<mbox>|<Maildir>...::
+(<mbox>|<Maildir>)...::
The list of mailbox files to read patches from. If you do not
supply this argument, the command reads from the standard input.
If you supply directories, they will be treated as Maildirs.
@@ -173,9 +173,9 @@ aborts in the middle. You can recover from this in one of two ways:
the index file to bring it into a state that the patch should
have produced. Then run the command with the '--resolved' option.
-The command refuses to process new mailboxes while the `.git/rebase-apply`
-directory exists, so if you decide to start over from scratch,
-run `rm -f -r .git/rebase-apply` before running the command with mailbox
+The command refuses to process new mailboxes until the current
+operation is finished, so if you decide to start over from scratch,
+run `git am --abort` before running the command with mailbox
names.
Before any patches are applied, ORIG_HEAD is set to the tip of the
@@ -189,15 +189,6 @@ SEE ALSO
--------
linkgit:git-apply[1].
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt
index 0590eec..05fd482 100644
--- a/Documentation/git-annotate.txt
+++ b/Documentation/git-annotate.txt
@@ -7,6 +7,7 @@ git-annotate - Annotate file lines with commit information
SYNOPSIS
--------
+[verse]
'git annotate' [options] file [revision]
DESCRIPTION
@@ -27,10 +28,6 @@ SEE ALSO
--------
linkgit:git-blame[1]
-AUTHOR
-------
-Written by Ryan Anderson <ryan@michonline.com>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 8463439..afd2c9a 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -12,20 +12,24 @@ SYNOPSIS
'git apply' [--stat] [--numstat] [--summary] [--check] [--index]
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z]
- [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
+ [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
[--ignore-space-change | --ignore-whitespace ]
- [--whitespace=<nowarn|warn|fix|error|error-all>]
- [--exclude=PATH] [--include=PATH] [--directory=<root>]
+ [--whitespace=(nowarn|warn|fix|error|error-all)]
+ [--exclude=<path>] [--include=<path>] [--directory=<root>]
[--verbose] [<patch>...]
DESCRIPTION
-----------
Reads the supplied diff output (i.e. "a patch") and applies it to files.
With the `--index` option the patch is also applied to the index, and
-with the `--cache` option the patch is only applied to the index.
+with the `--cached` option the patch is only applied to the index.
Without these options, the command applies the patch only to files,
and does not require them to be in a git repository.
+This command applies the patch but does not create a commit. Use
+linkgit:git-am[1] to create commits from patches generated by
+linkgit:git-format-patch[1] and/or received by email.
+
OPTIONS
-------
<patch>...::
@@ -242,13 +246,9 @@ If `--index` is not specified, then the submodule commits in the patch
are ignored and only the absence or presence of the corresponding
subdirectory is checked and (if possible) updated.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano
+SEE ALSO
+--------
+linkgit:git-am[1].
GIT
---
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
index 4d4325f..f4504ba 100644
--- a/Documentation/git-archimport.txt
+++ b/Documentation/git-archimport.txt
@@ -44,7 +44,7 @@ archives that it imports, it is also possible to specify git branch names
manually. To do so, write a git branch name after each <archive/branch>
parameter, separated by a colon. This way, you can shorten the Arch
branch names and convert Arch jargon to git jargon, for example mapping a
-"PROJECT--devo--VERSION" branch to "master".
+"PROJECT{litdd}devo{litdd}VERSION" branch to "master".
Associating multiple Arch branches to one git branch is possible; the
result will make the most sense only if no commits are made to the first
@@ -85,8 +85,8 @@ OPTIONS
-o::
Use this for compatibility with old-style branch names used by
earlier versions of 'git archimport'. Old-style branch names
- were category--branch, whereas new-style branch names are
- archive,category--branch--version. In both cases, names given
+ were category{litdd}branch, whereas new-style branch names are
+ archive,category{litdd}branch{litdd}version. In both cases, names given
on the command-line will override the automatically-generated
ones.
@@ -107,14 +107,6 @@ OPTIONS
Archive/branch identifier in a format that `tla log` understands.
-Author
-------
-Written by Martin Langhoff <martin@catalyst.net.nz>.
-
-Documentation
---------------
-Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
index 8d3e666..1320c87 100644
--- a/Documentation/git-archive.txt
+++ b/Documentation/git-archive.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>]
[-o | --output=<file>] [--worktree-attributes]
[--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
- [path...]
+ [<path>...]
DESCRIPTION
-----------
@@ -73,7 +73,7 @@ OPTIONS
<tree-ish>::
The tree or commit to produce an archive for.
-path::
+<path>::
Without an optional path parameter, all files and subdirectories
of the current working directory are included in the archive.
If one or more paths are specified, only these are included.
@@ -98,7 +98,27 @@ tar.umask::
tar archive entries. The default is 0002, which turns off the
world write bit. The special value "user" indicates that the
archiving user's umask will be used instead. See umask(2) for
- details.
+ details. If `--remote` is used then only the configuration of
+ the remote repository takes effect.
+
+tar.<format>.command::
+ This variable specifies a shell command through which the tar
+ output generated by `git archive` should be piped. The command
+ is executed using the shell with the generated tar file on its
+ standard input, and should produce the final output on its
+ standard output. Any compression-level options will be passed
+ to the command (e.g., "-9"). An output file with the same
+ extension as `<format>` will be use this format if no other
+ format is given.
++
+The "tar.gz" and "tgz" formats are defined automatically and default to
+`gzip -cn`. You may override them with custom commands.
+
+tar.<format>.remote::
+ If true, enable `<format>` for use by remote clients via
+ linkgit:git-upload-archive[1]. Defaults to false for
+ user-defined formats, but true for the "tar.gz" and "tgz"
+ formats.
ATTRIBUTES
----------
@@ -116,7 +136,7 @@ Note that attributes are by default taken from the `.gitattributes` files
in the tree that is being archived. If you want to tweak the way the
output is generated after the fact (e.g. you committed without adding an
appropriate export-ignore in its `.gitattributes`), adjust the checked out
-`.gitattributes` file as necessary and use `--work-tree-attributes`
+`.gitattributes` file as necessary and use `--worktree-attributes`
option. Alternatively you can keep necessary attributes that should apply
while archiving any tree in your `$GIT_DIR/info/attributes` file.
@@ -132,6 +152,14 @@ git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
Create a compressed tarball for v1.4.0 release.
+git archive --format=tar.gz --prefix=git-1.4.0/ v1.4.0 >git-1.4.0.tar.gz::
+
+ Same as above, but using the builtin tar.gz handling.
+
+git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0::
+
+ Same as above, but the format is inferred from the output file.
+
git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
Create a compressed tarball for v1.4.0 release, but without a
@@ -148,19 +176,17 @@ git archive -o latest.zip HEAD::
commit on the current branch. Note that the output format is
inferred by the extension of the output file.
+git config tar.tar.xz.command "xz -c"::
+
+ Configure a "tar.xz" format for making LZMA-compressed tarfiles.
+ You can use it specifying `--format=tar.xz`, or by creating an
+ output file like `-o foo.tar.xz`.
+
SEE ALSO
--------
linkgit:gitattributes[5]
-Author
-------
-Written by Franck Bui-Huu and Rene Scharfe.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt
index 86b3015..8a2ba37 100644
--- a/Documentation/git-bisect-lk2009.txt
+++ b/Documentation/git-bisect-lk2009.txt
@@ -873,7 +873,7 @@ c * N * T + b * M * log2(M) tests
where c is the number of rounds of test (so a small constant) and b is
the ratio of bug per commit (hopefully a small constant too).
-So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if
+So of course it's much better as it's O(N * T) vs O(N * T * M) if
you would test everything after each commit.
This means that test suites are good to prevent some bugs from being
@@ -971,7 +971,7 @@ logical change in each commit.
The smaller the changes in your commit, the most effective "git
bisect" will be. And you will probably need "git bisect" less in the
first place, as small changes are easier to review even if they are
-only reviewed by the commiter.
+only reviewed by the committer.
Another good idea is to have good commit messages. They can be very
helpful to understand why some changes were made.
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index c39d957..ab60a18 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -8,6 +8,7 @@ git-bisect - Find by binary search the change that introduced a bug
SYNOPSIS
--------
+[verse]
'git bisect' <subcommand> <options>
DESCRIPTION
@@ -241,7 +242,12 @@ exit(3) manual page), as the value is chopped with "& 0377".
The special exit code 125 should be used when the current source code
cannot be tested. If the script exits with this code, the current
-revision will be skipped (see `git bisect skip` above).
+revision will be skipped (see `git bisect skip` above). 125 was chosen
+as the highest sensible value to use for this purpose, because 126 and 127
+are used by POSIX shells to signal specific error status (127 is for
+command not found, 126 is for command found but not executable---these
+details do not matter, as they are normal errors in the script, as far as
+"bisect run" is concerned).
You may often find that during a bisect session you want to have
temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a
@@ -274,61 +280,68 @@ $ git bisect start HEAD origin -- # HEAD is bad, origin is good
$ git bisect run make test # "make test" builds and tests
------------
-* Automatically bisect a broken test suite:
+* Automatically bisect a broken test case:
+
------------
$ cat ~/test.sh
#!/bin/sh
-make || exit 125 # this skips broken builds
-make test # "make test" runs the test suite
-$ git bisect start v1.3 v1.1 -- # v1.3 is bad, v1.1 is good
+make || exit 125 # this skips broken builds
+~/check_test_case.sh # does the test case pass?
+$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10
$ git bisect run ~/test.sh
------------
+
Here we use a "test.sh" custom script. In this script, if "make"
fails, we skip the current commit.
+"check_test_case.sh" should "exit 0" if the test case passes,
+and "exit 1" otherwise.
+
-It is safer to use a custom script outside the repository to prevent
-interactions between the bisect, make and test processes and the
-script.
-+
-"make test" should "exit 0", if the test suite passes, and
-"exit 1" otherwise.
+It is safer if both "test.sh" and "check_test_case.sh" are
+outside the repository to prevent interactions between the bisect,
+make and test processes and the scripts.
-* Automatically bisect a broken test case:
+* Automatically bisect with temporary modifications (hot-fix):
+
------------
$ cat ~/test.sh
#!/bin/sh
-make || exit 125 # this skips broken builds
-~/check_test_case.sh # does the test case passes ?
-$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10
-$ git bisect run ~/test.sh
+
+# tweak the working tree by merging the hot-fix branch
+# and then attempt a build
+if git merge --no-commit hot-fix &&
+ make
+then
+ # run project specific test and report its status
+ ~/check_test_case.sh
+ status=$?
+else
+ # tell the caller this is untestable
+ status=125
+fi
+
+# undo the tweak to allow clean flipping to the next commit
+git reset --hard
+
+# return control
+exit $status
------------
+
-Here "check_test_case.sh" should "exit 0" if the test case passes,
-and "exit 1" otherwise.
-+
-It is safer if both "test.sh" and "check_test_case.sh" scripts are
-outside the repository to prevent interactions between the bisect,
-make and test processes and the scripts.
+This applies modifications from a hot-fix branch before each test run,
+e.g. in case your build or test environment changed so that older
+revisions may need a fix which newer ones have already. (Make sure the
+hot-fix branch is based off a commit which is contained in all revisions
+which you are bisecting, so that the merge does not pull in too much, or
+use `git cherry-pick` instead of `git merge`.)
-* Automatically bisect a broken test suite:
+* Automatically bisect a broken test case:
+
------------
$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10
$ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
------------
+
-Does the same as the previous example, but on a single line.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
--------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+This shows that you can do without a run script if you write the test
+on a single line.
SEE ALSO
--------
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index a27f439..9516914 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -8,8 +8,8 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
[verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
- [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
+'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
+ [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
[<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
@@ -65,10 +65,19 @@ include::blame-options.txt[]
-s::
Suppress the author name and timestamp from the output.
+-e::
+--show-email::
+ Show the author email instead of author name (Default: off).
+
-w::
Ignore whitespace when comparing the parent's version and
the child's to find where the lines came from.
+--abbrev=<n>::
+ Instead of using the default 7+1 hexadecimal digits as the
+ abbreviated object name, use <n>+1 digits. Note that 1 column
+ is used for a caret to mark the boundary commit.
+
THE PORCELAIN FORMAT
--------------------
@@ -96,6 +105,19 @@ The contents of the actual line is output after the above
header, prefixed by a TAB. This is to allow adding more
header elements later.
+The porcelain format generally suppresses commit information that has
+already been seen. For example, two lines that are blamed to the same
+commit will both be shown, but the details for that commit will be shown
+only once. This is more efficient, but may require more state be kept by
+the reader. The `--line-porcelain` option can be used to output full
+commit information for each line, allowing simpler (but less efficient)
+usage like:
+
+ # count the number of lines attributed to each author
+ git blame --line-porcelain file |
+ sed -n 's/^author //p' |
+ sort | uniq -c | sort -rn
+
SPECIFYING RANGES
-----------------
@@ -194,10 +216,6 @@ SEE ALSO
--------
linkgit:git-annotate[1]
-AUTHOR
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 1940256..c50f189 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -37,11 +37,12 @@ Note that this will create the new branch, but it will not switch the
working tree to it; use "git checkout <newbranch>" to switch to the
new branch.
-When a local branch is started off a remote branch, git sets up the
+When a local branch is started off a remote-tracking branch, git sets up the
branch so that 'git pull' will appropriately merge from
-the remote branch. This behavior may be changed via the global
+the remote-tracking branch. This behavior may be changed via the global
`branch.autosetupmerge` configuration flag. That setting can be
-overridden by using the `--track` and `--no-track` options.
+overridden by using the `--track` and `--no-track` options, and
+changed later using `git branch --set-upstream`.
With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
If <oldbranch> had a corresponding reflog, it is renamed to match
@@ -89,7 +90,8 @@ OPTIONS
Move/rename a branch even if the new branch name already exists.
--color[=<when>]::
- Color branches to highlight current, local, and remote branches.
+ Color branches to highlight current, local, and
+ remote-tracking branches.
The value must be always (the default), never, or auto.
--no-color::
@@ -125,11 +127,11 @@ OPTIONS
it directs `git pull` without arguments to pull from the
upstream when the new branch is checked out.
+
-This behavior is the default when the start point is a remote branch.
+This behavior is the default when the start point is a remote-tracking branch.
Set the branch.autosetupmerge configuration variable to `false` if you
want `git checkout` and `git branch` to always behave as if '--no-track'
were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote branch.
+start-point is either a local or remote-tracking branch.
--no-track::
Do not set up "upstream" configuration, even if the
@@ -230,14 +232,6 @@ linkgit:git-remote[1],
link:user-manual.html#what-is-a-branch[``Understanding history: What is
a branch?''] in the Git User's Manual.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index a5ed8fb..92b01ec2 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -9,10 +9,10 @@ git-bundle - Move objects and refs by archive
SYNOPSIS
--------
[verse]
-'git bundle' create <file> <git-rev-list args>
+'git bundle' create <file> <git-rev-list-args>
'git bundle' verify <file>
-'git bundle' list-heads <file> [refname...]
-'git bundle' unbundle <file> [refname...]
+'git bundle' list-heads <file> [<refname>...]
+'git bundle' unbundle <file> [<refname>...]
DESCRIPTION
-----------
@@ -34,57 +34,58 @@ OPTIONS
-------
create <file>::
- Used to create a bundle named 'file'. This requires the
- 'git rev-list' arguments to define the bundle contents.
+ Used to create a bundle named 'file'. This requires the
+ 'git-rev-list-args' arguments to define the bundle contents.
verify <file>::
- Used to check that a bundle file is valid and will apply
- cleanly to the current repository. This includes checks on the
- bundle format itself as well as checking that the prerequisite
- commits exist and are fully linked in the current repository.
- 'git bundle' prints a list of missing commits, if any, and exits
- with a non-zero status.
+ Used to check that a bundle file is valid and will apply
+ cleanly to the current repository. This includes checks on the
+ bundle format itself as well as checking that the prerequisite
+ commits exist and are fully linked in the current repository.
+ 'git bundle' prints a list of missing commits, if any, and exits
+ with a non-zero status.
list-heads <file>::
- Lists the references defined in the bundle. If followed by a
- list of references, only references matching those given are
- printed out.
+ Lists the references defined in the bundle. If followed by a
+ list of references, only references matching those given are
+ printed out.
unbundle <file>::
- Passes the objects in the bundle to 'git index-pack'
- for storage in the repository, then prints the names of all
- defined references. If a list of references is given, only
- references matching those in the list are printed. This command is
- really plumbing, intended to be called only by 'git fetch'.
-
-[git-rev-list-args...]::
- A list of arguments, acceptable to 'git rev-parse' and
- 'git rev-list', that specifies the specific objects and references
- to transport. For example, `master\~10..master` causes the
- current master reference to be packaged along with all objects
- added since its 10th ancestor commit. There is no explicit
- limit to the number of references and objects that may be
- packaged.
-
-
-[refname...]::
- A list of references used to limit the references reported as
- available. This is principally of use to 'git fetch', which
- expects to receive only those references asked for and not
- necessarily everything in the pack (in this case, 'git bundle' acts
- like 'git fetch-pack').
+ Passes the objects in the bundle to 'git index-pack'
+ for storage in the repository, then prints the names of all
+ defined references. If a list of references is given, only
+ references matching those in the list are printed. This command is
+ really plumbing, intended to be called only by 'git fetch'.
+
+<git-rev-list-args>::
+ A list of arguments, acceptable to 'git rev-parse' and
+ 'git rev-list' (and containing a named ref, see SPECIFYING REFERENCES
+ below), that specifies the specific objects and references
+ to transport. For example, `master{tilde}10..master` causes the
+ current master reference to be packaged along with all objects
+ added since its 10th ancestor commit. There is no explicit
+ limit to the number of references and objects that may be
+ packaged.
+
+
+[<refname>...]::
+ A list of references used to limit the references reported as
+ available. This is principally of use to 'git fetch', which
+ expects to receive only those references asked for and not
+ necessarily everything in the pack (in this case, 'git bundle' acts
+ like 'git fetch-pack').
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
+such as `master{tilde}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`).
+specified explicitly (e.g. `^master{tilde}10`), or implicitly (e.g.
+`master{tilde}10..master`, `--since=10.days.ago master`).
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
@@ -154,7 +155,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
-in the resulting bundle. The previous example used lastR2bundle tag
+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:
@@ -194,16 +195,12 @@ references when fetching:
$ git fetch mybundle master:localRef
----------------
-You can also see what references it offers.
+You can also see what references it offers:
----------------
$ git ls-remote mybundle
----------------
-Author
-------
-Written by Mark Levedahl <mdl123@verizon.net>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
index 9ebbe94..2fb95bb 100644
--- a/Documentation/git-cat-file.txt
+++ b/Documentation/git-cat-file.txt
@@ -27,7 +27,7 @@ OPTIONS
<object>::
The name of the object to show.
For a more complete list of ways to spell object names, see
- the "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+ the "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
-t::
Instead of the content, show the object type identified by
@@ -100,14 +100,6 @@ for each object specified on stdin that does not exist in the repository:
<object> SP missing LF
------------
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
index 50824e3..30eca6c 100644
--- a/Documentation/git-check-attr.txt
+++ b/Documentation/git-check-attr.txt
@@ -86,15 +86,6 @@ SEE ALSO
--------
linkgit:gitattributes[5].
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by James Bowes.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index 379eee6..c9fdf84 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -18,9 +18,12 @@ Checks if a given 'refname' is acceptable, and exits with a non-zero
status if it is not.
A reference is used in git to specify branches and tags. A
-branch head is stored under the `$GIT_DIR/refs/heads` directory, and
-a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs
-are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file).
+branch head is stored in the `refs/heads` hierarchy, while
+a tag is stored in the `refs/tags` hierarchy of the ref namespace
+(typically in `$GIT_DIR/refs/heads` and `$GIT_DIR/refs/tags`
+directories or, as entries in file `$GIT_DIR/packed-refs`
+if refs are packed by `git gc`).
+
git imposes the following rules on how references are named:
. They can include slash `/` for hierarchical (directory)
@@ -49,7 +52,7 @@ git imposes the following rules on how references are named:
These rules make it easy for shell script based tools to parse
reference names, pathname expansion by the shell when a reference name is used
unquoted (by mistake), and also avoids ambiguities in certain
-reference name expressions (see linkgit:git-rev-parse[1]):
+reference name expressions (see linkgit:gitrevisions[7]):
. A double-dot `..` is often used as in `ref1..ref2`, and in some
contexts this notation means `{caret}ref1 ref2` (i.e. not in
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
index d6aa6e1..4d33e7b 100644
--- a/Documentation/git-checkout-index.txt
+++ b/Documentation/git-checkout-index.txt
@@ -13,7 +13,7 @@ SYNOPSIS
[--stage=<number>|all]
[--temp]
[-z] [--stdin]
- [--] [<file>]\*
+ [--] [<file>...]
DESCRIPTION
-----------
@@ -172,18 +172,6 @@ $ git checkout-index --prefix=.merged- Makefile
This will check out the currently cached copy of `Makefile`
into the file `.merged-Makefile`.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-
-Documentation
---------------
-Documentation by David Greaves,
-Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 261dd90..c0a96e6 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -9,9 +9,10 @@ SYNOPSIS
--------
[verse]
'git checkout' [-q] [-f] [-m] [<branch>]
-'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>]
+'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
+'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
-'git checkout' --patch [<tree-ish>] [--] [<paths>...]
+'git checkout' [-p|--patch] [<tree-ish>] [--] [<paths>...]
DESCRIPTION
-----------
@@ -21,27 +22,39 @@ also update `HEAD` to set the specified branch as the current
branch.
'git checkout' [<branch>]::
-'git checkout' -b <new branch> [<start point>]::
+'git checkout' -b|-B <new_branch> [<start point>]::
+'git checkout' [--detach] [<commit>]::
This form switches branches by updating the index, working
- tree, and HEAD to reflect the specified branch.
+ tree, and HEAD to reflect the specified branch or commit.
+
If `-b` is given, a new branch is created as if linkgit:git-branch[1]
were called and then checked out; in this case you can
use the `--track` or `--no-track` options, which will be passed to
'git branch'. As a convenience, `--track` without `-b` implies branch
creation; see the description of `--track` below.
-
-'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...::
-
- When <paths> or `--patch` are given, 'git checkout' *not* switch
- branches. It updates the named paths in the working tree from
- the index file or from a named <tree-ish> (most often a commit). In
- this case, the `-b` and `--track` options are meaningless and giving
- either of them results in an error. The <tree-ish> argument can be
- used to specify a specific tree-ish (i.e. commit, tag or tree)
- to update the index for the given paths before updating the
- working tree.
++
+If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
+is reset. This is the transactional equivalent of
++
+------------
+$ git branch -f <branch> [<start point>]
+$ git checkout <branch>
+------------
++
+that is to say, the branch is not reset/created unless "git checkout" is
+successful.
+
+'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...::
+
+ When <paths> or `--patch` are given, 'git checkout' does *not*
+ switch branches. It updates the named paths in the working tree
+ from the index file or from a named <tree-ish> (most often a
+ commit). In this case, the `-b` and `--track` options are
+ meaningless and giving either of them results in an error. The
+ <tree-ish> argument can be used to specify a specific tree-ish
+ (i.e. commit, tag or tree) to update the index for the given
+ paths before updating the working tree.
+
The index may contain unmerged entries because of a previous failed merge.
By default, if you try to check out such an entry from the index, the
@@ -75,13 +88,19 @@ entries; instead, unmerged entries are ignored.
Create a new branch named <new_branch> and start it at
<start_point>; see linkgit:git-branch[1] for details.
+-B::
+ Creates the branch <new_branch> and start it at <start_point>;
+ if it already exists, then reset it to <start_point>. This is
+ equivalent to running "git branch" with "-f"; see
+ linkgit:git-branch[1] for details.
+
-t::
--track::
When creating a new branch, set up "upstream" configuration. See
"--track" in linkgit:git-branch[1] for details.
+
If no '-b' option is given, the name of the new branch will be
-derived from the remote branch. If "remotes/" or "refs/remotes/"
+derived from the remote-tracking branch. If "remotes/" or "refs/remotes/"
is prefixed it is stripped away, and then the part up to the
next slash (which would be the nickname of the remote) is removed.
This would tell us to use "hack" as the local branch when branching
@@ -98,6 +117,13 @@ explicitly give a name with '-b' in such a case.
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
+--detach::
+ Rather than checking out a branch to work on it, check out a
+ commit for inspection and discardable experiments.
+ This is the default behavior of "git checkout <commit>" when
+ <commit> is not a branch name. See the "DETACHED HEAD" section
+ below for details.
+
--orphan::
Create a new 'orphan' branch, named <new_branch>, started from
<start_point> and switch to it. The first commit made on this
@@ -157,7 +183,8 @@ the conflicted merge in the specified paths.
working tree (and if a <tree-ish> was specified, the index).
+
This means that you can use `git checkout -p` to selectively discard
-edits from your current working tree.
+edits from your current working tree. See the ``Interactive Mode''
+section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
@@ -170,7 +197,7 @@ As a special case, the `"@\{-N\}"` syntax for the N-th last branch
checks out the branch (instead of detaching). You may also specify
`-` which is synonymous with `"@\{-1\}"`.
+
-As a further special case, you may use `"A...B"` as a shortcut for the
+As a further special case, you may use `"A\...B"` as a shortcut for the
merge base of `A` and `B` if there is exactly one merge base. You can
leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
@@ -187,42 +214,140 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
-Detached HEAD
+DETACHED HEAD
-------------
+HEAD normally refers to a named branch (e.g. 'master'). Meanwhile, each
+branch refers to a specific commit. Let's look at a repo with three
+commits, one of them tagged, and with branch 'master' checked out:
-It is sometimes useful to be able to 'checkout' a commit that is
-not at the tip of one of your branches. The most obvious
-example is to check out the commit at a tagged official release
-point, like this:
+------------
+ HEAD (refers to branch 'master')
+ |
+ v
+a---b---c branch 'master' (refers to commit 'c')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
+
+When a commit is created in this state, the branch is updated to refer to
+the new commit. Specifically, 'git commit' creates a new commit 'd', whose
+parent is commit 'c', and then updates branch 'master' to refer to new
+commit 'd'. HEAD still refers to branch 'master' and so indirectly now refers
+to commit 'd':
------------
-$ git checkout v2.6.18
+$ edit; git add; git commit
+
+ HEAD (refers to branch 'master')
+ |
+ v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
------------
-Earlier versions of git did not allow this and asked you to
-create a temporary branch using the `-b` option, but starting from
-version 1.5.0, the above command 'detaches' your HEAD from the
-current branch and directly points at the commit named by the tag
-(`v2.6.18` in the example above).
+It is sometimes useful to be able to checkout a commit that is not at
+the tip of any named branch, or even to create a new commit that is not
+referenced by a named branch. Let's look at what happens when we
+checkout commit 'b' (here we show two ways this may be done):
-You can use all git commands while in this state. You can use
-`git reset --hard $othercommit` to further move around, for
-example. You can make changes and create a new commit on top of
-a detached HEAD. You can even create a merge by using `git
-merge $othercommit`.
+------------
+$ git checkout v2.0 # or
+$ git checkout master^^
+
+ HEAD (refers to commit 'b')
+ |
+ v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
-The state you are in while your HEAD is detached is not recorded
-by any branch (which is natural --- you are not on any branch).
-What this means is that you can discard your temporary commits
-and merges by switching back to an existing branch (e.g. `git
-checkout master`), and a later `git prune` or `git gc` would
-garbage-collect them. If you did this by mistake, you can ask
-the reflog for HEAD where you were, e.g.
+Notice that regardless of which checkout command we use, HEAD now refers
+directly to commit 'b'. This is known as being in detached HEAD state.
+It means simply that HEAD refers to a specific commit, as opposed to
+referring to a named branch. Let's see what happens when we create a commit:
------------
-$ git log -g -2 HEAD
+$ edit; git add; git commit
+
+ HEAD (refers to commit 'e')
+ |
+ v
+ e
+ /
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
+
+There is now a new commit 'e', but it is referenced only by HEAD. We can
+of course add yet another commit in this state:
+
+------------
+$ edit; git add; git commit
+
+ HEAD (refers to commit 'f')
+ |
+ v
+ e---f
+ /
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
------------
+In fact, we can perform all the normal git operations. But, let's look
+at what happens when we then checkout master:
+
+------------
+$ git checkout master
+
+ HEAD (refers to branch 'master')
+ e---f |
+ / v
+a---b---c---d branch 'master' (refers to commit 'd')
+ ^
+ |
+ tag 'v2.0' (refers to commit 'b')
+------------
+
+It is important to realize that at this point nothing refers to commit
+'f'. Eventually commit 'f' (and by extension commit 'e') will be deleted
+by the routine git garbage collection process, unless we create a reference
+before that happens. If we have not yet moved away from commit 'f',
+any of these will create a reference to it:
+
+------------
+$ git checkout -b foo <1>
+$ git branch foo <2>
+$ git tag foo <3>
+------------
+
+<1> creates a new branch 'foo', which refers to commit 'f', and then
+updates HEAD to refer to branch 'foo'. In other words, we'll no longer
+be in detached HEAD state after this command.
+
+<2> similarly creates a new branch 'foo', which refers to commit 'f',
+but leaves HEAD detached.
+
+<3> creates a new tag 'foo', which refers to commit 'f',
+leaving HEAD detached.
+
+If we have moved away from commit 'f', then we must first recover its object
+name (typically by using git reflog), and then we can create a reference to
+it. For example, to see the last two commits to which HEAD referred, we
+can use either of these commands:
+
+------------
+$ git reflog -2 HEAD # or
+$ git log -g -2 HEAD
+------------
EXAMPLES
--------
@@ -263,7 +388,7 @@ the above checkout would fail like this:
+
------------
$ git checkout mytopic
-fatal: Entry 'frotz' not uptodate. Cannot merge.
+error: You have local changes to 'frotz'; not switching branches.
------------
+
You can give the `-m` flag to the command, which would try a
@@ -298,15 +423,6 @@ $ edit frotz
$ git add frotz
------------
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index ca485db..6c9c2cb 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -7,6 +7,7 @@ git-cherry-pick - Apply the changes introduced by some existing commits
SYNOPSIS
--------
+[verse]
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
DESCRIPTION
@@ -16,12 +17,31 @@ Given one or more existing commits, apply the change each one
introduces, recording a new commit for each. This requires your
working tree to be clean (no modifications from the HEAD commit).
+When it is not obvious how to apply a change, the following
+happens:
+
+1. The current branch and `HEAD` pointer stay at the last commit
+ successfully made.
+2. The `CHERRY_PICK_HEAD` ref is set to point at the commit that
+ introduced the change that is difficult to apply.
+3. Paths in which the change applied cleanly are updated both
+ in the index file and in your working tree.
+4. For conflicting paths, the index file records up to three
+ versions, as described in the "TRUE MERGE" section of
+ linkgit:git-merge[1]. The working tree files will include
+ a description of the conflict bracketed by the usual
+ conflict markers `<<<<<<<` and `>>>>>>>`.
+5. No other modifications are made.
+
+See linkgit:git-merge[1] for some hints on resolving such
+conflicts.
+
OPTIONS
-------
<commit>...::
Commits to cherry-pick.
- For a more complete list of ways to spell commits, see the
- "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+ For a more complete list of ways to spell commits, see
+ linkgit:gitrevisions[7].
Sets of commits can be passed but no traversal is done by
default, as if the '--no-walk' option was specified, see
linkgit:git-rev-list[1].
@@ -32,9 +52,10 @@ OPTIONS
message prior to committing.
-x::
- When recording the commit, append to the original commit
- message a note that indicates which commit this change
- was cherry-picked from. Append the note only for cherry
+ When recording the commit, append a line that says
+ "(cherry picked from commit ...)" to the original commit
+ message in order to indicate which commit this change was
+ cherry-picked from. This is done only for cherry
picks without conflicts. Do not use this option if
you are cherry-picking from your private branch because
the information is useless to the recipient. If on the
@@ -79,6 +100,16 @@ effect to your index in a row.
cherry-pick'ed commit, then a fast forward to this commit will
be performed.
+--strategy=<strategy>::
+ Use the given merge strategy. Should only be used once.
+ See the MERGE STRATEGIES section in linkgit:git-merge[1]
+ for details.
+
+-X<option>::
+--strategy-option=<option>::
+ Pass the merge strategy-specific option through to the
+ merge strategy. See linkgit:git-merge[1] for details.
+
EXAMPLES
--------
git cherry-pick master::
@@ -92,7 +123,7 @@ git cherry-pick ^HEAD master::
Apply the changes introduced by all commits that are ancestors
of master but not of HEAD to produce new commits.
-git cherry-pick master\~4 master~2::
+git cherry-pick master{tilde}4 master{tilde}2::
Apply the changes introduced by the fifth and third last
commits pointed to by master and create 2 new commits with
@@ -120,13 +151,27 @@ git rev-list --reverse master \-- README | git cherry-pick -n --stdin::
so the result can be inspected and made into a single new
commit if suitable.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+The following sequence attempts to backport a patch, bails out because
+the code the patch applies to has changed too much, and then tries
+again, this time exercising more care about matching up context lines.
+
+------------
+$ git cherry-pick topic^ <1>
+$ git diff <2>
+$ git reset --merge ORIG_HEAD <3>
+$ git cherry-pick -Xpatience topic^ <4>
+------------
+<1> apply the change that would be shown by `git show topic^`.
+In this example, the patch does not apply cleanly, so
+information about the conflict is written to the index and
+working tree and no new commit results.
+<2> summarize changes to be reconciled
+<3> cancel the cherry-pick. In other words, return to the
+pre-cherry-pick state, preserving any local modifications you had in
+the working tree.
+<4> try to apply the change introduced by `topic^` again,
+spending extra time to avoid mistakes based on incorrectly matching
+context lines.
SEE ALSO
--------
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
index fed115a..f6c19c7 100644
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -7,6 +7,7 @@ git-cherry - Find commits not merged upstream
SYNOPSIS
--------
+[verse]
'git cherry' [-v] [<upstream> [<head> [<limit>]]]
DESCRIPTION
@@ -63,14 +64,6 @@ SEE ALSO
--------
linkgit:git-patch-id[1]
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt
index fb2753c..c7a11c3 100644
--- a/Documentation/git-citool.txt
+++ b/Documentation/git-citool.txt
@@ -7,6 +7,7 @@ git-citool - Graphical alternative to git-commit
SYNOPSIS
--------
+[verse]
'git citool'
DESCRIPTION
@@ -19,14 +20,6 @@ to the less interactive 'git commit' program.
'git citool' is actually a standard alias for `git gui citool`.
See linkgit:git-gui[1] for more details.
-Author
-------
-Written by Shawn O. Pearce <spearce@spearce.org>.
-
-Documentation
---------------
-Documentation by Shawn O. Pearce <spearce@spearce.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index a81cb6c..974e04e 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
SYNOPSIS
--------
[verse]
-'git clean' [-d] [-f] [-n] [-q] [-x | -X] [--] <path>...
+'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
DESCRIPTION
-----------
@@ -45,6 +45,12 @@ OPTIONS
Be quiet, only report errors, but not the files that are
successfully removed.
+-e <pattern>::
+--exclude=<pattern>::
+ Specify special exceptions to not be cleaned. Each <pattern> is
+ the same form as in $GIT_DIR/info/excludes and this option can be
+ given multiple times.
+
-x::
Don't use the ignore rules. This allows removing all untracked
files, including build products. This can be used (possibly in
@@ -55,12 +61,6 @@ OPTIONS
Remove only files ignored by git. This may be useful to rebuild
everything from scratch, but keep manually created files.
-
-Author
-------
-Written by Pavel Roskin <proski@gnu.org>
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index dc7d3d1..4b8b26b 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -12,7 +12,9 @@ SYNOPSIS
'git clone' [--template=<template_directory>]
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
- [--depth <depth>] [--recursive] [--] <repository> [<directory>]
+ [--separate-git-dir <git dir>]
+ [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+ [<directory>]
DESCRIPTION
-----------
@@ -128,7 +130,12 @@ objects from the source repository into a pack in the cloned repository.
configuration variables are created.
--mirror::
- Set up a mirror of the remote repository. This implies `--bare`.
+ Set up a mirror of the source repository. This implies `--bare`.
+ Compared to `--bare`, `--mirror` not only maps local branches of the
+ source to local branches of the target, it maps all refs (including
+ remote-tracking branches, notes etc.) and sets up a refspec configuration such
+ that all these refs are overwritten by a `git remote update` in the
+ target repository.
--origin <name>::
-o <name>::
@@ -152,6 +159,17 @@ objects from the source repository into a pack in the cloned repository.
Specify the directory from which templates will be used;
(See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+--config <key>=<value>::
+-c <key>=<value>::
+ Set a configuration variable in the newly-created repository;
+ this takes effect immediately after the repository is
+ initialized, but before the remote history is fetched or any
+ files checked out. The key is in the same format as expected by
+ linkgit:git-config[1] (e.g., `core.eol=true`). If multiple
+ values are given for the same key, each value will be written to
+ the config file. This makes it safe, for example, to add
+ additional fetch refspecs to the origin remote.
+
--depth <depth>::
Create a 'shallow' clone with a history truncated to the
specified number of revisions. A shallow repository has a
@@ -162,6 +180,7 @@ objects from the source repository into a pack in the cloned repository.
as patches.
--recursive::
+--recurse-submodules::
After the clone is created, initialize all submodules within,
using their default settings. This is equivalent to running
`git submodule update --init --recursive` immediately after
@@ -169,6 +188,14 @@ objects from the source repository into a pack in the cloned repository.
repository does not have a worktree/checkout (i.e. if any of
`--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
+--separate-git-dir=<git dir>::
+ Instead of placing the cloned repository where it is supposed
+ to be, place the cloned repository at the specified directory,
+ then make a filesytem-agnostic git symbolic link to there.
+ The result is git repository can be separated from working
+ tree.
+
+
<repository>::
The (possibly remote) repository to clone from. See the
<<URLS,URLS>> section below for more information on specifying
@@ -229,17 +256,6 @@ $ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \
/pub/scm/.../me/subsys-2.6.git
------------
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
index 6188854..0fdb82e 100644
--- a/Documentation/git-commit-tree.txt
+++ b/Documentation/git-commit-tree.txt
@@ -8,7 +8,8 @@ git-commit-tree - Create a new commit object
SYNOPSIS
--------
-'git commit-tree' <tree> [-p <parent commit>]\* < changelog
+[verse]
+'git commit-tree' <tree> [(-p <parent commit>)...] < changelog
DESCRIPTION
-----------
@@ -93,15 +94,6 @@ SEE ALSO
--------
linkgit:git-write-tree[1]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index c28603e..5cc84a1 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -8,11 +8,12 @@ git-commit - Record changes to the repository
SYNOPSIS
--------
[verse]
-'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
- [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
- [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
- [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--]
- [[-i | -o ]<file>...]
+'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
+ [--dry-run] [(-c | -C | --fixup | --squash) <commit>]
+ [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
+ [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
+ [--date=<date>] [--cleanup=<mode>] [--status | --no-status]
+ [-i | -o] [--] [<file>...]
DESCRIPTION
-----------
@@ -39,9 +40,10 @@ The content to be added can be specified in several ways:
that have been removed from the working tree, and then perform the
actual commit;
-5. by using the --interactive switch with the 'commit' command to decide one
- by one which files should be part of the commit, before finalizing the
- operation. Currently, this is done by invoking 'git add --interactive'.
+5. by using the --interactive or --patch switches with the 'commit' command
+ to decide one by one which files or hunks should be part of the commit,
+ before finalizing the operation. See the ``Interactive Mode`` section of
+ linkgit:git-add[1] to learn how to operate these modes.
The `--dry-run` option can be used to obtain a
summary of what is included by any of the above for the next
@@ -59,6 +61,12 @@ OPTIONS
been modified and deleted, but new files you have not
told git about are not affected.
+-p::
+--patch::
+ Use the interactive patch selection interface to chose
+ which changes to commit. See linkgit:git-add[1] for
+ details.
+
-C <commit>::
--reuse-message=<commit>::
Take an existing commit object, and reuse the log message
@@ -70,10 +78,24 @@ OPTIONS
Like '-C', but with '-c' the editor is invoked, so that
the user can further edit the commit message.
+--fixup=<commit>::
+ Construct a commit message for use with `rebase --autosquash`.
+ The commit message will be the subject line from the specified
+ commit with a prefix of "fixup! ". See linkgit:git-rebase[1]
+ for details.
+
+--squash=<commit>::
+ Construct a commit message for use with `rebase --autosquash`.
+ The commit message subject line is taken from the specified
+ commit with a prefix of "squash! ". Can be used with additional
+ commit message options (`-m`/`-c`/`-C`/`-F`). See
+ linkgit:git-rebase[1] for details.
+
--reset-author::
- When used with -C/-c/--amend options, declare that the
- authorship of the resulting commit now belongs of the committer.
- This also renews the author timestamp.
+ When used with -C/-c/--amend options, or when committing after a
+ a conflicting cherry-pick, declare that the authorship of the
+ resulting commit now belongs of the committer. This also renews
+ the author timestamp.
--short::
When doing a dry-run, give the output in the short-format. See
@@ -130,11 +152,11 @@ OPTIONS
Usually recording a commit that has the exact same tree as its
sole parent commit is a mistake, and the command prevents you
from making such a commit. This option bypasses the safety, and
- is primarily for use by foreign scm interface scripts.
+ is primarily for use by foreign SCM interface scripts.
--allow-empty-message::
Like --allow-empty this command is primarily for use by foreign
- scm interface scripts. It allows you to create a commit with an
+ SCM interface scripts. It allows you to create a commit with an
empty commit message without using plumbing commands like
linkgit:git-commit-tree[1].
@@ -201,10 +223,11 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
-u[<mode>]::
--untracked-files[=<mode>]::
- Show untracked files (Default: 'all').
+ Show untracked files.
+
-The mode parameter is optional, and is used to specify
-the handling of untracked files.
+The mode parameter is optional (defaults to 'all'), and is used to
+specify the handling of untracked files; when -u is not used, the
+default is 'normal', i.e. show untracked files and directories.
+
The possible options are:
+
@@ -212,9 +235,8 @@ The possible options are:
- 'normal' - Shows untracked files and directories
- 'all' - Also shows individual files in untracked directories.
+
-See linkgit:git-config[1] for configuration variable
-used to change the default for when the option is not
-specified.
+The default can be changed using the status.showUntrackedFiles
+configuration variable documented in linkgit:git-config[1].
-v::
--verbose::
@@ -262,7 +284,7 @@ When recording your own work, the contents of modified files in
your working tree are temporarily stored to a staging area
called the "index" with 'git add'. A file can be
reverted back, only in the index but not in the working tree,
-to that of the last commit with `git reset HEAD -- <file>`,
+to that of the last commit with `git reset HEAD \-- <file>`,
which effectively reverts 'git add' and prevents the changes to
this file from participating in the next commit. After building
the state to be committed incrementally with these commands,
@@ -383,12 +405,6 @@ linkgit:git-mv[1],
linkgit:git-merge[1],
linkgit:git-commit-tree[1]
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 543dd64..e7ecf5d 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -50,16 +50,18 @@ The default is to assume the config file of the current repository,
.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG
(see <<FILES>>).
-This command will fail if:
+This command will fail (with exit code ret) if:
-. The config file is invalid,
-. Can not write to the config file,
-. no section was provided,
-. the section or key is invalid,
-. you try to unset an option which does not exist,
-. you try to unset/set an option for which multiple lines match, or
-. you use '--global' option without $HOME being properly set.
+. The config file is invalid (ret=3),
+. can not write to the config file (ret=4),
+. no section or name was provided (ret=2),
+. the section or key is invalid (ret=1),
+. you try to unset an option which does not exist (ret=5),
+. you try to unset/set an option for which multiple lines match (ret=5),
+. you try to use an invalid regexp (ret=6), or
+. you use '--global' option without $HOME being properly set (ret=128).
+On success, the command returns the exit code 0.
OPTIONS
-------
@@ -336,15 +338,6 @@ echo "${WS}your whitespace color or blue reverse${RESET}"
include::config.txt[]
-
-Author
-------
-Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
-
-Documentation
---------------
-Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt
index 6bc1c21..23c80ce 100644
--- a/Documentation/git-count-objects.txt
+++ b/Documentation/git-count-objects.txt
@@ -7,6 +7,7 @@ git-count-objects - Count unpacked number of objects and their disk consumption
SYNOPSIS
--------
+[verse]
'git count-objects' [-v]
DESCRIPTION
@@ -25,15 +26,6 @@ OPTIONS
and number of objects that can be removed by running
`git prune-packed`.
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt
index b2696ef..7f79cec 100644
--- a/Documentation/git-cvsexportcommit.txt
+++ b/Documentation/git-cvsexportcommit.txt
@@ -8,6 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout
SYNOPSIS
--------
+[verse]
'git cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot]
[-w cvsworkdir] [-W] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
@@ -112,14 +113,6 @@ $ cd ~/project_cvs_checkout
$ git cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git cvsexportcommit -c -p -v
------------
-Author
-------
-Written by Martin Langhoff <martin@catalyst.net.nz> and others.
-
-Documentation
---------------
-Documentation by Martin Langhoff <martin@catalyst.net.nz> and others.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
index 8bcd875..6695ab3 100644
--- a/Documentation/git-cvsimport.txt
+++ b/Documentation/git-cvsimport.txt
@@ -188,7 +188,7 @@ ISSUES
------
Problems related to timestamps:
- * If timestamps of commits in the cvs repository are not stable enough
+ * If timestamps of commits in the CVS repository are not stable enough
to be used for ordering commits changes may show up in the wrong
order.
* If any files were ever "cvs import"ed more than once (e.g., import of
@@ -201,7 +201,7 @@ Problems related to branches:
* Branches on which no commits have been made are not imported.
* All files from the branching point are added to a branch even if
- never added in cvs.
+ never added in CVS.
* This applies to files added to the source branch *after* a daughter
branch was created: if previously no commit was made on the daughter
branch they will erroneously be added to the daughter branch in git.
@@ -217,15 +217,6 @@ more stable in practice:
* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org`
* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs`
-Author
-------
-Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
-various participants of the git-list <git@vger.kernel.org>.
-
-Documentation
---------------
-Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 7004dd2..827bc98 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -252,7 +252,7 @@ Configuring database backend
'git-cvsserver' uses the Perl DBI module. Please also read
its documentation if changing these variables, especially
-about `DBI->connect()`.
+about `DBI\->connect()`.
gitcvs.dbname::
Database name. The exact meaning depends on the
@@ -366,8 +366,8 @@ CRLF Line Ending Conversions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default the server leaves the '-k' mode blank for all files,
-which causes the cvs client to treat them as a text files, subject
-to crlf conversion on some platforms.
+which causes the CVS client to treat them as a text files, subject
+to end-of-line conversion on some platforms.
You can make the server use the end-of-line conversion attributes to
set the '-k' modes for files by setting the `gitcvs.usecrlfattr`
@@ -391,22 +391,6 @@ Dependencies
------------
'git-cvsserver' depends on DBD::SQLite.
-Copyright and Authors
----------------------
-
-This program is copyright The Open University UK - 2006.
-
-Authors:
-
-- Martyn Smith <martyn@catalyst.net.nz>
-- Martin Langhoff <martin@catalyst.net.nz>
-
-with ideas and patches from participants of the git-list <git@vger.kernel.org>.
-
-Documentation
---------------
-Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@catalyst.net.nz>, and Matthias Urlichs <smurf@smurf.noris.de>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 01c9f8e..ebd13be 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -9,15 +9,15 @@ SYNOPSIS
--------
[verse]
'git daemon' [--verbose] [--syslog] [--export-all]
- [--timeout=n] [--init-timeout=n] [--max-connections=n]
- [--strict-paths] [--base-path=path] [--base-path-relaxed]
- [--user-path | --user-path=path]
- [--interpolated-path=pathtemplate]
- [--reuseaddr] [--detach] [--pid-file=file]
- [--enable=service] [--disable=service]
- [--allow-override=service] [--forbid-override=service]
- [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]]
- [directory...]
+ [--timeout=<n>] [--init-timeout=<n>] [--max-connections=<n>]
+ [--strict-paths] [--base-path=<path>] [--base-path-relaxed]
+ [--user-path | --user-path=<path>]
+ [--interpolated-path=<pathtemplate>]
+ [--reuseaddr] [--detach] [--pid-file=<file>]
+ [--enable=<service>] [--disable=<service>]
+ [--allow-override=<service>] [--forbid-override=<service>]
+ [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
+ [<directory>...]
DESCRIPTION
-----------
@@ -48,7 +48,7 @@ OPTIONS
'git daemon' will refuse to start when this option is enabled and no
whitelist is specified.
---base-path=path::
+--base-path=<path>::
Remap all the path requests as relative to the given path.
This is sort of "GIT root" - if you run 'git daemon' with
'--base-path=/srv/git' on example.com, then if you later try to pull
@@ -61,7 +61,7 @@ OPTIONS
This is useful for switching to --base-path usage, while still
allowing the old paths.
---interpolated-path=pathtemplate::
+--interpolated-path=<pathtemplate>::
To support virtual hosting, an interpolated path template can be
used to dynamically construct alternate paths. The template
supports %H for the target hostname as supplied by the client but
@@ -78,29 +78,31 @@ OPTIONS
--inetd::
Have the server run as an inetd service. Implies --syslog.
- Incompatible with --port, --listen, --user and --group options.
+ Incompatible with --detach, --port, --listen, --user and --group
+ options.
---listen=host_or_ipaddr::
+--listen=<host_or_ipaddr>::
Listen on a specific IP address or hostname. IP addresses can
be either an IPv4 address or an IPv6 address if supported. If IPv6
is not supported, then --listen=hostname is also not supported and
--listen must be given an IPv4 address.
+ Can be given more than once.
Incompatible with '--inetd' option.
---port=n::
+--port=<n>::
Listen on an alternative port. Incompatible with '--inetd' option.
---init-timeout=n::
+--init-timeout=<n>::
Timeout between the moment the connection is established and the
client request is received (typically a rather low value, since
that should be basically immediate).
---timeout=n::
+--timeout=<n>::
Timeout for specific client sub-requests. This includes the time
it takes for the server to process the sub-request and the time spent
waiting for the next client's request.
---max-connections=n::
+--max-connections=<n>::
Maximum number of concurrent clients, defaults to 32. Set it to
zero for no limit.
@@ -109,7 +111,7 @@ OPTIONS
--verbose, thus by default only error conditions will be logged.
--user-path::
---user-path=path::
+--user-path=<path>::
Allow {tilde}user notation to be used in requests. When
specified with no parameter, requests to
git://host/{tilde}alice/foo is taken as a request to access
@@ -129,12 +131,12 @@ OPTIONS
--detach::
Detach from the shell. Implies --syslog.
---pid-file=file::
+--pid-file=<file>::
Save the process id in 'file'. Ignored when the daemon
is run under `--inetd`.
---user=user::
---group=group::
+--user=<user>::
+--group=<group>::
Change daemon's uid and gid before entering the service loop.
When only `--user` is given without `--group`, the
primary group ID for the user is used. The values of
@@ -145,16 +147,16 @@ Giving these options is an error when used with `--inetd`; use
the facility of inet daemon to achieve the same before spawning
'git daemon' if needed.
---enable=service::
---disable=service::
+--enable=<service>::
+--disable=<service>::
Enable/disable the service site-wide per default. Note
that a service disabled site-wide can still be enabled
per repository if it is marked overridable and the
repository enables the service with a configuration
item.
---allow-override=service::
---forbid-override=service::
+--allow-override=<service>::
+--forbid-override=<service>::
Allow/forbid overriding the site-wide default with per
repository configuration. By default, all the services
are overridable.
@@ -277,17 +279,6 @@ that connected to it, if the IP address is available. REMOTE_ADDR will
be available in the environment of hooks called when
services are performed.
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
-<yoshfuji@linux-ipv6.org> and the git-list <git@vger.kernel.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index 7ef9d51..039cce2 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -37,7 +37,7 @@ OPTIONS
--all::
Instead of using only the annotated tags, use any ref
found in `.git/refs/`. This option enables matching
- any known branch, remote branch, or lightweight tag.
+ any known branch, remote-tracking branch, or lightweight tag.
--tags::
Instead of using only the annotated tags, use any tag
@@ -156,17 +156,6 @@ selected and output. Here fewest commits different is defined as
the number of commits which would be shown by `git log tag..input`
will be the smallest number of commits possible.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>, but somewhat
-butchered by Junio C Hamano <gitster@pobox.com>. Later significantly
-updated by Shawn Pearce <spearce@spearce.org>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt
index 9cd8cce..906774f 100644
--- a/Documentation/git-diff-files.txt
+++ b/Documentation/git-diff-files.txt
@@ -8,6 +8,7 @@ git-diff-files - Compares files in the working tree and the index
SYNOPSIS
--------
+[verse]
'git diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...]
DESCRIPTION
@@ -46,15 +47,6 @@ omit diff output for unmerged entries and just show "Unmerged".
include::diff-format.txt[]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
index 162cb74..c0b7c58 100644
--- a/Documentation/git-diff-index.txt
+++ b/Documentation/git-diff-index.txt
@@ -8,6 +8,7 @@ git-diff-index - Compares content and mode of blobs between the index and reposi
SYNOPSIS
--------
+[verse]
'git diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
DESCRIPTION
@@ -96,8 +97,8 @@ show that. So let's say that you have edited `kernel/sched.c`, but
have not actually done a 'git update-index' on it yet - there is no
"object" associated with the new state, and you get:
- torvalds@ppc970:~/v2.6/linux> git diff-index HEAD
- *100644->100664 blob 7476bb......->000000...... kernel/sched.c
+ torvalds@ppc970:~/v2.6/linux> git diff-index --abbrev HEAD
+ :100644 100664 7476bb... 000000... kernel/sched.c
i.e., it shows that the tree has changed, and that `kernel/sched.c` has is
not up-to-date and may contain new stuff. The all-zero sha1 means that to
@@ -116,15 +117,6 @@ tell which file is in which state, since the "has been updated" ones
show a valid sha1, and the "not in sync with the index" ones will
always have the special all-zero sha1.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
index a7e37b8..1439486 100644
--- a/Documentation/git-diff-tree.txt
+++ b/Documentation/git-diff-tree.txt
@@ -138,8 +138,8 @@ so it can be used to name subdirectories.
An example of normal usage is:
- torvalds@ppc970:~/git> git diff-tree 5319e4......
- *100664->100664 blob ac348b.......->a01513....... git-fsck-objects.c
+ torvalds@ppc970:~/git> git diff-tree --abbrev 5319e4
+ :100664 100664 ac348b... a01513... git-fsck-objects.c
which tells you that the last commit changed just one file (it's from
this one:
@@ -162,15 +162,6 @@ in case you care).
include::diff-format.txt[]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index 723a648..f8d0819 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -8,12 +8,17 @@ git-diff - Show changes between commits, commit and working tree, etc
SYNOPSIS
--------
-'git diff' [<common diff options>] <commit>{0,2} [--] [<path>...]
+[verse]
+'git diff' [options] [<commit>] [--] [<path>...]
+'git diff' [options] --cached [<commit>] [--] [<path>...]
+'git diff' [options] <commit> <commit> [--] [<path>...]
+'git diff' [options] [--no-index] [--] <path> <path>
DESCRIPTION
-----------
-Show changes between two trees, a tree and the working tree, a
-tree and the index file, or the index file and the working tree.
+Show changes between the working tree and the index or a tree, changes
+between the index and a tree, changes between two trees, or changes
+between two files on disk.
'git diff' [--options] [--] [<path>...]::
@@ -23,9 +28,9 @@ tree and the index file, or the index file and the working tree.
further add to the index but you still haven't. You can
stage these changes by using linkgit:git-add[1].
+
-If exactly two paths are given, and at least one is untracked,
-compare the two files / directories. This behavior can be
-forced by --no-index.
+If exactly two paths are given and at least one points outside
+the current repository, 'git diff' will compare the two files /
+directories. This behavior can be forced by --no-index.
'git diff' [--options] --cached [<commit>] [--] [<path>...]::
@@ -33,6 +38,8 @@ forced by --no-index.
commit relative to the named <commit>. Typically you
would want comparison with the latest commit, so if you
do not give <commit>, it defaults to HEAD.
+ If HEAD does not exist (e.g. unborned branches) and
+ <commit> is not given, it shows all staged changes.
--staged is a synonym of --cached.
'git diff' [--options] <commit> [--] [<path>...]::
@@ -64,15 +71,16 @@ forced by --no-index.
Just in case if you are doing something exotic, it should be
noted that all of the <commit> in the above description, except
-for the last two forms that use ".." notations, can be any
-<tree-ish>.
+in the last two forms that use ".." notations, can be any
+<tree>. The third form ('git diff <commit> <commit>') can also
+be used to compare two <blob> objects.
For a more complete list of ways to spell <commit>, see
-"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
However, "diff" is about comparing two _endpoints_, not ranges,
and the range notations ("<commit>..<commit>" and
"<commit>\...<commit>") do not mean a range as defined in the
-"SPECIFYING RANGES" section in linkgit:git-rev-parse[1].
+"SPECIFYING RANGES" section in linkgit:gitrevisions[7].
OPTIONS
-------
@@ -159,16 +167,12 @@ rewrites (very expensive).
SEE ALSO
--------
-linkgit:git-difftool[1]::
- Show changes using common diff tools
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+diff(1),
+linkgit:git-difftool[1],
+linkgit:git-log[1],
+linkgit:gitdiffcore[7],
+linkgit:git-format-patch[1],
+linkgit:git-apply[1]
GIT
---
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index 8250bad..a03515f 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -7,13 +7,15 @@ git-difftool - Show changes using common diff tools
SYNOPSIS
--------
-'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
+[verse]
+'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...]
DESCRIPTION
-----------
'git difftool' is a git command that allows you to compare and edit files
between revisions using common diff tools. 'git difftool' is a frontend
-to 'git diff' and accepts the same options and arguments.
+to 'git diff' and accepts the same options and arguments. See
+linkgit:git-diff[1].
OPTIONS
-------
@@ -30,8 +32,8 @@ OPTIONS
--tool=<tool>::
Use the diff tool specified by <tool>.
Valid merge tools are:
- kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
- ecmerge, diffuse, opendiff, p4merge and araxis.
+ araxis, bc3, diffuse, emerge, ecmerge, gvimdiff, kdiff3,
+ kompare, meld, opendiff, p4merge, tkdiff, vimdiff and xxdiff.
+
If a diff tool is not specified, 'git difftool'
will use the configuration variable `diff.tool`. If the
@@ -55,14 +57,16 @@ the configured command line will be invoked with the following
variables available: `$LOCAL` is set to the name of the temporary
file containing the contents of the diff pre-image and `$REMOTE`
is set to the name of the temporary file containing the contents
-of the diff post-image. `$BASE` is provided for compatibility
-with custom merge tool commands and has the same value as `$LOCAL`.
+of the diff post-image. `$MERGED` is the name of the file which is
+being compared. `$BASE` is provided for compatibility
+with custom merge tool commands and has the same value as `$MERGED`.
-x <command>::
--extcmd=<command>::
Specify a custom command for viewing diffs.
'git-difftool' ignores the configured defaults and runs
`$command $LOCAL $REMOTE` when this option is specified.
+ Additionally, `$BASE` is set in the environment.
-g::
--gui::
@@ -106,15 +110,6 @@ linkgit:git-mergetool[1]::
linkgit:git-config[1]::
Get and set repository or global options
-
-AUTHOR
-------
-Written by David Aguilar <davvid@gmail.com>.
-
-Documentation
---------------
-Documentation by David Aguilar and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt
index 98ec6b5..a29ac02 100644
--- a/Documentation/git-fast-export.txt
+++ b/Documentation/git-fast-export.txt
@@ -8,6 +8,7 @@ git-fast-export - Git data exporter
SYNOPSIS
--------
+[verse]
'git fast-export [options]' | 'git fast-import'
DESCRIPTION
@@ -90,10 +91,16 @@ marks the same across runs.
resulting stream can only be used by a repository which
already contains the necessary objects.
-[git-rev-list-args...]::
+--full-tree::
+ This option will cause fast-export to issue a "deleteall"
+ directive for each commit followed by a full list of all files
+ in the commit (as opposed to just listing the files which are
+ different from the commit's first parent).
+
+[<git-rev-list-args>...]::
A list of arguments, acceptable to 'git rev-parse' and
'git rev-list', that specifies the specific objects and references
- to export. For example, `master\~10..master` causes the
+ to export. For example, `master{tilde}10..master` causes the
current master reference to be exported along with all objects
added since its 10th ancestor commit.
@@ -129,15 +136,6 @@ Since 'git fast-import' cannot tag trees, you will not be
able to export the linux-2.6.git repository completely, as it contains
a tag referencing a tree instead of a commit.
-
-Author
-------
-Written by Johannes E. Schindelin <johannes.schindelin@gmx.de>.
-
-Documentation
---------------
-Documentation by Johannes E. Schindelin <johannes.schindelin@gmx.de>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 19082b0..95e480e 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -8,6 +8,7 @@ git-fast-import - Backend for fast Git data importers
SYNOPSIS
--------
+[verse]
frontend | 'git fast-import' [options]
DESCRIPTION
@@ -78,8 +79,12 @@ OPTIONS
set of marks. If a mark is defined to different values,
the last file wins.
+--import-marks-if-exists=<file>::
+ Like --import-marks but instead of erroring out, silently
+ skips the file if it does not exist.
+
--relative-marks::
- After specifying --relative-marks= the paths specified
+ After specifying --relative-marks the paths specified
with --import-marks= and --export-marks= are relative
to an internal directory in the current repository.
In git-fast-import this means that the paths are relative
@@ -89,9 +94,14 @@ OPTIONS
--no-relative-marks::
Negates a previous --relative-marks. Allows for combining
relative and non-relative marks by interweaving
- --(no-)-relative-marks= with the --(import|export)-marks=
+ --(no-)-relative-marks with the --(import|export)-marks=
options.
+--cat-blob-fd=<fd>::
+ Specify the file descriptor that will be written to
+ when the `cat-blob` command is encountered in the stream.
+ The default behaviour is to write to `stdout`.
+
--export-pack-edges=<file>::
After creating a packfile, print a line of data to
<file> listing the filename of the packfile and the last
@@ -187,7 +197,8 @@ especially when a higher level language such as Perl, Python or
Ruby is being used.
fast-import is very strict about its input. Where we say SP below we mean
-*exactly* one space. Likewise LF means one (and only one) linefeed.
+*exactly* one space. Likewise LF means one (and only one) linefeed
+and HT one (and only one) horizontal tab.
Supplying additional whitespace characters will cause unexpected
results, such as branch names or file names with leading or trailing
spaces in their name, or early termination of fast-import when it encounters
@@ -320,6 +331,16 @@ and control the current import process. More detailed discussion
standard output. This command is optional and is not needed
to perform an import.
+`cat-blob`::
+ Causes fast-import to print a blob in 'cat-file --batch'
+ format to the file descriptor set with `--cat-blob-fd` or
+ `stdout` if unspecified.
+
+`ls`::
+ Causes fast-import to print a line describing a directory
+ entry in 'ls-tree' format to the file descriptor set with
+ `--cat-blob-fd` or `stdout` if unspecified.
+
`feature`::
Require that fast-import supports the specified feature, or
abort if it does not.
@@ -439,7 +460,7 @@ Marks must be declared (via `mark`) before they can be used.
* A complete 40 byte or abbreviated commit SHA-1 in hex.
* Any valid Git SHA-1 expression that resolves to a commit. See
- ``SPECIFYING REVISIONS'' in linkgit:git-rev-parse[1] for details.
+ ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[7] for details.
The special case of restarting an incremental import from the
current branch value should be written as:
@@ -482,9 +503,11 @@ External data format::
'M' SP <mode> SP <dataref> SP <path> LF
....
+
-Here `<dataref>` can be either a mark reference (`:<idnum>`)
+Here usually `<dataref>` must be either a mark reference (`:<idnum>`)
set by a prior `blob` command, or a full 40-byte SHA-1 of an
-existing Git blob object.
+existing Git blob object. If `<mode>` is `040000`` then
+`<dataref>` must be the full 40-byte SHA-1 of an existing
+Git tree object or a mark reference set with `--import-marks`.
Inline data format::
The data content for the file has not been supplied yet.
@@ -509,6 +532,8 @@ in octal. Git only supports the following modes:
* `160000`: A gitlink, SHA-1 of the object refers to a commit in
another repository. Git links can only be specified by SHA or through
a commit mark. They are used to implement submodules.
+* `040000`: A subdirectory. Subdirectories can only be specified by
+ SHA or through a tree mark set with `--import-marks`.
In both formats `<path>` is the complete path of the file to be added
(if not already existing) or modified (if already existing).
@@ -528,6 +553,8 @@ The value of `<path>` must be in canonical form. That is it must not:
* contain the special component `.` or `..` (e.g. `foo/./bar` and
`foo/../bar` are invalid).
+The root of the tree can be represented by an empty string as `<path>`.
+
It is recommended that `<path>` always be encoded using UTF-8.
`filedelete`
@@ -622,9 +649,14 @@ paths for a commit are encouraged to do so.
`notemodify`
^^^^^^^^^^^^
-Included in a `commit` command to add a new note (annotating a given
-commit) or change the content of an existing note. This command has
-two different means of specifying the content of the note.
+Included in a `commit` `<notes_ref>` command to add a new note
+annotating a `<committish>` or change this annotation contents.
+Internally it is similar to filemodify 100644 on `<committish>`
+path (maybe split into subdirectories). It's not advised to
+use any other commands to write to the `<notes_ref>` tree except
+`filedeleteall` to delete all existing notes in this tree.
+This command has two different means of specifying the content
+of the note.
External data format::
The data content for the note was already supplied by a prior
@@ -872,34 +904,123 @@ Placing a `progress` command immediately after a `checkpoint` will
inform the reader when the `checkpoint` has been completed and it
can safely access the refs that fast-import updated.
+`cat-blob`
+~~~~~~~~~~
+Causes fast-import to print a blob to a file descriptor previously
+arranged with the `--cat-blob-fd` argument. The command otherwise
+has no impact on the current import; its main purpose is to
+retrieve blobs that may be in fast-import's memory but not
+accessible from the target repository.
+
+....
+ 'cat-blob' SP <dataref> LF
+....
+
+The `<dataref>` can be either a mark reference (`:<idnum>`)
+set previously or a full 40-byte SHA-1 of a Git blob, preexisting or
+ready to be written.
+
+Output uses the same format as `git cat-file --batch`:
+
+====
+ <sha1> SP 'blob' SP <size> LF
+ <contents> LF
+====
+
+This command can be used anywhere in the stream that comments are
+accepted. In particular, the `cat-blob` command can be used in the
+middle of a commit but not in the middle of a `data` command.
+
+`ls`
+~~~~
+Prints information about the object at a path to a file descriptor
+previously arranged with the `--cat-blob-fd` argument. This allows
+printing a blob from the active commit (with `cat-blob`) or copying a
+blob or tree from a previous commit for use in the current one (with
+`filemodify`).
+
+The `ls` command can be used anywhere in the stream that comments are
+accepted, including the middle of a commit.
+
+Reading from the active commit::
+ This form can only be used in the middle of a `commit`.
+ The path names a directory entry within fast-import's
+ active commit. The path must be quoted in this case.
++
+....
+ 'ls' SP <path> LF
+....
+
+Reading from a named tree::
+ The `<dataref>` can be a mark reference (`:<idnum>`) or the
+ full 40-byte SHA-1 of a Git tag, commit, or tree object,
+ preexisting or waiting to be written.
+ The path is relative to the top level of the tree
+ named by `<dataref>`.
++
+....
+ 'ls' SP <dataref> SP <path> LF
+....
+
+See `filemodify` above for a detailed description of `<path>`.
+
+Output uses the same format as `git ls-tree <tree> {litdd} <path>`:
+
+====
+ <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
+====
+
+The <dataref> represents the blob, tree, or commit object at <path>
+and can be used in later 'cat-blob', 'filemodify', or 'ls' commands.
+
+If there is no file or subtree at that path, 'git fast-import' will
+instead report
+
+====
+ missing SP <path> LF
+====
+
`feature`
~~~~~~~~~
Require that fast-import supports the specified feature, or abort if
it does not.
....
- 'feature' SP <feature> LF
+ 'feature' SP <feature> ('=' <argument>)? LF
....
-The <feature> part of the command may be any string matching
-^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.
-
-Feature work identical as their option counterparts with the
-exception of the import-marks feature, see below.
+The <feature> part of the command may be any one of the following:
+
+date-format::
+export-marks::
+relative-marks::
+no-relative-marks::
+force::
+ Act as though the corresponding command-line option with
+ a leading '--' was passed on the command line
+ (see OPTIONS, above).
+
+import-marks::
+ Like --import-marks except in two respects: first, only one
+ "feature import-marks" command is allowed per stream;
+ second, an --import-marks= command-line option overrides
+ any "feature import-marks" command in the stream.
+
+cat-blob::
+ls::
+ Require that the backend support the 'cat-blob' or 'ls' command.
+ Versions of fast-import not supporting the specified command
+ will exit with a message indicating so.
+ This lets the import error out early with a clear message,
+ rather than wasting time on the early part of an import
+ before the unsupported command is detected.
+
+notes::
+ Require that the backend support the 'notemodify' (N)
+ subcommand to the 'commit' command.
+ Versions of fast-import not supporting notes will exit
+ with a message indicating so.
-The following features are currently supported:
-
-* date-format
-* import-marks
-* export-marks
-* relative-marks
-* no-relative-marks
-* force
-
-The import-marks behaves differently from when it is specified as
-commandline option in that only one "feature import-marks" is allowed
-per stream. Also, any --import-marks= specified on the commandline
-will override those from the stream (if any).
`option`
~~~~~~~~
@@ -926,6 +1047,7 @@ not be passed as option:
* date-format
* import-marks
* export-marks
+* cat-blob-fd
* force
Crash Reports
@@ -1226,14 +1348,13 @@ and lazy loading of subtrees, allows fast-import to efficiently import
projects with 2,000+ branches and 45,114+ files in a very limited
memory footprint (less than 2.7 MiB per active branch).
-
-Author
-------
-Written by Shawn O. Pearce <spearce@spearce.org>.
-
-Documentation
---------------
-Documentation by Shawn O. Pearce <spearce@spearce.org>.
+Signals
+-------
+Sending *SIGUSR1* to the 'git fast-import' process ends the current
+packfile early, simulating a `checkpoint` command. The impatient
+operator can use this facility to peek at the objects and refs from an
+import in progress, at the cost of some added running time and worse
+compression.
GIT
---
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index 4a8487c..ed1bdaa 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -8,6 +8,7 @@ git-fetch-pack - Receive missing objects from another repository
SYNOPSIS
--------
+[verse]
'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]
DESCRIPTION
@@ -90,15 +91,6 @@ OPTIONS
$GIT_DIR (e.g. "HEAD", "refs/heads/master"). When
unspecified, update from all heads the remote side has.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index 400fe7f..b41d7c1 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -8,12 +8,10 @@ git-fetch - Download objects and refs from another repository
SYNOPSIS
--------
+[verse]
'git fetch' [<options>] [<repository> [<refspec>...]]
-
'git fetch' [<options>] <group>
-
-'git fetch' --multiple [<options>] [<repository> | <group>]...
-
+'git fetch' --multiple [<options>] [(<repository> | <group>)...]
'git fetch' --all [<options>]
@@ -26,7 +24,7 @@ The ref names and their object names of fetched refs are stored
in `.git/FETCH_HEAD`. This information is left for a later merge
operation done by 'git merge'.
-When <refspec> stores the fetched result in tracking branches,
+When <refspec> stores the fetched result in remote-tracking branches,
the tags that point at these branches are automatically
followed. This is done by first fetching from the remote using
the given <refspec>s, and if the repository has objects that are
@@ -34,7 +32,7 @@ pointed by remote tags that it does not yet have, then fetch
those missing tags. If the other end has tags that point at
branches you are not interested in, you will not get them.
-'git fetch' can fetch from either a single named repository, or
+'git fetch' can fetch from either a single named repository,
or from several repositories at once if <group> is given and
there is a remotes.<group> entry in the configuration file.
(See linkgit:git-config[1]).
@@ -76,20 +74,19 @@ The `pu` branch will be updated even if it is does not fast-forward,
because it is prefixed with a plus sign; `tmp` will not be.
+BUGS
+----
+Using --recurse-submodules can only fetch new commits in already checked
+out submodules right now. When e.g. upstream added a new submodule in the
+just fetched commits of the superproject the submodule itself can not be
+fetched, making it impossible to check out that submodule later without
+having to do a fetch again. This is expected to be fixed in a future git
+version.
+
SEE ALSO
--------
linkgit:git-pull[1]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>
-
-Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 020028c..0f2f117 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -32,8 +32,9 @@ changes, which would normally have no effect. Nevertheless, this may be
useful in the future for compensating for some git bugs or such,
therefore such a usage is permitted.
-*NOTE*: This command honors `.git/info/grafts`. If you have any grafts
-defined, running this command will make them permanent.
+*NOTE*: This command honors `.git/info/grafts` and `.git/refs/replace/`.
+If you have any grafts or replacement refs defined, running this command
+will make them permanent.
*WARNING*! The rewritten history will have different object names for all
the objects and will not converge with the original branch. You will not
@@ -81,7 +82,7 @@ OPTIONS
This filter may be used if you only need to modify the environment
in which the commit will be performed. Specifically, you might
want to rewrite the author/committer name/email/time environment
- variables (see linkgit:git-commit[1] for details). Do not forget
+ variables (see linkgit:git-commit-tree[1] for details). Do not forget
to re-export the variables.
--tree-filter <command>::
@@ -117,7 +118,7 @@ OPTIONS
This is the filter for performing the commit.
If this filter is specified, it will be called instead of the
'git commit-tree' command, with arguments of the form
- "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
+ "<TREE_ID> [(-p <PARENT_COMMIT_ID>)...]" and the log message on
stdin. The commit id is expected on stdout.
+
As a special extension, the commit filter may emit multiple
@@ -159,18 +160,7 @@ to other tags will be rewritten to point to the underlying commit.
--subdirectory-filter <directory>::
Only look at the history which touches the given subdirectory.
The result will contain that directory (and only that) as its
- project root. Implies --remap-to-ancestor.
-
---remap-to-ancestor::
- Rewrite refs to the nearest rewritten ancestor instead of
- ignoring them.
-+
-Normally, positive refs on the command line are only changed if the
-commit they point to was rewritten. However, you can limit the extent
-of this rewriting by using linkgit:rev-list[1] arguments, e.g., path
-limiters. Refs pointing to such excluded commits would then normally
-be ignored. With this option, they are instead rewritten to point at
-the nearest ancestor that was not excluded.
+ project root. Implies <<Remap_to_ancestor>>.
--prune-empty::
Some kind of filters will generate empty commits, that left the tree
@@ -204,7 +194,18 @@ the nearest ancestor that was not excluded.
Arguments for 'git rev-list'. All positive refs included by
these options are rewritten. You may also specify options
such as '--all', but you must use '--' to separate them from
- the 'git filter-branch' options.
+ the 'git filter-branch' options. Implies <<Remap_to_ancestor>>.
+
+
+[[Remap_to_ancestor]]
+Remap to ancestor
+~~~~~~~~~~~~~~~~~
+
+By using linkgit:rev-list[1] arguments, e.g., path limiters, you can limit the
+set of revisions which get rewritten. However, positive refs on the command
+line are distinguished: we don't let them be excluded by such limiters. For
+this purpose, they are instead rewritten to point at the nearest ancestor that
+was not excluded.
Examples
@@ -361,7 +362,7 @@ git filter-branch --index-filter \
'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
- mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
+ mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD
---------------------------------------------------------------
@@ -405,16 +406,6 @@ warned.
(or if your git-gc is not new enough to support arguments to
`\--prune`, use `git repack -ad; git prune` instead).
-
-Author
-------
-Written by Petr "Pasky" Baudis <pasky@suse.cz>,
-and the git list <git@vger.kernel.org>
-
-Documentation
---------------
-Documentation by Petr Baudis and the git list.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
index a585dbe..32aff95 100644
--- a/Documentation/git-fmt-merge-msg.txt
+++ b/Documentation/git-fmt-merge-msg.txt
@@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message
SYNOPSIS
--------
[verse]
-'git fmt-merge-msg' [--log | --no-log] <$GIT_DIR/FETCH_HEAD
-'git fmt-merge-msg' [--log | --no-log] -F <file>
+'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] <$GIT_DIR/FETCH_HEAD
+'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] -F <file>
DESCRIPTION
-----------
@@ -24,10 +24,12 @@ automatically invoking 'git merge'.
OPTIONS
-------
---log::
+--log[=<n>]::
In addition to branch names, populate the log message with
one-line descriptions from the actual commits that are being
- merged.
+ merged. At most <n> commits from each merge parent will be
+ used (20 if <n> is omitted). This overrides the `merge.log`
+ configuration variable.
--no-log::
Do not list one-line descriptions from the actual commits being
@@ -38,6 +40,11 @@ OPTIONS
Synonyms to --log and --no-log; these are deprecated and will be
removed in the future.
+-m <message>::
+--message <message>::
+ Use <message> instead of the branch names for the first line
+ of the log message. For use with `--log`.
+
-F <file>::
--file <file>::
Take the list of merged objects from <file> instead of
@@ -47,8 +54,10 @@ CONFIGURATION
-------------
merge.log::
- Whether to include summaries of merged commits in newly
- merge commit messages. False by default.
+ In addition to branch names, populate the log message with at
+ most the specified number of one-line descriptions from the
+ actual commits that are being merged. Defaults to false, and
+ true is a synonym for 20.
merge.summary::
Synonym to `merge.log`; this is deprecated and will be removed in
@@ -58,15 +67,6 @@ SEE ALSO
--------
linkgit:git-merge[1]
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 390d85c..152e695 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
- [--sort=<key>]\* [--format=<format>] [<pattern>...]
+ [(--sort=<key>)...] [--format=<format>] [<pattern>...]
DESCRIPTION
-----------
@@ -123,7 +123,7 @@ EXAMPLES
--------
An example directly producing formatted text. Show the most recent
-3 tagged commits::
+3 tagged commits:
------------
#!/bin/sh
@@ -140,7 +140,7 @@ Ref: %(*refname)
A simple example showing the use of shell eval on the output,
-demonstrating the use of --shell. List the prefixes of all heads::
+demonstrating the use of --shell. List the prefixes of all heads:
------------
#!/bin/sh
@@ -154,7 +154,7 @@ done
A bit more elaborate report on tags, demonstrating that the format
-may be an entire script::
+may be an entire script:
------------
#!/bin/sh
@@ -204,3 +204,15 @@ eval=`git for-each-ref --shell --format="$fmt" \
refs/tags`
eval "$eval"
------------
+
+Author
+------
+Written by Junio C Hamano <gitster@pobox.com>.
+
+Documentation
+-------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index c8c81e8..d13c9b2 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -20,7 +20,7 @@ SYNOPSIS
[--ignore-if-in-upstream]
[--subject-prefix=Subject-Prefix]
[--to=<email>] [--cc=<email>]
- [--cover-letter]
+ [--cover-letter] [--quiet]
[<common diff options>]
[ <since> | <revision range> ]
@@ -39,7 +39,7 @@ There are two ways to specify which commits to operate on.
that leads to the <since> to be output.
2. Generic <revision range> expression (see "SPECIFYING
- REVISIONS" section in linkgit:git-rev-parse[1]) means the
+ REVISIONS" section in linkgit:gitrevisions[7]) means the
commits in the specified range.
The first rule takes precedence in the case of a single <commit>. To
@@ -74,7 +74,7 @@ OPTIONS
include::diff-options.txt[]
-<n>::
- Limits the number of patches to prepare.
+ Prepare patches from the topmost <n> commits.
-o <dir>::
--output-directory <dir>::
@@ -196,6 +196,9 @@ will want to ensure that threading is disabled for `git send-email`.
Note that the leading character does not have to be a dot; for example,
you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
+--quiet::
+ Do not print the names of the generated files to standard output.
+
--no-binary::
Do not output contents of changes in binary files, instead
display a notice that those files changed. Patches generated
@@ -229,6 +232,233 @@ attachments, and sign off patches with configuration variables.
------------
+DISCUSSION
+----------
+
+The patch produced by 'git format-patch' is in UNIX mailbox format,
+with a fixed "magic" time stamp to indicate that the file is output
+from format-patch rather than a real mailbox, like so:
+
+------------
+From 8f72bad1baf19a53459661343e21d6491c3908d3 Mon Sep 17 00:00:00 2001
+From: Tony Luck <tony.luck@intel.com>
+Date: Tue, 13 Jul 2010 11:42:54 -0700
+Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?=
+ =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?=
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+arch/arm config files were slimmed down using a python script
+(See commit c2330e286f68f1c408b4aa6515ba49d57f05beae comment)
+
+Do the same for ia64 so we can have sleek & trim looking
+...
+------------
+
+Typically it will be placed in a MUA's drafts folder, edited to add
+timely commentary that should not go in the changelog after the three
+dashes, and then sent as a message whose body, in our example, starts
+with "arch/arm config files were...". On the receiving end, readers
+can save interesting patches in a UNIX mailbox and apply them with
+linkgit:git-am[1].
+
+When a patch is part of an ongoing discussion, the patch generated by
+'git format-patch' can be tweaked to take advantage of the 'git am
+--scissors' feature. After your response to the discussion comes a
+line that consists solely of "`-- >8 --`" (scissors and perforation),
+followed by the patch with unnecessary header fields removed:
+
+------------
+...
+> So we should do such-and-such.
+
+Makes sense to me. How about this patch?
+
+-- >8 --
+Subject: [IA64] Put ia64 config files on the Uwe Kleine-König diet
+
+arch/arm config files were slimmed down using a python script
+...
+------------
+
+When sending a patch this way, most often you are sending your own
+patch, so in addition to the "`From $SHA1 $magic_timestamp`" marker you
+should omit `From:` and `Date:` lines from the patch file. The patch
+title is likely to be different from the subject of the discussion the
+patch is in response to, so it is likely that you would want to keep
+the Subject: line, like the example above.
+
+Checking for patch corruption
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Many mailers if not set up properly will corrupt whitespace. Here are
+two common types of corruption:
+
+* Empty context lines that do not have _any_ whitespace.
+
+* Non-empty context lines that have one extra whitespace at the
+ beginning.
+
+One way to test if your MUA is set up correctly is:
+
+* Send the patch to yourself, exactly the way you would, except
+ with To: and Cc: lines that do not contain the list and
+ maintainer address.
+
+* Save that patch to a file in UNIX mailbox format. Call it a.patch,
+ say.
+
+* Apply it:
+
+ $ git fetch <project> master:test-apply
+ $ git checkout test-apply
+ $ git reset --hard
+ $ git am a.patch
+
+If it does not apply correctly, there can be various reasons.
+
+* The patch itself does not apply cleanly. That is _bad_ but
+ does not have much to do with your MUA. You might want to rebase
+ the patch with linkgit:git-rebase[1] before regenerating it in
+ this case.
+
+* The MUA corrupted your patch; "am" would complain that
+ the patch does not apply. Look in the .git/rebase-apply/ subdirectory and
+ see what 'patch' file contains and check for the common
+ corruption patterns mentioned above.
+
+* While at it, check the 'info' and 'final-commit' files as well.
+ If what is in 'final-commit' is not exactly what you would want to
+ see in the commit log message, it is very likely that the
+ receiver would end up hand editing the log message when applying
+ your patch. Things like "Hi, this is my first patch.\n" in the
+ patch e-mail should come after the three-dash line that signals
+ the end of the commit message.
+
+MUA-SPECIFIC HINTS
+------------------
+Here are some hints on how to successfully submit patches inline using
+various mailers.
+
+GMail
+~~~~~
+GMail does not have any way to turn off line wrapping in the web
+interface, so it will mangle any emails that you send. You can however
+use "git send-email" and send your patches through the GMail SMTP server, or
+use any IMAP email client to connect to the google IMAP server and forward
+the emails through that.
+
+For hints on using 'git send-email' to send your patches through the
+GMail SMTP server, see the EXAMPLE section of linkgit:git-send-email[1].
+
+For hints on submission using the IMAP interface, see the EXAMPLE
+section of linkgit:git-imap-send[1].
+
+Thunderbird
+~~~~~~~~~~~
+By default, Thunderbird will both wrap emails as well as flag
+them as being 'format=flowed', both of which will make the
+resulting email unusable by git.
+
+There are three different approaches: use an add-on to turn off line wraps,
+configure Thunderbird to not mangle patches, or use
+an external editor to keep Thunderbird from mangling the patches.
+
+Approach #1 (add-on)
+^^^^^^^^^^^^^^^^^^^^
+
+Install the Toggle Word Wrap add-on that is available from
+https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/
+It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu
+that you can tick off. Now you can compose the message as you otherwise do
+(cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to
+insert line breaks manually in any text that you type.
+
+Approach #2 (configuration)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Three steps:
+
+1. Configure your mail server composition as plain text:
+ Edit...Account Settings...Composition & Addressing,
+ uncheck "Compose Messages in HTML".
+
+2. Configure your general composition window to not wrap.
++
+In Thunderbird 2:
+Edit..Preferences..Composition, wrap plain text messages at 0
++
+In Thunderbird 3:
+Edit..Preferences..Advanced..Config Editor. Search for
+"mail.wrap_long_lines".
+Toggle it to make sure it is set to `false`.
+
+3. Disable the use of format=flowed:
+Edit..Preferences..Advanced..Config Editor. Search for
+"mailnews.send_plaintext_flowed".
+Toggle it to make sure it is set to `false`.
+
+After that is done, you should be able to compose email as you
+otherwise would (cut + paste, 'git format-patch' | 'git imap-send', etc),
+and the patches will not be mangled.
+
+Approach #3 (external editor)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The following Thunderbird extensions are needed:
+AboutConfig from http://aboutconfig.mozdev.org/ and
+External Editor from http://globs.org/articles.php?lng=en&pg=8
+
+1. Prepare the patch as a text file using your method of choice.
+
+2. Before opening a compose window, use Edit->Account Settings to
+ uncheck the "Compose messages in HTML format" setting in the
+ "Composition & Addressing" panel of the account to be used to
+ send the patch.
+
+3. In the main Thunderbird window, 'before' you open the compose
+ window for the patch, use Tools->about:config to set the
+ following to the indicated values:
++
+----------
+ mailnews.send_plaintext_flowed => false
+ mailnews.wraplength => 0
+----------
+
+4. Open a compose window and click the external editor icon.
+
+5. In the external editor window, read in the patch file and exit
+ the editor normally.
+
+Side note: it may be possible to do step 2 with
+about:config and the following settings but no one's tried yet.
+
+----------
+ mail.html_compose => false
+ mail.identity.default.compose_html => false
+ mail.identity.id?.compose_html => false
+----------
+
+There is a script in contrib/thunderbird-patch-inline which can help
+you include patches with Thunderbird in an easy way. To use it, do the
+steps above and then use the script as the external editor.
+
+KMail
+~~~~~
+This should help you to submit patches inline using KMail.
+
+1. Prepare the patch as a text file.
+
+2. Click on New Mail.
+
+3. Go under "Options" in the Composer window and be sure that
+ "Word wrap" is not set.
+
+4. Use Message -> Insert file... and insert the patch.
+
+5. Back in the compose window: add whatever other text you wish to the
+ message, complete the addressing and subject fields, and press send.
+
+
EXAMPLES
--------
@@ -278,15 +508,6 @@ SEE ALSO
--------
linkgit:git-am[1], linkgit:git-send-email[1]
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt
index 965a827..eec4bdb 100644
--- a/Documentation/git-fsck-objects.txt
+++ b/Documentation/git-fsck-objects.txt
@@ -8,6 +8,7 @@ git-fsck-objects - Verifies the connectivity and validity of the objects in the
SYNOPSIS
--------
+[verse]
'git fsck-objects' ...
DESCRIPTION
@@ -15,3 +16,7 @@ DESCRIPTION
This is a synonym for linkgit:git-fsck[1]. Please refer to the
documentation of that command.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index 3ad48a6..a2a508d 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -26,7 +26,7 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
--no-reflogs is given) as heads.
--unreachable::
- Print out objects that exist but that aren't readable from any
+ Print out objects that exist but that aren't reachable from any
of the reference nodes.
--root::
@@ -76,7 +76,7 @@ It tests SHA1 and general object sanity, and it does full tracking of
the resulting reachability and everything else. It prints out any
corruption it finds (missing or bad objects), and if you use the
'--unreachable' flag it will also print out objects that exist but
-that aren't readable from any of the specified head nodes.
+that aren't reachable from any of the specified head nodes.
So for example
@@ -123,9 +123,6 @@ dangling <type> <object>::
The <type> object <object>, is present in the database but never
'directly' used. A dangling commit could be a root node.
-warning: git-fsck: tree <tree> has full pathnames in it::
- And it shouldn't...
-
sha1 mismatch <object>::
The database has an object who's sha1 doesn't match the
database value.
@@ -143,14 +140,6 @@ GIT_INDEX_FILE::
GIT_ALTERNATE_OBJECT_DIRECTORIES::
used to specify additional object database roots (usually unset)
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index a9e0882..815afcb 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -8,6 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
SYNOPSIS
--------
+[verse]
'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
DESCRIPTION
@@ -89,7 +90,7 @@ are not part of the current project most users will want to expire
them sooner. This option defaults to '30 days'.
The above two configuration variables can be given to a pattern. For
-example, this sets non-default expiry values only to remote tracking
+example, this sets non-default expiry values only to remote-tracking
branches:
------------
@@ -107,7 +108,7 @@ how long records of conflicted merge you have not resolved are
kept. This defaults to 15 days.
The optional configuration variable 'gc.packrefs' determines if
-'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable
+'git gc' runs 'git pack-refs'. This can be set to "notbare" to enable
it within all non-bare repos or it can be set to a boolean value.
This defaults to true.
@@ -128,8 +129,8 @@ Notes
'git gc' tries very hard to be safe about the garbage it collects. In
particular, it will keep not only objects referenced by your current set
-of branches and tags, but also objects referenced by the index, remote
-tracking branches, refs saved by 'git filter-branch' in
+of branches and tags, but also objects referenced by the index,
+remote-tracking branches, refs saved by 'git filter-branch' in
refs/original/, or reflogs (which may reference commits in branches
that were later amended or rewound).
@@ -137,6 +138,13 @@ If you are expecting some objects to be collected and they aren't, check
all of those locations and decide whether it makes sense in your case to
remove those references.
+HOOKS
+-----
+
+The 'git gc --auto' command will run the 'pre-auto-gc' hook. See
+linkgit:githooks[5] for more information.
+
+
SEE ALSO
--------
linkgit:git-prune[1]
@@ -144,10 +152,6 @@ linkgit:git-reflog[1]
linkgit:git-repack[1]
linkgit:git-rerere[1]
-Author
-------
-Written by Shawn O. Pearce <spearce@spearce.org>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt
index 790af95..1e2a20d 100644
--- a/Documentation/git-get-tar-commit-id.txt
+++ b/Documentation/git-get-tar-commit-id.txt
@@ -8,6 +8,7 @@ git-get-tar-commit-id - Extract commit ID from an archive created using git-arch
SYNOPSIS
--------
+[verse]
'git get-tar-commit-id' < <tarfile>
@@ -22,15 +23,6 @@ return code of 1. This can happen if <tarfile> had not been created
using 'git archive' or if the first parameter of 'git archive' had been
a tree ID instead of a commit ID or tag.
-
-Author
-------
-Written by Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 5474dd7..07b3c6a 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -12,7 +12,8 @@ SYNOPSIS
'git grep' [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
[-v | --invert-match] [-h|-H] [--full-name]
[-E | --extended-regexp] [-G | --basic-regexp]
- [-F | --fixed-strings] [-n]
+ [-P | --perl-regexp]
+ [-F | --fixed-strings] [-n | --line-number]
[-l | --files-with-matches] [-L | --files-without-match]
[(-O | --open-files-in-pager) [<pager>]]
[-z | --null]
@@ -31,6 +32,16 @@ Look for specified patterns in the tracked files in the work tree, blobs
registered in the index file, or blobs in given tree objects.
+CONFIGURATION
+-------------
+
+grep.lineNumber::
+ If set to true, enable '-n' option by default.
+
+grep.extendedRegexp::
+ If set to true, enable '--extended-regexp' option by default.
+
+
OPTIONS
-------
--cached::
@@ -87,12 +98,18 @@ OPTIONS
Use POSIX extended/basic regexp for patterns. Default
is to use basic regexp.
+-P::
+--perl-regexp::
+ Use Perl-compatible regexp for patterns. Requires libpcre to be
+ compiled in.
+
-F::
--fixed-strings::
Use fixed strings for patterns (don't interpret pattern
as a regex).
-n::
+--line-number::
Prefix the line number to matching lines.
-l::
@@ -131,6 +148,13 @@ OPTIONS
gives the default to color output.
Same as `--color=never`.
+--break::
+ Print an empty line between matches from different files.
+
+--heading::
+ Show the filename above the matches in that file instead of
+ at the start of each shown line.
+
-[ABC] <context>::
Show `context` trailing (`A` -- after), or leading (`B`
-- before), or both (`C` -- context) lines, and place a
@@ -191,11 +215,11 @@ OPTIONS
Examples
--------
-git grep 'time_t' \-- '*.[ch]'::
+git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}::
Looks for `time_t` in all tracked .c and .h files in the working
directory and its subdirectories.
-git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \)::
+git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)::
Looks for a line that has `#define` and either `MAX_PATH` or
`PATH_MAX`.
@@ -203,16 +227,6 @@ git grep --all-match -e NODE -e Unexpected::
Looks for a line that has `NODE` or `Unexpected` in
files that have lines that match both.
-Author
-------
-Originally written by Linus Torvalds <torvalds@osdl.org>, later
-revamped by Junio C Hamano.
-
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
index 2563710..18f713b 100644
--- a/Documentation/git-gui.txt
+++ b/Documentation/git-gui.txt
@@ -7,6 +7,7 @@ git-gui - A portable graphical interface to Git
SYNOPSIS
--------
+[verse]
'git gui' [<command>] [arguments]
DESCRIPTION
@@ -121,14 +122,6 @@ or
or browsed online at http://repo.or.cz/w/git-gui.git/[].
-Author
-------
-Written by Shawn O. Pearce <spearce@spearce.org>.
-
-Documentation
---------------
-Documentation by Shawn O. Pearce <spearce@spearce.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index 6904739..4b0a502 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -49,18 +49,10 @@ OPTIONS
--no-filters::
Hash the contents as is, ignoring any input filter that would
- have been chosen by the attributes mechanism, including crlf
+ have been chosen by the attributes mechanism, including the end-of-line
conversion. If the file is read from standard input then this
is always implied, unless the --path option is given.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt
index f8df109d..9e0b3f6 100644
--- a/Documentation/git-help.txt
+++ b/Documentation/git-help.txt
@@ -7,6 +7,7 @@ git-help - display help information about git
SYNOPSIS
--------
+[verse]
'git help' [-a|--all|-i|--info|-m|--man|-w|--web] [COMMAND]
DESCRIPTION
@@ -55,9 +56,9 @@ other display programs (see below).
+
The web browser can be specified using the configuration variable
'help.browser', or 'web.browser' if the former is not set. If none of
-these config variables is set, the 'git web--browse' helper script
+these config variables is set, the 'git web{litdd}browse' helper script
(called by 'git help') will pick a suitable default. See
-linkgit:git-web--browse[1] for more information about this.
+linkgit:git-web{litdd}browse[1] for more information about this.
CONFIGURATION VARIABLES
-----------------------
@@ -80,7 +81,7 @@ help.browser, web.browser and browser.<tool>.path
The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also
be checked if the 'web' format is chosen (either by command line
option or configuration variable). See '-w|--web' in the OPTIONS
-section above and linkgit:git-web--browse[1].
+section above and linkgit:git-web{litdd}browse[1].
man.viewer
~~~~~~~~~~
@@ -171,17 +172,6 @@ $ git config --global web.browser firefox
as they are probably more user specific than repository specific.
See linkgit:git-config[1] for more information about this.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com> and the git-list
-<git@vger.kernel.org>.
-
-Documentation
--------------
-Initial documentation was part of the linkgit:git[1] man page.
-Christian Couder <chriscool@tuxfamily.org> extracted and rewrote it a
-little. Maintenance is done by the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt
index d91cb7f..4d42073 100644
--- a/Documentation/git-http-fetch.txt
+++ b/Documentation/git-http-fetch.txt
@@ -8,6 +8,7 @@ git-http-fetch - Download from a remote git repository via HTTP
SYNOPSIS
--------
+[verse]
'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url>
DESCRIPTION
@@ -43,14 +44,6 @@ commit-id::
Verify that everything reachable from target is fetched. Used after
an earlier fetch is interrupted.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index ddf7a18..2e67362 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -8,6 +8,7 @@ git-http-push - Push objects over HTTP/DAV to another repository
SYNOPSIS
--------
+[verse]
'git http-push' [--all] [--dry-run] [--force] [--verbose] <url> <ref> [<ref>...]
DESCRIPTION
@@ -91,15 +92,6 @@ With '--force', the fast-forward check is disabled for all refs.
Optionally, a <ref> parameter can be prefixed with a plus '+' sign
to disable the fast-forward check only on that ref.
-
-Author
-------
-Written by Nick Hengeveld <nickh@reactrix.com>
-
-Documentation
---------------
-Documentation by Nick Hengeveld
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
index 57aba42..875d283 100644
--- a/Documentation/git-imap-send.txt
+++ b/Documentation/git-imap-send.txt
@@ -8,6 +8,7 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder
SYNOPSIS
--------
+[verse]
'git imap-send'
@@ -111,6 +112,31 @@ Using direct mode with SSL:
..........................
+EXAMPLE
+-------
+To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig
+to specify your account settings:
+
+---------
+[imap]
+ folder = "[Gmail]/Drafts"
+ host = imaps://imap.gmail.com
+ user = user@gmail.com
+ port = 993
+ sslverify = false
+---------
+
+You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+that the "Folder doesn't exist".
+
+Once the commits are ready to be sent, run the following command:
+
+ $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
+
+Just make sure to disable line wrapping in the email client (GMail's web
+interface will wrap lines no matter what, so you need to use a real
+IMAP client).
+
CAUTION
-------
It is still your responsibility to make sure that the email message
@@ -124,13 +150,9 @@ Thunderbird in particular is known to be problematic. Thunderbird
users may wish to visit this web page for more information:
http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
-Author
-------
-Derived from isync 1.0.1 by Mike McCormack.
-
-Documentation
---------------
-Documentation by Mike McCormack
+SEE ALSO
+--------
+linkgit:git-format-patch[1], linkgit:git-send-email[1], mbox(5)
GIT
---
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index f3ccc72..909687f 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -59,10 +59,10 @@ OPTIONS
the newly constructed pack and index before refs can be
updated to use objects contained in the pack.
---keep='why'::
+--keep=<msg>::
Like --keep create a .keep file before moving the index into
its final destination, but rather than creating an empty file
- place 'why' followed by an LF into the .keep file. The 'why'
+ place '<msg>' followed by an LF into the .keep file. The '<msg>'
message can later be searched for within all .keep files to
locate any which have outlived their usefulness.
@@ -85,15 +85,6 @@ new .keep file was successfully created. This is useful to remove a
.keep file used as a lock to prevent the race with 'git repack'
mentioned above.
-
-Author
-------
-Written by Sergey Vlasov <vsu@altlinux.ru>
-
-Documentation
--------------
-Documentation by Sergey Vlasov
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
index eba3cb4..a21e346 100644
--- a/Documentation/git-init-db.txt
+++ b/Documentation/git-init-db.txt
@@ -8,7 +8,8 @@ git-init-db - Creates an empty git repository
SYNOPSIS
--------
-'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]]
+[verse]
+'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]
DESCRIPTION
@@ -16,3 +17,7 @@ DESCRIPTION
This is a synonym for linkgit:git-init[1]. Please refer to the
documentation of that command.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
index 246b07e..9ac2bba 100644
--- a/Documentation/git-init.txt
+++ b/Documentation/git-init.txt
@@ -8,9 +8,33 @@ git-init - Create an empty git repository or reinitialize an existing one
SYNOPSIS
--------
-'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory]
+[verse]
+'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
+ [--separate-git-dir <git dir>]
+ [--shared[=<permissions>]] [directory]
+DESCRIPTION
+-----------
+
+This command creates an empty git repository - basically a `.git`
+directory with subdirectories for `objects`, `refs/heads`,
+`refs/tags`, and template files. An initial `HEAD` file that
+references the HEAD of the master branch is also created.
+
+If the `$GIT_DIR` environment variable is set then it specifies a path
+to use instead of `./.git` for the base of the repository.
+
+If the object storage directory is specified via the
+`$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories
+are created underneath - otherwise the default `$GIT_DIR/objects`
+directory is used.
+
+Running 'git init' in an existing repository is safe. It will not
+overwrite things that are already there. The primary reason for
+rerunning 'git init' is to pick up newly added templates (or to move
+the repository to another place if --separate-git-dir is given).
+
OPTIONS
-------
@@ -31,7 +55,16 @@ current working directory.
Specify the directory from which templates will be used. (See the "TEMPLATE
DIRECTORY" section below.)
---shared[={false|true|umask|group|all|world|everybody|0xxx}]::
+--separate-git-dir=<git dir>::
+
+Instead of initializing the repository where it is supposed to be,
+place a filesytem-agnostic git symbolic link there, pointing to the
+specified git path, and initialize a git repository at the path. The
+result is git repository can be separated from working tree. If this
+is reinitialization, the repository will be moved to the specified
+path.
+
+--shared[=(false|true|umask|group|all|world|everybody|0xxx)]::
Specify that the git repository is to be shared amongst several users. This
allows users belonging to the same group to push into that
@@ -74,32 +107,6 @@ line, the command is run inside the directory (possibly after creating it).
--
-DESCRIPTION
------------
-This command creates an empty git repository - basically a `.git` directory
-with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
-template files.
-An initial `HEAD` file that references the HEAD of the master branch
-is also created.
-
-If the `$GIT_DIR` environment variable is set then it specifies a path
-to use instead of `./.git` for the base of the repository.
-
-If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
-environment variable then the sha1 directories are created underneath -
-otherwise the default `$GIT_DIR/objects` directory is used.
-
-Running 'git init' in an existing repository is safe. It will not overwrite
-things that are already there. The primary reason for rerunning 'git init'
-is to pick up newly added templates.
-
-Note that 'git init' is the same as 'git init-db'. The command
-was primarily meant to initialize the object database, but over
-time it has become responsible for setting up the other aspects
-of the repository, such as installing the default hooks and
-setting the configuration variables. The old name is retained
-for backward compatibility reasons.
-
TEMPLATE DIRECTORY
------------------
@@ -134,15 +141,6 @@ $ git add . <2>
<1> prepare /path/to/my/codebase/.git directory
<2> add all existing file to the index
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt
index 2c3c4d2..ea95c90 100644
--- a/Documentation/git-instaweb.txt
+++ b/Documentation/git-instaweb.txt
@@ -44,23 +44,26 @@ OPTIONS
-b::
--browser::
The web browser that should be used to view the gitweb
- page. This will be passed to the 'git web--browse' helper
+ page. This will be passed to the 'git web{litdd}browse' helper
script along with the URL of the gitweb instance. See
- linkgit:git-web--browse[1] for more information about this. If
+ linkgit:git-web{litdd}browse[1] for more information about this. If
the script fails, the URL will be printed to stdout.
+start::
--start::
- Start the httpd instance and exit. This does not generate
- any of the configuration files for spawning a new instance.
+ Start the httpd instance and exit. Regenerate configuration files
+ as necessary for spawning a new instance.
+stop::
--stop::
Stop the httpd instance and exit. This does not generate
any of the configuration files for spawning a new instance,
nor does it close the browser.
+restart::
--restart::
- Restart the httpd instance and exit. This does not generate
- any of the configuration files for spawning a new instance.
+ Restart the httpd instance and exit. Regenerate configuration files
+ as necessary for spawning a new instance.
CONFIGURATION
-------------
@@ -79,15 +82,7 @@ You may specify configuration in your .git/config
If the configuration variable 'instaweb.browser' is not set,
'web.browser' will be used instead if it is defined. See
-linkgit:git-web--browse[1] for more information about this.
-
-Author
-------
-Written by Eric Wong <normalperson@yhbt.net>
-
-Documentation
---------------
-Documentation by Eric Wong <normalperson@yhbt.net>.
+linkgit:git-web{litdd}browse[1] for more information about this.
GIT
---
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 0e6ff31..59f8be0 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -8,6 +8,7 @@ git-log - Show commit logs
SYNOPSIS
--------
+[verse]
'git log' [<options>] [<since>..<until>] [[\--] <path>...]
DESCRIPTION
@@ -23,19 +24,20 @@ each commit introduces are shown.
OPTIONS
-------
-:git-log: 1
-include::diff-options.txt[]
-
-<n>::
Limits the number of commits to show.
+ Note that this is a commit limiting option, see below.
<since>..<until>::
Show only commits between the named two commits. When
either <since> or <until> is omitted, it defaults to
`HEAD`, i.e. the tip of the current branch.
For a more complete list of ways to spell <since>
- and <until>, see "SPECIFYING REVISIONS" section in
- linkgit:git-rev-parse[1].
+ and <until>, see linkgit:gitrevisions[7].
+
+--follow::
+ Continue listing the history of a file beyond renames
+ (works only for a single file).
--no-decorate::
--decorate[=short|full|no]::
@@ -55,9 +57,9 @@ include::diff-options.txt[]
paths. With this, the full diff is shown for commits that touch
the specified paths; this means that "<path>..." limits only
commits, and doesn't limit diff for those commits.
-
---follow::
- Continue listing the history of a file beyond renames/copies.
++
+Note that this affects all diff-based output types, e.g. those
+produced by --stat etc.
--log-size::
Before the log message print out its size in bytes. Intended
@@ -72,11 +74,16 @@ include::diff-options.txt[]
to be prefixed with "\-- " to separate them from options or
refnames.
-
include::rev-list-options.txt[]
include::pretty-formats.txt[]
+Common diff options
+-------------------
+
+:git-log: 1
+include::diff-options.txt[]
+
include::diff-generate-patch.txt[]
Examples
@@ -111,7 +118,7 @@ git log --follow builtin-rev-list.c::
git log --branches --not --remotes=origin::
Shows all commits that are in any of local branches but not in
- any of remote tracking branches for 'origin' (what you have that
+ any of remote-tracking branches for 'origin' (what you have that
origin doesn't).
git log master --not --remotes=*/master::
@@ -172,17 +179,9 @@ May be an unabbreviated ref name or a glob and may be specified
multiple times. A warning will be issued for refs that do not exist,
but a glob that does not match any refs is silently ignored.
+
-This setting can be disabled by the `--no-standard-notes` option,
+This setting can be disabled by the `--no-notes` option,
overridden by the 'GIT_NOTES_DISPLAY_REF' environment variable,
-and supplemented by the `--show-notes` option.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+and overridden by the `--notes=<ref>` option.
GIT
---
diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt
index 602b8d5..c406a11 100644
--- a/Documentation/git-lost-found.txt
+++ b/Documentation/git-lost-found.txt
@@ -7,6 +7,7 @@ git-lost-found - Recover lost refs that luckily have not yet been pruned
SYNOPSIS
--------
+[verse]
'git lost-found'
DESCRIPTION
@@ -67,15 +68,6 @@ $ git rev-parse not-lost-anymore
1ef2b196d909eed523d4f3c9bf54b78cdd6843c6
------------
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 3521637..4b28292 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -10,14 +10,14 @@ SYNOPSIS
--------
[verse]
'git ls-files' [-z] [-t] [-v]
- (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
- (-[c|d|o|i|s|u|k|m])\*
+ (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
+ (-[c|d|o|i|s|u|k|m])*
[-x <pattern>|--exclude=<pattern>]
[-X <file>|--exclude-from=<file>]
[--exclude-per-directory=<file>]
[--exclude-standard]
[--error-unmatch] [--with-tree=<tree-ish>]
- [--full-name] [--abbrev] [--] [<file>]\*
+ [--full-name] [--abbrev] [--] [<file>...]
DESCRIPTION
-----------
@@ -79,15 +79,16 @@ OPTIONS
-x <pattern>::
--exclude=<pattern>::
- Skips files matching pattern.
- Note that pattern is a shell wildcard pattern.
+ Skip untracked files matching pattern.
+ Note that pattern is a shell wildcard pattern. See EXCLUDE PATTERNS
+ below for more information.
-X <file>::
--exclude-from=<file>::
- exclude patterns are read from <file>; 1 per line.
+ Read exclude patterns from <file>; 1 per line.
--exclude-per-directory=<file>::
- read additional exclude patterns that apply only to the
+ Read additional exclude patterns that apply only to the
directory and its subdirectories in <file>.
--exclude-standard::
@@ -106,8 +107,16 @@ OPTIONS
with `-s` or `-u` options does not make any sense.
-t::
- Identify the file status with the following tags (followed by
- a space) at the start of each line:
+ This feature is semi-deprecated. For scripting purpose,
+ linkgit:git-status[1] `--porcelain` and
+ linkgit:git-diff-files[1] `--name-status` are almost always
+ superior alternatives, and users should look at
+ linkgit:git-status[1] `--short` or linkgit:git-diff[1]
+ `--name-status` for more user-friendly alternatives.
++
+This option identifies the file status with the following tags (followed by
+a space) at the start of each line:
+
H:: cached
S:: skip-worktree
M:: unmerged
@@ -132,6 +141,12 @@ OPTIONS
lines, show only a partial prefix.
Non default number of digits can be specified with --abbrev=<n>.
+--debug::
+ After each line that describes a file, add more data about its
+ cache entry. This is intended to show as much information as
+ possible for manual inspection; the exact format may change at
+ any time.
+
\--::
Do not interpret any more arguments as options.
@@ -178,7 +193,7 @@ These exclude patterns come from these places, in order:
file containing a list of patterns. Patterns are ordered
in the same order they appear in the file.
- 3. command line flag --exclude-per-directory=<name> specifies
+ 3. The command line flag --exclude-per-directory=<name> specifies
a name of the file in each directory 'git ls-files'
examines, normally `.gitignore`. Files in deeper
directories take precedence. Patterns are ordered in the
@@ -194,15 +209,6 @@ SEE ALSO
--------
linkgit:git-read-tree[1], linkgit:gitignore[5]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano, Josh Triplett, and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index abe7bf9..7a9b86a 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>]
- <repository> <refs>...
+ [--exit-code] <repository> [<refs>...]
DESCRIPTION
-----------
@@ -36,6 +36,12 @@ OPTIONS
SSH and where the SSH daemon does not use the PATH configured by the
user.
+--exit-code::
+ Exit with status "2" when no matching refs are found in the remote
+ repository. Usually the command exits with status "0" to indicate
+ it successfully talked with the remote repository, whether it
+ found any matching refs.
+
<repository>::
Location of the repository. The shorthand defined in
$GIT_DIR/branches/ can be used. Use "." (dot) to list references in
@@ -67,10 +73,6 @@ EXAMPLES
c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2
7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index 1f89d36..16e87fd 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,8 +10,8 @@ SYNOPSIS
--------
[verse]
'git ls-tree' [-d] [-r] [-t] [-l] [-z]
- [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev=[<n>]]
- <tree-ish> [paths...]
+ [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+ <tree-ish> [<path>...]
DESCRIPTION
-----------
@@ -19,11 +19,11 @@ Lists the contents of a given tree object, like what "/bin/ls -a" does
in the current working directory. Note that:
- the behaviour is slightly different from that of "/bin/ls" in that the
- 'paths' denote just a list of patterns to match, e.g. so specifying
+ '<path>' denotes just a list of patterns to match, e.g. so specifying
directory name (without '-r') will behave differently, and order of the
arguments does not matter.
- - the behaviour is similar to that of "/bin/ls" in that the 'paths' is
+ - the behaviour is similar to that of "/bin/ls" in that the '<path>' is
taken as relative to the current working directory. E.g. when you are
in a directory 'sub' that has a directory 'dir', you can run 'git
ls-tree -r HEAD dir' to list the contents of the tree (that is
@@ -72,7 +72,7 @@ OPTIONS
Do not limit the listing to the current working directory.
Implies --full-name.
-paths::
+[<path>...]::
When paths are given, show them (note that this isn't really raw
pathnames, but rather a list of patterns to match). Otherwise
implicitly uses the root level of the tree as the sole path argument.
@@ -95,18 +95,6 @@ Object size identified by <object> is given in bytes, and right-justified
with minimum width of 7 characters. Object size is given only for blobs
(file) entries; for other entries `-` character is used in place of size.
-
-Author
-------
-Written by Petr Baudis <pasky@suse.cz>
-Completely rewritten from scratch by Junio C Hamano <gitster@pobox.com>,
-another major rewrite by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list
-<git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 3ea5aad..51dc325 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -8,6 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
SYNOPSIS
--------
+[verse]
'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
@@ -80,17 +81,6 @@ This can enabled by default with the configuration option mailinfo.scissors.
<patch>::
The patch extracted from e-mail.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>
-
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt
index a634485..4d1b871 100644
--- a/Documentation/git-mailsplit.txt
+++ b/Documentation/git-mailsplit.txt
@@ -7,7 +7,8 @@ git-mailsplit - Simple UNIX mbox splitter program
SYNOPSIS
--------
-'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [<mbox>|<Maildir>...]
+[verse]
+'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [(<mbox>|<Maildir>)...]
DESCRIPTION
-----------
@@ -46,16 +47,6 @@ OPTIONS
--keep-cr::
Do not remove `\r` from lines ending with `\r\n`.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-and Junio C Hamano <gitster@pobox.com>
-
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
index ce5b369..b295bf8 100644
--- a/Documentation/git-merge-base.txt
+++ b/Documentation/git-merge-base.txt
@@ -8,7 +8,10 @@ git-merge-base - Find as good common ancestors as possible for a merge
SYNOPSIS
--------
+[verse]
'git merge-base' [-a|--all] <commit> <commit>...
+'git merge-base' [-a|--all] --octopus <commit>...
+'git merge-base' --independent <commit>...
DESCRIPTION
-----------
@@ -20,17 +23,33 @@ that does not have any better common ancestor is a 'best common
ancestor', i.e. a 'merge base'. Note that there can be more than one
merge base for a pair of commits.
-Among the two commits to compute the merge base from, one is specified by
-the first commit argument on the command line; the other commit is a
-(possibly hypothetical) commit that is a merge across all the remaining
-commits on the command line. As the most common special case, specifying only
-two commits on the command line means computing the merge base between
-the given two commits.
+OPERATION MODE
+--------------
+
+As the most common special case, specifying only two commits on the
+command line means computing the merge base between the given two commits.
+
+More generally, among the two commits to compute the merge base from,
+one is specified by the first commit argument on the command line;
+the other commit is a (possibly hypothetical) commit that is a merge
+across all the remaining commits on the command line.
As a consequence, the 'merge base' is not necessarily contained in each of the
commit arguments if more than two commits are specified. This is different
from linkgit:git-show-branch[1] when used with the `--merge-base` option.
+--octopus::
+ Compute the best common ancestors of all supplied commits,
+ in preparation for an n-way merge. This mimics the behavior
+ of 'git show-branch --merge-base'.
+
+--independent::
+ Instead of printing merge bases, print a minimal subset of
+ the supplied commits with the same ancestors. In other words,
+ among the commits given, list those which cannot be reached
+ from any other. This mimics the behavior of 'git show-branch
+ --independent'.
+
OPTIONS
-------
-a::
@@ -75,6 +94,9 @@ and the result of `git merge-base A M` is '1'. Commit '2' is also a
common ancestor between 'A' and 'M', but '1' is a better common ancestor,
because '2' is an ancestor of '1'. Hence, '2' is not a merge base.
+The result of `git merge-base --octopus A B C` is '2', because '2' is
+the best common ancestor of all commits.
+
When the history involves criss-cross merges, there can be more than one
'best' common ancestor for two commits. For example, with this topology:
@@ -88,13 +110,11 @@ both '1' and '2' are merge-bases of A and B. Neither one is better than
the other (both are 'best' merge bases). When the `--all` option is not given,
it is unspecified which best one is output.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+See also
+--------
+linkgit:git-rev-list[1],
+linkgit:git-show-branch[1],
+linkgit:git-merge[1]
GIT
---
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt
index f334d69..635c669 100644
--- a/Documentation/git-merge-file.txt
+++ b/Documentation/git-merge-file.txt
@@ -86,17 +86,6 @@ git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345::
merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels
`a` and `c` instead of `tmp/a123` and `tmp/c345`.
-
-Author
-------
-Written by Johannes Schindelin <johannes.schindelin@gmx.de>
-
-
-Documentation
---------------
-Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>,
-with parts copied from the original documentation of RCS 'merge'.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt
index 4d266de..e0df1b3 100644
--- a/Documentation/git-merge-index.txt
+++ b/Documentation/git-merge-index.txt
@@ -8,7 +8,8 @@ git-merge-index - Run a merge for files needing merging
SYNOPSIS
--------
-'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>\*)
+[verse]
+'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*)
DESCRIPTION
-----------
@@ -73,15 +74,6 @@ merge once anything has returned an error (i.e., `cat` returned an error
for the AA file, because it didn't exist in the original, and thus
'git merge-index' didn't even try to merge the MM thing).
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-One-shot merge by Petr Baudis <pasky@ucw.cz>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt
index a163cfc..04e803d 100644
--- a/Documentation/git-merge-one-file.txt
+++ b/Documentation/git-merge-one-file.txt
@@ -8,6 +8,7 @@ git-merge-one-file - The standard helper program to use with git-merge-index
SYNOPSIS
--------
+[verse]
'git merge-one-file'
DESCRIPTION
@@ -15,15 +16,6 @@ DESCRIPTION
This is the standard helper program to use with 'git merge-index'
to resolve a merge after the trivial merge done with 'git read-tree -m'.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>,
-Junio C Hamano <gitster@pobox.com> and Petr Baudis <pasky@suse.cz>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt
index f869a7f..c5f84b6 100644
--- a/Documentation/git-merge-tree.txt
+++ b/Documentation/git-merge-tree.txt
@@ -8,6 +8,7 @@ git-merge-tree - Show three-way merge without touching index
SYNOPSIS
--------
+[verse]
'git merge-tree' <base-tree> <branch1> <branch2>
DESCRIPTION
@@ -23,14 +24,6 @@ merge results outside of the index, and stuff the results back into the
index. For this reason, the output from the command omits
entries that match the <branch1> tree.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 84043cc..e2e6aba 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -11,8 +11,9 @@ SYNOPSIS
[verse]
'git merge' [-n] [--stat] [--no-commit] [--squash]
[-s <strategy>] [-X <strategy-option>]
- [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
+ [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
'git merge' <msg> HEAD <commit>...
+'git merge' --abort
DESCRIPTION
-----------
@@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for
historical reasons. Do not use it from the command line or in
new scripts. It is the same as `git merge -m <msg> <commit>...`.
+The third syntax ("`git merge --abort`") can only be run after the
+merge has resulted in conflicts. 'git merge --abort' will abort the
+merge process and try to reconstruct the pre-merge state. However,
+if there were uncommitted changes when the merge started (and
+especially if those changes were further modified after the merge
+was started), 'git merge --abort' will in some cases be unable to
+reconstruct the original (pre-merge) changes. Therefore:
+
*Warning*: Running 'git merge' with uncommitted changes is
discouraged: while possible, it leaves you in a state that is hard to
back out of in the case of a conflict.
@@ -59,23 +68,40 @@ include::merge-options.txt[]
-m <msg>::
Set the commit message to be used for the merge commit (in
case one is created).
-
- If `--log` is specified, a shortlog of the commits being merged
- will be appended to the specified message.
-
- The 'git fmt-merge-msg' command can be
- used to give a good default for automated 'git merge'
- invocations.
++
+If `--log` is specified, a shortlog of the commits being merged
+will be appended to the specified message.
++
+The 'git fmt-merge-msg' command can be
+used to give a good default for automated 'git merge'
+invocations.
--rerere-autoupdate::
--no-rerere-autoupdate::
Allow the rerere mechanism to update the index with the
result of auto-conflict resolution if possible.
+--abort::
+ Abort the current conflict resolution process, and
+ try to reconstruct the pre-merge state.
++
+If there were uncommitted worktree changes present when the merge
+started, 'git merge --abort' will in some cases be unable to
+reconstruct these changes. It is therefore recommended to always
+commit or stash your changes before running 'git merge'.
++
+'git merge --abort' is equivalent to 'git reset --merge' when
+`MERGE_HEAD` is present.
+
<commit>...::
Commits, usually other branch heads, to merge into our branch.
- You need at least one <commit>. Specifying more than one
- <commit> obviously means you are trying an Octopus.
+ Specifying more than one commit will create a merge with
+ more than two parents (affectionately called an Octopus merge).
++
+If no commit is given from the command line, and if `merge.defaultToUpstream`
+configuration variable is set, merge the remote tracking branches
+that the current branch is configured to use as its upstream.
+See also the configuration section of this manual page.
PRE-MERGE CHECKS
@@ -142,7 +168,7 @@ happens:
i.e. matching `HEAD`.
If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with `git reset --merge`.
+want to start over, you can recover with `git merge --abort`.
HOW CONFLICTS ARE PRESENTED
---------------------------
@@ -213,8 +239,8 @@ After seeing a conflict, you can do two things:
* Decide not to merge. The only clean-ups you need are to reset
the index file to the `HEAD` commit to reverse 2. and to clean
- up working tree changes made by 2. and 3.; `git-reset --hard` can
- be used for this.
+ up working tree changes made by 2. and 3.; `git merge --abort`
+ can be used for this.
* Resolve the conflicts. Git will mark the conflicts in
the working tree. Edit the files into shape and
@@ -291,15 +317,6 @@ linkgit:git-diff[1], linkgit:git-ls-files[1],
linkgit:git-add[1], linkgit:git-rm[1],
linkgit:git-mergetool[1]
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt
index 78eb03f..f98a41b 100644
--- a/Documentation/git-mergetool--lib.txt
+++ b/Documentation/git-mergetool--lib.txt
@@ -1,5 +1,5 @@
-git-mergetool--lib(1)
-=====================
+git-mergetool{litdd}lib(1)
+==========================
NAME
----
@@ -7,7 +7,8 @@ git-mergetool--lib - Common git merge tool shell scriptlets
SYNOPSIS
--------
-'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"'
+[verse]
+'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool{litdd}lib"'
DESCRIPTION
-----------
@@ -16,11 +17,11 @@ This is not a command the end user would want to run. Ever.
This documentation is meant for people who are studying the
Porcelain-ish scripts and/or are writing new ones.
-The 'git-mergetool--lib' scriptlet is designed to be sourced (using
+The 'git-mergetool{litdd}lib' scriptlet is designed to be sourced (using
`.`) by other shell scripts to set up functions for working
with git merge tools.
-Before sourcing 'git-mergetool--lib', your script must set `TOOL_MODE`
+Before sourcing 'git-mergetool{litdd}lib', your script must set `TOOL_MODE`
to define the operation mode for the functions listed below.
'diff' and 'merge' are valid values.
@@ -41,14 +42,6 @@ run_merge_tool::
'$MERGED', '$LOCAL', '$REMOTE', and '$BASE' must be defined
for use by the merge tool.
-Author
-------
-Written by David Aguilar <davvid@gmail.com>
-
-Documentation
---------------
-Documentation by David Aguilar and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
index 55735fa..3470910 100644
--- a/Documentation/git-mergetool.txt
+++ b/Documentation/git-mergetool.txt
@@ -7,7 +7,8 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts
SYNOPSIS
--------
-'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]...
+[verse]
+'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...]
DESCRIPTION
-----------
@@ -26,8 +27,8 @@ OPTIONS
--tool=<tool>::
Use the merge resolution program specified by <tool>.
Valid merge tools are:
- kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
- diffuse, tortoisemerge, opendiff, p4merge and araxis.
+ araxis, bc3, diffuse, ecmerge, emerge, gvimdiff, kdiff3,
+ meld, opendiff, p4merge, tkdiff, tortoisemerge, vimdiff and xxdiff.
+
If a merge resolution program is not specified, 'git mergetool'
will use the configuration variable `merge.tool`. If the
@@ -72,13 +73,15 @@ success of the resolution after the custom tool has exited.
This is the default behaviour; the option is provided to
override any configuration settings.
-Author
-------
-Written by Theodore Y Ts'o <tytso@mit.edu>
+TEMPORARY FILES
+---------------
+`git mergetool` creates `*.orig` backup files while resolving merges.
+These are safe to remove once a file has been merged and its
+`git mergetool` session has completed.
-Documentation
---------------
-Documentation by Theodore Y Ts'o.
+Setting the `mergetool.keepBackup` configuration variable to `false`
+causes `git mergetool` to automatically remove the backup as files
+are successfully merged.
GIT
---
diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
index 8bcc114..65e167a 100644
--- a/Documentation/git-mktag.txt
+++ b/Documentation/git-mktag.txt
@@ -8,6 +8,7 @@ git-mktag - Creates a tag object
SYNOPSIS
--------
+[verse]
'git mktag' < signature_file
DESCRIPTION
@@ -32,15 +33,6 @@ exists, is separated by a blank line from the header. The
message part may contain a signature that git itself doesn't
care about, but that can be verified with gpg.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt
index 81e3326..5c6ebdf 100644
--- a/Documentation/git-mktree.txt
+++ b/Documentation/git-mktree.txt
@@ -8,6 +8,7 @@ git-mktree - Build a tree-object from ls-tree formatted text
SYNOPSIS
--------
+[verse]
'git mktree' [-z] [--missing] [--batch]
DESCRIPTION
@@ -34,14 +35,6 @@ OPTIONS
optional. Note - if the '-z' option is used, lines are terminated
with NUL.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
index bdcb585..b8db373 100644
--- a/Documentation/git-mv.txt
+++ b/Documentation/git-mv.txt
@@ -8,6 +8,7 @@ git-mv - Move or rename a file, a directory, or a symlink
SYNOPSIS
--------
+[verse]
'git mv' <options>... <args>...
DESCRIPTION
@@ -39,17 +40,6 @@ OPTIONS
--dry-run::
Do nothing; only show what would happen
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-Rewritten by Ryan Anderson <ryan@michonline.com>
-Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
index 2108237..ad1d146 100644
--- a/Documentation/git-name-rev.txt
+++ b/Documentation/git-name-rev.txt
@@ -70,15 +70,6 @@ Another nice thing you can do is:
% git log | git name-rev --stdin
------------
-
-Author
-------
-Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
-
-Documentation
---------------
-Documentation by Johannes Schindelin.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 5540af5..6a187f2 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -14,8 +14,12 @@ SYNOPSIS
'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
'git notes' edit [<object>]
'git notes' show [<object>]
-'git notes' remove [<object>]
+'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
+'git notes' merge --commit [-v | -q]
+'git notes' merge --abort [-v | -q]
+'git notes' remove [--ignore-missing] [--stdin] [<object>...]
'git notes' prune [-n | -v]
+'git notes' get-ref
DESCRIPTION
@@ -53,8 +57,11 @@ list::
add::
Add notes for a given object (defaults to HEAD). Abort if the
- object already has notes (use `-f` to overwrite an
- existing note).
+ object already has notes (use `-f` to overwrite existing notes).
+ However, if you're using `add` interactively (using an editor
+ to supply the notes contents), then - instead of aborting -
+ the existing notes will be opened in the editor (like the `edit`
+ subcommand).
copy::
Copy the notes for the first object onto the second object.
@@ -83,14 +90,34 @@ edit::
show::
Show the notes for a given object (defaults to HEAD).
+merge::
+ Merge the given notes ref into the current notes ref.
+ This will try to merge the changes made by the given
+ notes ref (called "remote") since the merge-base (if
+ any) into the current notes ref (called "local").
++
+If conflicts arise and a strategy for automatically resolving
+conflicting notes (see the -s/--strategy option) is not given,
+the "manual" resolver is used. This resolver checks out the
+conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
+and instructs the user to manually resolve the conflicts there.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
remove::
- Remove the notes for a given object (defaults to HEAD).
- This is equivalent to specifying an empty note message to
+ Remove the notes for given objects (defaults to HEAD). When
+ giving zero or one object from the command line, this is
+ equivalent to specifying an empty note message to
the `edit` subcommand.
prune::
Remove all notes for non-existing/unreachable objects.
+get-ref::
+ Print the current notes ref. This provides an easy way to
+ retrieve the current notes ref (e.g. from scripts).
+
OPTIONS
-------
-f::
@@ -128,12 +155,51 @@ OPTIONS
'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref
is taken to be in `refs/notes/` if it is not qualified.
+--ignore-missing::
+ Do not consider it an error to request removing notes from an
+ object that does not have notes attached to it.
+
+--stdin::
+ Also read the object names to remove notes from from the standard
+ input (there is no reason you cannot combine this with object
+ names from the command line).
+
-n::
+--dry-run::
Do not remove anything; just report the object names whose notes
would be removed.
+-s <strategy>::
+--strategy=<strategy>::
+ When merging notes, resolve notes conflicts using the given
+ strategy. The following strategies are recognized: "manual"
+ (default), "ours", "theirs", "union" and "cat_sort_uniq".
+ See the "NOTES MERGE STRATEGIES" section below for more
+ information on each notes merge strategy.
+
+--commit::
+ Finalize an in-progress 'git notes merge'. Use this option
+ when you have resolved the conflicts that 'git notes merge'
+ stored in .git/NOTES_MERGE_WORKTREE. This amends the partial
+ merge commit created by 'git notes merge' (stored in
+ .git/NOTES_MERGE_PARTIAL) by adding the notes in
+ .git/NOTES_MERGE_WORKTREE. The notes ref stored in the
+ .git/NOTES_MERGE_REF symref is updated to the resulting commit.
+
+--abort::
+ Abort/reset a in-progress 'git notes merge', i.e. a notes merge
+ with conflicts. This simply removes all files related to the
+ notes merge.
+
+-q::
+--quiet::
+ When merging notes, operate quietly.
+
-v::
- Report all object names whose notes are removed.
+--verbose::
+ When merging notes, be more verbose.
+ When pruning notes, report all object names whose notes are
+ removed.
DISCUSSION
@@ -161,6 +227,38 @@ object, in which case the history of the notes can be read with
`git log -p -g <refname>`.
+NOTES MERGE STRATEGIES
+----------------------
+
+The default notes merge strategy is "manual", which checks out
+conflicting notes in a special work tree for resolving notes conflicts
+(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the
+conflicts in that work tree.
+When done, the user can either finalize the merge with
+'git notes merge --commit', or abort the merge with
+'git notes merge --abort'.
+
+"ours" automatically resolves conflicting notes in favor of the local
+version (i.e. the current notes ref).
+
+"theirs" automatically resolves notes conflicts in favor of the remote
+version (i.e. the given notes ref being merged into the current notes
+ref).
+
+"union" automatically resolves notes conflicts by concatenating the
+local and remote versions.
+
+"cat_sort_uniq" is similar to "union", but in addition to concatenating
+the local and remote versions, this strategy also sorts the resulting
+lines, and removes duplicate lines from the result. This is equivalent
+to applying the "cat | sort | uniq" shell pipeline to the local and
+remote versions. This strategy is useful if the notes follow a line-based
+format where one wants to avoid duplicated lines in the merge result.
+Note that if either the local or remote version contain duplicate lines
+prior to the merge, these will also be removed by this notes merge
+strategy.
+
+
EXAMPLES
--------
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 8ed09c0..20c8551 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -11,8 +11,8 @@ SYNOPSIS
[verse]
'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
[--no-reuse-delta] [--delta-base-offset] [--non-empty]
- [--local] [--incremental] [--window=N] [--depth=N]
- [--revs [--unpacked | --all]*] [--stdout | base-name]
+ [--local] [--incremental] [--window=<n>] [--depth=<n>]
+ [--revs [--unpacked | --all]] [--stdout | base-name]
[--keep-true-parents] < object-list
@@ -82,8 +82,8 @@ base-name::
reference was included in the resulting packfile. This
can be useful to send new tags to native git clients.
---window=[N]::
---depth=[N]::
+--window=<n>::
+--depth=<n>::
These two options affect how the objects contained in
the pack are stored using delta compression. The
objects are first internally sorted by type, size and
@@ -95,10 +95,10 @@ base-name::
times to get to the necessary object.
The default value for --window is 10 and --depth is 50.
---window-memory=[N]::
+--window-memory=<n>::
This option provides an additional limit on top of `--window`;
the window size will dynamically scale down so as to not take
- up more than N bytes in memory. This is useful in
+ up more than '<n>' bytes in memory. This is useful in
repositories with a mix of large and small objects to not run
out of memory with a large window, but still be able to take
advantage of the large window for the smaller objects. The
@@ -106,7 +106,7 @@ base-name::
`--window-memory=0` makes memory usage unlimited, which is the
default.
---max-pack-size=[N]::
+--max-pack-size=<n>::
Maximum size of each output pack file. The size can be suffixed with
"k", "m", or "g". The minimum size allowed is limited to 1 MiB.
If specified, multiple packfiles may be created.
@@ -115,7 +115,7 @@ base-name::
--honor-pack-keep::
This flag causes an object already in a local pack that
- has a .keep file to be ignored, even if it it would have
+ has a .keep file to be ignored, even if it would have
otherwise been packed.
--incremental::
@@ -171,7 +171,7 @@ base-name::
wholesale enforcement of a different compression level on the
packed data is desired.
---compression=[N]::
+--compression=<n>::
Specifies compression level for newly-compressed data in the
generated pack. If not specified, pack compression level is
determined first by pack.compression, then by core.compression,
@@ -190,15 +190,20 @@ self-contained. Use `git index-pack --fix-thin`
(see linkgit:git-index-pack[1]) to restore the self-contained property.
--delta-base-offset::
- A packed archive can express base object of a delta as
- either 20-byte object name or as an offset in the
- stream, but older version of git does not understand the
+ A packed archive can express the base object of a delta as
+ either a 20-byte object name or as an offset in the
+ stream, but ancient versions of git don't understand the
latter. By default, 'git pack-objects' only uses the
former format for better compatibility. This option
allows the command to use the latter format for
compactness. Depending on the average delta chain
length, this option typically shrinks the resulting
packfile by 3-5 per-cent.
++
+Note: Porcelain commands such as `git gc` (see linkgit:git-gc[1]),
+`git repack` (see linkgit:git-repack[1]) pass this option by default
+in modern git when they put objects in your repository into pack files.
+So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
--threads=<n>::
Specifies the number of threads to spawn when searching for best
@@ -219,15 +224,6 @@ self-contained. Use `git index-pack --fix-thin`
With this option, parents that are hidden by grafts are packed
nevertheless.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
--------------
-Documentation by Junio C Hamano
-
SEE ALSO
--------
linkgit:git-rev-list[1]
diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt
index d060787..f2869da 100644
--- a/Documentation/git-pack-redundant.txt
+++ b/Documentation/git-pack-redundant.txt
@@ -8,6 +8,7 @@ git-pack-redundant - Find redundant pack files
SYNOPSIS
--------
+[verse]
'git pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >
DESCRIPTION
@@ -38,14 +39,6 @@ OPTIONS
--verbose::
Outputs some statistics to stderr. Has a small performance penalty.
-Author
-------
-Written by Lukas Sandström <lukass@etek.chalmers.se>
-
-Documentation
---------------
-Documentation by Lukas Sandström <lukass@etek.chalmers.se>
-
SEE ALSO
--------
linkgit:git-pack-objects[1]
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
index 1ee99c2..a3c6677 100644
--- a/Documentation/git-pack-refs.txt
+++ b/Documentation/git-pack-refs.txt
@@ -7,6 +7,7 @@ git-pack-refs - Pack heads and tags for efficient repository access
SYNOPSIS
--------
+[verse]
'git pack-refs' [--all] [--no-prune]
DESCRIPTION
@@ -56,11 +57,6 @@ a repository with many branches of historical interests.
The command usually removes loose refs under `$GIT_DIR/refs`
hierarchy after packing them. This option tells it not to.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt
index 39d9daa..a45ea1e 100644
--- a/Documentation/git-parse-remote.txt
+++ b/Documentation/git-parse-remote.txt
@@ -8,6 +8,7 @@ git-parse-remote - Routines to help parsing remote repository access parameters
SYNOPSIS
--------
+[verse]
'. "$(git --exec-path)/git-parse-remote"'
DESCRIPTION
@@ -17,14 +18,6 @@ routines to parse files under $GIT_DIR/remotes/ and
$GIT_DIR/branches/ and configuration variables that are related
to fetching, pulling and pushing.
-Author
-------
-Written by Junio C Hamano.
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt
index 4dae139..90268f0 100644
--- a/Documentation/git-patch-id.txt
+++ b/Documentation/git-patch-id.txt
@@ -7,6 +7,7 @@ git-patch-id - Compute unique ID for a patch
SYNOPSIS
--------
+[verse]
'git patch-id' < <patch>
DESCRIPTION
@@ -29,14 +30,6 @@ OPTIONS
<patch>::
The diff to create the ID of.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
index 87dacd7..87ea3fb 100644
--- a/Documentation/git-peek-remote.txt
+++ b/Documentation/git-peek-remote.txt
@@ -8,6 +8,7 @@ git-peek-remote - List the references in a remote repository
SYNOPSIS
--------
+[verse]
'git peek-remote' [--upload-pack=<git-upload-pack>] [<host>:]<directory>
DESCRIPTION
@@ -37,14 +38,6 @@ OPTIONS
The repository to sync from.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt
index abfc6b6..80dc022 100644
--- a/Documentation/git-prune-packed.txt
+++ b/Documentation/git-prune-packed.txt
@@ -8,6 +8,7 @@ git-prune-packed - Remove extra objects that are already in pack files
SYNOPSIS
--------
+[verse]
'git prune-packed' [-n|--dry-run] [-q|--quiet]
@@ -36,14 +37,6 @@ OPTIONS
--quiet::
Squelch the progress indicator.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Ryan Anderson <ryan@michonline.com>
-
SEE ALSO
--------
linkgit:git-pack-objects[1]
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index 15cfb7a..80d01b0 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -8,6 +8,7 @@ git-prune - Prune all unreachable objects from the object database
SYNOPSIS
--------
+[verse]
'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
DESCRIPTION
@@ -31,10 +32,12 @@ OPTIONS
-------
-n::
+--dry-run::
Do not remove anything; just report what it would
remove.
-v::
+--verbose::
Report all removed objects.
\--::
@@ -76,14 +79,6 @@ linkgit:git-fsck[1],
linkgit:git-gc[1],
linkgit:git-reflog[1]
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index ab4de10..e1da468 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -8,29 +8,73 @@ git-pull - Fetch from and merge with another repository or a local branch
SYNOPSIS
--------
-'git pull' <options> <repository> <refspec>...
+[verse]
+'git pull' [options] [<repository> [<refspec>...]]
DESCRIPTION
-----------
-Runs 'git fetch' with the given parameters, and calls 'git merge'
-to merge the retrieved head(s) into the current branch.
-With `--rebase`, calls 'git rebase' instead of 'git merge'.
-Note that you can use `.` (current directory) as the
-<repository> to pull from the local repository -- this is useful
-when merging local branches into the current branch.
+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`.
-Also note that options meant for 'git pull' itself and underlying
-'git merge' must be given before the options meant for 'git fetch'.
+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'.
-*Warning*: Running 'git pull' (actually, the underlying 'git merge')
+<repository> should be the name of a remote repository as
+passed to linkgit:git-fetch[1]. <refspec> can name an
+arbitrary remote ref (for example, the name of a tag) or even
+a collection of refs with corresponding remote-tracking branches
+(e.g., refs/heads/{asterisk}:refs/remotes/origin/{asterisk}),
+but usually it is the name of a branch in the remote repository.
+
+Default values for <repository> and <branch> are read from the
+"remote" and "merge" configuration for the current branch
+as set by linkgit:git-branch[1] `--track`.
+
+Assume the following history exists and the current branch is
+"`master`":
+
+------------
+ A---B---C master on origin
+ /
+ D---E---F---G master
+------------
+
+Then "`git pull`" will fetch and replay the changes from the remote
+`master` branch since it diverged from the local `master` (i.e., `E`)
+until its current commit (`C`) on top of `master` and record the
+result in a new commit along with the names of the two parent commits
+and a log message from the user describing the changes.
+
+------------
+ A---B---C remotes/origin/master
+ / \
+ D---E---F---G---H master
+------------
+
+See linkgit:git-merge[1] for details, including how conflicts
+are presented and handled.
+
+In git 1.7.0 or later, to cancel a conflicting merge, use
+`git reset --merge`. *Warning*: In older versions of git, running 'git pull'
with uncommitted changes is discouraged: while possible, it leaves you
-in a state that is hard to back out of in the case of a conflict.
+in a state that may be hard to back out of in the case of a conflict.
+
+If any of the remote changes overlap with local uncommitted changes,
+the merge will be automatically cancelled and the work tree untouched.
+It is generally best to get any local changes in working order before
+pulling or stash them away with linkgit:git-stash[1].
OPTIONS
-------
+Options meant for 'git pull' itself and the underlying 'git merge'
+must be given before the options meant for 'git fetch'.
+
-q::
--quiet::
This is passed to both underlying git-fetch to squelch reporting of
@@ -41,6 +85,15 @@ OPTIONS
--verbose::
Pass --verbose to git-fetch and git-merge.
+--[no-]recurse-submodules[=yes|on-demand|no]::
+ This option controls if new commits of all populated submodules should
+ be fetched too (see linkgit:git-config[1] and linkgit:gitmodules[5]).
+ That might be necessary to get the data needed for merging submodule
+ commits, a feature git learned in 1.7.3. Notice that the result of a
+ merge will not be checked out in the submodule, "git submodule update"
+ has to be called afterwards to bring the work tree up to date with the
+ merge result.
+
Options related to merging
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -49,12 +102,15 @@ include::merge-options.txt[]
:git-pull: 1
--rebase::
- Instead of a merge, perform a rebase after fetching. If
- there is a remote ref for the upstream branch, and this branch
- was rebased since last fetched, the rebase uses that information
- to avoid rebasing non-local changes. To make this the default
- for branch `<name>`, set configuration `branch.<name>.rebase`
- to `true`.
+ Rebase the current branch on top of the upstream branch after
+ fetching. If there is a remote-tracking branch corresponding to
+ the upstream branch and the upstream branch was rebased since last
+ fetched, the rebase uses that information to avoid rebasing
+ non-local changes.
++
+See `branch.<name>.rebase` and `branch.autosetuprebase` in
+linkgit:git-config[1] if you want to make `git pull` always use
+`{litdd}rebase` instead of merging.
+
[NOTE]
This is a potentially _dangerous_ mode of operation.
@@ -91,7 +147,7 @@ and if there is not any such variable, the value on `URL: ` line
in `$GIT_DIR/remotes/<origin>` file is used.
In order to determine what remote branches to fetch (and
-optionally store in the tracking branches) when the command is
+optionally store in the remote-tracking branches) when the command is
run without any refspec parameters on the command line, values
of the configuration variable `remote.<origin>.fetch` are
consulted, and if there aren't any, `$GIT_DIR/remotes/<origin>`
@@ -104,9 +160,9 @@ refs/heads/*:refs/remotes/origin/*
------------
A globbing refspec must have a non-empty RHS (i.e. must store
-what were fetched in tracking branches), and its LHS and RHS
+what were fetched in remote-tracking branches), and its LHS and RHS
must end with `/*`. The above specifies that all remote
-branches are tracked using tracking branches in
+branches are tracked using remote-tracking branches in
`refs/remotes/origin/` hierarchy under the same name.
The rule to determine which remote branch to merge after
@@ -165,22 +221,19 @@ If you tried a pull which resulted in a complex conflicts and
would want to start over, you can recover with 'git reset'.
+BUGS
+----
+Using --recurse-submodules can only fetch new commits in already checked
+out submodules right now. When e.g. upstream added a new submodule in the
+just fetched commits of the superproject the submodule itself can not be
+fetched, making it impossible to check out that submodule later without
+having to do a fetch again. This is expected to be fixed in a future git
+version.
+
SEE ALSO
--------
linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-config[1]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-and Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Jon Loeliger,
-David Greaves,
-Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 4857024..88acfcd 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -41,7 +41,7 @@ OPTIONS[[OPTIONS]]
+
The <src> is often the name of the branch you would want to push, but
it can be any arbitrary "SHA-1 expression", such as `master~4` or
-`HEAD` (see linkgit:git-rev-parse[1]).
+`HEAD` (see linkgit:gitrevisions[7]).
+
The <dst> tells which ref on the remote side is updated with this
push. Arbitrary expressions cannot be used here, an actual ref must
@@ -200,16 +200,29 @@ summary::
For a successfully pushed ref, the summary shows the old and new
values of the ref in a form suitable for using as an argument to
`git log` (this is `<old>..<new>` in most cases, and
- `<old>...<new>` for forced non-fast-forward updates). For a
- failed update, more details are given for the failure.
- The string `rejected` indicates that git did not try to send the
- ref at all (typically because it is not a fast-forward). The
- string `remote rejected` indicates that the remote end refused
- the update; this rejection is typically caused by a hook on the
- remote side. The string `remote failure` indicates that the
- remote end did not report the successful update of the ref
- (perhaps because of a temporary error on the remote side, a
- break in the network connection, or other transient error).
+ `<old>\...<new>` for forced non-fast-forward updates).
++
+For a failed update, more details are given:
++
+--
+rejected::
+ Git did not try to send the ref at all, typically because it
+ is not a fast-forward and you did not force the update.
+
+remote rejected::
+ The remote end refused the update. Usually caused by a hook
+ on the remote side, or because the remote repository has one
+ of the following safety options in effect:
+ `receive.denyCurrentBranch` (for pushes to the checked out
+ branch), `receive.denyNonFastForwards` (for forced
+ non-fast-forward updates), `receive.denyDeletes` or
+ `receive.denyDeleteCurrent`. See linkgit:git-config[1].
+
+remote failure::
+ The remote end did not report the successful update of the ref,
+ perhaps because of a temporary error on the remote side, a
+ break in the network connection, or other transient error.
+--
from::
The name of the local ref being pushed, minus its
@@ -393,16 +406,6 @@ Commits A and B would no longer belong to a branch with a symbolic name,
and so would be unreachable. As such, these commits would be removed by
a `git gc` command on the origin repository.
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>, later rewritten in C
-by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt
index 579e8d2..7f112f3 100644
--- a/Documentation/git-quiltimport.txt
+++ b/Documentation/git-quiltimport.txt
@@ -49,14 +49,6 @@ The default for the patch directory is patches
or the value of the $QUILT_PATCHES environment
variable.
-Author
-------
-Written by Eric Biederman <ebiederm@lnxi.com>
-
-Documentation
---------------
-Documentation by Eric Biederman <ebiederm@lnxi.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index f6037c4..c45d53c 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -8,10 +8,11 @@ git-read-tree - Reads tree information into the index
SYNOPSIS
--------
+[verse]
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
[-u [--exclude-per-directory=<gitignore>] | -i]]
[--index-output=<file>] [--no-sparse-checkout]
- <tree-ish1> [<tree-ish2> [<tree-ish3>]]
+ (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
@@ -53,6 +54,11 @@ OPTIONS
trees that are not directly related to the current
working tree status into a temporary index file.
+-n::
+--dry-run::
+ Check if the command would error out, without updating the index
+ nor the files in the working tree for real.
+
-v::
Show the progress of checking files out.
@@ -114,6 +120,10 @@ OPTIONS
Disable sparse checkout support even if `core.sparseCheckout`
is true.
+--empty::
+ Instead of reading tree object(s) into the index, just empty
+ it.
+
<tree-ish#>::
The id of the tree object(s) to be read/merged.
@@ -417,15 +427,6 @@ SEE ALSO
linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
linkgit:gitignore[5]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index be23ad2..504945c 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -9,10 +9,9 @@ SYNOPSIS
--------
[verse]
'git rebase' [-i | --interactive] [options] [--onto <newbase>]
- <upstream> [<branch>]
+ [<upstream>] [<branch>]
'git rebase' [-i | --interactive] [options] --onto <newbase>
--root [<branch>]
-
'git rebase' --continue | --skip | --abort
DESCRIPTION
@@ -21,6 +20,12 @@ If <branch> is specified, 'git rebase' will perform an automatic
`git checkout <branch>` before doing anything else. Otherwise
it remains on the current branch.
+If <upstream> is not specified, the upstream configured in
+branch.<name>.remote and branch.<name>.merge options will be used; see
+linkgit:git-config[1] for details. If you are currently not on any
+branch or if the current branch does not have a configured upstream,
+the rebase will abort.
+
All changes made by commits in the current branch but that are not
in <upstream> are saved to a temporary area. This is the same set
of commits that would be shown by `git log <upstream>..HEAD` (or
@@ -40,7 +45,7 @@ with a different commit message or timestamp will be skipped).
It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run `git rebase --continue`. Another option is to bypass the commit
-that caused the merge failure with `git rebase --skip`. To restore the
+that caused the merge failure with `git rebase --skip`. To check out the
original <branch> and remove the .git/rebase-apply working files, use the
command `git rebase --abort` instead.
@@ -66,8 +71,9 @@ would be:
D---E---F---G master
------------
-The latter form is just a short-hand of `git checkout topic`
-followed by `git rebase master`.
+*NOTE:* The latter form is just a short-hand of `git checkout topic`
+followed by `git rebase master`. When rebase exits `topic` will
+remain the checked-out branch.
If the upstream branch already contains a change you have made (e.g.,
because you mailed a patch which was applied upstream), then that commit
@@ -199,6 +205,9 @@ rebase.stat::
Whether to show a diffstat of what changed upstream since the last
rebase. False by default.
+rebase.autosquash::
+ If set to true enable '--autosquash' option by default.
+
OPTIONS
-------
<newbase>::
@@ -207,13 +216,14 @@ OPTIONS
<upstream>. May be any valid commit, and not just an
existing branch name.
+
-As a special case, you may use "A...B" as a shortcut for the
+As a special case, you may use "A\...B" as a shortcut for the
merge base of A and B if there is exactly one merge base. You can
leave out at most one of A and B, in which case it defaults to HEAD.
<upstream>::
Upstream branch to compare against. May be any valid commit,
- not just an existing branch name.
+ not just an existing branch name. Defaults to the configured
+ upstream for the current branch.
<branch>::
Working branch; defaults to HEAD.
@@ -222,7 +232,11 @@ leave out at most one of A and B, in which case it defaults to HEAD.
Restart the rebasing process after having resolved a merge conflict.
--abort::
- Restore the original branch and abort the rebase operation.
+ Abort the rebase operation and reset HEAD to the original
+ branch. If <branch> was provided when the rebase operation was
+ started, then HEAD will be reset to <branch>. Otherwise HEAD
+ will be reset to where it was when the rebase operation was
+ started.
--skip::
Restart the rebasing process by skipping the current patch.
@@ -250,6 +264,13 @@ on top of the <upstream> branch using the given strategy, using
the 'ours' strategy simply discards all patches from the <branch>,
which makes little sense.
+-X <strategy-option>::
+--strategy-option=<strategy-option>::
+ Pass the <strategy-option> through to the merge strategy.
+ This implies `\--merge` and, if no strategy has been
+ specified, `-s recursive`. Note the reversal of 'ours' and
+ 'theirs' as noted in above for the `-m` option.
+
-q::
--quiet::
Be quiet. Implies --no-stat.
@@ -269,6 +290,10 @@ which makes little sense.
--no-verify::
This option bypasses the pre-rebase hook. See also linkgit:githooks[5].
+--verify::
+ Allows the pre-rebase hook to run, which is the default. This option can
+ be used to override --no-verify. See also linkgit:githooks[5].
+
-C<n>::
Ensure at least <n> lines of surrounding context match before
and after each change. When fewer lines of surrounding
@@ -326,6 +351,7 @@ idea unless you know what you are doing (see BUGS below).
instead.
--autosquash::
+--no-autosquash::
When the commit log message begins with "squash! ..." (or
"fixup! ..."), and there is a commit whose title begins with
the same ..., automatically modify the todo list of rebase -i
@@ -334,6 +360,10 @@ idea unless you know what you are doing (see BUGS below).
commit from `pick` to `squash` (or `fixup`).
+
This option is only valid when the '--interactive' option is used.
++
+If the '--autosquash' option is enabled by default using the
+configuration variable `rebase.autosquash`, this option can be
+used to override and disable this setting.
--no-ff::
With --interactive, cherry-pick all rebased commits instead of
@@ -459,6 +489,30 @@ sure that the current HEAD is "B", and call
$ git rebase -i -p --onto Q O
-----------------------------
+Reordering and editing commits usually creates untested intermediate
+steps. You may want to check that your history editing did not break
+anything by running a test, or at least recompiling at intermediate
+points in history by using the "exec" command (shortcut "x"). You may
+do so by creating a todo list like this one:
+
+-------------------------------------------
+pick deadbee Implement feature XXX
+fixup f1a5c00 Fix to feature XXX
+exec make
+pick c0ffeee The oneline of the next commit
+edit deadbab The oneline of the commit after
+exec cd subdir; make test
+...
+-------------------------------------------
+
+The interactive rebase will stop when a command fails (i.e. exits with
+non-0 status) to give you an opportunity to fix the problem. You can
+continue with `git rebase --continue`.
+
+The "exec" command launches the command in a shell (the one specified
+in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
+use shell features (like "cd", ">", ";" ...). The command is run from
+the root of the working tree.
SPLITTING COMMITS
-----------------
@@ -615,7 +669,6 @@ The ripple effect of a "hard case" recovery is especially bad:
'everyone' downstream from 'topic' will now have to perform a "hard
case" recovery too!
-
BUGS
----
The todo list presented by `--preserve-merges --interactive` does not
@@ -638,15 +691,6 @@ by moving the "pick 4" line will result in the following history:
1 --- 2 --- 4 --- 5
------------
-Authors
-------
-Written by Junio C Hamano <gitster@pobox.com> and
-Johannes E. Schindelin <johannes.schindelin@gmx.de>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 2790eeb..459c085 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -8,6 +8,7 @@ git-receive-pack - Receive what is pushed into the repository
SYNOPSIS
--------
+[verse]
'git-receive-pack' <directory>
DESCRIPTION
@@ -151,15 +152,6 @@ SEE ALSO
--------
linkgit:git-send-pack[1]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
index 4eaa62b..976dc14 100644
--- a/Documentation/git-reflog.txt
+++ b/Documentation/git-reflog.txt
@@ -8,6 +8,7 @@ git-reflog - Manage reflog information
SYNOPSIS
--------
+[verse]
'git reflog' <subcommand> <options>
DESCRIPTION
@@ -40,7 +41,7 @@ see linkgit:git-log[1].
The reflog is useful in various git commands, to specify the old value
of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be
two moves ago", `master@\{one.week.ago\}` means "where master used to
-point to one week ago", and so on. See linkgit:git-rev-parse[1] for
+point to one week ago", and so on. See linkgit:gitrevisions[7] for
more details.
To delete single entries from the reflog, use the subcommand "delete"
@@ -90,14 +91,6 @@ them.
--verbose::
Print extra information on screen.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
index 25ff8f9..3b33c99 100644
--- a/Documentation/git-relink.txt
+++ b/Documentation/git-relink.txt
@@ -7,7 +7,8 @@ git-relink - Hardlink common objects in local repositories
SYNOPSIS
--------
-'git relink' [--safe] <dir> [<dir>]\* <master_dir>
+[verse]
+'git relink' [--safe] <dir>... <master_dir>
DESCRIPTION
-----------
@@ -24,14 +25,6 @@ OPTIONS
<dir>::
Directories containing a .git/objects/ subdirectory.
-Author
-------
-Written by Ryan Anderson <ryan@michonline.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt
new file mode 100644
index 0000000..8a8e1d7
--- /dev/null
+++ b/Documentation/git-remote-ext.txt
@@ -0,0 +1,126 @@
+git-remote-ext(1)
+=================
+
+NAME
+----
+git-remote-ext - Bridge smart transport to external command.
+
+SYNOPSIS
+--------
+[verse]
+git remote add <nick> "ext::<command>[ <arguments>...]"
+
+DESCRIPTION
+-----------
+This remote helper uses the specified '<command>' to connect
+to a remote git server.
+
+Data written to stdin of the specified '<command>' is assumed
+to be sent to a git:// server, git-upload-pack, git-receive-pack
+or git-upload-archive (depending on situation), and data read
+from stdout of <command> is assumed to be received from
+the same service.
+
+Command and arguments are separated by an unescaped space.
+
+The following sequences have a special meaning:
+
+'% '::
+ Literal space in command or argument.
+
+'%%'::
+ Literal percent sign.
+
+'%s'::
+ Replaced with name (receive-pack, upload-pack, or
+ upload-archive) of the service git wants to invoke.
+
+'%S'::
+ Replaced with long name (git-receive-pack,
+ git-upload-pack, or git-upload-archive) of the service
+ git wants to invoke.
+
+'%G' (must be the first characters in an argument)::
+ This argument will not be passed to '<command>'. Instead, it
+ will cause the helper to start by sending git:// service requests to
+ the remote side with the service field set to an appropriate value and
+ the repository field set to rest of the argument. Default is not to send
+ such a request.
++
+This is useful if remote side is git:// server accessed over
+some tunnel.
+
+'%V' (must be first characters in argument)::
+ This argument will not be passed to '<command>'. Instead it sets
+ the vhost field in the git:// service request (to rest of the argument).
+ Default is not to send vhost in such request (if sent).
+
+ENVIRONMENT VARIABLES:
+----------------------
+
+GIT_TRANSLOOP_DEBUG::
+ If set, prints debugging information about various reads/writes.
+
+ENVIRONMENT VARIABLES PASSED TO COMMAND:
+----------------------------------------
+
+GIT_EXT_SERVICE::
+ Set to long name (git-upload-pack, etc...) of service helper needs
+ to invoke.
+
+GIT_EXT_SERVICE_NOPREFIX::
+ Set to long name (upload-pack, etc...) of service helper needs
+ to invoke.
+
+
+EXAMPLES:
+---------
+This remote helper is transparently used by git when
+you use commands such as "git fetch <URL>", "git clone <URL>",
+, "git push <URL>" or "git remote add <nick> <URL>", where <URL>
+begins with `ext::`. Examples:
+
+"ext::ssh -i /home/foo/.ssh/somekey user&#64;host.example %S 'foo/repo'"::
+ Like host.example:foo/repo, but use /home/foo/.ssh/somekey as
+ keypair and user as user on remote side. This avoids needing to
+ edit .ssh/config.
+
+"ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo"::
+ Represents repository with path /somerepo accessable over
+ git protocol at abstract namespace address /git-server.
+
+"ext::git-server-alias foo %G/repo"::
+ Represents a repository with path /repo accessed using the
+ helper program "git-server-alias foo". The path to the
+ repository and type of request are not passed on the command
+ line but as part of the protocol stream, as usual with git://
+ protocol.
+
+"ext::git-server-alias foo %G/repo %Vfoo"::
+ Represents a repository with path /repo accessed using the
+ helper program "git-server-alias foo". The hostname for the
+ remote server passed in the protocol stream will be "foo"
+ (this allows multiple virtual git servers to share a
+ link-level address).
+
+"ext::git-server-alias foo %G/repo% with% spaces %Vfoo"::
+ Represents a repository with path '/repo with spaces' accessed
+ using the helper program "git-server-alias foo". The hostname for
+ the remote server passed in the protocol stream will be "foo"
+ (this allows multiple virtual git servers to share a
+ link-level address).
+
+"ext::git-ssl foo.example /bar"::
+ Represents a repository accessed using the helper program
+ "git-ssl foo.example /bar". The type of request can be
+ determined by the helper using environment variables (see
+ above).
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara, Jonathan Nieder and the git list
+<git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt
new file mode 100644
index 0000000..4aecd4d
--- /dev/null
+++ b/Documentation/git-remote-fd.txt
@@ -0,0 +1,59 @@
+git-remote-fd(1)
+================
+
+NAME
+----
+git-remote-fd - Reflect smart transport stream back to caller
+
+SYNOPSIS
+--------
+"fd::<infd>[,<outfd>][/<anything>]" (as URL)
+
+DESCRIPTION
+-----------
+This helper uses specified file descriptors to connect to a remote git server.
+This is not meant for end users but for programs and scripts calling git
+fetch, push or archive.
+
+If only <infd> is given, it is assumed to be a bidirectional socket connected
+to remote git server (git-upload-pack, git-receive-pack or
+git-upload-achive). If both <infd> and <outfd> are given, they are assumed
+to be pipes connected to a remote git server (<infd> being the inbound pipe
+and <outfd> being the outbound pipe.
+
+It is assumed that any handshaking procedures have already been completed
+(such as sending service request for git://) before this helper is started.
+
+<anything> can be any string. It is ignored. It is meant for providing
+information to user in the URL in case that URL is displayed in some
+context.
+
+ENVIRONMENT VARIABLES
+---------------------
+GIT_TRANSLOOP_DEBUG::
+ If set, prints debugging information about various reads/writes.
+
+EXAMPLES
+--------
+git fetch fd::17 master::
+ Fetch master, using file descriptor #17 to communicate with
+ git-upload-pack.
+
+git fetch fd::17/foo master::
+ Same as above.
+
+git push fd::7,8 master (as URL)::
+ Push master, using file descriptor #7 to read data from
+ git-receive-pack and file descriptor #8 to write data to
+ same service.
+
+git push fd::7,8/bar master::
+ Same as above.
+
+Documentation
+--------------
+Documentation by Ilari Liusvaara and the git list <git@vger.kernel.org>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 3a23477..930b403 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -7,6 +7,7 @@ git-remote-helpers - Helper programs to interact with remote repositories
SYNOPSIS
--------
+[verse]
'git remote-<transport>' <repository> [<URL>]
DESCRIPTION
@@ -181,11 +182,11 @@ CAPABILITIES
When using the import command, expect the source ref to have
been written to the destination ref. The earliest applicable
refspec takes precedence. For example
- "refs/heads/*:refs/svn/origin/branches/*" means that, after an
- "import refs/heads/name", the script has written to
+ "refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}" means
+ that, after an "import refs/heads/name", the script has written to
refs/svn/origin/branches/name. If this capability is used at
all, it must cover all refs reported by the list command; if
- it is not used, it is effectively "*:*"
+ it is not used, it is effectively "{asterisk}:{asterisk}"
REF LIST ATTRIBUTES
-------------------
@@ -201,12 +202,12 @@ REF LIST ATTRIBUTES
OPTIONS
-------
-'option verbosity' <N>::
+'option verbosity' <n>::
Changes the verbosity of messages displayed by the helper.
- A value of 0 for N means that processes operate
+ A value of 0 for <n> means that processes operate
quietly, and the helper produces only error output.
1 is the default level of verbosity, and higher values
- of N correspond to the number of -v flags passed on the
+ of <n> correspond to the number of -v flags passed on the
command line.
'option progress' \{'true'|'false'\}::
@@ -239,10 +240,6 @@ SEE ALSO
--------
linkgit:git-remote[1]
-Documentation
--------------
-Documentation by Daniel Barkalow and Ilari Liusvaara
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index aa021b0..5a8c506 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url>
+'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>
'git remote rename' <old> <new>
'git remote rm' <name>
'git remote set-head' <name> (-a | -d | <branch>)
@@ -20,7 +20,7 @@ SYNOPSIS
'git remote set-url --delete' [--push] <name> <url>
'git remote' [-v | --verbose] 'show' [-n] <name>
'git remote prune' [-n | --dry-run] <name>
-'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
+'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...]
DESCRIPTION
-----------
@@ -60,22 +60,25 @@ the remote repository.
+
With `-t <branch>` option, instead of the default glob
refspec for the remote to track all branches under
-`$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>`
+the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>`
is created. You can give more than one `-t <branch>` to track
multiple branches without grabbing all branches.
+
-With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
+With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
up to point at remote's `<master>` branch. See also the set-head command.
+
-In mirror mode, enabled with `\--mirror`, the refs will not be stored
-in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option
-only makes sense in bare repositories. If a remote uses mirror
-mode, furthermore, `git push` will always behave as if `\--mirror`
-was passed.
+When a fetch mirror is created with `\--mirror=fetch`, the refs will not
+be stored in the 'refs/remotes/' namespace, but rather everything in
+'refs/' on the remote will be directly mirrored into 'refs/' in the
+local repository. This option only makes sense in bare repositories,
+because a fetch would overwrite any local commits.
++
+When a push mirror is created with `\--mirror=push`, then `git push`
+will always behave as if `\--mirror` was passed.
'rename'::
-Rename the remote named <old> to <new>. All remote tracking branches and
+Rename the remote named <old> to <new>. All remote-tracking branches and
configuration settings for the remote are updated.
+
In case <old> and <new> are the same, and <old> is a file under
@@ -84,29 +87,30 @@ the configuration file format.
'rm'::
-Remove the remote named <name>. All remote tracking branches and
+Remove the remote named <name>. All remote-tracking branches and
configuration settings for the remote are removed.
'set-head'::
-Sets or deletes the default branch (`$GIT_DIR/remotes/<name>/HEAD`) for
+Sets or deletes the default branch (i.e. the target of the
+symbolic-ref `refs/remotes/<name>/HEAD`) for
the named remote. Having a default branch for a remote is not required,
but allows the name of the remote to be specified in lieu of a specific
branch. For example, if the default branch for `origin` is set to
`master`, then `origin` may be specified wherever you would normally
specify `origin/master`.
+
-With `-d`, `$GIT_DIR/remotes/<name>/HEAD` is deleted.
+With `-d`, the symbolic ref `refs/remotes/<name>/HEAD` is deleted.
+
-With `-a`, the remote is queried to determine its `HEAD`, then
-`$GIT_DIR/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote
+With `-a`, the remote is queried to determine its `HEAD`, then the
+symbolic-ref `refs/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote
`HEAD` is pointed at `next`, "`git remote set-head origin -a`" will set
-`$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will
+the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will
only work if `refs/remotes/origin/next` already exists; if not it must be
fetched first.
+
-Use `<branch>` to set `$GIT_DIR/remotes/<name>/HEAD` explicitly. e.g., "git
-remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
+Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., "git
+remote set-head origin master" will set the symbolic-ref `refs/remotes/origin/HEAD` to
`refs/remotes/origin/master`. This will only work if
`refs/remotes/origin/master` already exists; if not it must be fetched first.
+
@@ -146,7 +150,7 @@ With `-n` option, the remote heads are not queried first with
'prune'::
-Deletes all stale tracking branches under <name>.
+Deletes all stale remote-tracking branches under <name>.
These stale branches have already been removed from the remote repository
referenced by <name>, but are still locally available in
"remotes/<name>".
@@ -214,16 +218,6 @@ linkgit:git-fetch[1]
linkgit:git-branch[1]
linkgit:git-config[1]
-Author
-------
-Written by Junio Hamano
-
-
-Documentation
---------------
-Documentation by J. Bruce Fields and the git-list <git@vger.kernel.org>.
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index 8c67d17..40af321 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -8,7 +8,8 @@ git-repack - Pack unpacked objects in a repository
SYNOPSIS
--------
-'git repack' [-a] [-A] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
+[verse]
+'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [--window=<n>] [--depth=<n>]
DESCRIPTION
-----------
@@ -62,6 +63,10 @@ other objects in that pack they already have locally.
linkgit:git-pack-objects[1].
-f::
+ Pass the `--no-reuse-delta` option to `git-pack-objects`, see
+ linkgit:git-pack-objects[1].
+
+-F::
Pass the `--no-reuse-object` option to `git-pack-objects`, see
linkgit:git-pack-objects[1].
@@ -76,8 +81,8 @@ other objects in that pack they already have locally.
this repository (or a direct copy of it)
over HTTP or FTP. See linkgit:git-update-server-info[1].
---window=[N]::
---depth=[N]::
+--window=<n>::
+--depth=<n>::
These two options affect how the objects contained in the pack are
stored using delta compression. The objects are first internally
sorted by type, size and optionally names and compared against the
@@ -87,10 +92,10 @@ other objects in that pack they already have locally.
to be applied that many times to get to the necessary object.
The default value for --window is 10 and --depth is 50.
---window-memory=[N]::
+--window-memory=<n>::
This option provides an additional limit on top of `--window`;
the window size will dynamically scale down so as to not take
- up more than N bytes in memory. This is useful in
+ up more than '<n>' bytes in memory. This is useful in
repositories with a mix of large and small objects to not run
out of memory with a large window, but still be able to take
advantage of the large window for the smaller objects. The
@@ -98,7 +103,7 @@ other objects in that pack they already have locally.
`--window-memory=0` makes memory usage unlimited, which is the
default.
---max-pack-size=[N]::
+--max-pack-size=<n>::
Maximum size of each output pack file. The size can be suffixed with
"k", "m", or "g". The minimum size allowed is limited to 1 MiB.
If specified, multiple packfiles may be created.
@@ -119,15 +124,6 @@ need to set the configuration variable `repack.UseDeltaBaseOffset` to
is unaffected by this option as the conversion is performed on the fly
as needed in that case.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Ryan Anderson <ryan@michonline.com>
-
SEE ALSO
--------
linkgit:git-pack-objects[1]
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt
index fde2092..17df525 100644
--- a/Documentation/git-replace.txt
+++ b/Documentation/git-replace.txt
@@ -80,17 +80,6 @@ linkgit:git-tag[1]
linkgit:git-branch[1]
linkgit:git[1]
-Author
-------
-Written by Christian Couder <chriscool@tuxfamily.org> and Junio C
-Hamano <gitster@pobox.com>, based on 'git tag' by Kristian Hogsberg
-<krh@redhat.com> and Carlos Rica <jasampler@gmail.com>.
-
-Documentation
---------------
-Documentation by Christian Couder <chriscool@tuxfamily.org> and the
-git-list <git@vger.kernel.org>, based on 'git tag' documentation.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index e5bdb55..9ec115b 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -8,6 +8,7 @@ git-repo-config - Get and set repository or global options
SYNOPSIS
--------
+[verse]
'git repo-config' ...
@@ -16,3 +17,7 @@ DESCRIPTION
This is a synonym for linkgit:git-config[1]. Please refer to the
documentation of that command.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt
index 19335fd..b99681c 100644
--- a/Documentation/git-request-pull.txt
+++ b/Documentation/git-request-pull.txt
@@ -7,7 +7,8 @@ git-request-pull - Generates a summary of pending changes
SYNOPSIS
--------
-'git request-pull' <start> <url> [<end>]
+[verse]
+'git request-pull' [-p] <start> <url> [<end>]
DESCRIPTION
-----------
@@ -17,6 +18,9 @@ the given URL in the generated summary.
OPTIONS
-------
+-p::
+ Show patch text
+
<start>::
Commit to start at.
@@ -26,14 +30,6 @@ OPTIONS
<end>::
Commit to end at; defaults to HEAD.
-Author
-------
-Written by Ryan Anderson <ryan@michonline.com> and Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index acc220a..a6253ba 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -7,7 +7,8 @@ git-rerere - Reuse recorded resolution of conflicted merges
SYNOPSIS
--------
-'git rerere' ['clear'|'diff'|'status'|'gc']
+[verse]
+'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc']
DESCRIPTION
-----------
@@ -40,6 +41,11 @@ This resets the metadata used by rerere if a merge resolution is to be
aborted. Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]'
will automatically invoke this command.
+'forget' <pathspec>::
+
+This resets the conflict resolutions which rerere has recorded for the current
+conflict in <pathspec>.
+
'diff'::
This displays diffs for the current state of the resolution. It is
@@ -200,11 +206,6 @@ would conflict the same way as the test merge you resolved earlier.
'git rerere' will be run by 'git rebase' to help you resolve this
conflict.
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 645f0c1..b2832fc 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -8,183 +8,119 @@ git-reset - Reset current HEAD to the specified state
SYNOPSIS
--------
[verse]
-'git reset' [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]
'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' --patch [<commit>] [--] [<paths>...]
+'git reset' [--patch|-p] [<commit>] [--] [<paths>...]
+'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
DESCRIPTION
-----------
-Sets the current head to the specified commit and optionally resets the
-index and working tree to match.
-
-This command is useful if you notice some small error in a recent
-commit (or set of commits) and want to redo that part without showing
-the undo in the history.
-
-If you want to undo a commit other than the latest on a branch,
-linkgit:git-revert[1] is your friend.
-
-The second and third forms with 'paths' and/or --patch are used to
-revert selected paths in the index from a given commit, without moving
-HEAD.
-
+In the first and second form, copy entries from <commit> to the index.
+In the third form, set the current branch head (HEAD) to <commit>, optionally
+modifying index and working tree to match. The <commit> defaults to HEAD
+in all forms.
+
+'git reset' [-q] [<commit>] [--] <paths>...::
+ This form resets the index entries for all <paths> to their
+ state at <commit>. (It does not affect the working tree, nor
+ the current branch.)
++
+This means that `git reset <paths>` is the opposite of `git add
+<paths>`.
++
+After running `git reset <paths>` to update the index entry, you can
+use linkgit:git-checkout[1] to check the contents out of the index to
+the working tree.
+Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
+can copy the contents of a path out of a commit to the index and to the
+working tree in one go.
+
+'git reset' --patch|-p [<commit>] [--] [<paths>...]::
+ Interactively select hunks in the difference between the index
+ and <commit> (defaults to HEAD). The chosen hunks are applied
+ in reverse to the index.
++
+This means that `git reset -p` is the opposite of `git add -p`, i.e.
+you can use it to selectively reset hunks. See the ``Interactive Mode''
+section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+
+'git reset' [--<mode>] [<commit>]::
+ This form resets the current branch head to <commit> and
+ possibly updates the index (resetting it to the tree of <commit>) and
+ the working tree depending on <mode>, which
+ must be one of the following:
++
+--
+--soft::
+ Does not touch the index file nor the working tree at all (but
+ resets the head to <commit>, just like all modes do). This leaves
+ all your changed files "Changes to be committed", as 'git status'
+ would put it.
-OPTIONS
--------
--mixed::
Resets the index but not the working tree (i.e., the changed files
are preserved but not marked for commit) and reports what has not
been updated. This is the default action.
---soft::
- Does not touch the index file nor the working tree at all, but
- requires them to be in a good order. This leaves all your changed
- files "Changes to be committed", as 'git status' would
- put it.
-
--hard::
- Matches the working tree and index to that of the tree being
- switched to. Any changes to tracked files in the working tree
- since <commit> are lost.
+ Resets the index and working tree. Any changes to tracked files in the
+ working tree since <commit> are discarded.
--merge::
- Resets the index to match the tree recorded by the named commit,
- and updates the files that are different between the named commit
- and the current commit in the working tree.
-
---keep::
- Reset the index to the given commit, keeping local changes in
- the working tree since the current commit, while updating
- working tree files without local changes to what appears in
- the given commit. If a file that is different between the
- current commit and the given commit has local changes, reset
- is aborted.
-
--p::
---patch::
- Interactively select hunks in the difference between the index
- and <commit> (defaults to HEAD). The chosen hunks are applied
- in reverse to the index.
+ Resets the index and updates the files in the working tree that are
+ different between <commit> and HEAD, but keeps those which are
+ different between the index and working tree (i.e. which have changes
+ which have not been added).
+ If a file that is different between <commit> and the index has unstaged
+ changes, reset is aborted.
+
-This means that `git reset -p` is the opposite of `git add -p` (see
-linkgit:git-add[1]).
-
--q::
---quiet::
- Be quiet, only report errors.
-
-<commit>::
- Commit to make the current HEAD. If not given defaults to HEAD.
-
-DISCUSSION
-----------
-
-The tables below show what happens when running:
-
-----------
-git reset --option target
-----------
+In other words, --merge does something like a 'git read-tree -u -m <commit>',
+but carries forward unmerged index entries.
-to reset the HEAD to another commit (`target`) with the different
-reset options depending on the state of the files.
-
-In these tables, A, B, C and D are some different states of a
-file. For example, the first line of the first table means that if a
-file is in state A in the working tree, in state B in the index, in
-state C in HEAD and in state D in the target, then "git reset --soft
-target" will put the file in state A in the working tree, in state B
-in the index and in state D in HEAD.
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- A B C D --soft A B D
- --mixed A D D
- --hard D D D
- --merge (disallowed)
- --keep (disallowed)
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- A B C C --soft A B C
- --mixed A C C
- --hard C C C
- --merge (disallowed)
- --keep A C C
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- B B C D --soft B B D
- --mixed B D D
- --hard D D D
- --merge D D D
- --keep (disallowed)
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- B B C C --soft B B C
- --mixed B C C
- --hard C C C
- --merge C C C
- --keep B C C
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- B C C D --soft B C D
- --mixed B D D
- --hard D D D
- --merge (disallowed)
- --keep (disallowed)
-
- working index HEAD target working index HEAD
- ----------------------------------------------------
- B C C C --soft B C C
- --mixed B C C
- --hard C C C
- --merge B C C
- --keep B C C
-
-"reset --merge" is meant to be used when resetting out of a conflicted
-merge. Any mergy operation guarantees that the work tree file that is
-involved in the merge does not have local change wrt the index before
-it starts, and that it writes the result out to the work tree. So if
-we see some difference between the index and the target and also
-between the index and the work tree, then it means that we are not
-resetting out from a state that a mergy operation left after failing
-with a conflict. That is why we disallow --merge option in this case.
+--keep::
+ Resets index entries and updates files in the working tree that are
+ different between <commit> and HEAD.
+ If a file that is different between <commit> and HEAD has local changes,
+ reset is aborted.
+--
-"reset --keep" is meant to be used when removing some of the last
-commits in the current branch while keeping changes in the working
-tree. If there could be conflicts between the changes in the commit we
-want to remove and the changes in the working tree we want to keep,
-the reset is disallowed. That's why it is disallowed if there are both
-changes between the working tree and HEAD, and between HEAD and the
-target. To be safe, it is also disallowed when there are unmerged
-entries.
+If you want to undo a commit other than the latest on a branch,
+linkgit:git-revert[1] is your friend.
-The following tables show what happens when there are unmerged
-entries:
- working index HEAD target working index HEAD
- ----------------------------------------------------
- X U A B --soft (disallowed)
- --mixed X B B
- --hard B B B
- --merge B B B
- --keep (disallowed)
+OPTIONS
+-------
- working index HEAD target working index HEAD
- ----------------------------------------------------
- X U A A --soft (disallowed)
- --mixed X A A
- --hard A A A
- --merge A A A
- --keep (disallowed)
+-q::
+--quiet::
+ Be quiet, only report errors.
-X means any state and U means an unmerged index.
-Examples
+EXAMPLES
--------
+Undo add::
++
+------------
+$ edit <1>
+$ git add frotz.c filfre.c
+$ mailx <2>
+$ git reset <3>
+$ git pull git://info.example.com/ nitfol <4>
+------------
++
+<1> You are happily working on something, and find the changes
+in these files are in good order. You do not want to see them
+when you run "git diff", because you plan to work on other files
+and changes with these files are distracting.
+<2> Somebody asks you to pull, and the changes sounds worthy of merging.
+<3> However, you already dirtied the index (i.e. your index does
+not match the HEAD commit). But you know the pull you are going
+to make does not affect frotz.c nor filfre.c, so you revert the
+index changes for these two files. Your changes in working tree
+remain there.
+<4> Then you can pull and merge, leaving frotz.c and filfre.c
+changes still in the working tree.
+
Undo a commit and redo::
+
------------
@@ -204,19 +140,6 @@ edit the message further, you can give -C option instead.
+
See also the --amend option to linkgit:git-commit[1].
-Undo commits permanently::
-+
-------------
-$ git commit ...
-$ git reset --hard HEAD~3 <1>
-------------
-+
-<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
-and you do not want to ever see them again. Do *not* do this if
-you have already given these commits to somebody else. (See the
-"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
-the implications of doing so.)
-
Undo a commit, making it a topic branch::
+
------------
@@ -232,28 +155,18 @@ current HEAD.
<2> Rewind the master branch to get rid of those three commits.
<3> Switch to "topic/wip" branch and keep working.
-Undo add::
+Undo commits permanently::
+
------------
-$ edit <1>
-$ git add frotz.c filfre.c
-$ mailx <2>
-$ git reset <3>
-$ git pull git://info.example.com/ nitfol <4>
+$ git commit ...
+$ git reset --hard HEAD~3 <1>
------------
+
-<1> You are happily working on something, and find the changes
-in these files are in good order. You do not want to see them
-when you run "git diff", because you plan to work on other files
-and changes with these files are distracting.
-<2> Somebody asks you to pull, and the changes sounds worthy of merging.
-<3> However, you already dirtied the index (i.e. your index does
-not match the HEAD commit). But you know the pull you are going
-to make does not affect frotz.c nor filfre.c, so you revert the
-index changes for these two files. Your changes in working tree
-remain there.
-<4> Then you can pull and merge, leaving frotz.c and filfre.c
-changes still in the working tree.
+<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
+and you do not want to ever see them again. Do *not* do this if
+you have already given these commits to somebody else. (See the
+"RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1] for
+the implications of doing so.)
Undo a merge or pull::
+
@@ -283,7 +196,7 @@ tip of the current branch in ORIG_HEAD, so resetting hard to it
brings your index file and the working tree back to that state,
and resets the tip of the branch to that commit.
-Undo a merge or pull inside a dirty work tree::
+Undo a merge or pull inside a dirty working tree::
+
------------
$ git pull <1>
@@ -355,8 +268,8 @@ Keep changes in working tree while discarding some previous commits::
Suppose you are working on something and you commit it, and then you
continue working a bit more, but now you think that what you have in
your working tree should be in another branch that has nothing to do
-with what you commited previously. You can start a new branch and
-reset it while keeping the changes in your work tree.
+with what you committed previously. You can start a new branch and
+reset it while keeping the changes in your working tree.
+
------------
$ git tag start
@@ -376,13 +289,114 @@ $ git reset --keep start <3>
<3> But you can use "reset --keep" to remove the unwanted commit after
you switched to "branch2".
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds <torvalds@osdl.org>
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+DISCUSSION
+----------
+
+The tables below show what happens when running:
+
+----------
+git reset --option target
+----------
+
+to reset the HEAD to another commit (`target`) with the different
+reset options depending on the state of the files.
+
+In these tables, A, B, C and D are some different states of a
+file. For example, the first line of the first table means that if a
+file is in state A in the working tree, in state B in the index, in
+state C in HEAD and in state D in the target, then "git reset --soft
+target" will leave the file in the working tree in state A and in the
+index in state B. It resets (i.e. moves) the HEAD (i.e. the tip of
+the current branch, if you are on one) to "target" (which has the file
+in state D).
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ A B C D --soft A B D
+ --mixed A D D
+ --hard D D D
+ --merge (disallowed)
+ --keep (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ A B C C --soft A B C
+ --mixed A C C
+ --hard C C C
+ --merge (disallowed)
+ --keep A C C
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B B C D --soft B B D
+ --mixed B D D
+ --hard D D D
+ --merge D D D
+ --keep (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B B C C --soft B B C
+ --mixed B C C
+ --hard C C C
+ --merge C C C
+ --keep B C C
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B C C D --soft B C D
+ --mixed B D D
+ --hard D D D
+ --merge (disallowed)
+ --keep (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B C C C --soft B C C
+ --mixed B C C
+ --hard C C C
+ --merge B C C
+ --keep B C C
+
+"reset --merge" is meant to be used when resetting out of a conflicted
+merge. Any mergy operation guarantees that the working tree file that is
+involved in the merge does not have local change wrt the index before
+it starts, and that it writes the result out to the working tree. So if
+we see some difference between the index and the target and also
+between the index and the working tree, then it means that we are not
+resetting out from a state that a mergy operation left after failing
+with a conflict. That is why we disallow --merge option in this case.
+
+"reset --keep" is meant to be used when removing some of the last
+commits in the current branch while keeping changes in the working
+tree. If there could be conflicts between the changes in the commit we
+want to remove and the changes in the working tree we want to keep,
+the reset is disallowed. That's why it is disallowed if there are both
+changes between the working tree and HEAD, and between HEAD and the
+target. To be safe, it is also disallowed when there are unmerged
+entries.
+
+The following tables show what happens when there are unmerged
+entries:
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ X U A B --soft (disallowed)
+ --mixed X B B
+ --hard B B B
+ --merge B B B
+ --keep (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ X U A A --soft (disallowed)
+ --mixed X A A
+ --hard A A A
+ --merge A A A
+ --keep (disallowed)
+
+X means any state and U means an unmerged index.
GIT
---
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 173f3fc..38fafca 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,35 +9,43 @@ git-rev-list - Lists commit objects in reverse chronological order
SYNOPSIS
--------
[verse]
-'git rev-list' [ \--max-count=number ]
- [ \--skip=number ]
- [ \--max-age=timestamp ]
- [ \--min-age=timestamp ]
+'git rev-list' [ \--max-count=<number> ]
+ [ \--skip=<number> ]
+ [ \--max-age=<timestamp> ]
+ [ \--min-age=<timestamp> ]
[ \--sparse ]
[ \--merges ]
[ \--no-merges ]
+ [ \--min-parents=<number> ]
+ [ \--no-min-parents ]
+ [ \--max-parents=<number> ]
+ [ \--no-max-parents ]
[ \--first-parent ]
[ \--remove-empty ]
[ \--full-history ]
[ \--not ]
[ \--all ]
- [ \--branches[=pattern] ]
- [ \--tags[=pattern] ]
- [ \--remotes[=pattern] ]
- [ \--glob=glob-pattern ]
+ [ \--branches[=<pattern>] ]
+ [ \--tags[=<pattern>] ]
+ [ \--remotes[=<pattern>] ]
+ [ \--glob=<glob-pattern> ]
+ [ \--ignore-missing ]
[ \--stdin ]
[ \--quiet ]
[ \--topo-order ]
[ \--parents ]
[ \--timestamp ]
[ \--left-right ]
+ [ \--left-only ]
+ [ \--right-only ]
+ [ \--cherry-mark ]
[ \--cherry-pick ]
[ \--encoding[=<encoding>] ]
[ \--(author|committer|grep)=<pattern> ]
[ \--regexp-ignore-case | -i ]
[ \--extended-regexp | -E ]
[ \--fixed-strings | -F ]
- [ \--date={local|relative|default|iso|rfc|short} ]
+ [ \--date=(local|relative|default|iso|rfc|short) ]
[ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
[ \--bisect ]
@@ -105,16 +113,6 @@ include::rev-list-options.txt[]
include::pretty-formats.txt[]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano, Jonas Fonseca
-and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 833a2a2..42c9676 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -8,6 +8,7 @@ git-rev-parse - Pick out and massage parameters
SYNOPSIS
--------
+[verse]
'git rev-parse' [ --option ] <args>...
DESCRIPTION
@@ -74,7 +75,7 @@ OPTIONS
properly quoted for consumption by shell. Useful when
you expect your parameter to contain whitespaces and
newlines (e.g. when using pickaxe `-S` with
- 'git diff-\*'). In contrast to the `--sq-quote` option,
+ 'git diff-{asterisk}'). In contrast to the `--sq-quote` option,
the command input is still interpreted as usual.
--not::
@@ -95,7 +96,7 @@ OPTIONS
unfortunately named tag "master"), and show them as full
refnames (e.g. "refs/heads/master").
---abbrev-ref[={strict|loose}]::
+--abbrev-ref[=(strict|loose)]::
A non-ambiguous short name of the objects name.
The option core.warnAmbiguousRefs is used to select the strict
abbreviation mode.
@@ -112,14 +113,15 @@ OPTIONS
+
If a `pattern` is given, only refs matching the given shell glob are
shown. If the pattern does not contain a globbing character (`?`,
-`\*`, or `[`), it is turned into a prefix match by appending `/\*`.
+`{asterisk}`, or `[`), it is turned into a prefix match by
+appending `/{asterisk}`.
--glob=pattern::
Show all refs matching the shell glob pattern `pattern`. If
the pattern does not start with `refs/`, this is automatically
prepended. If the pattern does not contain a globbing
- character (`?`, `\*`, or `[`), it is turned into a prefix
- match by appending `/\*`.
+ character (`?`, `{asterisk}`, or `[`), it is turned into a prefix
+ match by appending `/{asterisk}`.
--show-toplevel::
Show the absolute path of the top-level directory.
@@ -135,7 +137,12 @@ shown. If the pattern does not contain a globbing character (`?`,
directory (typically a sequence of "../", or an empty string).
--git-dir::
- Show `$GIT_DIR` if defined else show the path to the .git directory.
+ Show `$GIT_DIR` if defined. Otherwise show the path to
+ the .git directory, relative to the current directory.
++
+If `$GIT_DIR` is not defined and the current directory
+is not detected to lie in a git repository or work tree
+print a message to stderr and exit with nonzero status.
--is-inside-git-dir::
When the current working directory is below the repository
@@ -174,205 +181,7 @@ shown. If the pattern does not contain a globbing character (`?`,
Flags and parameters to be parsed.
-SPECIFYING REVISIONS
---------------------
-
-A revision parameter typically, but not necessarily, names a
-commit object. They use what is called an 'extended SHA1'
-syntax. Here are various ways to spell object names. The
-ones listed near the end of this list are to name trees and
-blobs contained in a commit.
-
-* The full SHA1 object name (40-byte hexadecimal string), or
- a substring of such that is unique within the repository.
- E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both
- name the same commit object if there are no other object in
- your repository whose object name starts with dae86e.
-
-* An output from 'git describe'; i.e. a closest tag, optionally
- followed by a dash and a number of commits, followed by a dash, a
- `g`, and an abbreviated object name.
-
-* A symbolic ref name. E.g. 'master' typically means the commit
- object referenced by refs/heads/master. If you
- happen to have both heads/master and tags/master, you can
- explicitly say 'heads/master' to tell git which one you mean.
- When ambiguous, a `<name>` is disambiguated by taking the
- first match in the following rules:
-
- . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
- useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`);
-
- . otherwise, `refs/<name>` if exists;
-
- . otherwise, `refs/tags/<name>` if exists;
-
- . otherwise, `refs/heads/<name>` if exists;
-
- . otherwise, `refs/remotes/<name>` if exists;
-
- . otherwise, `refs/remotes/<name>/HEAD` if exists.
-+
-HEAD names the commit your changes in the working tree is based on.
-FETCH_HEAD records the branch you fetched from a remote repository
-with your last 'git fetch' invocation.
-ORIG_HEAD is created by commands that moves your HEAD in a drastic
-way, to record the position of the HEAD before their operation, so that
-you can change the tip of the branch back to the state before you ran
-them easily.
-MERGE_HEAD records the commit(s) you are merging into your branch
-when you run 'git merge'.
-+
-Note that any of the `refs/*` cases above may come either from
-the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.
-
-* A ref followed by the suffix '@' with a date specification
- enclosed in a brace
- pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
- second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
- of the ref at a prior point in time. This suffix may only be
- used immediately following a ref name and the ref must have an
- existing log ($GIT_DIR/logs/<ref>). Note that this looks up the state
- of your *local* ref at a given time; e.g., what was in your local
- `master` branch last week. If you want to look at commits made during
- certain times, see `--since` and `--until`.
-
-* A ref followed by the suffix '@' with an ordinal specification
- enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify
- the n-th prior value of that ref. For example 'master@\{1\}'
- is the immediate prior value of 'master' while 'master@\{5\}'
- is the 5th prior value of 'master'. This suffix may only be used
- immediately following a ref name and the ref must have an existing
- log ($GIT_DIR/logs/<ref>).
-
-* You can use the '@' construct with an empty ref part to get at a
- reflog of the current branch. For example, if you are on the
- branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
-
-* The special construct '@\{-<n>\}' means the <n>th branch checked out
- before the current one.
-
-* The suffix '@\{upstream\}' to a ref (short form 'ref@\{u\}') refers to
- the branch the ref is set to build on top of. Missing ref defaults
- to the current branch.
-
-* A suffix '{caret}' to a revision parameter (e.g. 'HEAD{caret}') means the first parent of
- that commit object. '{caret}<n>' means the <n>th parent (i.e.
- 'rev{caret}'
- is equivalent to 'rev{caret}1'). As a special rule,
- 'rev{caret}0' means the commit itself and is used when 'rev' is the
- object name of a tag object that refers to a commit object.
-
-* A suffix '{tilde}<n>' to a revision parameter means the commit
- object that is the <n>th generation grand-parent of the named
- commit object, following only the first parent. I.e. rev~3 is
- equivalent to rev{caret}{caret}{caret} which is equivalent to
- rev{caret}1{caret}1{caret}1. See below for a illustration of
- the usage of this form.
-
-* A suffix '{caret}' followed by an object type name enclosed in
- brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
- could be a tag, and dereference the tag recursively until an
- object of that type is found or the object cannot be
- dereferenced anymore (in which case, barf). `rev{caret}0`
- introduced earlier is a short-hand for `rev{caret}\{commit\}`.
-
-* A suffix '{caret}' followed by an empty brace pair
- (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag,
- and dereference the tag recursively until a non-tag object is
- found.
-
-* A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
- a commit whose commit message starts with the specified text.
- This name returns the youngest matching commit which is
- reachable from any ref. If the commit message starts with a
- '!', you have to repeat that; the special sequence ':/!',
- followed by something else than '!' is reserved for now.
-
-* A suffix ':' followed by a path (e.g. `HEAD:README`); this names the blob or tree
- at the given path in the tree-ish object named by the part
- before the colon.
- ':path' (with an empty part before the colon, e.g. `:README`)
- is a special case of the syntax described next: content
- recorded in the index at the given path.
-
-* A colon, optionally followed by a stage number (0 to 3) and a
- colon, followed by a path (e.g. `:0:README`); this names a blob object in the
- index at the given path. Missing stage number (and the colon
- that follows it, e.g. `:README`) names a stage 0 entry. During a merge, stage
- 1 is the common ancestor, stage 2 is the target branch's version
- (typically the current branch), and stage 3 is the version from
- the branch being merged.
-
-Here is an illustration, by Jon Loeliger. Both commit nodes B
-and C are parents of commit node A. Parent commits are ordered
-left-to-right.
-
-........................................
-G H I J
- \ / \ /
- D E F
- \ | / \
- \ | / |
- \|/ |
- B C
- \ /
- \ /
- A
-........................................
-
- A = = A^0
- B = A^ = A^1 = A~1
- C = A^2 = A^2
- D = A^^ = A^1^1 = A~2
- E = B^2 = A^^2
- F = B^3 = A^^3
- G = A^^^ = A^1^1^1 = A~3
- H = D^2 = B^^2 = A^^^2 = A~2^2
- I = F^ = B^3^ = A^^3^
- J = F^2 = B^3^2 = A^^3^2
-
-
-SPECIFYING RANGES
------------------
-
-History traversing commands such as 'git log' operate on a set
-of commits, not just a single commit. To these commands,
-specifying a single revision with the notation described in the
-previous section means the set of commits reachable from that
-commit, following the commit ancestry chain.
-
-To exclude commits reachable from a commit, a prefix `{caret}`
-notation is used. E.g. `{caret}r1 r2` means commits reachable
-from `r2` but exclude the ones reachable from `r1`.
-
-This set operation appears so often that there is a shorthand
-for it. When you have two commits `r1` and `r2` (named according
-to the syntax explained in SPECIFYING REVISIONS above), you can ask
-for commits that are reachable from r2 excluding those that are reachable
-from r1 by `{caret}r1 r2` and it can be written as `r1..r2`.
-
-A similar notation `r1\...r2` is called symmetric difference
-of `r1` and `r2` and is defined as
-`r1 r2 --not $(git merge-base --all r1 r2)`.
-It is the set of commits that are reachable from either one of
-`r1` or `r2` but not from both.
-
-Two other shorthands for naming a set that is formed by a commit
-and its parent commits exist. The `r1{caret}@` notation means all
-parents of `r1`. `r1{caret}!` includes commit `r1` but excludes
-all of its parents.
-
-Here are a handful of examples:
-
- D G H D
- D F G H I J D F
- ^G D H D
- ^D B E I J F B
- B...C G H D E B C
- ^D B C E I J F B C
- C^@ I J F
- F^! D G H D F
+include::revisions.txt[]
PARSEOPT
--------
@@ -382,10 +191,13 @@ scripts the same facilities C builtins have. It works as an option normalizer
(e.g. splits single switches aggregate values), a bit like `getopt(1)` does.
It takes on the standard input the specification of the options to parse and
-understand, and echoes on the standard output a line suitable for `sh(1)` `eval`
+understand, and echoes on the standard output a string suitable for `sh(1)` `eval`
to replace the arguments with normalized ones. In case of error, it outputs
usage on the standard error stream, and exits with code 129.
+Note: Make sure you quote the result when passing it to `eval`. See
+below for an example.
+
Input Format
~~~~~~~~~~~~
@@ -442,7 +254,7 @@ bar= some cool option --bar with an argument
An option group Header
C? option C with an optional argument"
-eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?`
+eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
------------
SQ-QUOTE
@@ -497,16 +309,6 @@ $ git rev-parse --default master --verify $REV
+
but if $REV is empty, the commit object name from master will be printed.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> .
-Junio C Hamano <gitster@pobox.com> and Pierre Habouzit <madcoder@debian.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index dea4f53..3d0a7d1 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -7,6 +7,7 @@ git-revert - Revert some existing commits
SYNOPSIS
--------
+[verse]
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
DESCRIPTION
@@ -23,7 +24,7 @@ throw away all uncommitted changes in your working directory, you
should see linkgit:git-reset[1], particularly the '--hard' option. If
you want to extract specific files as they were in another commit, you
should see linkgit:git-checkout[1], specifically the `git checkout
-<commit> -- <filename>` syntax. Take care with these alternatives as
+<commit> \-- <filename>` syntax. Take care with these alternatives as
both will discard uncommitted changes in your working directory.
OPTIONS
@@ -31,7 +32,7 @@ OPTIONS
<commit>...::
Commits to revert.
For a more complete list of ways to spell commit names, see
- "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+ linkgit:gitrevisions[7].
Sets of commits can also be given but no traversal is done by
default, see linkgit:git-rev-list[1] and its '--no-walk'
option.
@@ -80,6 +81,16 @@ effect to your index in a row.
--signoff::
Add Signed-off-by line at the end of the commit message.
+--strategy=<strategy>::
+ Use the given merge strategy. Should only be used once.
+ See the MERGE STRATEGIES section in linkgit:git-merge[1]
+ for details.
+
+-X<option>::
+--strategy-option=<option>::
+ Pass the merge strategy-specific option through to the
+ merge strategy. See linkgit:git-merge[1] for details.
+
EXAMPLES
--------
git revert HEAD~3::
@@ -87,7 +98,7 @@ git revert HEAD~3::
Revert the changes specified by the fourth last commit in HEAD
and create a new commit with the reverted changes.
-git revert -n master\~5..master~2::
+git revert -n master{tilde}5..master{tilde}2::
Revert the changes done by commits from the fifth last commit
in master (included) to the third last commit in master
@@ -95,14 +106,6 @@ git revert -n master\~5..master~2::
changes. The revert only modifies the working tree and the
index.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
SEE ALSO
--------
linkgit:git-cherry-pick[1]
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
index c21d19e..da0215d 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -7,6 +7,7 @@ git-rm - Remove files from the working tree and from the index
SYNOPSIS
--------
+[verse]
'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...
DESCRIPTION
@@ -78,7 +79,8 @@ a file that you have not told git about does not remove that file.
File globbing matches across directory boundaries. Thus, given
two directories `d` and `d2`, there is a difference between
-using `git rm \'d\*\'` and `git rm \'d/\*\'`, as the former will
+using `git rm {apostrophe}d{asterisk}{apostrophe}` and
+`git rm {apostrophe}d/{asterisk}{apostrophe}`, as the former will
also remove all of directory `d2`.
REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
@@ -88,8 +90,8 @@ the paths that have disappeared from the filesystem. However,
depending on the use case, there are several ways that can be
done.
-Using "git commit -a"
-~~~~~~~~~~~~~~~~~~~~~
+Using ``git commit -a''
+~~~~~~~~~~~~~~~~~~~~~~~
If you intend that your next commit should record all modifications
of tracked files in the working tree and record all removals of
files that have been removed from the working tree with `rm`
@@ -97,8 +99,8 @@ files that have been removed from the working tree with `rm`
automatically notice and record all removals. You can also have a
similar effect without committing by using `git add -u`.
-Using "git add -A"
-~~~~~~~~~~~~~~~~~~
+Using ``git add -A''
+~~~~~~~~~~~~~~~~~~~~
When accepting a new code drop for a vendor branch, you probably
want to record both the removal of paths and additions of new paths
as well as modifications of existing paths.
@@ -110,8 +112,8 @@ tree using this command:
git ls-files -z | xargs -0 rm -f
----------------
-and then "untar" the new code in the working tree. Alternately
-you could "rsync" the changes into the working tree.
+and then untar the new code in the working tree. Alternately
+you could 'rsync' the changes into the working tree.
After that, the easiest way to record all removals, additions, and
modifications in the working tree is:
@@ -135,11 +137,11 @@ git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
EXAMPLES
--------
-git rm Documentation/\\*.txt::
- Removes all `\*.txt` files from the index that are under the
+git rm Documentation/\*.txt::
+ Removes all `*.txt` files from the index that are under the
`Documentation` directory and any of its subdirectories.
+
-Note that the asterisk `\*` is quoted from the shell in this
+Note that the asterisk `*` is quoted from the shell in this
example; this lets git, and not the shell, expand the pathnames
of files and subdirectories under the `Documentation/` directory.
@@ -152,14 +154,6 @@ SEE ALSO
--------
linkgit:git-add[1]
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index c283084..327233c 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -8,6 +8,7 @@ git-send-email - Send a collection of patches as emails
SYNOPSIS
--------
+[verse]
'git send-email' [options] <file|directory|rev-list options>...
@@ -82,11 +83,26 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
set, as returned by "git var -l".
--in-reply-to=<identifier>::
- Specify the contents of the first In-Reply-To header.
- Subsequent emails will refer to the previous email
- instead of this if --chain-reply-to is set.
- Only necessary if --compose is also set. If --compose
- is not set, this will be prompted for.
+ Make the first mail (or all the mails with `--no-thread`) appear as a
+ reply to the given Message-Id, which avoids breaking threads to
+ provide a new patch series.
+ The second and subsequent emails will be sent as replies according to
+ the `--[no]-chain-reply-to` setting.
++
+So for example when `--thread` and `--no-chain-reply-to` are specified, the
+second and subsequent patches will be replies to the first one like in the
+illustration below where `[PATCH v2 0/3]` is in reply to `[PATCH 0/2]`:
++
+ [PATCH 0/2] Here is what I did...
+ [PATCH 1/2] Clean up and tests
+ [PATCH 2/2] Implementation
+ [PATCH v2 0/3] Here is a reroll
+ [PATCH v2 1/3] Clean up
+ [PATCH v2 2/3] New tests
+ [PATCH v2 3/3] Implementation
++
+Only necessary if --compose is also set. If --compose
+is not set, this will be prompted for.
--subject=<string>::
Specify the initial subject of the email thread.
@@ -97,7 +113,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
Specify the primary recipient of the emails generated. Generally, this
will be the upstream maintainer of the project involved. Default is the
value of the 'sendemail.to' configuration value; if that is unspecified,
- this will be prompted for.
+ and --to-cmd is not specified, this will be prompted for.
+
The --to option must be repeated for each user you want on the to list.
@@ -165,6 +181,15 @@ user is prompted for a password while the input is masked for privacy.
are also accepted. The port can also be set with the
'sendemail.smtpserverport' configuration variable.
+--smtp-server-option=<option>::
+ If set, specifies the outgoing SMTP server option to use.
+ Default value can be specified by the 'sendemail.smtpserveroption'
+ configuration option.
++
+The --smtp-server-option option must be repeated for each option you want
+to pass to the server. Likewise, different lines in the configuration files
+must be used for each option.
+
--smtp-ssl::
Legacy alias for '--smtp-encryption ssl'.
@@ -177,6 +202,12 @@ user is prompted for a password while the input is masked for privacy.
Automating
~~~~~~~~~~
+--to-cmd=<command>::
+ Specify a command to execute once per patch file which
+ should generate patch file specific "To:" entries.
+ Output of this command must be single email address per line.
+ Default is the value of 'sendemail.tocmd' configuration value.
+
--cc-cmd=<command>::
Specify a command to execute once per patch file which
should generate patch file specific "Cc:" entries.
@@ -292,6 +323,9 @@ have been specified, in which case default to 'compose'.
Default is the value of 'sendemail.validate'; if this is not set,
default to '--validate'.
+--force::
+ Send emails even if safety checks would prevent it.
+
CONFIGURATION
-------------
@@ -315,11 +349,12 @@ sendemail.confirm::
one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm'
in the previous section for the meaning of these values.
-
+EXAMPLE
+-------
Use gmail as the smtp server
-----------------------------
-
-Add the following section to the config file:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To use 'git send-email' to send your patches through the GMail SMTP server,
+edit ~/.gitconfig to specify your account settings:
[sendemail]
smtpencryption = tls
@@ -327,22 +362,19 @@ Add the following section to the config file:
smtpuser = yourname@gmail.com
smtpserverport = 587
-Note: the following perl modules are required
- Net::SMTP::SSL, MIME::Base64 and Authen::SASL
-
+Once your commits are ready to be sent to the mailing list, run the
+following commands:
-Author
-------
-Written by Ryan Anderson <ryan@michonline.com>
+ $ git format-patch --cover-letter -M origin/master -o outgoing/
+ $ edit outgoing/0000-*
+ $ git send-email outgoing/*
-git-send-email is originally based upon
-send_lots_of_email.pl by Greg Kroah-Hartman.
-
-
-Documentation
---------------
-Documentation by Ryan Anderson
+Note: the following perl modules are required
+ Net::SMTP::SSL, MIME::Base64 and Authen::SASL
+SEE ALSO
+--------
+linkgit:git-format-patch[1], linkgit:git-imap-send[1], mbox(5)
GIT
---
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index deaa7d9..bd3eaa6 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -8,6 +8,7 @@ git-send-pack - Push objects over git protocol to another repository
SYNOPSIS
--------
+[verse]
'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
DESCRIPTION
@@ -114,15 +115,6 @@ With '--force', the fast-forward check is disabled for all refs.
Optionally, a <ref> parameter can be prefixed with a plus '+' sign
to disable the fast-forward check only on that ref.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-sh-i18n--envsubst.txt b/Documentation/git-sh-i18n--envsubst.txt
new file mode 100644
index 0000000..5c3ec32
--- /dev/null
+++ b/Documentation/git-sh-i18n--envsubst.txt
@@ -0,0 +1,36 @@
+git-sh-i18n{litdd}envsubst(1)
+=============================
+
+NAME
+----
+git-sh-i18n--envsubst - Git's own envsubst(1) for i18n fallbacks
+
+SYNOPSIS
+--------
+[verse]
+eval_gettext () {
+ printf "%s" "$1" | (
+ export PATH $('git sh-i18n{litdd}envsubst' --variables "$1");
+ 'git sh-i18n{litdd}envsubst' "$1"
+ )
+}
+
+DESCRIPTION
+-----------
+
+This is not a command the end user would want to run. Ever.
+This documentation is meant for people who are studying the
+plumbing scripts and/or are writing new ones.
+
+'git sh-i18n{litdd}envsubst' is Git's stripped-down copy of the GNU
+`envsubst(1)` program that comes with the GNU gettext package. It's
+used internally by linkgit:git-sh-i18n[1] to interpolate the variables
+passed to the the `eval_gettext` function.
+
+No promises are made about the interface, or that this
+program won't disappear without warning in the next version
+of Git. Don't use it.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-sh-i18n.txt b/Documentation/git-sh-i18n.txt
new file mode 100644
index 0000000..60cf49c
--- /dev/null
+++ b/Documentation/git-sh-i18n.txt
@@ -0,0 +1,43 @@
+git-sh-i18n(1)
+==============
+
+NAME
+----
+git-sh-i18n - Git's i18n setup code for shell scripts
+
+SYNOPSIS
+--------
+[verse]
+'. "$(git --exec-path)/git-sh-i18n"'
+
+DESCRIPTION
+-----------
+
+This is not a command the end user would want to run. Ever.
+This documentation is meant for people who are studying the
+Porcelain-ish scripts and/or are writing new ones.
+
+The 'git sh-i18n scriptlet is designed to be sourced (using
+`.`) by Git's porcelain programs implemented in shell
+script. It provides wrappers for the GNU `gettext` and
+`eval_gettext` functions accessible through the `gettext.sh`
+script, and provides pass-through fallbacks on systems
+without GNU gettext.
+
+FUNCTIONS
+---------
+
+gettext::
+ Currently a dummy fall-through function implemented as a wrapper
+ around `printf(1)`. Will be replaced by a real gettext
+ implementation in a later version.
+
+eval_gettext::
+ Currently a dummy fall-through function implemented as a wrapper
+ around `printf(1)` with variables expanded by the
+ linkgit:git-sh-i18n{litdd}envsubst[1] helper. Will be replaced by a
+ real gettext implementation in a later version.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
index 3da2413..a2f346c 100644
--- a/Documentation/git-sh-setup.txt
+++ b/Documentation/git-sh-setup.txt
@@ -7,6 +7,7 @@ git-sh-setup - Common git shell script setup code
SYNOPSIS
--------
+[verse]
'. "$(git --exec-path)/git-sh-setup"'
DESCRIPTION
@@ -58,23 +59,19 @@ cd_to_toplevel::
runs chdir to the toplevel of the working tree.
require_work_tree::
- checks if the repository is a bare repository, and dies
- if so. Used by scripts that require working tree
- (e.g. `checkout`).
+ checks if the current directory is within the working tree
+ of the repository, and otherwise dies.
+
+require_work_tree_exists::
+ checks if the working tree associated with the repository
+ exists, and otherwise dies. Often done before calling
+ cd_to_toplevel, which is impossible to do if there is no
+ working tree.
get_author_ident_from_commit::
outputs code for use with eval to set the GIT_AUTHOR_NAME,
GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
index 0f3ad81..9b92506 100644
--- a/Documentation/git-shell.txt
+++ b/Documentation/git-shell.txt
@@ -3,32 +3,31 @@ git-shell(1)
NAME
----
-git-shell - Restricted login shell for GIT-only SSH access
+git-shell - Restricted login shell for Git-only SSH access
SYNOPSIS
--------
-'$(git --exec-path)/git-shell' -c <command> <argument>
+[verse]
+'git shell' [-c <command> <argument>]
DESCRIPTION
-----------
-This is meant to be used as a login shell for SSH accounts you want
-to restrict to GIT pull/push access only. It permits execution only
-of server-side GIT commands implementing the pull/push functionality.
-The commands can be executed only by the '-c' option; the shell is not
-interactive.
-
-Currently, only four commands are permitted to be called, 'git-receive-pack'
-'git-upload-pack' and 'git-upload-archive' with a single required argument, or
-'cvs server' (to invoke 'git-cvsserver').
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
+
+A login shell for SSH accounts to provide restricted Git access. When
+'-c' is given, the program executes <command> non-interactively;
+<command> can be one of 'git receive-pack', 'git upload-pack', 'git
+upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell
+is started in interactive mode when no arguments are given; in this
+case, COMMAND_DIR must exist, and any of the executables in it can be
+invoked.
+
+'cvs server' is a special command which executes git-cvsserver.
+
+COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have
+read and execute permissions to the directory in order to execute the
+programs in it. The programs are executed with a cwd of $HOME, and
+<argument> is parsed as a command-line string.
GIT
---
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index bc1ac77..ff3755b 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -44,7 +44,7 @@ OPTIONS
--email::
Show the email address of each author.
---format[='<format>']::
+--format[=<format>]::
Instead of the commit subject, use some other information to
describe each commit. '<format>' can be any string accepted
by the `--format` option of 'git log', such as '{asterisk} [%h] %s'.
@@ -68,15 +68,6 @@ spelled differently.
include::mailmap.txt[]
-
-Author
-------
-Written by Jeff Garzik <jgarzik@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
index f1499bb..a8e77b5 100644
--- a/Documentation/git-show-branch.txt
+++ b/Documentation/git-show-branch.txt
@@ -12,8 +12,7 @@ SYNOPSIS
[--current] [--color[=<when>] | --no-color] [--sparse]
[--more=<n> | --list | --independent | --merge-base]
[--no-name | --sha1-name] [--topics]
- [<rev> | <glob>]...
-
+ [(<rev> | <glob>)...]
'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]
DESCRIPTION
@@ -32,7 +31,7 @@ no <rev> nor <glob> is given on the command line.
OPTIONS
-------
<rev>::
- Arbitrary extended SHA1 expression (see linkgit:git-rev-parse[1])
+ Arbitrary extended SHA1 expression (see linkgit:gitrevisions[7])
that typically names a branch head or a tag.
<glob>::
@@ -168,10 +167,10 @@ $ git show-branch master fixes mhf
------------------------------------------------
These three branches all forked from a common commit, [master],
-whose commit message is "Add \'git show-branch\'". The "fixes"
-branch adds one commit "Introduce "reset type" flag to "git reset"".
-The "mhf" branch adds many other commits. The current branch
-is "master".
+whose commit message is "Add {apostrophe}git show-branch{apostrophe}".
+The "fixes" branch adds one commit "Introduce "reset type" flag to
+"git reset"". The "mhf" branch adds many other commits.
+The current branch is "master".
EXAMPLE
@@ -200,17 +199,6 @@ shows 10 reflog entries going back from the tip as of 1 hour ago.
Without `--list`, the output also shows how these tips are
topologically related with each other.
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt
index 8382fbe..2dcbbb2 100644
--- a/Documentation/git-show-index.txt
+++ b/Documentation/git-show-index.txt
@@ -8,6 +8,7 @@ git-show-index - Show packed archive index
SYNOPSIS
--------
+[verse]
'git show-index' < idx-file
@@ -20,15 +21,6 @@ The information it outputs is subset of what you can get from
'git verify-pack -v'; this command only shows the packfile
offset and SHA1 of each object.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 3f9d9c6..3c45895 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -73,8 +73,8 @@ OPTIONS
--exclude-existing[=<pattern>]::
Make 'git show-ref' act as a filter that reads refs from stdin of the
- form "^(?:<anything>\s)?<refname>(?:\^\{\})?$" and performs the
- following actions on each:
+ form "^(?:<anything>\s)?<refname>(?:{backslash}{caret}\{\})?$"
+ and performs the following actions on each:
(1) strip "^{}" at the end of line if any;
(2) ignore if pattern is provided and does not head-match refname;
(3) warn if refname is not a well-formed refname and skip;
@@ -84,7 +84,11 @@ OPTIONS
<pattern>...::
- Show references matching one or more patterns.
+ Show references matching one or more patterns. Patterns are matched from
+ the end of the full name, and only complete parts are matched, e.g.
+ 'master' matches 'refs/heads/master', 'refs/remotes/origin/master',
+ 'refs/tags/jedi/master' but not 'refs/heads/mymaster' nor
+ 'refs/remotes/master/jedi'.
OUTPUT
------
@@ -163,14 +167,15 @@ flag, so you can do
to get a listing of all tags together with what they dereference.
+FILES
+-----
+`.git/refs/*`, `.git/packed-refs`
+
SEE ALSO
--------
-linkgit:git-ls-remote[1]
-
-AUTHORS
--------
-Written by Linus Torvalds <torvalds@osdl.org>.
-Man page by Jonas Fonseca <fonseca@diku.dk>.
+linkgit:git-ls-remote[1],
+linkgit:git-update-ref[1],
+linkgit:gitrepository-layout[5]
GIT
---
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 55e687a..1f0e30b 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -8,6 +8,7 @@ git-show - Show various types of objects
SYNOPSIS
--------
+[verse]
'git show' [options] <object>...
DESCRIPTION
@@ -36,7 +37,7 @@ OPTIONS
<object>...::
The names of objects to show.
For a more complete list of ways to spell object names, see
- "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+ "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
include::pretty-options.txt[]
@@ -54,6 +55,10 @@ git show v1.0.0::
git show v1.0.0^\{tree\}::
Shows the tree pointed to by the tag `v1.0.0`.
+git show -s --format=%s v1.0.0^\{commit\}::
+ Shows the subject of the commit pointed to by the
+ tag `v1.0.0`.
+
git show next~10:Documentation/README::
Shows the contents of the file `Documentation/README` as
they were current in the 10th last commit of the branch
@@ -68,17 +73,6 @@ Discussion
include::i18n.txt[]
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>. Significantly enhanced by
-Johannes Schindelin <Johannes.Schindelin@gmx.de>.
-
-
-Documentation
--------------
-Documentation by David Greaves, Petr Baudis and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-stage.txt b/Documentation/git-stage.txt
index 7f251a5..ba3fe0d 100644
--- a/Documentation/git-stage.txt
+++ b/Documentation/git-stage.txt
@@ -17,3 +17,7 @@ DESCRIPTION
This is a synonym for linkgit:git-add[1]. Please refer to the
documentation of that command.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 473889a..15f051f 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -13,7 +13,7 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
-'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
'git stash' clear
'git stash' create
@@ -42,7 +42,7 @@ is also possible).
OPTIONS
-------
-save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
+save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. The <message> part is optional and gives
@@ -54,12 +54,13 @@ save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
If the `--keep-index` option is used, all changes already added to the
index are left intact.
+
-With `--patch`, you can interactively select hunks from in the diff
+With `--patch`, you can interactively select hunks from the diff
between HEAD and the working tree to be stashed. The stash entry is
constructed such that its index state is the same as the index state
of your repository, and its worktree contains only the changes you
selected interactively. The selected changes are then rolled back
-from your worktree.
+from your worktree. See the ``Interactive Mode'' section of
+linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+
The `--patch` option implies `--keep-index`. You can use
`--no-keep-index` to override this.
@@ -104,18 +105,22 @@ tree's changes, but also the index's ones. However, this can fail, when you
have conflicts (which are stored in the index, where you therefore can no
longer apply the changes as they were originally).
+
-When no `<stash>` is given, `stash@\{0}` is assumed.
+When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
+be a reference of the form `stash@\{<revision>}`.
apply [--index] [-q|--quiet] [<stash>]::
- Like `pop`, but do not remove the state from the stash list.
+ Like `pop`, but do not remove the state from the stash list. Unlike `pop`,
+ `<stash>` may be any commit that looks like a commit created by
+ `stash save` or `stash create`.
branch <branchname> [<stash>]::
Creates and checks out a new branch named `<branchname>` starting from
the commit at which the `<stash>` was originally created, applies the
- changes recorded in `<stash>` to the new working tree and index, then
- drops the `<stash>` if that completes successfully. When no `<stash>`
+ changes recorded in `<stash>` to the new working tree and index.
+ If that succeeds, and `<stash>` is a reference of the form
+ `stash@{<revision>}`, it then drops the `<stash>`. When no `<stash>`
is given, applies the latest one.
+
This is useful if the branch on which you ran `git stash save` has
@@ -132,7 +137,9 @@ clear::
drop [-q|--quiet] [<stash>]::
Remove a single stashed state from the stash list. When no `<stash>`
- is given, it removes the latest one. i.e. `stash@\{0}`
+ is given, it removes the latest one. i.e. `stash@\{0}`, otherwise
+ `<stash>` must a valid stash log reference of the form
+ `stash@\{<revision>}`.
create::
@@ -251,10 +258,6 @@ linkgit:git-commit[1],
linkgit:git-reflog[1],
linkgit:git-reset[1]
-AUTHOR
-------
-Written by Nanako Shiraishi <nanako3@bluebottle.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 2fd054c..3d51717 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -8,6 +8,7 @@ git-status - Show the working tree status
SYNOPSIS
--------
+[verse]
'git status' [<options>...] [--] [<pathspec>...]
DESCRIPTION
@@ -32,30 +33,35 @@ OPTIONS
Show the branch and tracking info even in short-format.
--porcelain::
- Give the output in a stable, easy-to-parse format for scripts.
- Currently this is identical to --short output, but is guaranteed
- not to change in the future, making it safe for scripts.
+ Give the output in an easy-to-parse format for scripts.
+ This is similar to the short output, but will remain stable
+ across git versions and regardless of user configuration. See
+ below for details.
-u[<mode>]::
--untracked-files[=<mode>]::
- Show untracked files (Default: 'all').
+ Show untracked files.
+
-The mode parameter is optional, and is used to specify
-the handling of untracked files. The possible options are:
+The mode parameter is optional (defaults to 'all'), and is used to
+specify the handling of untracked files; when -u is not used, the
+default is 'normal', i.e. show untracked files and directories.
++
+The possible options are:
+
---
- 'no' - Show no untracked files
- 'normal' - Shows untracked files and directories
- 'all' - Also shows individual files in untracked directories.
---
+
-See linkgit:git-config[1] for configuration variable
-used to change the default for when the option is not
-specified.
+The default can be changed using the status.showUntrackedFiles
+configuration variable documented in linkgit:git-config[1].
--ignore-submodules[=<when>]::
Ignore changes to submodules when looking for changes. <when> can be
- either "untracked", "dirty" or "all", which is the default. When
+ either "none", "untracked", "dirty" or "all", which is the default.
+ Using "none" will consider the submodule modified when it either contains
+ untracked or modified files or its HEAD differs from the commit recorded
+ in the superproject and can be used to override any settings of the
+ 'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
"untracked" is used submodules are not considered dirty when they only
contain untracked content (but they are still scanned for modified
content). Using "dirty" ignores all changes to the work tree of submodules,
@@ -64,6 +70,9 @@ specified.
(and suppresses the output of submodule summaries when the config option
`status.submodulesummary` is set).
+--ignored::
+ Show ignored files as well.
+
-z::
Terminate entries with NUL, instead of LF. This implies
the `--porcelain` output format if no other format is given.
@@ -74,23 +83,27 @@ OUTPUT
The output from this command is designed to be used as a commit
template comment, and all the output lines are prefixed with '#'.
The default, long format, is designed to be human readable,
-verbose and descriptive. They are subject to change in any time.
+verbose and descriptive. Its contents and format are subject to change
+at any time.
The paths mentioned in the output, unlike many other git commands, are
made relative to the current directory if you are working in a
subdirectory (this is on purpose, to help cutting and pasting). See
the status.relativePaths config option below.
-In short-format, the status of each path is shown as
+Short Format
+~~~~~~~~~~~~
+
+In the short-format, the status of each path is shown as
XY PATH1 -> PATH2
-where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is
+where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is
shown only when `PATH1` corresponds to a different path in the
index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
status code.
-The fields (including the `->`) are separated from each other by a
+The fields (including the `\->`) are separated from each other by a
single space. If a filename contains whitespace or other nonprintable
characters, that field will be quoted in the manner of a C string
literal: surrounded by ASCII double quote (34) characters, and with
@@ -110,7 +123,8 @@ codes can be interpreted as follows:
* 'C' = copied
* 'U' = updated but unmerged
-Ignored files are not listed.
+Ignored files are not listed, unless `--ignored` option is in effect,
+in which case `XY` are `!!`.
X Y Meaning
-------------------------------------------------
@@ -133,16 +147,32 @@ Ignored files are not listed.
U U unmerged, both modified
-------------------------------------------------
? ? untracked
+ ! ! ignored
-------------------------------------------------
If -b is used the short-format status is preceded by a line
## branchname tracking info
-There is an alternate -z format recommended for machine parsing. In
+Porcelain Format
+~~~~~~~~~~~~~~~~
+
+The porcelain format is similar to the short format, but is guaranteed
+not to change in a backwards-incompatible way between git versions or
+based on user configuration. This makes it ideal for parsing by scripts.
+The description of the short format above also describes the porcelain
+format, with a few exceptions:
+
+1. The user's color.status configuration is not respected; color will
+ always be off.
+
+2. The user's status.relativePaths configuration is not respected; paths
+ shown will always be relative to the repository root.
+
+There is also an alternate -z format recommended for machine parsing. In
that format, the status field is the same, but some other things
-change. First, the '->' is omitted from rename entries and the field
-order is reversed (e.g 'from -> to' becomes 'to from'). Second, a NUL
+change. First, the '\->' is omitted from rename entries and the field
+order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
(ASCII 0) follows each filename, replacing space as a field separator
and the terminating newline (but a space still separates the status
field from the first filename). Third, filenames containing special
@@ -170,14 +200,6 @@ SEE ALSO
--------
linkgit:gitignore[5]
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
index 7508c0e..b78f031 100644
--- a/Documentation/git-stripspace.txt
+++ b/Documentation/git-stripspace.txt
@@ -8,6 +8,7 @@ git-stripspace - Filter out empty lines
SYNOPSIS
--------
+[verse]
'git stripspace' [-s | --strip-comments] < <stream>
DESCRIPTION
@@ -23,14 +24,6 @@ OPTIONS
<stream>::
Byte stream to act on.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index cdabfd2..0ec8574 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules
SYNOPSIS
--------
[verse]
-'git submodule' [--quiet] add [-b branch]
+'git submodule' [--quiet] add [-b branch] [-f|--force]
[--reference <repository>] [--] <repository> [<path>]
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
@@ -78,7 +78,9 @@ to exist in the superproject. If <path> is not given, the
<repository> is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ./
or ../), the location relative to the superproject's origin
-repository.
+repository. If the superproject doesn't have an origin configured
+the superproject is its own authoritative upstream and the current
+working directory is used instead.
+
<path> is the relative location for the cloned submodule to
exist in the superproject. If <path> does not exist, then the
@@ -101,9 +103,10 @@ status::
currently checked out commit for each submodule, along with the
submodule path and the output of 'git describe' for the
SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not
- initialized and `+` if the currently checked out submodule commit
+ initialized, `+` if the currently checked out submodule commit
does not match the SHA-1 found in the index of the containing
- repository. This command is the default command for 'git submodule'.
+ repository and `U` if the submodule has merge conflicts.
+ This command is the default command for 'git submodule'.
+
If '--recursive' is specified, this command will recurse into nested
submodules, and show their status as well.
@@ -166,12 +169,14 @@ commit for each submodule.
sync::
Synchronizes submodules' remote URL configuration setting
- to the value specified in .gitmodules. This is useful when
+ to the value specified in .gitmodules. It will only affect those
+ submodules which already have an url entry in .git/config (that is the
+ case when they are initialized or freshly added). This is useful when
submodule URLs change upstream and you need to update your local
repositories accordingly.
+
"git submodule sync" synchronizes all submodules while
-"git submodule sync -- A" synchronizes submodule "A" only.
+"git submodule sync \-- A" synchronizes submodule "A" only.
OPTIONS
-------
@@ -183,6 +188,13 @@ OPTIONS
--branch::
Branch of repository to add as submodule.
+-f::
+--force::
+ This option is only valid for add and update commands.
+ When running add, allow adding an otherwise ignored submodule path.
+ When running update, throw away local changes in submodules when
+ switching to a different commit.
+
--cached::
This option is only valid for status and summary commands. These
commands typically use the commit found in the submodule HEAD, but
@@ -252,11 +264,6 @@ This file should be formatted in the same way as `$GIT_DIR/config`. The key
to each submodule url is "submodule.$name.url". See linkgit:gitmodules[5]
for details.
-
-AUTHOR
-------
-Written by Lars Hjemli <hjemli@gmail.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index b09bd97..ed5eca1 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -7,6 +7,7 @@ git-svn - Bidirectional operation between a Subversion repository and git
SYNOPSIS
--------
+[verse]
'git svn' <command> [options] [arguments]
DESCRIPTION
@@ -56,6 +57,8 @@ COMMANDS
as well, they take precedence.
--no-metadata;;
Set the 'noMetadata' option in the [svn-remote] config.
+ This option is not recommended, please read the 'svn.noMetadata'
+ section of this manpage before using this option.
--use-svm-props;;
Set the 'useSvmProps' option in the [svn-remote] config.
--use-svnsync-props;;
@@ -64,7 +67,7 @@ COMMANDS
Set the 'rewriteRoot' option in the [svn-remote] config.
--rewrite-uuid=<UUID>;;
Set the 'rewriteUUID' option in the [svn-remote] config.
---username=<USER>;;
+--username=<user>;;
For transports that SVN handles authentication for (http,
https, and plain svn), specify the username. For other
transports (eg svn+ssh://), you must include the username in
@@ -143,17 +146,6 @@ Skip "branches" and "tags" of first level directories;;
------------------------------------------------------------------------
--
---use-log-author;;
- When retrieving svn commits into git (as part of fetch, rebase, or
- dcommit operations), look for the first From: or Signed-off-by: line
- in the log message and use that as the author string.
---add-author-from;;
- When committing to svn from git (as part of commit or dcommit
- operations), if the existing log message doesn't already have a
- From: or Signed-off-by: line, append a From: line based on the
- git commit's author string. If you use this, then --use-log-author
- will retrieve a valid author string for all commits.
-
'clone'::
Runs 'init' and 'fetch'. It will automatically create a
directory based on the basename of the URL passed to it;
@@ -215,6 +207,13 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
Using this option for any other purpose (don't ask) is very strongly
discouraged.
+--mergeinfo=<mergeinfo>;;
+ Add the given merge information during the dcommit
+ (e.g. `--mergeinfo="/branches/foo:1-10"`). All svn server versions can
+ store this information (as a property), and svn clients starting from
+ version 1.5 can make use of it. 'git svn' currently does not use it
+ and does not set it automatically.
+
'branch'::
Create a branch in the SVN repository.
@@ -341,6 +340,8 @@ Any other arguments are passed directly to 'git log'
Empty directories are automatically recreated when using
"git svn clone" and "git svn rebase", so "mkdirs" is intended
for use after commands like "git checkout" or "git reset".
+ (See the svn-remote.<name>.automkdirs config file option for
+ more information.)
'commit-diff'::
Commits the diff of two tree-ish arguments from the
@@ -436,13 +437,13 @@ git rebase --onto remotes/git-svn A^ master
OPTIONS
-------
---shared[={false|true|umask|group|all|world|everybody}]::
+--shared[=(false|true|umask|group|all|world|everybody)]::
--template=<template_directory>::
Only used with the 'init' command.
These are passed directly to 'git init'.
--r <ARG>::
---revision <ARG>::
+-r <arg>::
+--revision <arg>::
Used with the 'fetch' command.
+
This allows revision ranges for partial/cauterized history
@@ -563,6 +564,17 @@ repository that will be fetched from.
For 'branch' and 'tag', display the urls that will be used for copying when
creating the branch or tag.
+--use-log-author::
+ When retrieving svn commits into git (as part of 'fetch', 'rebase', or
+ 'dcommit' operations), look for the first `From:` or `Signed-off-by:` line
+ in the log message and use that as the author string.
+--add-author-from::
+ When committing to svn from git (as part of 'commit-diff', 'set-tree' or 'dcommit'
+ operations), if the existing log message doesn't already have a
+ `From:` or `Signed-off-by:` line, append a `From:` line based on the
+ git commit's author string. If you use this, then `--use-log-author`
+ will retrieve a valid author string for all commits.
+
ADVANCED OPTIONS
----------------
@@ -597,13 +609,22 @@ svn.noMetadata::
svn-remote.<name>.noMetadata::
This gets rid of the 'git-svn-id:' lines at the end of every commit.
+
-If you lose your .git/svn/git-svn/.rev_db file, 'git svn' will not
-be able to rebuild it and you won't be able to fetch again,
-either. This is fine for one-shot imports.
+This option can only be used for one-shot imports as 'git svn'
+will not be able to fetch again without metadata. Additionally,
+if you lose your .git/svn/**/.rev_map.* files, 'git svn' will not
+be able to rebuild them.
+
The 'git svn log' command will not work on repositories using
this, either. Using this conflicts with the 'useSvmProps'
option for (hopefully) obvious reasons.
++
+This option is NOT recommended as it makes it difficult to track down
+old references to SVN revision numbers in existing documentation, bug
+reports and archives. If you plan to eventually migrate from SVN to git
+and are certain about dropping SVN history, consider
+linkgit:git-filter-branch[1] instead. filter-branch also allows
+reformatting of metadata for ease-of-reading and rewriting authorship
+info for non-"svn.authorsFile" users.
svn.useSvmProps::
svn-remote.<name>.useSvmProps::
@@ -637,6 +658,16 @@ svn-remote.<name>.rewriteUUID::
where the original UUID is not available via either useSvmProps
or useSvnsyncProps.
+svn-remote.<name>.pushurl::
+
+ Similar to git's 'remote.<name>.pushurl', this key is designed
+ to be used in cases where 'url' points to an SVN repository
+ via a read-only transport, to provide an alternate read/write
+ transport. It is assumed that both keys point to the same
+ repository. Unlike 'commiturl', 'pushurl' is a base path. If
+ either 'commiturl' or 'pushurl' could be used, 'commiturl'
+ takes precedence.
+
svn.brokenSymlinkWorkaround::
This disables potentially expensive checks to workaround
broken symlinks checked into SVN by broken clients. Set this
@@ -646,6 +677,20 @@ svn.brokenSymlinkWorkaround::
revision fetched. If unset, 'git svn' assumes this option to
be "true".
+svn.pathnameencoding::
+ This instructs git svn to recode pathnames to a given encoding.
+ It can be used by windows users and by those who work in non-utf8
+ locales to avoid corrupted file names with non-ASCII characters.
+ Valid encodings are the ones supported by Perl's Encode module.
+
+svn-remote.<name>.automkdirs::
+ Normally, the "git svn clone" and "git svn rebase" commands
+ attempt to recreate empty directories that are in the
+ Subversion repository. If this option is set to "false", then
+ empty directories will only be created if the "git svn mkdirs"
+ command is run explicitly. If unset, 'git svn' assumes this
+ option to be "true".
+
Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps
options all affect the metadata generated and used by 'git svn'; they
*must* be set in the configuration file before any history is imported
@@ -712,8 +757,11 @@ have each person clone that repository with 'git clone':
cd project
git init
git remote add origin server:/pub/project
- git config --add remote.origin.fetch '+refs/remotes/*:refs/remotes/*'
+ git config --replace-all remote.origin.fetch '+refs/remotes/*:refs/remotes/*'
git fetch
+# Prevent fetch/pull from remote git server in the future,
+# we only want to use git svn for future updates
+ git config --remove-section remote.origin
# Create a local branch from one of the branches just fetched
git checkout -b master FETCH_HEAD
# Initialize 'git svn' locally (be sure to use the same URL and -T/-b/-t options as were used on server)
@@ -737,10 +785,9 @@ use `git svn rebase` to update your work branch instead of `git pull` or
when committing into SVN, which can lead to merge commits reversing
previous commits in SVN.
-DESIGN PHILOSOPHY
------------------
-Merge tracking in Subversion is lacking and doing branched development
-with Subversion can be cumbersome as a result. While 'git svn' can track
+MERGE TRACKING
+--------------
+While 'git svn' can track
copy history (including branches and tags) for repositories adopting a
standard layout, it cannot yet represent merge history that happened
inside git back upstream to SVN users. Therefore it is advised that
@@ -750,16 +797,15 @@ compatibility with SVN (see the CAVEATS section below).
CAVEATS
-------
-For the sake of simplicity and interoperating with a less-capable system
-(SVN), it is recommended that all 'git svn' users clone, fetch and dcommit
+For the sake of simplicity and interoperating with Subversion,
+it is recommended that all 'git svn' users clone, fetch and dcommit
directly from the SVN server, and avoid all 'git clone'/'pull'/'merge'/'push'
operations between git repositories and branches. The recommended
method of exchanging code between git branches and users is
'git format-patch' and 'git am', or just 'dcommit'ing to the SVN repository.
Running 'git merge' or 'git pull' is NOT recommended on a branch you
-plan to 'dcommit' from. Subversion does not represent merges in any
-reasonable or useful fashion; so users using Subversion cannot see any
+plan to 'dcommit' from because Subversion users cannot see any
merges you've made. Furthermore, if you merge or pull from a git branch
that is a mirror of an SVN branch, 'dcommit' may commit to the wrong
branch.
@@ -809,7 +855,7 @@ Renamed and copied directories are not detected by git and hence not
tracked when committing to SVN. I do not plan on adding support for
this as it's quite difficult and time-consuming to get working for all
the possible corner cases (git doesn't do it, either). Committing
-renamed and copied files are fully supported if they're similar enough
+renamed and copied files is fully supported if they're similar enough
for git to detect them.
CONFIGURATION
@@ -858,10 +904,6 @@ SEE ALSO
--------
linkgit:git-rebase[1]
-Author
-------
-Written by Eric Wong <normalperson@yhbt.net>.
-
-Documentation
--------------
-Written by Eric Wong <normalperson@yhbt.net>.
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index 33a1536..75b1ae5 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -7,6 +7,7 @@ git-symbolic-ref - Read and modify symbolic refs
SYNOPSIS
--------
+[verse]
'git symbolic-ref' [-q] [-m <reason>] <name> [<ref>]
DESCRIPTION
@@ -53,10 +54,6 @@ and symbolic refs are used by default.
symbolic ref were printed correctly, with status 1 if the requested
name is not a symbolic ref, or 128 if another error occurs.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 31c78a8..fb1c0ac 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -12,27 +12,28 @@ SYNOPSIS
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
<tagname> [<commit> | <object>]
'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
+'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
'git tag' -v <tagname>...
DESCRIPTION
-----------
-Adds a tag reference in `.git/refs/tags/`.
+Add a tag reference in `.git/refs/tags/`, unless `-d/-l/-v` is given
+to delete, list or verify tags.
-Unless `-f` is given, the tag must not yet exist in
+Unless `-f` is given, the tag to be created must not yet exist in the
`.git/refs/tags/` directory.
If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
-creates a 'tag' object, and requires the tag message. Unless
+creates a 'tag' object, and requires a tag message. Unless
`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
in the tag message.
If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
are absent, `-a` is implied.
-Otherwise just the SHA1 object name of the commit object is
-written (i.e. a lightweight tag).
+Otherwise just a tag reference for the SHA1 object name of the commit object is
+created (i.e. a lightweight tag).
A GnuPG signed tag object will be created when `-s` or `-u
<key-id>` is used. When `-u <key-id>` is not used, the
@@ -68,8 +69,11 @@ OPTIONS
If the tag is not annotated, the commit message is displayed instead.
-l <pattern>::
- List tags with names that match the given pattern (or all if no pattern is given).
- Typing "git tag" without arguments, also lists all tags.
+ List tags with names that match the given pattern (or all if no
+ pattern is given). Running "git tag" without arguments also
+ lists all tags. The pattern is a shell wildcard (i.e., matched
+ using fnmatch(3)). Multiple patterns may be given; if any of
+ them matches, the tag is shown.
--contains <commit>::
Only list tags which contain the specified commit.
@@ -164,20 +168,19 @@ You can test which tag you have by doing
which should return 0123456789abcdef.. if you have the new version.
-Sorry for inconvenience.
+Sorry for the inconvenience.
------------
Does this seem a bit complicated? It *should* be. There is no
-way that it would be correct to just "fix" it behind peoples
-backs. People need to know that their tags might have been
-changed.
+way that it would be correct to just "fix" it automatically.
+People need to know that their tags might have been changed.
On Automatic following
~~~~~~~~~~~~~~~~~~~~~~
If you are following somebody else's tree, you are most likely
-using tracking branches (`refs/heads/origin` in traditional
+using remote-tracking branches (`refs/heads/origin` in traditional
layout, or `refs/remotes/origin/master` in the separate-remote
layout). You usually want the tags from the other end.
@@ -188,9 +191,10 @@ the toplevel but not limited to them. Mere mortals when pulling
from each other do not necessarily want to automatically get
private anchor point tags from the other person.
-You would notice "please pull" messages on the mailing list says
-repo URL and branch name alone. This is designed to be easily
-cut&pasted to a 'git fetch' command line:
+Often, "please pull" messages on the mailing list just provide
+two pieces of information: a repo URL and a branch name; this
+is designed to be easily cut&pasted at the end of a 'git fetch'
+command line:
------------
Linus, please pull from
@@ -206,14 +210,14 @@ becomes:
$ git pull git://git..../proj.git master
------------
-In such a case, you do not want to automatically follow other's
-tags.
+In such a case, you do not want to automatically follow the other
+person's tags.
-One important aspect of git is it is distributed, and being
-distributed largely means there is no inherent "upstream" or
+One important aspect of git is its distributed nature, which
+largely means there is no inherent "upstream" or
"downstream" in the system. On the face of it, the above
example might seem to indicate that the tag namespace is owned
-by upper echelon of people and tags only flow downwards, but
+by the upper echelon of people and that tags only flow downwards, but
that is not the case. It only shows that the usage pattern
determines who are interested in whose tags.
@@ -231,8 +235,8 @@ this case.
It may well be that among networking people, they may want to
exchange the tags internal to their group, but in that workflow
-they are most likely tracking with each other's progress by
-having tracking branches. Again, the heuristic to automatically
+they are most likely tracking each other's progress by
+having remote-tracking branches. Again, the heuristic to automatically
follow such tags is a good thing.
@@ -241,35 +245,26 @@ On Backdating Tags
If you have imported some changes from another VCS and would like
to add tags for major releases of your work, it is useful to be able
-to specify the date to embed inside of the tag object. The data in
+to specify the date to embed inside of the tag object; such data in
the tag object affects, for example, the ordering of tags in the
gitweb interface.
To set the date used in future tag objects, set the environment
-variable GIT_COMMITTER_DATE to one or more of the date and time. The
-date and time can be specified in a number of ways; the most common
-is "YYYY-MM-DD HH:MM".
+variable GIT_COMMITTER_DATE (see the later discussion of possible
+values; the most common form is "YYYY-MM-DD HH:MM").
-An example follows.
+For example:
------------
$ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1
------------
+include::date-formats.txt[]
SEE ALSO
--------
linkgit:git-check-ref-format[1].
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>,
-Junio C Hamano <gitster@pobox.com> and Chris Wright <chrisw@osdl.org>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
index 3c786bd..95b135d 100644
--- a/Documentation/git-tar-tree.txt
+++ b/Documentation/git-tar-tree.txt
@@ -8,6 +8,7 @@ git-tar-tree - Create a tar archive of the files in the named tree object
SYNOPSIS
--------
+[verse]
'git tar-tree' [--remote=<repo>] <tree-ish> [ <base> ]
DESCRIPTION
@@ -76,14 +77,6 @@ git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar::
Put everything in the current head's Documentation/ directory
into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'.
-Author
-------
-Written by Rene Scharfe.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt
index 995db9f..e9f148a 100644
--- a/Documentation/git-unpack-file.txt
+++ b/Documentation/git-unpack-file.txt
@@ -9,6 +9,7 @@ git-unpack-file - Creates a temporary file with a blob's contents
SYNOPSIS
--------
+[verse]
'git unpack-file' <blob>
DESCRIPTION
@@ -22,14 +23,6 @@ OPTIONS
<blob>::
Must be a blob id
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
index 36d1038..ff23494 100644
--- a/Documentation/git-unpack-objects.txt
+++ b/Documentation/git-unpack-objects.txt
@@ -8,6 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive
SYNOPSIS
--------
+[verse]
'git unpack-objects' [-n] [-q] [-r] [--strict] <pack-file
@@ -43,15 +44,6 @@ OPTIONS
--strict::
Don't write objects with broken content or links.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
--------------
-Documentation by Junio C Hamano
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 765d4b3..d393129 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git update-index'
[--add] [--remove | --force-remove] [--replace]
[--refresh] [-q] [--unmerged] [--ignore-missing]
- [--cacheinfo <mode> <object> <file>]\*
+ [(--cacheinfo <mode> <object> <file>)...]
[--chmod=(+|-)x]
[--assume-unchanged | --no-assume-unchanged]
[--skip-worktree | --no-skip-worktree]
@@ -21,7 +21,7 @@ SYNOPSIS
[--info-only] [--index-info]
[-z] [--stdin]
[--verbose]
- [--] [<file>]\*
+ [--] [<file>...]
DESCRIPTION
-----------
@@ -144,8 +144,8 @@ you will need to handle the situation manually.
Report what is being added and removed from index.
-z::
- Only meaningful with `--stdin`; paths are separated with
- NUL character instead of LF.
+ Only meaningful with `--stdin` or `--index-info`; paths are
+ separated with NUL character instead of LF.
\--::
Do not interpret any more arguments as options.
@@ -365,15 +365,6 @@ SEE ALSO
linkgit:git-config[1],
linkgit:git-add[1]
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 9639f70..d377a35 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -7,6 +7,7 @@ git-update-ref - Update the object name stored in a ref safely
SYNOPSIS
--------
+[verse]
'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
DESCRIPTION
@@ -60,8 +61,9 @@ still contains <oldvalue>.
Logging Updates
---------------
-If config parameter "core.logAllRefUpdates" is true or the file
-"$GIT_DIR/logs/<ref>" exists then `git update-ref` will append
+If config parameter "core.logAllRefUpdates" is true and the ref is one under
+"refs/heads/", "refs/remotes/", "refs/notes/", or the symbolic ref HEAD; or
+the file "$GIT_DIR/logs/<ref>" exists then `git update-ref` will append
a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
symbolic refs before creating the log name) describing the change
in ref value. Log lines are formatted as:
@@ -84,10 +86,6 @@ An update will fail (without changing <ref>) if the current user is
unable to create a new log file, append to the existing log file
or does not have committer information available.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt
index 035cc30..bd0e364 100644
--- a/Documentation/git-update-server-info.txt
+++ b/Documentation/git-update-server-info.txt
@@ -8,6 +8,7 @@ git-update-server-info - Update auxiliary info file to help dumb servers
SYNOPSIS
--------
+[verse]
'git update-server-info' [--force]
DESCRIPTION
@@ -38,15 +39,6 @@ what they are for:
* info/refs
-
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
index f5f2b39..4d52d38 100644
--- a/Documentation/git-upload-archive.txt
+++ b/Documentation/git-upload-archive.txt
@@ -8,6 +8,7 @@ git-upload-archive - Send archive back to git-archive
SYNOPSIS
--------
+[verse]
'git upload-archive' <directory>
DESCRIPTION
@@ -24,14 +25,6 @@ OPTIONS
<directory>::
The repository to get a tar archive from.
-Author
-------
-Written by Franck Bui-Huu.
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index 71ca4ef..a58e90c 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -8,6 +8,7 @@ git-upload-pack - Send objects packed back to git-fetch-pack
SYNOPSIS
--------
+[verse]
'git-upload-pack' [--strict] [--timeout=<n>] <directory>
DESCRIPTION
@@ -33,14 +34,6 @@ OPTIONS
<directory>::
The repository to sync from.
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index 458f3e2..5317cc2 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -8,6 +8,7 @@ git-var - Show a git logical variable
SYNOPSIS
--------
+[verse]
'git var' ( -l | <variable> )
DESCRIPTION
@@ -65,14 +66,6 @@ linkgit:git-commit-tree[1]
linkgit:git-tag[1]
linkgit:git-config[1]
-Author
-------
-Written by Eric Biederman <ebiederm@xmission.com>
-
-Documentation
---------------
-Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
index 916a38a..cd23076 100644
--- a/Documentation/git-verify-pack.txt
+++ b/Documentation/git-verify-pack.txt
@@ -8,7 +8,8 @@ git-verify-pack - Validate packed git archive files
SYNOPSIS
--------
-'git verify-pack' [-v|--verbose] [--] <pack>.idx ...
+[verse]
+'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ...
DESCRIPTION
@@ -47,14 +48,6 @@ for objects that are not deltified in the pack, and
for objects that are deltified.
-Author
-------
-Written by Junio C Hamano <gitster@pobox.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index dada212..5ff76e8 100644
--- a/Documentation/git-verify-tag.txt
+++ b/Documentation/git-verify-tag.txt
@@ -7,6 +7,7 @@ git-verify-tag - Check the GPG signature of tags
SYNOPSIS
--------
+[verse]
'git verify-tag' <tag>...
DESCRIPTION
@@ -15,17 +16,13 @@ Validates the gpg signature created by 'git tag'.
OPTIONS
-------
+-v::
+--verbose::
+ Print the contents of the tag object before validating it.
+
<tag>...::
SHA1 identifiers of git tag objects.
-Author
-------
-Written by Jan Harkes <jaharkes@cs.cmu.edu> and Eric W. Biederman <ebiederm@xmission.com>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt
index 7572049..c2bc87b 100644
--- a/Documentation/git-web--browse.txt
+++ b/Documentation/git-web--browse.txt
@@ -1,5 +1,5 @@
-git-web--browse(1)
-==================
+git-web{litdd}browse(1)
+=======================
NAME
----
@@ -7,7 +7,8 @@ git-web--browse - git helper script to launch a web browser
SYNOPSIS
--------
-'git web--browse' [OPTIONS] URL/FILE ...
+[verse]
+'git web{litdd}browse' [OPTIONS] URL/FILE ...
DESCRIPTION
-----------
@@ -20,8 +21,14 @@ The following browsers (or commands) are currently supported:
* firefox (this is the default under X Window when not using KDE)
* iceweasel
+* seamonkey
+* iceape
+* chromium (also supported as chromium-browser)
+* google-chrome (also supported as chrome)
* konqueror (this is the default under KDE, see 'Note about konqueror' below)
+* opera
* w3m (this is the default outside graphical environments)
+* elinks
* links
* lynx
* dillo
@@ -32,19 +39,19 @@ Custom commands may also be specified.
OPTIONS
-------
--b BROWSER::
---browser=BROWSER::
- Use the specified BROWSER. It must be in the list of supported
+-b <browser>::
+--browser=<browser>::
+ Use the specified browser. It must be in the list of supported
browsers.
--t BROWSER::
---tool=BROWSER::
+-t <browser>::
+--tool=<browser>::
Same as above.
--c CONF.VAR::
---config=CONF.VAR::
+-c <conf.var>::
+--config=<conf.var>::
CONF.VAR is looked up in the git config files. If it's set,
- then its value specify the browser that should be used.
+ then its value specifies the browser that should be used.
CONFIGURATION VARIABLES
-----------------------
@@ -62,7 +69,7 @@ browser.<tool>.path
You can explicitly provide a full path to your preferred browser by
setting the configuration variable 'browser.<tool>.path'. For example,
you can configure the absolute path to firefox by setting
-'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool
+'browser.firefox.path'. Otherwise, 'git web{litdd}browse' assumes the tool
is available in PATH.
browser.<tool>.cmd
@@ -71,7 +78,7 @@ browser.<tool>.cmd
When the browser, specified by options or configuration variables, is
not among the supported ones, then the corresponding
'browser.<tool>.cmd' configuration variable will be looked up. If this
-variable exists then 'git web--browse' will treat the specified tool
+variable exists then 'git web{litdd}browse' will treat the specified tool
as a custom command and will use a shell eval to run the command with
the URLs passed as arguments.
@@ -110,16 +117,6 @@ $ git config --global web.browser firefox
as they are probably more user specific than repository specific.
See linkgit:git-config[1] for more information about this.
-Author
-------
-Written by Christian Couder <chriscool@tuxfamily.org> and the git-list
-<git@vger.kernel.org>, based on 'git mergetool' by Theodore Y. Ts'o.
-
-Documentation
--------------
-Documentation by Christian Couder <chriscool@tuxfamily.org> and the
-git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
index ea753cd..99388bd 100644
--- a/Documentation/git-whatchanged.txt
+++ b/Documentation/git-whatchanged.txt
@@ -8,6 +8,7 @@ git-whatchanged - Show logs with difference each commit introduces
SYNOPSIS
--------
+[verse]
'git whatchanged' <option>...
DESCRIPTION
@@ -63,17 +64,6 @@ git whatchanged --since="2 weeks ago" \-- gitk::
The "--" is necessary to avoid confusion with the *branch* named
'gitk'
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>
-
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
index bfceaca..f22041a 100644
--- a/Documentation/git-write-tree.txt
+++ b/Documentation/git-write-tree.txt
@@ -8,6 +8,7 @@ git-write-tree - Create a tree object from the current index
SYNOPSIS
--------
+[verse]
'git write-tree' [--missing-ok] [--prefix=<prefix>/]
DESCRIPTION
@@ -36,15 +37,6 @@ OPTIONS
`<prefix>`. This can be used to write the tree object
for a subproject that is in the named subdirectory.
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 8f0dd7f..0172cd7 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -9,11 +9,11 @@ git - the stupid content tracker
SYNOPSIS
--------
[verse]
-'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]
+'git' [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects]
- [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
- [-c name=value]
- [--help] COMMAND [ARGS]
+ [--bare] [--git-dir=<path>] [--work-tree=<path>]
+ [-c <name>=<value>]
+ [--help] <command> [<args>]
DESCRIPTION
-----------
@@ -28,7 +28,7 @@ also want to read linkgit:gitcvs-migration[7]. See
the link:user-manual.html[Git User's Manual] for a more in-depth
introduction.
-The COMMAND is either a name of a Git command (see below) or an alias
+The '<command>' is either a name of a Git command (see below) or an alias
as defined in the configuration file (see linkgit:git-config[1]).
Formatted and hyperlinked version of the latest git
@@ -44,166 +44,219 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.7.1.1/git.html[documentation for release 1.7.1.1]
+* link:v1.7.6/git.html[documentation for release 1.7.6]
* release notes for
- link:RelNotes-1.7.1.1.txt[1.7.1.1],
- link:RelNotes-1.7.1.txt[1.7.1].
+ link:RelNotes/1.7.6.txt[1.7.6].
-* link:v1.7.0.6/git.html[documentation for release 1.7.0.6]
+* link:v1.7.5.4/git.html[documentation for release 1.7.5.4]
* release notes for
- link:RelNotes-1.7.0.6.txt[1.7.0.6],
- link:RelNotes-1.7.0.5.txt[1.7.0.5],
- link:RelNotes-1.7.0.4.txt[1.7.0.4],
- link:RelNotes-1.7.0.3.txt[1.7.0.3],
- link:RelNotes-1.7.0.2.txt[1.7.0.2],
- link:RelNotes-1.7.0.1.txt[1.7.0.1],
- link:RelNotes-1.7.0.txt[1.7.0].
+ link:RelNotes/1.7.5.4.txt[1.7.5.4],
+ link:RelNotes/1.7.5.3.txt[1.7.5.3],
+ link:RelNotes/1.7.5.2.txt[1.7.5.2],
+ link:RelNotes/1.7.5.1.txt[1.7.5.1],
+ link:RelNotes/1.7.5.txt[1.7.5].
-* link:v1.6.6.2/git.html[documentation for release 1.6.6.2]
+* link:v1.7.4.5/git.html[documentation for release 1.7.4.5]
* release notes for
- link:RelNotes-1.6.6.2.txt[1.6.6.2],
- link:RelNotes-1.6.6.1.txt[1.6.6.1],
- link:RelNotes-1.6.6.txt[1.6.6].
+ link:RelNotes/1.7.4.5.txt[1.7.4.5],
+ link:RelNotes/1.7.4.4.txt[1.7.4.4],
+ link:RelNotes/1.7.4.3.txt[1.7.4.3],
+ link:RelNotes/1.7.4.2.txt[1.7.4.2],
+ link:RelNotes/1.7.4.1.txt[1.7.4.1],
+ link:RelNotes/1.7.4.txt[1.7.4].
-* link:v1.6.5.8/git.html[documentation for release 1.6.5.8]
+* link:v1.7.3.5/git.html[documentation for release 1.7.3.5]
* release notes for
- link:RelNotes-1.6.5.8.txt[1.6.5.8],
- link:RelNotes-1.6.5.7.txt[1.6.5.7],
- link:RelNotes-1.6.5.6.txt[1.6.5.6],
- link:RelNotes-1.6.5.5.txt[1.6.5.5],
- link:RelNotes-1.6.5.4.txt[1.6.5.4],
- link:RelNotes-1.6.5.3.txt[1.6.5.3],
- link:RelNotes-1.6.5.2.txt[1.6.5.2],
- link:RelNotes-1.6.5.1.txt[1.6.5.1],
- link:RelNotes-1.6.5.txt[1.6.5].
-
-* link:v1.6.4.4/git.html[documentation for release 1.6.4.4]
+ link:RelNotes/1.7.3.5.txt[1.7.3.5],
+ link:RelNotes/1.7.3.4.txt[1.7.3.4],
+ link:RelNotes/1.7.3.3.txt[1.7.3.3],
+ link:RelNotes/1.7.3.2.txt[1.7.3.2],
+ link:RelNotes/1.7.3.1.txt[1.7.3.1],
+ link:RelNotes/1.7.3.txt[1.7.3].
+
+* link:v1.7.2.5/git.html[documentation for release 1.7.2.5]
+
+* release notes for
+ link:RelNotes/1.7.2.5.txt[1.7.2.5],
+ link:RelNotes/1.7.2.4.txt[1.7.2.4],
+ link:RelNotes/1.7.2.3.txt[1.7.2.3],
+ link:RelNotes/1.7.2.2.txt[1.7.2.2],
+ link:RelNotes/1.7.2.1.txt[1.7.2.1],
+ link:RelNotes/1.7.2.txt[1.7.2].
+
+* link:v1.7.1.4/git.html[documentation for release 1.7.1.4]
+
+* release notes for
+ link:RelNotes/1.7.1.4.txt[1.7.1.4],
+ link:RelNotes/1.7.1.3.txt[1.7.1.3],
+ link:RelNotes/1.7.1.2.txt[1.7.1.2],
+ link:RelNotes/1.7.1.1.txt[1.7.1.1],
+ link:RelNotes/1.7.1.txt[1.7.1].
+
+* link:v1.7.0.9/git.html[documentation for release 1.7.0.9]
+
+* release notes for
+ link:RelNotes/1.7.0.9.txt[1.7.0.9],
+ link:RelNotes/1.7.0.8.txt[1.7.0.8],
+ link:RelNotes/1.7.0.7.txt[1.7.0.7],
+ link:RelNotes/1.7.0.6.txt[1.7.0.6],
+ link:RelNotes/1.7.0.5.txt[1.7.0.5],
+ link:RelNotes/1.7.0.4.txt[1.7.0.4],
+ link:RelNotes/1.7.0.3.txt[1.7.0.3],
+ link:RelNotes/1.7.0.2.txt[1.7.0.2],
+ link:RelNotes/1.7.0.1.txt[1.7.0.1],
+ link:RelNotes/1.7.0.txt[1.7.0].
+
+* link:v1.6.6.3/git.html[documentation for release 1.6.6.3]
+
+* release notes for
+ link:RelNotes/1.6.6.3.txt[1.6.6.3],
+ link:RelNotes/1.6.6.2.txt[1.6.6.2],
+ link:RelNotes/1.6.6.1.txt[1.6.6.1],
+ link:RelNotes/1.6.6.txt[1.6.6].
+
+* link:v1.6.5.9/git.html[documentation for release 1.6.5.9]
* release notes for
- link:RelNotes-1.6.4.4.txt[1.6.4.4],
- link:RelNotes-1.6.4.3.txt[1.6.4.3],
- link:RelNotes-1.6.4.2.txt[1.6.4.2],
- link:RelNotes-1.6.4.1.txt[1.6.4.1],
- link:RelNotes-1.6.4.txt[1.6.4].
+ link:RelNotes/1.6.5.9.txt[1.6.5.9],
+ link:RelNotes/1.6.5.8.txt[1.6.5.8],
+ link:RelNotes/1.6.5.7.txt[1.6.5.7],
+ link:RelNotes/1.6.5.6.txt[1.6.5.6],
+ link:RelNotes/1.6.5.5.txt[1.6.5.5],
+ link:RelNotes/1.6.5.4.txt[1.6.5.4],
+ link:RelNotes/1.6.5.3.txt[1.6.5.3],
+ link:RelNotes/1.6.5.2.txt[1.6.5.2],
+ link:RelNotes/1.6.5.1.txt[1.6.5.1],
+ link:RelNotes/1.6.5.txt[1.6.5].
+
+* link:v1.6.4.5/git.html[documentation for release 1.6.4.5]
+
+* release notes for
+ link:RelNotes/1.6.4.5.txt[1.6.4.5],
+ link:RelNotes/1.6.4.4.txt[1.6.4.4],
+ link:RelNotes/1.6.4.3.txt[1.6.4.3],
+ link:RelNotes/1.6.4.2.txt[1.6.4.2],
+ link:RelNotes/1.6.4.1.txt[1.6.4.1],
+ link:RelNotes/1.6.4.txt[1.6.4].
* link:v1.6.3.4/git.html[documentation for release 1.6.3.4]
* release notes for
- link:RelNotes-1.6.3.4.txt[1.6.3.4],
- link:RelNotes-1.6.3.3.txt[1.6.3.3],
- link:RelNotes-1.6.3.2.txt[1.6.3.2],
- link:RelNotes-1.6.3.1.txt[1.6.3.1],
- link:RelNotes-1.6.3.txt[1.6.3].
+ link:RelNotes/1.6.3.4.txt[1.6.3.4],
+ link:RelNotes/1.6.3.3.txt[1.6.3.3],
+ link:RelNotes/1.6.3.2.txt[1.6.3.2],
+ link:RelNotes/1.6.3.1.txt[1.6.3.1],
+ link:RelNotes/1.6.3.txt[1.6.3].
* release notes for
- link:RelNotes-1.6.2.5.txt[1.6.2.5],
- link:RelNotes-1.6.2.4.txt[1.6.2.4],
- link:RelNotes-1.6.2.3.txt[1.6.2.3],
- link:RelNotes-1.6.2.2.txt[1.6.2.2],
- link:RelNotes-1.6.2.1.txt[1.6.2.1],
- link:RelNotes-1.6.2.txt[1.6.2].
+ link:RelNotes/1.6.2.5.txt[1.6.2.5],
+ link:RelNotes/1.6.2.4.txt[1.6.2.4],
+ link:RelNotes/1.6.2.3.txt[1.6.2.3],
+ link:RelNotes/1.6.2.2.txt[1.6.2.2],
+ link:RelNotes/1.6.2.1.txt[1.6.2.1],
+ link:RelNotes/1.6.2.txt[1.6.2].
* link:v1.6.1.3/git.html[documentation for release 1.6.1.3]
* release notes for
- link:RelNotes-1.6.1.3.txt[1.6.1.3],
- link:RelNotes-1.6.1.2.txt[1.6.1.2],
- link:RelNotes-1.6.1.1.txt[1.6.1.1],
- link:RelNotes-1.6.1.txt[1.6.1].
+ link:RelNotes/1.6.1.3.txt[1.6.1.3],
+ link:RelNotes/1.6.1.2.txt[1.6.1.2],
+ link:RelNotes/1.6.1.1.txt[1.6.1.1],
+ link:RelNotes/1.6.1.txt[1.6.1].
* link:v1.6.0.6/git.html[documentation for release 1.6.0.6]
* release notes for
- link:RelNotes-1.6.0.6.txt[1.6.0.6],
- link:RelNotes-1.6.0.5.txt[1.6.0.5],
- link:RelNotes-1.6.0.4.txt[1.6.0.4],
- link:RelNotes-1.6.0.3.txt[1.6.0.3],
- link:RelNotes-1.6.0.2.txt[1.6.0.2],
- link:RelNotes-1.6.0.1.txt[1.6.0.1],
- link:RelNotes-1.6.0.txt[1.6.0].
+ link:RelNotes/1.6.0.6.txt[1.6.0.6],
+ link:RelNotes/1.6.0.5.txt[1.6.0.5],
+ link:RelNotes/1.6.0.4.txt[1.6.0.4],
+ link:RelNotes/1.6.0.3.txt[1.6.0.3],
+ link:RelNotes/1.6.0.2.txt[1.6.0.2],
+ link:RelNotes/1.6.0.1.txt[1.6.0.1],
+ link:RelNotes/1.6.0.txt[1.6.0].
* link:v1.5.6.6/git.html[documentation for release 1.5.6.6]
* release notes for
- link:RelNotes-1.5.6.6.txt[1.5.6.6],
- link:RelNotes-1.5.6.5.txt[1.5.6.5],
- link:RelNotes-1.5.6.4.txt[1.5.6.4],
- link:RelNotes-1.5.6.3.txt[1.5.6.3],
- link:RelNotes-1.5.6.2.txt[1.5.6.2],
- link:RelNotes-1.5.6.1.txt[1.5.6.1],
- link:RelNotes-1.5.6.txt[1.5.6].
+ link:RelNotes/1.5.6.6.txt[1.5.6.6],
+ link:RelNotes/1.5.6.5.txt[1.5.6.5],
+ link:RelNotes/1.5.6.4.txt[1.5.6.4],
+ link:RelNotes/1.5.6.3.txt[1.5.6.3],
+ link:RelNotes/1.5.6.2.txt[1.5.6.2],
+ link:RelNotes/1.5.6.1.txt[1.5.6.1],
+ link:RelNotes/1.5.6.txt[1.5.6].
* link:v1.5.5.6/git.html[documentation for release 1.5.5.6]
* release notes for
- link:RelNotes-1.5.5.6.txt[1.5.5.6],
- link:RelNotes-1.5.5.5.txt[1.5.5.5],
- link:RelNotes-1.5.5.4.txt[1.5.5.4],
- link:RelNotes-1.5.5.3.txt[1.5.5.3],
- link:RelNotes-1.5.5.2.txt[1.5.5.2],
- link:RelNotes-1.5.5.1.txt[1.5.5.1],
- link:RelNotes-1.5.5.txt[1.5.5].
+ link:RelNotes/1.5.5.6.txt[1.5.5.6],
+ link:RelNotes/1.5.5.5.txt[1.5.5.5],
+ link:RelNotes/1.5.5.4.txt[1.5.5.4],
+ link:RelNotes/1.5.5.3.txt[1.5.5.3],
+ link:RelNotes/1.5.5.2.txt[1.5.5.2],
+ link:RelNotes/1.5.5.1.txt[1.5.5.1],
+ link:RelNotes/1.5.5.txt[1.5.5].
* link:v1.5.4.7/git.html[documentation for release 1.5.4.7]
* release notes for
- link:RelNotes-1.5.4.7.txt[1.5.4.7],
- link:RelNotes-1.5.4.6.txt[1.5.4.6],
- link:RelNotes-1.5.4.5.txt[1.5.4.5],
- link:RelNotes-1.5.4.4.txt[1.5.4.4],
- link:RelNotes-1.5.4.3.txt[1.5.4.3],
- link:RelNotes-1.5.4.2.txt[1.5.4.2],
- link:RelNotes-1.5.4.1.txt[1.5.4.1],
- link:RelNotes-1.5.4.txt[1.5.4].
+ link:RelNotes/1.5.4.7.txt[1.5.4.7],
+ link:RelNotes/1.5.4.6.txt[1.5.4.6],
+ link:RelNotes/1.5.4.5.txt[1.5.4.5],
+ link:RelNotes/1.5.4.4.txt[1.5.4.4],
+ link:RelNotes/1.5.4.3.txt[1.5.4.3],
+ link:RelNotes/1.5.4.2.txt[1.5.4.2],
+ link:RelNotes/1.5.4.1.txt[1.5.4.1],
+ link:RelNotes/1.5.4.txt[1.5.4].
* link:v1.5.3.8/git.html[documentation for release 1.5.3.8]
* release notes for
- link:RelNotes-1.5.3.8.txt[1.5.3.8],
- link:RelNotes-1.5.3.7.txt[1.5.3.7],
- link:RelNotes-1.5.3.6.txt[1.5.3.6],
- link:RelNotes-1.5.3.5.txt[1.5.3.5],
- link:RelNotes-1.5.3.4.txt[1.5.3.4],
- link:RelNotes-1.5.3.3.txt[1.5.3.3],
- link:RelNotes-1.5.3.2.txt[1.5.3.2],
- link:RelNotes-1.5.3.1.txt[1.5.3.1],
- link:RelNotes-1.5.3.txt[1.5.3].
+ link:RelNotes/1.5.3.8.txt[1.5.3.8],
+ link:RelNotes/1.5.3.7.txt[1.5.3.7],
+ link:RelNotes/1.5.3.6.txt[1.5.3.6],
+ link:RelNotes/1.5.3.5.txt[1.5.3.5],
+ link:RelNotes/1.5.3.4.txt[1.5.3.4],
+ link:RelNotes/1.5.3.3.txt[1.5.3.3],
+ link:RelNotes/1.5.3.2.txt[1.5.3.2],
+ link:RelNotes/1.5.3.1.txt[1.5.3.1],
+ link:RelNotes/1.5.3.txt[1.5.3].
* link:v1.5.2.5/git.html[documentation for release 1.5.2.5]
* release notes for
- link:RelNotes-1.5.2.5.txt[1.5.2.5],
- link:RelNotes-1.5.2.4.txt[1.5.2.4],
- link:RelNotes-1.5.2.3.txt[1.5.2.3],
- link:RelNotes-1.5.2.2.txt[1.5.2.2],
- link:RelNotes-1.5.2.1.txt[1.5.2.1],
- link:RelNotes-1.5.2.txt[1.5.2].
+ link:RelNotes/1.5.2.5.txt[1.5.2.5],
+ link:RelNotes/1.5.2.4.txt[1.5.2.4],
+ link:RelNotes/1.5.2.3.txt[1.5.2.3],
+ link:RelNotes/1.5.2.2.txt[1.5.2.2],
+ link:RelNotes/1.5.2.1.txt[1.5.2.1],
+ link:RelNotes/1.5.2.txt[1.5.2].
* link:v1.5.1.6/git.html[documentation for release 1.5.1.6]
* release notes for
- link:RelNotes-1.5.1.6.txt[1.5.1.6],
- link:RelNotes-1.5.1.5.txt[1.5.1.5],
- link:RelNotes-1.5.1.4.txt[1.5.1.4],
- link:RelNotes-1.5.1.3.txt[1.5.1.3],
- link:RelNotes-1.5.1.2.txt[1.5.1.2],
- link:RelNotes-1.5.1.1.txt[1.5.1.1],
- link:RelNotes-1.5.1.txt[1.5.1].
+ link:RelNotes/1.5.1.6.txt[1.5.1.6],
+ link:RelNotes/1.5.1.5.txt[1.5.1.5],
+ link:RelNotes/1.5.1.4.txt[1.5.1.4],
+ link:RelNotes/1.5.1.3.txt[1.5.1.3],
+ link:RelNotes/1.5.1.2.txt[1.5.1.2],
+ link:RelNotes/1.5.1.1.txt[1.5.1.1],
+ link:RelNotes/1.5.1.txt[1.5.1].
* link:v1.5.0.7/git.html[documentation for release 1.5.0.7]
* release notes for
- link:RelNotes-1.5.0.7.txt[1.5.0.7],
- link:RelNotes-1.5.0.6.txt[1.5.0.6],
- link:RelNotes-1.5.0.5.txt[1.5.0.5],
- link:RelNotes-1.5.0.3.txt[1.5.0.3],
- link:RelNotes-1.5.0.2.txt[1.5.0.2],
- link:RelNotes-1.5.0.1.txt[1.5.0.1],
- link:RelNotes-1.5.0.txt[1.5.0].
+ link:RelNotes/1.5.0.7.txt[1.5.0.7],
+ link:RelNotes/1.5.0.6.txt[1.5.0.6],
+ link:RelNotes/1.5.0.5.txt[1.5.0.5],
+ link:RelNotes/1.5.0.3.txt[1.5.0.3],
+ link:RelNotes/1.5.0.2.txt[1.5.0.2],
+ link:RelNotes/1.5.0.1.txt[1.5.0.1],
+ link:RelNotes/1.5.0.txt[1.5.0].
* documentation for release link:v1.4.4.4/git.html[1.4.4.4],
link:v1.3.3/git.html[1.3.3],
@@ -236,15 +289,23 @@ help ...`.
The <name> is expected in the same format as listed by
'git config' (subkeys separated by dots).
---exec-path::
+--exec-path[=<path>]::
Path to wherever your core git programs are installed.
This can also be controlled by setting the GIT_EXEC_PATH
environment variable. If no path is given, 'git' will print
the current setting and then exit.
--html-path::
- Print the path to wherever your git HTML documentation is installed
- and exit.
+ Print the path, without trailing slash, where git's HTML
+ documentation is installed and exit.
+
+--man-path::
+ Print the manpath (see `man(1)`) for the man pages for
+ this version of git and exit.
+
+--info-path::
+ Print the path where the Info files documenting this
+ version of git are installed and exit.
-p::
--paginate::
@@ -262,17 +323,12 @@ help ...`.
path or relative path to current working directory.
--work-tree=<path>::
- Set the path to the working tree. The value will not be
- used in combination with repositories found automatically in
- a .git directory (i.e. $GIT_DIR is not set).
+ Set the path to the working tree. It can be an absolute path
+ or a path relative to the current working directory.
This can also be controlled by setting the GIT_WORK_TREE
environment variable and the core.worktree configuration
- variable. It can be an absolute path or relative path to
- the directory specified by --git-dir or GIT_DIR.
- Note: If --git-dir or GIT_DIR are specified but none of
- --work-tree, GIT_WORK_TREE and core.worktree is specified,
- the current working directory is regarded as the top directory
- of your working tree.
+ variable (see core.worktree in linkgit:git-config[1] for a
+ more detailed discussion).
--bare::
Treat the repository as a bare repository. If GIT_DIR
@@ -467,19 +523,18 @@ Any git command accepting any <object> can also use the following
symbolic notation:
HEAD::
- indicates the head of the current branch (i.e. the
- contents of `$GIT_DIR/HEAD`).
+ indicates the head of the current branch.
<tag>::
a valid tag 'name'
- (i.e. the contents of `$GIT_DIR/refs/tags/<tag>`).
+ (i.e. a `refs/tags/<tag>` reference).
<head>::
a valid head 'name'
- (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
+ (i.e. a `refs/heads/<head>` reference).
For a more complete list of ways to spell object names, see
-"SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+"SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
File/Directory Structure
@@ -589,7 +644,6 @@ where:
contents of <old|new>,
<old|new>-hex:: are the 40-hexdigit SHA1 hashes,
<old|new>-mode:: are the octal representation of the file modes.
-
+
The file parameters can point at the user's working file
(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
@@ -629,6 +683,13 @@ Usually it is easier to configure any desired options through your
personal `.ssh/config` file. Please consult your ssh documentation
for further details.
+'GIT_ASKPASS'::
+ If this environment variable is set, then git commands which need to
+ acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
+ will call this program with a suitable prompt as command line argument
+ and read the password from its STDOUT. See also the 'core.askpass'
+ option in linkgit:git-config[1].
+
'GIT_FLUSH'::
If this environment variable is set to "1", then commands such
as 'git blame' (in incremental mode), 'git rev-list', 'git log',
@@ -708,16 +769,19 @@ unmerged version of a file when a merge is in progress.
Authors
-------
-* git's founding father is Linus Torvalds <torvalds@osdl.org>.
-* The current git nurse is Junio C Hamano <gitster@pobox.com>.
-* The git potty was written by Andreas Ericsson <ae@op5.se>.
-* General upbringing is handled by the git-list <git@vger.kernel.org>.
-
-Documentation
+Git was started by Linus Torvalds, and is currently maintained by Junio
+C Hamano. Numerous contributions have come from the git mailing list
+<git@vger.kernel.org>. For a more complete list of contributors, see
+http://git-scm.com/about. If you have a clone of git.git itself, the
+output of linkgit:git-shortlog[1] and linkgit:git-blame[1] can show you
+the authors for specific parts of the project.
+
+Reporting Bugs
--------------
-The documentation for git suite was started by David Greaves
-<david@dgreaves.com>, and later enhanced greatly by the
-contributors on the git-list <git@vger.kernel.org>.
+
+Report bugs to the Git mailing list <git@vger.kernel.org> where the
+development and maintenance is primarily done. You do not have to be
+subscribed to the list to send a message there.
SEE ALSO
--------
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 564586b..412c55b 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -62,14 +62,21 @@ consults `$GIT_DIR/info/attributes` file (which has the highest
precedence), `.gitattributes` file in the same directory as the
path in question, and its parent directories up to the toplevel of the
work tree (the further the directory that contains `.gitattributes`
-is from the path in question, the lower its precedence).
+is from the path in question, the lower its precedence). Finally
+global and system-wide files are considered (they have the lowest
+precedence).
If you wish to affect only a single repository (i.e., to assign
-attributes to files that are particular to one user's workflow), then
+attributes to files that are particular to
+one user's workflow for that repository), then
attributes should be placed in the `$GIT_DIR/info/attributes` file.
Attributes which should be version-controlled and distributed to other
repositories (i.e., attributes of interest to all users) should go into
-`.gitattributes` files.
+`.gitattributes` files. Attributes that should affect all repositories
+for a single user should be placed in a file specified by the
+`core.attributesfile` configuration option (see linkgit:git-config[1]).
+Attributes for all users on a system should be placed in the
+`$(prefix)/etc/gitattributes` file.
Sometimes you would need to override an setting of an attribute
for a path to `unspecified` state. This can be done by listing
@@ -317,6 +324,27 @@ command is "cat").
smudge = cat
------------------------
+For best results, `clean` should not alter its output further if it is
+run twice ("clean->clean" should be equivalent to "clean"), and
+multiple `smudge` commands should not alter `clean`'s output
+("smudge->smudge->clean" should be equivalent to "clean"). See the
+section on merging below.
+
+The "indent" filter is well-behaved in this regard: it will not modify
+input that is already correctly indented. In this case, the lack of a
+smudge filter means that the clean filter _must_ accept its own output
+without modifying it.
+
+Sequence "%f" on the filter command line is replaced with the name of
+the file the filter is working on. A filter might use this in keyword
+substitution. For example:
+
+------------------------
+[filter "p4"]
+ clean = git-p4-filter --clean %f
+ smudge = git-p4-filter --smudge %f
+------------------------
+
Interaction between checkin/checkout attributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -331,6 +359,29 @@ In the check-out codepath, the blob content is first converted
with `text`, and then `ident` and fed to `filter`.
+Merging branches with differing checkin/checkout attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you have added attributes to a file that cause the canonical
+repository format for that file to change, such as adding a
+clean/smudge filter or text/eol/ident attributes, merging anything
+where the attribute is not in place would normally cause merge
+conflicts.
+
+To prevent these unnecessary merge conflicts, git can be told to run a
+virtual check-out and check-in of all three stages of a file when
+resolving a three-way merge by setting the `merge.renormalize`
+configuration variable. This prevents changes caused by check-in
+conversion from causing spurious merge conflicts when a converted file
+is merged with an unconverted file.
+
+As long as a "smudge->clean" results in the same output as a "clean"
+even on files that are already smudged, this strategy will
+automatically resolve all filter-related conflicts. Filters that do
+not act in this way may cause additional merge conflicts that must be
+resolved manually.
+
+
Generating diff text
~~~~~~~~~~~~~~~~~~~~
@@ -441,6 +492,10 @@ patterns are available:
- `cpp` suitable for source code in the C and C++ languages.
+- `csharp` suitable for source code in the C# language.
+
+- `fortran` suitable for source code in the Fortran language.
+
- `html` suitable for HTML/XHTML documents.
- `java` suitable for source code in the Java language.
@@ -449,6 +504,8 @@ patterns are available:
- `pascal` suitable for source code in the Pascal/Delphi language.
+- `perl` suitable for source code in the Perl language.
+
- `php` suitable for source code in the PHP language.
- `python` suitable for source code in the Python language.
@@ -536,13 +593,77 @@ and now produces better output), you can remove the cache
manually with `git update-ref -d refs/notes/textconv/jpg` (where
"jpg" is the name of the diff driver, as in the example above).
+Choosing textconv versus external diff
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to show differences between binary or specially-formatted
+blobs in your repository, you can choose to use either an external diff
+command, or to use textconv to convert them to a diff-able text format.
+Which method you choose depends on your exact situation.
+
+The advantage of using an external diff command is flexibility. You are
+not bound to find line-oriented changes, nor is it necessary for the
+output to resemble unified diff. You are free to locate and report
+changes in the most appropriate way for your data format.
+
+A textconv, by comparison, is much more limiting. You provide a
+transformation of the data into a line-oriented text format, and git
+uses its regular diff tools to generate the output. There are several
+advantages to choosing this method:
+
+1. Ease of use. It is often much simpler to write a binary to text
+ transformation than it is to perform your own diff. In many cases,
+ existing programs can be used as textconv filters (e.g., exif,
+ odt2txt).
+
+2. Git diff features. By performing only the transformation step
+ yourself, you can still utilize many of git's diff features,
+ including colorization, word-diff, and combined diffs for merges.
+
+3. Caching. Textconv caching can speed up repeated diffs, such as those
+ you might trigger by running `git log -p`.
+
+
+Marking files as binary
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Git usually guesses correctly whether a blob contains text or binary
+data by examining the beginning of the contents. However, sometimes you
+may want to override its decision, either because a blob contains binary
+data later in the file, or because the content, while technically
+composed of text characters, is opaque to a human reader. For example,
+many postscript files contain only ascii characters, but produce noisy
+and meaningless diffs.
+
+The simplest way to mark a file as binary is to unset the diff
+attribute in the `.gitattributes` file:
+
+------------------------
+*.ps -diff
+------------------------
+
+This will cause git to generate `Binary files differ` (or a binary
+patch, if binary patches are enabled) instead of a regular diff.
+
+However, one may also want to specify other diff driver attributes. For
+example, you might want to use `textconv` to convert postscript files to
+an ascii representation for human viewing, but otherwise treat them as
+binary files. You cannot specify both `-diff` and `diff=ps` attributes.
+The solution is to use the `diff.*.binary` config option:
+
+------------------------
+[diff "ps"]
+ textconv = ps2ascii
+ binary = true
+------------------------
+
Performing a three-way merge
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`merge`
^^^^^^^
-The attribute `merge` affects how three versions of a file is
+The attribute `merge` affects how three versions of a file are
merged when a file-level merge is necessary during `git merge`,
and other commands such as `git revert` and `git cherry-pick`.
@@ -556,15 +677,15 @@ Unset::
Take the version from the current branch as the
tentative merge result, and declare that the merge has
- conflicts. This is suitable for binary files that does
+ conflicts. This is suitable for binary files that do
not have a well-defined merge semantics.
Unspecified::
By default, this uses the same built-in 3-way merge
- driver as is the case the `merge` attribute is set.
- However, `merge.default` configuration variable can name
- different merge driver to be used for paths to which the
+ driver as is the case when the `merge` attribute is set.
+ However, the `merge.default` configuration variable can name
+ different merge driver to be used with paths for which the
`merge` attribute is unspecified.
String::
@@ -678,6 +799,8 @@ control per path.
Set::
Notice all types of potential whitespace errors known to git.
+ The tab width is taken from the value of the `core.whitespace`
+ configuration variable.
Unset::
@@ -685,13 +808,13 @@ Unset::
Unspecified::
- Use the value of `core.whitespace` configuration variable to
+ Use the value of the `core.whitespace` configuration variable to
decide what to notice as error.
String::
Specify a comma separate list of common whitespace problems to
- notice in the same format as `core.whitespace` configuration
+ notice in the same format as the `core.whitespace` configuration
variable.
diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt
index 6928724..f734f97 100644
--- a/Documentation/gitcli.txt
+++ b/Documentation/gitcli.txt
@@ -169,10 +169,6 @@ See also http://marc.info/?l=git&m=116563135620359 and
http://marc.info/?l=git&m=119150393620273 for further
information.
-Documentation
--------------
-Documentation by Pierre Habouzit and the git-list <git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index f7815e9..c27d086 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -110,7 +110,7 @@ An 'object' is identified by its 160-bit SHA1 hash, aka 'object name',
and a reference to an object is always the 40-byte hex
representation of that SHA1 name. The files in the `refs`
subdirectory are expected to contain these hex references
-(usually with a final `\'\n\'` at the end), and you should thus
+(usually with a final `\n` at the end), and you should thus
expect to see a number of 41-byte files containing these
references in these `refs` subdirectories when you actually start
populating your tree.
@@ -310,7 +310,7 @@ and this will just output the name of the resulting tree, in this case
----------------
which is another incomprehensible object name. Again, if you want to,
-you can use `git cat-file -t 8988d\...` to see that this time the object
+you can use `git cat-file -t 8988d...` to see that this time the object
is not a "blob" object, but a "tree" object (you can also use
`git cat-file` to actually output the raw object contents, but you'll see
mainly a binary mess, so that's less interesting).
@@ -436,8 +436,8 @@ $ git update-index hello
(note how we didn't need the `\--add` flag this time, since git knew
about the file already).
-Note what happens to the different 'git diff-\*' versions here. After
-we've updated `hello` in the index, `git diff-files -p` now shows no
+Note what happens to the different 'git diff-{asterisk}' versions here.
+After we've updated `hello` in the index, `git diff-files -p` now shows no
differences, but `git diff-index -p HEAD` still *does* show that the
current state is different from the state we committed. In fact, now
'git diff-index' shows the same difference whether we use the `--cached`
@@ -494,7 +494,7 @@ and it will show what the last commit (in `HEAD`) actually changed.
[NOTE]
============
Here is an ASCII art by Jon Loeliger that illustrates how
-various diff-\* commands compare things.
+various 'diff-{asterisk}' commands compare things.
diff-tree
+----+
@@ -958,11 +958,11 @@ $ git show-branch --topo-order --more=1 master mybranch
The first two lines indicate that it is showing the two branches
and the first line of the commit log message from their
top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `\*` character), and the first column for
+(notice the asterisk `{asterisk}` character), and the first column for
the later output lines is used to show commits contained in the
`master` branch, and the second column for the `mybranch`
branch. Three commits are shown along with their log messages.
-All of them have non blank characters in the first column (`*`
+All of them have non blank characters in the first column (`{asterisk}`
shows an ordinary commit on the current branch, `-` is a merge commit), which
means they are now part of the `master` branch. Only the "Some
work" commit has the plus `+` character in the second column,
@@ -971,7 +971,7 @@ commits from the master branch. The string inside brackets
before the commit log message is a short name you can use to
name the commit. In the above example, 'master' and 'mybranch'
are branch heads. 'master^' is the first parent of 'master'
-branch head. Please see linkgit:git-rev-parse[1] if you want to
+branch head. Please see linkgit:gitrevisions[7] if you want to
see more complex cases.
[NOTE]
@@ -1092,7 +1092,7 @@ Downloader from http and https URL
first obtains the topmost commit object name from the remote site
by looking at the specified refname under `repo.git/refs/` directory,
and then tries to obtain the
-commit object by downloading from `repo.git/objects/xx/xxx\...`
+commit object by downloading from `repo.git/objects/xx/xxx...`
using the object name of that commit object. Then it reads the
commit object to find out its parent commits and the associate
tree object; it repeats this process until it gets all the
@@ -1420,7 +1420,7 @@ packed, and stores the packed file in `.git/objects/pack`
directory.
[NOTE]
-You will see two files, `pack-\*.pack` and `pack-\*.idx`,
+You will see two files, `pack-{asterisk}.pack` and `pack-{asterisk}.idx`,
in `.git/objects/pack` directory. They are closely related to
each other, and if you ever copy them by hand to a different
repository for whatever reason, you should make sure you copy
diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt
index d861ec4..aeb0cdc 100644
--- a/Documentation/gitcvs-migration.txt
+++ b/Documentation/gitcvs-migration.txt
@@ -7,7 +7,8 @@ gitcvs-migration - git for CVS users
SYNOPSIS
--------
-git cvsimport *
+[verse]
+'git cvsimport' *
DESCRIPTION
-----------
diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt
index 5d91a7e..370624c 100644
--- a/Documentation/gitdiffcore.txt
+++ b/Documentation/gitdiffcore.txt
@@ -7,6 +7,7 @@ gitdiffcore - Tweaking diff output
SYNOPSIS
--------
+[verse]
'git diff' *
DESCRIPTION
@@ -227,9 +228,9 @@ changes that touch a specified string, and is controlled by the
commands.
When diffcore-pickaxe is in use, it checks if there are
-filepairs whose "result" side has the specified string and
-whose "origin" side does not. Such a filepair represents "the
-string appeared in this changeset". It also checks for the
+filepairs whose "result" side and whose "origin" side have
+different number of specified string. Such a filepair represents
+"the string appeared in this changeset". It also checks for the
opposite case that loses the specified string.
When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 7183aa9..28edefa 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -350,10 +350,6 @@ rebase::
The commits are guaranteed to be listed in the order that they were
processed by rebase.
-There is no default 'post-rewrite' hook, but see the
-`post-receive-copy-notes` script in `contrib/hooks` for an example
-that copies your git-notes to the rewritten commits.
-
GIT
---
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index e10fa88..2e7328b 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -14,11 +14,8 @@ DESCRIPTION
A `gitignore` file specifies intentionally untracked files that
git should ignore.
-Note that all the `gitignore` files really concern only files
-that are not already tracked by git;
-in order to ignore uncommitted changes in already tracked files,
-please refer to the 'git update-index --assume-unchanged'
-documentation.
+Files already tracked by git are not affected; see the NOTES
+below for details.
Each line in a `gitignore` file specifies a pattern.
When deciding whether to ignore a path, git normally checks
@@ -62,7 +59,8 @@ files specified by command-line options. Higher-level git
tools, such as 'git status' and 'git add',
use patterns from the sources specified above.
-Patterns have the following format:
+PATTERN FORMAT
+--------------
- A blank line matches no files, so it can serve as a separator
for readability.
@@ -90,15 +88,28 @@ Patterns have the following format:
- Otherwise, git treats the pattern as a shell glob suitable
for consumption by fnmatch(3) with the FNM_PATHNAME flag:
wildcards in the pattern will not match a / in the pathname.
- For example, "Documentation/\*.html" matches
+ For example, "Documentation/{asterisk}.html" matches
"Documentation/git.html" but not "Documentation/ppc/ppc.html"
or "tools/perf/Documentation/perf.html".
- A leading slash matches the beginning of the pathname.
- For example, "/*.c" matches "cat-file.c" but not
+ For example, "/{asterisk}.c" matches "cat-file.c" but not
"mozilla-sha1/sha1.c".
-An example:
+NOTES
+-----
+
+The purpose of gitignore files is to ensure that certain files
+not tracked by git remain untracked.
+
+To ignore uncommitted changes in a file that is already tracked,
+use 'git update-index {litdd}assume-unchanged'.
+
+To stop tracking a file that is currently tracked, use
+'git rm --cached'.
+
+EXAMPLES
+--------
--------------------------------------------------------------
$ git status
@@ -140,10 +151,10 @@ Another example:
The second .gitignore prevents git from ignoring
`arch/foo/kernel/vmlinux.lds.S`.
-Documentation
--------------
-Documentation by David Greaves, Junio C Hamano, Josh Triplett,
-Frank Lichtenheld, and the git-list <git@vger.kernel.org>.
+SEE ALSO
+--------
+linkgit:git-rm[1], linkgit:git-update-index[1],
+linkgit:gitrepository-layout[5]
GIT
---
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index 99baa24..a17a354 100644
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
@@ -7,6 +7,7 @@ gitk - The git repository browser
SYNOPSIS
--------
+[verse]
'gitk' [<option>...] [<revs>] [--] [<path>...]
DESCRIPTION
@@ -69,7 +70,7 @@ frequently used options.
the form "'<from>'..'<to>'" to show all revisions between '<from>' and
back to '<to>'. Note, more advanced revision selection can be applied.
For a more complete list of ways to spell object names, see
- "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+ linkgit:gitrevisions[7].
<path>...::
@@ -113,15 +114,6 @@ SEE ALSO
A minimal repository browser and git tool output highlighter written
in C using Ncurses.
-Author
-------
-Written by Paul Mackerras <paulus@samba.org>.
-
-Documentation
---------------
-Documentation by Junio C Hamano, Jonas Fonseca, and the git-list
-<git@vger.kernel.org>.
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index 5daf750..4040941 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -29,6 +29,9 @@ submodule.<name>.path::
submodule.<name>.url::
Defines an url from where the submodule repository can be cloned.
+ This may be either an absolute URL ready to be passed to
+ linkgit:git-clone[1] or (if it begins with ./ or ../) a location
+ relative to the superproject's origin repository.
submodule.<name>.update::
Defines what to do when the submodule is updated by the superproject.
@@ -41,6 +44,29 @@ submodule.<name>.update::
This config option is overridden if 'git submodule update' is given
the '--merge' or '--rebase' options.
+submodule.<name>.fetchRecurseSubmodules::
+ This option can be used to control recursive fetching of this
+ submodule. If this option is also present in the submodules entry in
+ .git/config of the superproject, the setting there will override the
+ one found in .gitmodules.
+ Both settings can be overridden on the command line by using the
+ "--[no-]recurse-submodules" option to "git fetch" and "git pull".
+
+submodule.<name>.ignore::
+ Defines under what circumstances "git status" and the diff family show
+ a submodule as modified. When set to "all", it will never be considered
+ modified, "dirty" will ignore all changes to the submodules work tree and
+ takes only differences between the HEAD of the submodule and the commit
+ recorded in the superproject into account. "untracked" will additionally
+ let submodules with modified tracked files in their work tree show up.
+ Using "none" (the default when this option is not set) also shows
+ submodules that have untracked files in their work tree as changed.
+ If this option is also present in the submodules entry in .git/config of
+ the superproject, the setting there will override the one found in
+ .gitmodules.
+ Both settings can be overridden on the command line by using the
+ "--ignore-submodule" option.
+
EXAMPLES
--------
@@ -64,10 +90,6 @@ SEE ALSO
--------
linkgit:git-submodule[1] linkgit:git-config[1]
-DOCUMENTATION
--------------
-Documentation by Lars Hjemli <hjemli@gmail.com>
-
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 3cd32d6..eb3d040 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -16,7 +16,7 @@ You may find these things in your git repository (`.git`
directory for a repository associated with your working tree, or
`<project>.git` directory for a public 'bare' repository. It is
also possible to have a working tree where `.git` is a plain
-ascii file containing `gitdir: <path>`, i.e. the path to the
+ASCII file containing `gitdir: <path>`, i.e. the path to the
real git repository).
objects::
diff --git a/Documentation/gitrevisions.txt b/Documentation/gitrevisions.txt
new file mode 100644
index 0000000..fc4789f
--- /dev/null
+++ b/Documentation/gitrevisions.txt
@@ -0,0 +1,35 @@
+gitrevisions(7)
+================
+
+NAME
+----
+gitrevisions - specifying revisions and ranges for git
+
+SYNOPSIS
+--------
+gitrevisions
+
+
+DESCRIPTION
+-----------
+
+Many Git commands take revision parameters as arguments. Depending on
+the command, they denote a specific commit or, for commands which
+walk the revision graph (such as linkgit:git-log[1]), all commits which can
+be reached from that commit. In the latter case one can also specify a
+range of revisions explicitly.
+
+In addition, some Git commands (such as linkgit:git-show[1]) also take
+revision parameters which denote other objects than commits, e.g. blobs
+("files") or trees ("directories of files").
+
+include::revisions.txt[]
+
+
+SEE ALSO
+--------
+linkgit:git-rev-parse[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
index ecab0c0..f1e4422 100644
--- a/Documentation/gittutorial-2.txt
+++ b/Documentation/gittutorial-2.txt
@@ -7,6 +7,7 @@ gittutorial-2 - A tutorial introduction to git: part two
SYNOPSIS
--------
+[verse]
git *
DESCRIPTION
@@ -373,7 +374,7 @@ $ git status
#
# new file: closing.txt
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: file.txt
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
index 1c16066..dee0505 100644
--- a/Documentation/gittutorial.txt
+++ b/Documentation/gittutorial.txt
@@ -7,6 +7,7 @@ gittutorial - A tutorial introduction to git (for version 1.5.1 or newer)
SYNOPSIS
--------
+[verse]
git *
DESCRIPTION
@@ -385,7 +386,7 @@ alice$ git fetch bob
Unlike the longhand form, when Alice fetches from Bob using a
remote repository shorthand set up with 'git remote', what was
-fetched is stored in a remote tracking branch, in this case
+fetched is stored in a remote-tracking branch, in this case
`bob/master`. So after this:
-------------------------------------
@@ -402,8 +403,8 @@ could merge the changes into her master branch:
alice$ git merge bob/master
-------------------------------------
-This `merge` can also be done by 'pulling from her own remote
-tracking branch', like this:
+This `merge` can also be done by 'pulling from her own remote-tracking
+branch', like this:
-------------------------------------
alice$ git pull . remotes/bob/master
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index 1ef55ff..5e4f362 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -7,6 +7,7 @@ gitworkflows - An overview of recommended workflows with git
SYNOPSIS
--------
+[verse]
git *
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 1f029f8..3595b58 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -131,7 +131,7 @@ to point at the new commit.
you have. In such these cases, 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_tracking_branch,tracking branch>> of a remote
+ <<def_remote_tracking_branch,remote-tracking branch>> of a remote
<<def_repository,repository>>.
[[def_fetch]]fetch::
@@ -161,8 +161,8 @@ to point at the new commit.
[[def_head]]head::
A <<def_ref,named reference>> to the <<def_commit,commit>> at the tip of a
- <<def_branch,branch>>. Heads are stored in
- `$GIT_DIR/refs/heads/`, except when using packed refs. (See
+ <<def_branch,branch>>. Heads are stored in a file in
+ `$GIT_DIR/refs/heads/` directory, except when using packed refs. (See
linkgit:git-pack-refs[1].)
[[def_HEAD]]HEAD::
@@ -170,8 +170,8 @@ to point at the new commit.
working tree>> is normally derived from the state of the tree
referred to by HEAD. HEAD is a reference to one of the
<<def_head,heads>> in your repository, except when using a
- <<def_detached_HEAD,detached HEAD>>, in which case it may
- reference an arbitrary commit.
+ <<def_detached_HEAD,detached HEAD>>, in which case it directly
+ references an arbitrary commit.
[[def_head_ref]]head ref::
A synonym for <<def_head,head>>.
@@ -260,7 +260,7 @@ This commit is referred to as a "merge commit", or sometimes just a
The default upstream <<def_repository,repository>>. Most projects have
at least one upstream project which they track. By default
'origin' is used for that purpose. New upstream updates
- will be fetched into remote <<def_tracking_branch,tracking branches>> named
+ will be fetched into remote <<def_remote_tracking_branch,remote-tracking branches>> named
origin/name-of-upstream-branch, which you can see using
`git branch -r`.
@@ -273,6 +273,61 @@ This commit is referred to as a "merge commit", or sometimes just a
<<def_pack,pack>>, to assist in efficiently accessing the contents of a
pack.
+[[def_pathspec]]pathspec::
+ Pattern used to specify paths.
++
+Pathspecs are used on the command line of "git ls-files", "git
+ls-tree", "git add", "git grep", "git diff", "git checkout",
+and many other commands to
+limit the scope of operations to some subset of the tree or
+worktree. See the documentation of each command for whether
+paths are relative to the current directory or toplevel. The
+pathspec syntax is as follows:
+
+* any path matches itself
+* the pathspec up to the last slash represents a
+ directory prefix. The scope of that pathspec is
+ limited to that subtree.
+* the rest of the pathspec is a pattern for the remainder
+ of the pathname. Paths relative to the directory
+ prefix will be matched against that pattern using fnmatch(3);
+ in particular, '*' and '?' _can_ match directory separators.
++
+For example, Documentation/*.jpg will match all .jpg files
+in the Documentation subtree,
+including Documentation/chapter_1/figure_1.jpg.
+
++
+A pathspec that begins with a colon `:` has special meaning. In the
+short form, the leading colon `:` is followed by zero or more "magic
+signature" letters (which optionally is terminated by another colon `:`),
+and the remainder is the pattern to match against the path. The optional
+colon that terminates the "magic signature" can be omitted if the pattern
+begins with a character that cannot be a "magic signature" and is not a
+colon.
++
+In the long form, the leading colon `:` is followed by a open
+parenthesis `(`, a comma-separated list of zero or more "magic words",
+and a close parentheses `)`, and the remainder is the pattern to match
+against the path.
++
+The "magic signature" consists of an ASCII symbol that is not
+alphanumeric.
++
+--
+top `/`;;
+ The magic word `top` (mnemonic: `/`) makes the pattern match
+ from the root of the working tree, even when you are running
+ the command from inside a subdirectory.
+--
++
+Currently only the slash `/` is recognized as the "magic signature",
+but it is envisioned that we will support more types of magic in later
+versions of git.
++
+A pathspec with only a colon means "there is no pathspec". This form
+should not be combined with other pathspec.
+
[[def_parent]]parent::
A <<def_commit_object,commit object>> contains a (possibly empty) list
of the logical predecessor(s) in the line of development, i.e. its
@@ -327,8 +382,9 @@ This commit is referred to as a "merge commit", or sometimes just a
[[def_ref]]ref::
A 40-byte hex representation of a <<def_SHA1,SHA1>> or a name that
- denotes a particular <<def_object,object>>. These may be stored in
- `$GIT_DIR/refs/`.
+ denotes a particular <<def_object,object>>. They may be stored in
+ a file under `$GIT_DIR/refs/` directory, or
+ in the `$GIT_DIR/packed-refs` file.
[[def_reflog]]reflog::
A reflog shows the local "history" of a ref. In other words,
@@ -349,6 +405,14 @@ This commit is referred to as a "merge commit", or sometimes just a
master branch head as to-upstream branch at $URL". See also
linkgit:git-push[1].
+[[def_remote_tracking_branch]]remote-tracking branch::
+ A regular git <<def_branch,branch>> that is used to follow changes from
+ another <<def_repository,repository>>. A remote-tracking
+ branch should not contain direct modifications or have local commits
+ made to it. A remote-tracking branch can usually be
+ identified as the right-hand-side <<def_ref,ref>> in a Pull:
+ <<def_refspec,refspec>>.
+
[[def_repository]]repository::
A collection of <<def_ref,refs>> together with an
<<def_object_database,object database>> containing all objects
@@ -396,14 +460,14 @@ This commit is referred to as a "merge commit", or sometimes just a
command.
[[def_tag]]tag::
- A <<def_ref,ref>> pointing to a <<def_tag_object,tag>> or
- <<def_commit_object,commit object>>. In contrast to a <<def_head,head>>,
- a tag is not changed by a <<def_commit,commit>>. Tags (not
- <<def_tag_object,tag objects>>) are stored in `$GIT_DIR/refs/tags/`. A
- git tag has nothing to do with a Lisp tag (which would be
- called an <<def_object_type,object type>> in git's context). A
- tag is most typically used to mark a particular point in the
- commit ancestry <<def_chain,chain>>.
+ A <<def_ref,ref>> under `refs/tags/` namespace that points to an
+ object of an arbitrary type (typically a tag points to either a
+ <<def_tag_object,tag>> or a <<def_commit_object,commit object>>).
+ In contrast to a <<def_head,head>>, a tag is not updated by
+ the `commit` command. A git tag has nothing to do with a Lisp
+ tag (which would be called an <<def_object_type,object type>>
+ in git's context). A tag is most typically used to mark a particular
+ point in the commit ancestry <<def_chain,chain>>.
[[def_tag_object]]tag object::
An <<def_object,object>> containing a <<def_ref,ref>> pointing to
@@ -418,14 +482,6 @@ This commit is referred to as a "merge commit", or sometimes just a
that each contain very well defined concepts or small incremental yet
related changes.
-[[def_tracking_branch]]tracking branch::
- A regular git <<def_branch,branch>> that is used to follow changes from
- another <<def_repository,repository>>. A tracking
- branch should not contain direct modifications or have local commits
- made to it. A tracking branch can usually be
- identified as the right-hand-side <<def_ref,ref>> in a Pull:
- <<def_refspec,refspec>>.
-
[[def_tree]]tree::
Either a <<def_working_tree,working tree>>, or a <<def_tree_object,tree
object>> together with the dependent <<def_blob_object,blob>> and tree objects
diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt
index ff5c0bc..6fd7119 100644
--- a/Documentation/howto/revert-a-faulty-merge.txt
+++ b/Documentation/howto/revert-a-faulty-merge.txt
@@ -229,7 +229,7 @@ reverting W. Mainline's history would look like this:
A---B---C
But if you don't actually need to change commit A, then you need some way to
-recreate it as a new commit with the same changes in it. The rebase commmand's
+recreate it as a new commit with the same changes in it. The rebase command's
--no-ff option provides a way to do this:
$ git rebase [-i] --no-ff P
diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt
index 8c32da6..093c656 100644
--- a/Documentation/howto/revert-branch-rebase.txt
+++ b/Documentation/howto/revert-branch-rebase.txt
@@ -112,25 +112,19 @@ $ git tag pu-anchor pu
$ git rebase master
* Applying: Redo "revert" using three-way merge machinery.
First trying simple merge strategy to cherry-pick.
-Finished one cherry-pick.
* Applying: Remove git-apply-patch-script.
First trying simple merge strategy to cherry-pick.
Simple cherry-pick fails; trying Automatic cherry-pick.
Removing Documentation/git-apply-patch-script.txt
Removing git-apply-patch-script
-Finished one cherry-pick.
* Applying: Document "git cherry-pick" and "git revert"
First trying simple merge strategy to cherry-pick.
-Finished one cherry-pick.
* Applying: mailinfo and applymbox updates
First trying simple merge strategy to cherry-pick.
-Finished one cherry-pick.
* Applying: Show commits in topo order and name all commits.
First trying simple merge strategy to cherry-pick.
-Finished one cherry-pick.
* Applying: More documentation updates.
First trying simple merge strategy to cherry-pick.
-Finished one cherry-pick.
------------------------------------------------
The temporary tag 'pu-anchor' is me just being careful, in case 'git
diff --git a/Documentation/howto/using-merge-subtree.txt b/Documentation/howto/using-merge-subtree.txt
index 0953a50..2933056 100644
--- a/Documentation/howto/using-merge-subtree.txt
+++ b/Documentation/howto/using-merge-subtree.txt
@@ -71,5 +71,5 @@ Additional tips
relevant parts of your tree.
- Please note that if the other project merges from you, then it will
- connects its history to yours, which can be something they don't want
+ connect its history to yours, which can be something they don't want
to.
diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh
index 34d02a2..76d69a9 100755
--- a/Documentation/install-webdoc.sh
+++ b/Documentation/install-webdoc.sh
@@ -6,13 +6,13 @@ for h in \
*.txt *.html \
howto/*.txt howto/*.html \
technical/*.txt technical/*.html \
- RelNotes-*.txt *.css
+ RelNotes/*.txt *.css
do
if test ! -f "$h"
then
: did not match
elif test -f "$T/$h" &&
- $DIFF -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
+ $DIFF -u -I'^Last updated ' "$T/$h" "$h"
then
:; # up to date
else
@@ -30,7 +30,7 @@ for th in \
do
h=`expr "$th" : "$strip_leading"'\(.*\)'`
case "$h" in
- index.html) continue ;;
+ RelNotes-*.txt | index.html) continue ;;
esac
test -f "$h" && continue
echo >&2 "# rm -f $th"
diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt
index a403155..861bd6f 100644
--- a/Documentation/merge-config.txt
+++ b/Documentation/merge-config.txt
@@ -6,25 +6,57 @@ merge.conflictstyle::
a `>>>>>>>` marker. An alternate style, "diff3", adds a `|||||||`
marker and the original text before the `=======` marker.
+merge.defaultToUpstream::
+ If merge is called without any commit argument, merge the upstream
+ branches configured for the current branch by using their last
+ observed values stored in their remote tracking branches.
+ The values of the `branch.<current branch>.merge` that name the
+ 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.
+
+merge.ff::
+ By default, git does not create an extra merge commit when merging
+ a commit that is a descendant of the current commit. Instead, the
+ tip of the current branch is fast-forwarded. When set to `false`,
+ this variable tells git to create an extra merge commit in such
+ a case (equivalent to giving the `--no-ff` option from the command
+ line). When set to `only`, only such fast-forward merges are
+ allowed (equivalent to giving the `--ff-only` option from the
+ command line).
+
merge.log::
- Whether to include summaries of merged commits in newly created
- merge commit messages. False by default.
+ In addition to branch names, populate the log message with at
+ most the specified number of one-line descriptions from the
+ actual commits that are being merged. Defaults to false, and
+ true is a synonym for 20.
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.
+merge.renormalize::
+ Tell git that canonical representation of files in the
+ repository has changed over time (e.g. earlier commits record
+ text files with CRLF line endings, but recent ones use LF line
+ endings). In such a repository, git can convert the data
+ recorded in commits to a canonical form before performing a
+ merge to reduce unnecessary conflicts. For more information,
+ see section "Merging branches with differing checkin/checkout
+ attributes" in linkgit:gitattributes[5].
+
merge.stat::
Whether to print the diffstat between ORIG_HEAD and the merge result
at the end of the merge. True by default.
merge.tool::
Controls which merge resolution program is used by
- linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3",
- "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff",
- "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and
- "opendiff". Any other value is treated is custom merge tool
+ linkgit:git-mergetool[1]. Valid built-in values are: "araxis",
+ "bc3", "diffuse", "ecmerge", "emerge", "gvimdiff", "kdiff3", "meld",
+ "opendiff", "p4merge", "tkdiff", "tortoisemerge", "vimdiff"
+ and "xxdiff". Any other value is treated is custom merge tool
and there must be a corresponding mergetool.<tool>.cmd option.
merge.verbosity::
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 722d704..b613d4e 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -16,11 +16,11 @@ inspect and further tweak the merge result before committing.
With --no-ff Generate a merge commit even if the merge
resolved as a fast-forward.
---log::
+--log[=<n>]::
--no-log::
In addition to branch names, populate the log message with
- one-line descriptions from the actual commits that are being
- merged.
+ one-line descriptions from at most <n> actual commits that are being
+ merged. See also linkgit:git-fmt-merge-msg[1].
+
With --no-log do not list one-line descriptions from the
actual commits being merged.
@@ -75,9 +75,17 @@ option can be used to override --squash.
ifndef::git-pull[]
-q::
--quiet::
- Operate quietly.
+ Operate quietly. Implies --no-progress.
-v::
--verbose::
Be verbose.
+
+--progress::
+--no-progress::
+ Turn progress on/off explicitly. If neither is specified,
+ progress is shown if standard error is connected to a terminal.
+ Note that not all merge strategies may support progress
+ reporting.
+
endif::git-pull[]
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index a5bc1db..595a3cf 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -40,7 +40,45 @@ the other tree did, declaring 'our' history contains all that happened in it.
theirs;;
This is opposite of 'ours'.
-subtree[=path];;
+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`.
+
+ignore-space-change;;
+ignore-all-space;;
+ignore-space-at-eol;;
+ Treats lines with the indicated type of whitespace change as
+ unchanged for the sake of a three-way merge. Whitespace
+ changes mixed with other changes to a line are not ignored.
+ See also linkgit:git-diff[1] `-b`, `-w`, and
+ `--ignore-space-at-eol`.
++
+* If 'their' version only introduces whitespace changes to a line,
+ 'our' version is used;
+* If 'our' version introduces whitespace changes but 'their'
+ version includes a substantial change, 'their' version is used;
+* Otherwise, the merge proceeds in the usual way.
+
+renormalize;;
+ This runs a virtual check-out and check-in of all three stages
+ of a file when resolving a three-way merge. This option is
+ meant to be used when merging branches with different clean
+ filters or end-of-line normalization rules. See "Merging
+ branches with differing checkin/checkout attributes" in
+ linkgit:gitattributes[5] for details.
+
+no-renormalize;;
+ Disables the `renormalize` option. This overrides the
+ `merge.renormalize` configuration variable.
+
+rename-threshold=<n>;;
+ Controls the similarity threshold used for rename detection.
+ See also linkgit:git-diff[1] `-M`.
+
+subtree[=<path>];;
This option is a more advanced form of 'subtree' strategy, where
the strategy makes a guess on how two trees must be shifted to
match with each other when merging. Instead, the specified path
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index d78e121..2a3dc86 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -1,5 +1,5 @@
---pretty[='<format>']::
---format[='<format>']::
+--pretty[=<format>]::
+--format=<format>::
Pretty-print the contents of the commit logs in a given format,
where '<format>' can be one of 'oneline', 'short', 'medium',
@@ -19,6 +19,11 @@ configuration (see linkgit:git-config[1]).
This should make "--pretty=oneline" a whole lot more readable for
people using 80-column terminals.
+--no-abbrev-commit::
+ Show the full 40-byte hexadecimal commit object name. This negates
+ `--abbrev-commit` and those options which imply it such as
+ "--oneline". It also overrides the 'log.abbrevCommit' variable.
+
--oneline::
This is a shorthand for "--pretty=oneline --abbrev-commit"
used together.
@@ -30,19 +35,34 @@ people using 80-column terminals.
preferred by the user. For non plumbing commands this
defaults to UTF-8.
---no-notes::
---show-notes[=<ref>]::
+--notes[=<ref>]::
Show the notes (see linkgit:git-notes[1]) that annotate the
commit, when showing the commit log message. This is the default
for `git log`, `git show` and `git whatchanged` commands when
- there is no `--pretty`, `--format` nor `--oneline` option is
- given on the command line.
+ there is no `--pretty`, `--format` nor `--oneline` option given
+ on the command line.
++
+By default, the notes shown are from the notes refs listed in the
+'core.notesRef' and 'notes.displayRef' variables (or corresponding
+environment overrides). See linkgit:git-config[1] for more details.
+
-With an optional argument, add this ref to the list of notes. The ref
-is taken to be in `refs/notes/` if it is not qualified.
+With an optional '<ref>' argument, show this notes ref instead of the
+default notes ref(s). The ref is taken to be in `refs/notes/` if it
+is not qualified.
++
+Multiple --notes options can be combined to control which notes are
+being displayed. Examples: "--notes=foo" will show only notes from
+"refs/notes/foo"; "--notes=foo --notes" will show both notes from
+"refs/notes/foo" and from the default notes ref(s).
+
+--no-notes::
+ Do not show notes. This negates the above `--notes` option, by
+ resetting the list of notes refs from which notes are shown.
+ Options are parsed in the order given on the command line, so e.g.
+ "--notes --notes=foo --no-notes --notes=bar" will only show notes
+ from "refs/notes/bar".
+--show-notes[=<ref>]::
--[no-]standard-notes::
- Enable or disable populating the notes ref list from the
- 'core.notesRef' and 'notes.displayRef' variables (or
- corresponding environment overrides). Enabled by default.
- See linkgit:git-config[1].
+ These options are deprecated. Use the above --notes/--no-notes
+ options instead.
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index cc562a0..39e6207 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -1,166 +1,17 @@
-Commit Formatting
-~~~~~~~~~~~~~~~~~
-
-ifdef::git-rev-list[]
-Using these options, linkgit:git-rev-list[1] will act similar to the
-more specialized family of commit log tools: linkgit:git-log[1],
-linkgit:git-show[1], and linkgit:git-whatchanged[1]
-endif::git-rev-list[]
-
-include::pretty-options.txt[]
-
---relative-date::
-
- Synonym for `--date=relative`.
-
---date={relative,local,default,iso,rfc,short,raw}::
-
- Only takes effect for dates shown in human-readable format, such
- as when using "--pretty". `log.date` config variable sets a default
- value for log command's --date option.
-+
-`--date=relative` shows dates relative to the current time,
-e.g. "2 hours ago".
-+
-`--date=local` shows timestamps in user's local timezone.
-+
-`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format.
-+
-`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822
-format, often found in E-mail messages.
-+
-`--date=short` shows only date but not time, in `YYYY-MM-DD` format.
-+
-`--date=raw` shows the date in the internal raw git format `%s %z` format.
-+
-`--date=default` shows timestamps in the original timezone
-(either committer's or author's).
-
-ifdef::git-rev-list[]
---header::
-
- Print the contents of the commit in raw-format; each record is
- separated with a NUL character.
-endif::git-rev-list[]
-
---parents::
-
- Print the parents of the commit. Also enables parent
- rewriting, see 'History Simplification' below.
-
---children::
-
- Print the children of the commit. Also enables parent
- rewriting, see 'History Simplification' below.
-
-ifdef::git-rev-list[]
---timestamp::
- Print the raw commit timestamp.
-endif::git-rev-list[]
-
---left-right::
-
- Mark which side of a symmetric diff a commit is reachable from.
- Commits from the left side are prefixed with `<` and those from
- the right with `>`. If combined with `--boundary`, those
- commits are prefixed with `-`.
-+
-For example, if you have this topology:
-+
------------------------------------------------------------------------
- y---b---b branch B
- / \ /
- / .
- / / \
- o---x---a---a branch A
------------------------------------------------------------------------
-+
-you would get an output like this:
-+
------------------------------------------------------------------------
- $ git rev-list --left-right --boundary --pretty=oneline A...B
-
- >bbbbbbb... 3rd on b
- >bbbbbbb... 2nd on b
- <aaaaaaa... 3rd on a
- <aaaaaaa... 2nd on a
- -yyyyyyy... 1st on b
- -xxxxxxx... 1st on a
------------------------------------------------------------------------
-
---graph::
-
- Draw a text-based graphical representation of the commit history
- on the left hand side of the output. This may cause extra lines
- to be printed in between commits, in order for the graph history
- to be drawn properly.
-+
-This implies the '--topo-order' option by default, but the
-'--date-order' option may also be specified.
-
-ifdef::git-rev-list[]
---count::
- Print a number stating how many commits would have been
- listed, and suppress all other output. When used together
- with '--left-right', instead print the counts for left and
- right commits, separated by a tab.
-endif::git-rev-list[]
-
-
-ifndef::git-rev-list[]
-Diff Formatting
-~~~~~~~~~~~~~~~
-
-Below are listed options that control the formatting of diff output.
-Some of them are specific to linkgit:git-rev-list[1], however other diff
-options may be given. See linkgit:git-diff-files[1] for more options.
-
--c::
-
- With this option, diff output for a merge commit
- shows the differences from each of the parents to the merge result
- simultaneously instead of showing pairwise diff between a parent
- and the result one at a time. Furthermore, it lists only files
- which were modified from all parents.
-
---cc::
-
- This flag implies the '-c' options and further compresses the
- patch output by omitting uninteresting hunks whose contents in
- the parents have only two variants and the merge result picks
- one of them without modification.
-
--m::
-
- This flag makes the merge commits show the full diff like
- regular commits; for each merge parent, a separate log entry
- and diff is generated. An exception is that only diff against
- the first parent is shown when '--first-parent' option is given;
- in that case, the output represents the changes the merge
- brought _into_ the then-current branch.
-
--r::
-
- Show recursive diffs.
-
--t::
-
- Show the tree objects in the diff output. This implies '-r'.
-endif::git-rev-list[]
-
Commit Limiting
~~~~~~~~~~~~~~~
Besides specifying a range of commits that should be listed using the
special notations explained in the description, additional commit
-limiting may be applied.
+limiting may be applied. Note that they are applied before commit
+ordering and formatting options, such as '--reverse'.
--
-n 'number'::
--max-count=<number>::
- Limit the number of commits output.
+ Limit the number of commits to output.
--skip=<number>::
@@ -221,11 +72,26 @@ endif::git-rev-list[]
--merges::
- Print only merge commits.
+ Print only merge commits. This is exactly the same as `--min-parents=2`.
--no-merges::
- Do not print commits with more than one parent.
+ Do not print commits with more than one parent. This is
+ exactly the same as `--max-parents=1`.
+
+--min-parents=<number>::
+--max-parents=<number>::
+--no-min-parents::
+--no-max-parents::
+
+ Show only commits which have at least (or at most) that many
+ commits. In particular, `--max-parents=1` is the same as `--no-merges`,
+ `--min-parents=2` is the same as `--merges`. `--max-parents=0`
+ gives all root commits and `--min-parents=3` all octopus merges.
++
+`--no-min-parents` and `--no-max-parents` reset these limits (to no limit)
+again. Equivalent forms are `--min-parents=0` (any commit has 0 or more
+parents) and `--max-parents=-1` (negative numbers denote no upper limit).
--first-parent::
Follow only the first parent commit upon seeing a merge
@@ -246,33 +112,37 @@ endif::git-rev-list[]
Pretend as if all the refs in `refs/` are listed on the
command line as '<commit>'.
---branches[=pattern]::
+--branches[=<pattern>]::
Pretend as if all the refs in `refs/heads` are listed
- on the command line as '<commit>'. If `pattern` is given, limit
+ on the command line as '<commit>'. If '<pattern>' is given, limit
branches to ones matching given shell glob. If pattern lacks '?',
'*', or '[', '/*' at the end is implied.
---tags[=pattern]::
+--tags[=<pattern>]::
Pretend as if all the refs in `refs/tags` are listed
- on the command line as '<commit>'. If `pattern` is given, limit
+ on the command line as '<commit>'. If '<pattern>' is given, limit
tags to ones matching given shell glob. If pattern lacks '?', '*',
or '[', '/*' at the end is implied.
---remotes[=pattern]::
+--remotes[=<pattern>]::
Pretend as if all the refs in `refs/remotes` are listed
- on the command line as '<commit>'. If `pattern`is given, limit
- remote tracking branches to ones matching given shell glob.
+ on the command line as '<commit>'. If '<pattern>' is given, limit
+ remote-tracking branches to ones matching given shell glob.
If pattern lacks '?', '*', or '[', '/*' at the end is implied.
---glob=glob-pattern::
- Pretend as if all the refs matching shell glob `glob-pattern`
+--glob=<glob-pattern>::
+ Pretend as if all the refs matching shell glob '<glob-pattern>'
are listed on the command line as '<commit>'. Leading 'refs/',
is automatically prepended if missing. If pattern lacks '?', '*',
or '[', '/*' at the end is implied.
+--ignore-missing::
+
+ Upon seeing an invalid object name in the input, pretend as if
+ the bad input was not given.
ifndef::git-rev-list[]
--bisect::
@@ -300,6 +170,11 @@ ifdef::git-rev-list[]
to /dev/null as the output does not have to be formatted.
endif::git-rev-list[]
+--cherry-mark::
+
+ Like `--cherry-pick` (see below) but mark equivalent commits
+ with `=` rather than omitting them, and inequivalent ones with `+`.
+
--cherry-pick::
Omit any commit that introduces the same change as
@@ -308,12 +183,33 @@ endif::git-rev-list[]
+
For example, if you have two branches, `A` and `B`, a usual way
to list all commits on only one side of them is with
-`--left-right`, like the example above in the description of
-that option. It however shows the commits that were cherry-picked
+`--left-right` (see the example below in the description of
+the `--left-right` option). It however shows the commits that were cherry-picked
from the other branch (for example, "3rd on b" may be cherry-picked
from branch A). With this option, such pairs of commits are
excluded from the output.
+--left-only::
+--right-only::
+
+ List only commits on the respective side of a symmetric range,
+ i.e. only those which would be marked `<` resp. `>` by
+ `--left-right`.
++
+For example, `--cherry-pick --right-only A...B` omits those
+commits from `B` which are in `A` or are patch-equivalent to a commit in
+`A`. In other words, this lists the `{plus}` commits from `git cherry A B`.
+More precisely, `--cherry-pick --right-only --no-merges` gives the exact
+list.
+
+--cherry::
+
+ A synonym for `--right-only --cherry-mark --no-merges`; useful to
+ limit the output to the commits on our side and mark those that
+ have been applied to the other side of a forked history with
+ `git log --cherry upstream...mybranch`, similar to
+ `git cherry upstream mybranch`.
+
-g::
--walk-reflogs::
@@ -321,7 +217,7 @@ excluded from the output.
reflog entries from the most recent one to older ones.
When this option is used you cannot specify commits to
exclude (that is, '{caret}commit', 'commit1..commit2',
- nor 'commit1...commit2' notations cannot be used).
+ nor 'commit1\...commit2' notations cannot be used).
+
With '\--pretty' format other than oneline (for obvious reasons),
this causes the output to have two extra lines of information
@@ -376,7 +272,7 @@ Default mode::
--full-history::
- As the default mode but does not prune some history.
+ Same as the default mode, but does not prune some history.
--dense::
@@ -417,7 +313,7 @@ that you are filtering for a file `foo` in this commit graph:
\ / / / /
`-------------'
-----------------------------------------------------------------------
-The horizontal line of history A--P is taken to be the first parent of
+The horizontal line of history A---P is taken to be the first parent of
each merge. The commits are:
* `I` is the initial commit, in which `foo` exists with contents
@@ -730,3 +626,161 @@ These options are mostly targeted for packing of git repositories.
--do-walk::
Overrides a previous --no-walk.
+
+Commit Formatting
+~~~~~~~~~~~~~~~~~
+
+ifdef::git-rev-list[]
+Using these options, linkgit:git-rev-list[1] will act similar to the
+more specialized family of commit log tools: linkgit:git-log[1],
+linkgit:git-show[1], and linkgit:git-whatchanged[1]
+endif::git-rev-list[]
+
+include::pretty-options.txt[]
+
+--relative-date::
+
+ Synonym for `--date=relative`.
+
+--date=(relative|local|default|iso|rfc|short|raw)::
+
+ Only takes effect for dates shown in human-readable format, such
+ as when using "--pretty". `log.date` config variable sets a default
+ value for log command's --date option.
++
+`--date=relative` shows dates relative to the current time,
+e.g. "2 hours ago".
++
+`--date=local` shows timestamps in user's local timezone.
++
+`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format.
++
+`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822
+format, often found in E-mail messages.
++
+`--date=short` shows only date but not time, in `YYYY-MM-DD` format.
++
+`--date=raw` shows the date in the internal raw git format `%s %z` format.
++
+`--date=default` shows timestamps in the original timezone
+(either committer's or author's).
+
+ifdef::git-rev-list[]
+--header::
+
+ Print the contents of the commit in raw-format; each record is
+ separated with a NUL character.
+endif::git-rev-list[]
+
+--parents::
+
+ Print also the parents of the commit (in the form "commit parent...").
+ Also enables parent rewriting, see 'History Simplification' below.
+
+--children::
+
+ Print also the children of the commit (in the form "commit child...").
+ Also enables parent rewriting, see 'History Simplification' below.
+
+ifdef::git-rev-list[]
+--timestamp::
+ Print the raw commit timestamp.
+endif::git-rev-list[]
+
+--left-right::
+
+ Mark which side of a symmetric diff a commit is reachable from.
+ Commits from the left side are prefixed with `<` and those from
+ the right with `>`. If combined with `--boundary`, those
+ commits are prefixed with `-`.
++
+For example, if you have this topology:
++
+-----------------------------------------------------------------------
+ y---b---b branch B
+ / \ /
+ / .
+ / / \
+ o---x---a---a branch A
+-----------------------------------------------------------------------
++
+you would get an output like this:
++
+-----------------------------------------------------------------------
+ $ git rev-list --left-right --boundary --pretty=oneline A...B
+
+ >bbbbbbb... 3rd on b
+ >bbbbbbb... 2nd on b
+ <aaaaaaa... 3rd on a
+ <aaaaaaa... 2nd on a
+ -yyyyyyy... 1st on b
+ -xxxxxxx... 1st on a
+-----------------------------------------------------------------------
+
+--graph::
+
+ Draw a text-based graphical representation of the commit history
+ on the left hand side of the output. This may cause extra lines
+ to be printed in between commits, in order for the graph history
+ to be drawn properly.
++
+This enables parent rewriting, see 'History Simplification' below.
++
+This implies the '--topo-order' option by default, but the
+'--date-order' option may also be specified.
+
+ifdef::git-rev-list[]
+--count::
+ Print a number stating how many commits would have been
+ listed, and suppress all other output. When used together
+ with '--left-right', instead print the counts for left and
+ right commits, separated by a tab. When used together with
+ '--cherry-mark', omit patch equivalent commits from these
+ counts and print the count for equivalent commits separated
+ by a tab.
+endif::git-rev-list[]
+
+
+ifndef::git-rev-list[]
+Diff Formatting
+~~~~~~~~~~~~~~~
+
+Below are listed options that control the formatting of diff output.
+Some of them are specific to linkgit:git-rev-list[1], however other diff
+options may be given. See linkgit:git-diff-files[1] for more options.
+
+-c::
+
+ With this option, diff output for a merge commit
+ shows the differences from each of the parents to the merge result
+ simultaneously instead of showing pairwise diff between a parent
+ and the result one at a time. Furthermore, it lists only files
+ which were modified from all parents.
+
+--cc::
+
+ This flag implies the '-c' options and further compresses the
+ patch output by omitting uninteresting hunks whose contents in
+ the parents have only two variants and the merge result picks
+ one of them without modification.
+
+-m::
+
+ This flag makes the merge commits show the full diff like
+ regular commits; for each merge parent, a separate log entry
+ and diff is generated. An exception is that only diff against
+ the first parent is shown when '--first-parent' option is given;
+ in that case, the output represents the changes the merge
+ brought _into_ the then-current branch.
+
+-r::
+
+ Show recursive diffs.
+
+-t::
+
+ Show the tree objects in the diff output. This implies '-r'.
+
+-s::
+ Suppress diff output.
+endif::git-rev-list[]
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
new file mode 100644
index 0000000..b290b61
--- /dev/null
+++ b/Documentation/revisions.txt
@@ -0,0 +1,230 @@
+SPECIFYING REVISIONS
+--------------------
+
+A revision parameter '<rev>' typically, but not necessarily, names a
+commit object. It uses what is called an 'extended SHA1'
+syntax. Here are various ways to spell object names. The
+ones listed near the end of this list name trees and
+blobs contained in a commit.
+
+'<sha1>', e.g. 'dae86e1950b1277e545cee180551750029cfe735', 'dae86e'::
+ The full SHA1 object name (40-byte hexadecimal string), or
+ a leading substring that is unique within the repository.
+ E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both
+ name the same commit object if there is no other object in
+ your repository whose object name starts with dae86e.
+
+'<describeOutput>', e.g. 'v1.7.4.2-679-g3bee7fb'::
+ Output from `git describe`; i.e. a closest tag, optionally
+ followed by a dash and a number of commits, followed by a dash, a
+ 'g', and an abbreviated object name.
+
+'<refname>', e.g. 'master', 'heads/master', 'refs/heads/master'::
+ A symbolic ref name. E.g. 'master' typically means the commit
+ object referenced by 'refs/heads/master'. If you
+ happen to have both 'heads/master' and 'tags/master', you can
+ explicitly say 'heads/master' to tell git which one you mean.
+ When ambiguous, a '<name>' is disambiguated by taking the
+ first match in the following rules:
+
+ . If '$GIT_DIR/<name>' exists, that is what you mean (this is usually
+ useful only for 'HEAD', 'FETCH_HEAD', 'ORIG_HEAD', 'MERGE_HEAD'
+ and 'CHERRY_PICK_HEAD');
+
+ . otherwise, 'refs/<name>' if it exists;
+
+ . otherwise, 'refs/tags/<refname>' if it exists;
+
+ . otherwise, 'refs/heads/<name>' if it exists;
+
+ . otherwise, 'refs/remotes/<name>' if it exists;
+
+ . otherwise, 'refs/remotes/<name>/HEAD' if it exists.
++
+'HEAD' names the commit on which you based the changes in the working tree.
+'FETCH_HEAD' records the branch which you fetched from a remote repository
+with your last `git fetch` invocation.
+'ORIG_HEAD' is created by commands that move your 'HEAD' in a drastic
+way, to record the position of the 'HEAD' before their operation, so that
+you can easily change the tip of the branch back to the state before you ran
+them.
+'MERGE_HEAD' records the commit(s) which you are merging into your branch
+when you run `git merge`.
+'CHERRY_PICK_HEAD' records the commit which you are cherry-picking
+when you run `git cherry-pick`.
++
+Note that any of the 'refs/*' cases above may come either from
+the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
+
+'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
+ A ref followed by the suffix '@' with a date specification
+ enclosed in a brace
+ pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
+ second ago\}' or '\{1979-02-26 18:30:00\}') specifies the value
+ of the ref at a prior point in time. This suffix may only be
+ used immediately following a ref name and the ref must have an
+ existing log ('$GIT_DIR/logs/<ref>'). Note that this looks up the state
+ of your *local* ref at a given time; e.g., what was in your local
+ 'master' branch last week. If you want to look at commits made during
+ certain times, see '--since' and '--until'.
+
+'<refname>@\{<n>\}', e.g. 'master@\{1\}'::
+ A ref followed by the suffix '@' with an ordinal specification
+ enclosed in a brace pair (e.g. '\{1\}', '\{15\}') specifies
+ the n-th prior value of that ref. For example 'master@\{1\}'
+ is the immediate prior value of 'master' while 'master@\{5\}'
+ is the 5th prior value of 'master'. This suffix may only be used
+ immediately following a ref name and the ref must have an existing
+ log ('$GIT_DIR/logs/<refname>').
+
+'@\{<n>\}', e.g. '@\{1\}'::
+ You can use the '@' construct with an empty ref part to get at a
+ reflog entry of the current branch. For example, if you are on
+ branch 'blabla' then '@\{1\}' means the same as 'blabla@\{1\}'.
+
+'@\{-<n>\}', e.g. '@\{-1\}'::
+ The construct '@\{-<n>\}' means the <n>th branch checked out
+ before the current one.
+
+'<refname>@\{upstream\}', e.g. 'master@\{upstream\}', '@\{u\}'::
+ The suffix '@\{upstream\}' to a ref (short form '<refname>@\{u\}') refers to
+ the branch the ref is set to build on top of. A missing ref defaults
+ to the current branch.
+
+'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
+ A suffix '{caret}' to a revision parameter means the first parent of
+ that commit object. '{caret}<n>' means the <n>th parent (i.e.
+ '<rev>{caret}'
+ is equivalent to '<rev>{caret}1'). As a special rule,
+ '<rev>{caret}0' means the commit itself and is used when '<rev>' is the
+ object name of a tag object that refers to a commit object.
+
+'<rev>{tilde}<n>', e.g. 'master{tilde}3'::
+ A suffix '{tilde}<n>' to a revision parameter means the commit
+ object that is the <n>th generation grand-parent of the named
+ commit object, following only the first parents. I.e. '<rev>{tilde}3' is
+ equivalent to '<rev>{caret}{caret}{caret}' which is equivalent to
+ '<rev>{caret}1{caret}1{caret}1'. See below for an illustration of
+ the usage of this form.
+
+'<rev>{caret}\{<type>\}', e.g. 'v0.99.8{caret}\{commit\}'::
+ A suffix '{caret}' followed by an object type name enclosed in
+ brace pair means the object
+ could be a tag, and dereference the tag recursively until an
+ object of that type is found or the object cannot be
+ dereferenced anymore (in which case, barf). '<rev>{caret}0'
+ is a short-hand for '<rev>{caret}\{commit\}'.
+
+'<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}'::
+ A suffix '{caret}' followed by an empty brace pair
+ means the object could be a tag,
+ and dereference the tag recursively until a non-tag object is
+ found.
+
+'<rev>{caret}\{/<text>\}', e.g. 'HEAD^{/fix nasty bug}'::
+ A suffix '{caret}' to a revision parameter, followed by a brace
+ pair that contains a text led by a slash,
+ is the same as the ':/fix nasty bug' syntax below except that
+ it returns the youngest matching commit which is reachable from
+ the '<rev>' before '{caret}'.
+
+':/<text>', e.g. ':/fix nasty bug'::
+ A colon, followed by a slash, followed by a text, names
+ a commit whose commit message matches the specified regular expression.
+ This name returns the youngest matching commit which is
+ reachable from any ref. If the commit message starts with a
+ '!' you have to repeat that; the special sequence ':/!',
+ followed by something else than '!', is reserved for now.
+ The regular expression can match any part of the commit message. To
+ match messages starting with a string, one can use e.g. ':/^foo'.
+
+'<rev>:<path>', e.g. 'HEAD:README', ':README', 'master:./README'::
+ A suffix ':' followed by a path names the blob or tree
+ at the given path in the tree-ish object named by the part
+ before the colon.
+ ':path' (with an empty part before the colon)
+ is a special case of the syntax described next: content
+ recorded in the index at the given path.
+ A path starting with './' or '../' is relative to the current working directory.
+ The given path will be converted to be relative to the working tree's root directory.
+ This is most useful to address a blob or tree from a commit or tree that has
+ the same tree structure as the working tree.
+
+':<n>:<path>', e.g. ':0:README', ':README'::
+ A colon, optionally followed by a stage number (0 to 3) and a
+ colon, followed by a path, names a blob object in the
+ index at the given path. A missing stage number (and the colon
+ that follows it) names a stage 0 entry. During a merge, stage
+ 1 is the common ancestor, stage 2 is the target branch's version
+ (typically the current branch), and stage 3 is the version from
+ the branch which is being merged.
+
+Here is an illustration, by Jon Loeliger. Both commit nodes B
+and C are parents of commit node A. Parent commits are ordered
+left-to-right.
+
+........................................
+G H I J
+ \ / \ /
+ D E F
+ \ | / \
+ \ | / |
+ \|/ |
+ B C
+ \ /
+ \ /
+ A
+........................................
+
+ A = = A^0
+ B = A^ = A^1 = A~1
+ C = A^2 = A^2
+ D = A^^ = A^1^1 = A~2
+ E = B^2 = A^^2
+ F = B^3 = A^^3
+ G = A^^^ = A^1^1^1 = A~3
+ H = D^2 = B^^2 = A^^^2 = A~2^2
+ I = F^ = B^3^ = A^^3^
+ J = F^2 = B^3^2 = A^^3^2
+
+
+SPECIFYING RANGES
+-----------------
+
+History traversing commands such as `git log` operate on a set
+of commits, not just a single commit. To these commands,
+specifying a single revision with the notation described in the
+previous section means the set of commits reachable from that
+commit, following the commit ancestry chain.
+
+To exclude commits reachable from a commit, a prefix '{caret}'
+notation is used. E.g. '{caret}r1 r2' means commits reachable
+from 'r2' but exclude the ones reachable from 'r1'.
+
+This set operation appears so often that there is a shorthand
+for it. When you have two commits 'r1' and 'r2' (named according
+to the syntax explained in SPECIFYING REVISIONS above), you can ask
+for commits that are reachable from r2 excluding those that are reachable
+from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
+
+A similar notation 'r1\...r2' is called symmetric difference
+of 'r1' and 'r2' and is defined as
+'r1 r2 --not $(git merge-base --all r1 r2)'.
+It is the set of commits that are reachable from either one of
+'r1' or 'r2' but not from both.
+
+Two other shorthands for naming a set that is formed by a commit
+and its parent commits exist. The 'r1{caret}@' notation means all
+parents of 'r1'. 'r1{caret}!' includes commit 'r1' but excludes
+all of its parents.
+
+Here are a handful of examples:
+
+ D G H D
+ D F G H I J D F
+ ^G D H D
+ ^D B E I J F B
+ B...C G H D E B C
+ ^D B C E I J F B C
+ C^@ I J F
+ F^! D G H D F
diff --git a/Documentation/technical/api-diff.txt b/Documentation/technical/api-diff.txt
index 20b0241..2d2ebc0 100644
--- a/Documentation/technical/api-diff.txt
+++ b/Documentation/technical/api-diff.txt
@@ -32,7 +32,7 @@ Calling sequence
* As you find different pairs of files, call `diff_change()` to feed
modified files, `diff_addremove()` to feed created or deleted files,
- or `diff_unmerged()` to feed a file whose state is 'unmerged' to the
+ or `diff_unmerge()` to feed a file whose state is 'unmerged' to the
API. These are thin wrappers to a lower-level `diff_queue()` function
that is flexible enough to record any of these kinds of changes.
@@ -50,7 +50,7 @@ Data structures
This is the internal representation for a single file (blob). It
records the blob object name (if known -- for a work tree file it
typically is a NUL SHA-1), filemode and pathname. This is what the
-`diff_addremove()`, `diff_change()` and `diff_unmerged()` synthesize and
+`diff_addremove()`, `diff_change()` and `diff_unmerge()` synthesize and
feed `diff_queue()` function with.
* `struct diff_filepair`
diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt
new file mode 100644
index 0000000..9dc1bed
--- /dev/null
+++ b/Documentation/technical/api-merge.txt
@@ -0,0 +1,104 @@
+merge API
+=========
+
+The merge API helps a program to reconcile two competing sets of
+improvements to some files (e.g., unregistered changes from the work
+tree versus changes involved in switching to a new branch), reporting
+conflicts if found. The library called through this API is
+responsible for a few things.
+
+ * determining which trees to merge (recursive ancestor consolidation);
+
+ * lining up corresponding files in the trees to be merged (rename
+ detection, subtree shifting), reporting edge cases like add/add
+ and rename/rename conflicts to the user;
+
+ * performing a three-way merge of corresponding files, taking
+ path-specific merge drivers (specified in `.gitattributes`)
+ into account.
+
+Data structures
+---------------
+
+* `mmbuffer_t`, `mmfile_t`
+
+These store data usable for use by the xdiff backend, for writing and
+for reading, respectively. See `xdiff/xdiff.h` for the definitions
+and `diff.c` for examples.
+
+* `struct ll_merge_options`
+
+This describes the set of options the calling program wants to affect
+the operation of a low-level (single file) merge. Some options:
+
+`virtual_ancestor`::
+ Behave as though this were part of a merge between common
+ ancestors in a recursive merge.
+ If a helper program is specified by the
+ `[merge "<driver>"] recursive` configuration, it will
+ be used (see linkgit:gitattributes[5]).
+
+`variant`::
+ Resolve local conflicts automatically in favor
+ of one side or the other (as in 'git merge-file'
+ `--ours`/`--theirs`/`--union`). Can be `0`,
+ `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, or
+ `XDL_MERGE_FAVOR_UNION`.
+
+`renormalize`::
+ Resmudge and clean the "base", "theirs" and "ours" files
+ before merging. Use this when the merge is likely to have
+ overlapped with a change in smudge/clean or end-of-line
+ normalization rules.
+
+Low-level (single file) merge
+-----------------------------
+
+`ll_merge`::
+
+ Perform a three-way single-file merge in core. This is
+ a thin wrapper around `xdl_merge` that takes the path and
+ any merge backend specified in `.gitattributes` or
+ `.git/info/attributes` into account. Returns 0 for a
+ clean merge.
+
+Calling sequence:
+
+* Prepare a `struct ll_merge_options` to record options.
+ If you have no special requests, skip this and pass `NULL`
+ as the `opts` parameter to use the default options.
+
+* Allocate an mmbuffer_t variable for the result.
+
+* Allocate and fill variables with the file's original content
+ and two modified versions (using `read_mmfile`, for example).
+
+* Call `ll_merge()`.
+
+* Read the merged content from `result_buf.ptr` and `result_buf.size`.
+
+* Release buffers when finished. A simple
+ `free(ancestor.ptr); free(ours.ptr); free(theirs.ptr);
+ free(result_buf.ptr);` will do.
+
+If the modifications do not merge cleanly, `ll_merge` will return a
+nonzero value and `result_buf` will generally include a description of
+the conflict bracketed by markers such as the traditional `<<<<<<<`
+and `>>>>>>>`.
+
+The `ancestor_label`, `our_label`, and `their_label` parameters are
+used to label the different sides of a conflict if the merge driver
+supports this.
+
+Everything else
+---------------
+
+Talk about <merge-recursive.h> and merge_file():
+
+ - merge_trees() to merge with rename detection
+ - merge_recursive() for ancestor consolidation
+ - try_merge_command() for other strategies
+ - conflict format
+ - merge options
+
+(Daniel, Miklos, Stephan, JC)
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index 312e3b2..f6a4a36 100644
--- a/Documentation/technical/api-parse-options.txt
+++ b/Documentation/technical/api-parse-options.txt
@@ -118,13 +118,16 @@ There are some macros to easily define options:
`OPT__COLOR(&int_var, description)`::
Add `\--color[=<when>]` and `--no-color`.
-`OPT__DRY_RUN(&int_var)`::
+`OPT__DRY_RUN(&int_var, description)`::
Add `-n, \--dry-run`.
-`OPT__QUIET(&int_var)`::
+`OPT__FORCE(&int_var, description)`::
+ Add `-f, \--force`.
+
+`OPT__QUIET(&int_var, description)`::
Add `-q, \--quiet`.
-`OPT__VERBOSE(&int_var)`::
+`OPT__VERBOSE(&int_var, description)`::
Add `-v, \--verbose`.
`OPT_GROUP(description)`::
@@ -201,7 +204,7 @@ The last element of the array must be `OPT_END()`.
If not stated otherwise, interpret the arguments as follows:
* `short` is a character for the short option
- (e.g. `\'e\'` for `-e`, use `0` to omit),
+ (e.g. `{apostrophe}e{apostrophe}` for `-e`, use `0` to omit),
* `long` is a string for the long option
(e.g. `"example"` for `\--example`, use `NULL` to omit),
@@ -228,10 +231,10 @@ The function must be defined in this form:
The callback mechanism is as follows:
* Inside `func`, the only interesting member of the structure
- given by `opt` is the void pointer `opt->value`.
- `\*opt->value` will be the value that is saved into `var`, if you
+ given by `opt` is the void pointer `opt\->value`.
+ `\*opt\->value` will be the value that is saved into `var`, if you
use `OPT_CALLBACK()`.
- For example, do `*(unsigned long *)opt->value = 42;` to get 42
+ For example, do `*(unsigned long *)opt\->value = 42;` to get 42
into an `unsigned long` variable.
* Return value `0` indicates success and non-zero return
diff --git a/Documentation/technical/api-sigchain.txt b/Documentation/technical/api-sigchain.txt
new file mode 100644
index 0000000..9e1189e
--- /dev/null
+++ b/Documentation/technical/api-sigchain.txt
@@ -0,0 +1,41 @@
+sigchain API
+============
+
+Code often wants to set a signal handler to clean up temporary files or
+other work-in-progress when we die unexpectedly. For multiple pieces of
+code to do this without conflicting, each piece of code must remember
+the old value of the handler and restore it either when:
+
+ 1. The work-in-progress is finished, and the handler is no longer
+ necessary. The handler should revert to the original behavior
+ (either another handler, SIG_DFL, or SIG_IGN).
+
+ 2. The signal is received. We should then do our cleanup, then chain
+ to the next handler (or die if it is SIG_DFL).
+
+Sigchain is a tiny library for keeping a stack of handlers. Your handler
+and installation code should look something like:
+
+------------------------------------------
+ void clean_foo_on_signal(int sig)
+ {
+ clean_foo();
+ sigchain_pop(sig);
+ raise(sig);
+ }
+
+ void other_func()
+ {
+ sigchain_push_common(clean_foo_on_signal);
+ mess_up_foo();
+ clean_foo();
+ }
+------------------------------------------
+
+Handlers are given the typedef of sigchain_fun. This is the same type
+that is given to signal() or sigaction(). It is perfectly reasonable to
+push SIG_DFL or SIG_IGN onto the stack.
+
+You can sigchain_push and sigchain_pop individual signals. For
+convenience, sigchain_push_common will push the handler onto the stack
+for many common signals.
diff --git a/Documentation/technical/api-tree-walking.txt b/Documentation/technical/api-tree-walking.txt
index 55b7286..14af37c 100644
--- a/Documentation/technical/api-tree-walking.txt
+++ b/Documentation/technical/api-tree-walking.txt
@@ -42,6 +42,8 @@ information.
* `data` can be anything the `fn` callback would want to use.
+* `show_all_errors` tells whether to stop at the first error or not.
+
Initializing
------------
diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt
new file mode 100644
index 0000000..7b233ca
--- /dev/null
+++ b/Documentation/technical/index-format.txt
@@ -0,0 +1,185 @@
+GIT index format
+================
+
+= The git index file has the following format
+
+ All binary numbers are in network byte order. Version 2 is described
+ here unless stated otherwise.
+
+ - A 12-byte header consisting of
+
+ 4-byte signature:
+ The signature is { 'D', 'I', 'R', 'C' } (stands for "dircache")
+
+ 4-byte version number:
+ The current supported versions are 2 and 3.
+
+ 32-bit number of index entries.
+
+ - A number of sorted index entries (see below).
+
+ - Extensions
+
+ Extensions are identified by signature. Optional extensions can
+ be ignored if GIT does not understand them.
+
+ GIT currently supports cached tree and resolve undo extensions.
+
+ 4-byte extension signature. If the first byte is 'A'..'Z' the
+ extension is optional and can be ignored.
+
+ 32-bit size of the extension
+
+ Extension data
+
+ - 160-bit SHA-1 over the content of the index file before this
+ checksum.
+
+== Index entry
+
+ Index entries are sorted in ascending order on the name field,
+ interpreted as a string of unsigned bytes (i.e. memcmp() order, no
+ localization, no special casing of directory separator '/'). Entries
+ with the same name are sorted by their stage field.
+
+ 32-bit ctime seconds, the last time a file's metadata changed
+ this is stat(2) data
+
+ 32-bit ctime nanosecond fractions
+ this is stat(2) data
+
+ 32-bit mtime seconds, the last time a file's data changed
+ this is stat(2) data
+
+ 32-bit mtime nanosecond fractions
+ this is stat(2) data
+
+ 32-bit dev
+ this is stat(2) data
+
+ 32-bit ino
+ this is stat(2) data
+
+ 32-bit mode, split into (high to low bits)
+
+ 4-bit object type
+ valid values in binary are 1000 (regular file), 1010 (symbolic link)
+ and 1110 (gitlink)
+
+ 3-bit unused
+
+ 9-bit unix permission. Only 0755 and 0644 are valid for regular files.
+ Symbolic links and gitlinks have value 0 in this field.
+
+ 32-bit uid
+ this is stat(2) data
+
+ 32-bit gid
+ this is stat(2) data
+
+ 32-bit file size
+ This is the on-disk size from stat(2), truncated to 32-bit.
+
+ 160-bit SHA-1 for the represented object
+
+ A 16-bit 'flags' field split into (high to low bits)
+
+ 1-bit assume-valid flag
+
+ 1-bit extended flag (must be zero in version 2)
+
+ 2-bit stage (during merge)
+
+ 12-bit name length if the length is less than 0xFFF; otherwise 0xFFF
+ is stored in this field.
+
+ (Version 3) A 16-bit field, only applicable if the "extended flag"
+ above is 1, split into (high to low bits).
+
+ 1-bit reserved for future
+
+ 1-bit skip-worktree flag (used by sparse checkout)
+
+ 1-bit intent-to-add flag (used by "git add -N")
+
+ 13-bit unused, must be zero
+
+ Entry path name (variable length) relative to top level directory
+ (without leading slash). '/' is used as path separator. The special
+ path components ".", ".." and ".git" (without quotes) are disallowed.
+ Trailing slash is also disallowed.
+
+ The exact encoding is undefined, but the '.' and '/' characters
+ are encoded in 7-bit ASCII and the encoding cannot contain a NUL
+ byte (iow, this is a UNIX pathname).
+
+ 1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes
+ while keeping the name NUL-terminated.
+
+== Extensions
+
+=== Cached tree
+
+ Cached tree extension contains pre-computed hashes for trees that can
+ be derived from the index. It helps speed up tree object generation
+ from index for a new commit.
+
+ When a path is updated in index, the path must be invalidated and
+ removed from tree cache.
+
+ The signature for this extension is { 'T', 'R', 'E', 'E' }.
+
+ A series of entries fill the entire extension; each of which
+ consists of:
+
+ - NUL-terminated path component (relative to its parent directory);
+
+ - ASCII decimal number of entries in the index that is covered by the
+ tree this entry represents (entry_count);
+
+ - A space (ASCII 32);
+
+ - ASCII decimal number that represents the number of subtrees this
+ tree has;
+
+ - A newline (ASCII 10); and
+
+ - 160-bit object name for the object that would result from writing
+ this span of index as a tree.
+
+ An entry can be in an invalidated state and is represented by having -1
+ in the entry_count field.
+
+ The entries are written out in the top-down, depth-first order. The
+ first entry represents the root level of the repository, followed by the
+ first subtree---let's call this A---of the root level (with its name
+ relative to the root level), followed by the first subtree of A (with
+ its name relative to A), ...
+
+=== Resolve undo
+
+ A conflict is represented in the index as a set of higher stage entries.
+ When a conflict is resolved (e.g. with "git add path"), these higher
+ stage entries will be removed and a stage-0 entry with proper resoluton
+ is added.
+
+ When these higher stage entries are removed, they are saved in the
+ resolve undo extension, so that conflicts can be recreated (e.g. with
+ "git checkout -m"), in case users want to redo a conflict resolution
+ from scratch.
+
+ The signature for this extension is { 'R', 'E', 'U', 'C' }.
+
+ A series of entries fill the entire extension; each of which
+ consists of:
+
+ - NUL-terminated pathname the entry describes (relative to the root of
+ the repository, i.e. full pathname);
+
+ - Three NUL-terminated ASCII octal numbers, entry mode of entries in
+ stage 1 to 3 (a missing stage is represented by "0" in this field);
+ and
+
+ - At most three 160-bit object names of the entry in stages from 1 to 3
+ (nothing is written for a missing stage).
+
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index 369f91d..a7004c6 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -179,34 +179,36 @@ and descriptions.
Packfile Negotiation
--------------------
-After reference and capabilities discovery, the client can decide
-to terminate the connection by sending a flush-pkt, telling the
-server it can now gracefully terminate (as happens with the ls-remote
-command) or it can enter the negotiation phase, where the client and
-server determine what the minimal packfile necessary for transport is.
-
-Once the client has the initial list of references that the server
-has, as well as the list of capabilities, it will begin telling the
-server what objects it wants and what objects it has, so the server
-can make a packfile that only contains the objects that the client needs.
-The client will also send a list of the capabilities it wants to be in
-effect, out of what the server said it could do with the first 'want' line.
+After reference and capabilities discovery, the client can decide to
+terminate the connection by sending a flush-pkt, telling the server it can
+now gracefully terminate, and disconnect, when it does not need any pack
+data. This can happen with the ls-remote command, and also can happen when
+the client already is up-to-date.
+
+Otherwise, it enters the negotiation phase, where the client and
+server determine what the minimal packfile necessary for transport is,
+by telling the server what objects it wants, its shallow objects
+(if any), and the maximum commit depth it wants (if any). The client
+will also send a list of the capabilities it wants to be in effect,
+out of what the server said it could do with the first 'want' line.
----
upload-request = want-list
- have-list
- compute-end
+ *shallow-line
+ *1depth-request
+ flush-pkt
want-list = first-want
*additional-want
- flush-pkt
+
+ shallow-line = PKT_LINE("shallow" SP obj-id)
+
+ depth-request = PKT_LINE("deepen" SP depth)
first-want = PKT-LINE("want" SP obj-id SP capability-list LF)
additional-want = PKT-LINE("want" SP obj-id LF)
- have-list = *have-line
- have-line = PKT-LINE("have" SP obj-id LF)
- compute-end = flush-pkt / PKT-LINE("done")
+ depth = 1*DIGIT
----
Clients MUST send all the obj-ids it wants from the reference
@@ -215,21 +217,64 @@ discovery phase as 'want' lines. Clients MUST send at least one
obj-id in a 'want' command which did not appear in the response
obtained through ref discovery.
-If client is requesting a shallow clone, it will now send a 'deepen'
-line with the depth it is requesting.
+The client MUST write all obj-ids which it only has shallow copies
+of (meaning that it does not have the parents of a commit) as
+'shallow' lines so that the server is aware of the limitations of
+the client's history. Clients MUST NOT mention an obj-id which
+it does not know exists on the server.
+
+The client now sends the maximum commit history depth it wants for
+this transaction, which is the number of commits it wants from the
+tip of the history, if any, as a 'deepen' line. A depth of 0 is the
+same as not making a depth request. The client does not want to receive
+any commits beyond this depth, nor objects needed only to complete
+those commits. Commits whose parents are not received as a result are
+defined as shallow and marked as such in the server. This information
+is sent back to the client in the next step.
+
+Once all the 'want's and 'shallow's (and optional 'deepen') are
+transferred, clients MUST send a flush-pkt, to tell the server side
+that it is done sending the list.
+
+Otherwise, if the client sent a positive depth request, the server
+will determine which commits will and will not be shallow and
+send this information to the client. If the client did not request
+a positive depth, this step is skipped.
-Once all the "want"s (and optional 'deepen') are transferred,
-clients MUST send a flush-pkt. If the client has all the references
-on the server, client flushes and disconnects.
+----
+ shallow-update = *shallow-line
+ *unshallow-line
+ flush-pkt
-TODO: shallow/unshallow response and document the deepen command in the ABNF.
+ shallow-line = PKT-LINE("shallow" SP obj-id)
+
+ unshallow-line = PKT-LINE("unshallow" SP obj-id)
+----
+
+If the client has requested a positive depth, the server will compute
+the set of commits which are no deeper than the desired depth, starting
+at the client's wants. The server writes 'shallow' lines for each
+commit whose parents will not be sent as a result. The server writes
+an 'unshallow' line for each commit which the client has indicated is
+shallow, but is no longer shallow at the currently requested depth
+(that is, its parents will now be sent). The server MUST NOT mark
+as unshallow anything which the client has not indicated was shallow.
Now the client will send a list of the obj-ids it has using 'have'
-lines. In multi_ack mode, the canonical implementation will send up
-to 32 of these at a time, then will send a flush-pkt. The canonical
-implementation will skip ahead and send the next 32 immediately,
-so that there is always a block of 32 "in-flight on the wire" at a
-time.
+lines, so the server can make a packfile that only contains the objects
+that the client needs. In multi_ack mode, the canonical implementation
+will send up to 32 of these at a time, then will send a flush-pkt. The
+canonical implementation will skip ahead and send the next 32 immediately,
+so that there is always a block of 32 "in-flight on the wire" at a time.
+
+----
+ upload-haves = have-list
+ compute-end
+
+ have-list = *have-line
+ have-line = PKT-LINE("have" SP obj-id LF)
+ compute-end = flush-pkt / PKT-LINE("done")
+----
If the server reads 'have' lines, it then will respond by ACKing any
of the obj-ids the client said it had that the server also has. The
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index fd1a593..b15517f 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -119,7 +119,7 @@ both.
ofs-delta
---------
-Server can send, and client understand PACKv2 with delta refering to
+Server can send, and client understand PACKv2 with delta referring to
its base by position in pack rather than by an obj-id. That is, they can
send/read OBJ_OFS_DELTA (aka type 6) in a packfile.
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 1dcd1e7..2890194 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -25,7 +25,7 @@ The ssh and git protocols additionally support ~username expansion:
- git://host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
- {startsb}user@{endsb}host.xz:/~{startsb}user{endsb}/path/to/repo.git/
-For local respositories, also supported by git natively, the following
+For local repositories, also supported by git natively, the following
syntaxes may be used:
- /path/to/repo.git/
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index fe6fb72..f13a846 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -344,7 +344,8 @@ Examining branches from a remote repository
The "master" branch that was created at the time you cloned is a copy
of the HEAD in the repository that you cloned from. That repository
may also have had other branches, though, and your local repository
-keeps branches which track each of those remote branches, which you
+keeps branches which track each of those remote branches, called
+remote-tracking branches, which you
can view using the "-r" option to linkgit:git-branch[1]:
------------------------------------------------
@@ -359,13 +360,23 @@ $ git branch -r
origin/todo
------------------------------------------------
-You cannot check out these remote-tracking branches, but you can
-examine them on a branch of your own, just as you would a tag:
+In this example, "origin" is called a remote repository, or "remote"
+for short. The branches of this repository are called "remote
+branches" from our point of view. The remote-tracking branches listed
+above were created based on the remote branches at clone time and will
+be updated by "git fetch" (hence "git pull") and "git push". See
+<<Updating-a-repository-With-git-fetch>> for details.
+
+You might want to build on one of these remote-tracking branches
+on a branch of your own, just as you would for a tag:
------------------------------------------------
$ git checkout -b my-todo-copy origin/todo
------------------------------------------------
+You can also check out "origin/todo" directly to examine it or
+write a one-off patch. See <<detached-head,detached head>>.
+
Note that the name "origin" is just the name that git uses by default
to refer to the repository that you cloned from.
@@ -397,7 +408,7 @@ is usually a shortcut for the HEAD branch in the repository "origin".
For the complete list of paths which git checks for references, and
the order it uses to decide which to choose when there are multiple
references with the same shorthand name, see the "SPECIFYING
-REVISIONS" section of linkgit:git-rev-parse[1].
+REVISIONS" section of linkgit:gitrevisions[7].
[[Updating-a-repository-With-git-fetch]]
Updating a repository with git fetch
@@ -435,7 +446,7 @@ linux-nfs/master
origin/master
-------------------------------------------------
-If you run "git fetch <remote>" later, the tracking branches for the
+If you run "git fetch <remote>" later, the remote-tracking branches for the
named <remote> will be updated.
If you examine the file .git/config, you will see that git has added
@@ -568,7 +579,7 @@ We have seen several ways of naming commits already:
- HEAD: refers to the head of the current branch
There are many more; see the "SPECIFYING REVISIONS" section of the
-linkgit:git-rev-parse[1] man page for the complete list of ways to
+linkgit:gitrevisions[7] man page for the complete list of ways to
name revisions. Some examples:
-------------------------------------------------
@@ -909,7 +920,7 @@ commits reachable from some head but not from any tag in the repository:
$ gitk $( git show-ref --heads ) --not $( git show-ref --tags )
-------------------------------------------------
-(See linkgit:git-rev-parse[1] for explanations of commit-selecting
+(See linkgit:gitrevisions[7] for explanations of commit-selecting
syntax such as `--not`.)
[[making-a-release]]
@@ -955,7 +966,7 @@ echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"
and then he just cut-and-pastes the output commands after verifying that
they look OK.
-[[Finding-comments-With-given-Content]]
+[[Finding-commits-With-given-Content]]
Finding commits referencing a file with given content
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1635,7 +1646,7 @@ you've checked out.
The reflogs are kept by default for 30 days, after which they may be
pruned. See linkgit:git-reflog[1] and linkgit:git-gc[1] to learn
how to control this pruning, and see the "SPECIFYING REVISIONS"
-section of linkgit:git-rev-parse[1] for details.
+section of linkgit:gitrevisions[7] for details.
Note that the reflog history is very different from normal git history.
While normal history is shared by every repository that works on the
@@ -1700,7 +1711,7 @@ may wish to check the original repository for updates and merge them
into your own work.
We have already seen <<Updating-a-repository-With-git-fetch,how to
-keep remote tracking branches up to date>> with linkgit:git-fetch[1],
+keep remote-tracking branches up to date>> with linkgit:git-fetch[1],
and how to merge two branches. So you can merge in changes from the
original repository's master branch with:
@@ -1716,15 +1727,21 @@ one step:
$ git pull origin master
-------------------------------------------------
-In fact, if you have "master" checked out, then by default "git pull"
-merges from the HEAD branch of the origin repository. So often you can
+In fact, if you have "master" checked out, then this branch has been
+configured by "git clone" to get changes from the HEAD branch of the
+origin repository. So often you can
accomplish the above with just a simple
-------------------------------------------------
$ git pull
-------------------------------------------------
-More generally, a branch that is created from a remote branch will pull
+This command will fetch changes from the remote branches to your
+remote-tracking branches `origin/*`, and merge the default branch into
+the current branch.
+
+More generally, a branch that is created from a remote-tracking branch
+will pull
by default from that branch. See the descriptions of the
branch.<name>.remote and branch.<name>.merge options in
linkgit:git-config[1], and the discussion of the `--track` option in
@@ -2106,7 +2123,7 @@ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
$ cd work
-------------------------------------------------
-Linus's tree will be stored in the remote branch named origin/master,
+Linus's tree will be stored in the remote-tracking branch named origin/master,
and can be updated using linkgit:git-fetch[1]; you can track other
public trees using linkgit:git-remote[1] to set up a "remote" and
linkgit:git-fetch[1] to keep them up-to-date; see
@@ -2171,11 +2188,14 @@ $ git push mytree release
Now to apply some patches from the community. Think of a short
snappy name for a branch to hold this patch (or related group of
-patches), and create a new branch from the current tip of Linus's
-branch:
+patches), and create a new branch from a recent stable tag of
+Linus's branch. Picking a stable base for your branch will:
+1) help you: by avoiding inclusion of unrelated and perhaps lightly
+tested changes
+2) help future bug hunters that use "git bisect" to find problems
-------------------------------------------------
-$ git checkout -b speed-up-spinlocks origin
+$ git checkout -b speed-up-spinlocks v2.6.35
-------------------------------------------------
Now you apply the patch(es), run some tests, and commit the change(s). If
@@ -2439,9 +2459,9 @@ You have performed no merges into mywork, so it is just a simple linear
sequence of patches on top of "origin":
................................................
- o--o--o <-- origin
+ o--o--O <-- origin
\
- o--o--o <-- mywork
+ a--b--c <-- mywork
................................................
Some more interesting work has been done in the upstream project, and
@@ -2797,8 +2817,8 @@ Be aware that commits that the old version of example/master pointed at
may be lost, as we saw in the previous section.
[[remote-branch-configuration]]
-Configuring remote branches
----------------------------
+Configuring remote-tracking branches
+------------------------------------
We saw above that "origin" is just a shortcut to refer to the
repository that you originally cloned from. This information is
@@ -3850,7 +3870,7 @@ You create a commit object by giving it the tree that describes the
state at the time of the commit, and a list of parents:
-------------------------------------------------
-$ git commit-tree <tree> -p <parent> [-p <parent2> ..]
+$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]
-------------------------------------------------
and then giving the reason for the commit on stdin (either through
@@ -4251,9 +4271,9 @@ Two things are interesting here:
negative numbers in case of different errors--and 0 on success.
- the variable `sha1` in the function signature of `get_sha1()` is `unsigned
- char \*`, but is actually expected to be a pointer to `unsigned
+ char {asterisk}`, but is actually expected to be a pointer to `unsigned
char[20]`. This variable will contain the 160-bit SHA-1 of the given
- commit. Note that whenever a SHA-1 is passed as `unsigned char \*`, it
+ commit. Note that whenever a SHA-1 is passed as `unsigned char {asterisk}`, it
is the binary representation, as opposed to the ASCII representation in
hex characters, which is passed as `char *`.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index e45513d..8270abb 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.1.GIT
+DEF_VER=v1.7.6.GIT
LF='
'
diff --git a/INSTALL b/INSTALL
index 61086ab..bbb9d4d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -25,6 +25,19 @@ set up install paths (via config.mak.autogen), so you can write instead
$ make all doc ;# as yourself
# make install install-doc install-html;# as root
+If you're willing to trade off (much) longer build time for a later
+faster git you can also do a profile feedback build with
+
+ $ make profile-all
+ # make prefix=... install
+
+This will run the complete test suite as training workload and then
+rebuild git with the generated profile feedback. This results in a git
+which is a few percent faster on CPU intensive workloads. This
+may be a good tradeoff for distribution packagers.
+
+Note that the profile feedback build stage currently generates
+a lot of additional compiler warnings.
Issues of note:
@@ -67,10 +80,10 @@ Issues of note:
- A POSIX-compliant shell is required to run many scripts needed
for everyday use (e.g. "bisect", "pull").
- - "Perl" is needed to use some of the features (e.g. preparing a
- partial commit using "git add -i/-p", interacting with svn
- repositories with "git svn"). If you can live without these, use
- NO_PERL.
+ - "Perl" version 5.8 or later is needed to use some of the
+ features (e.g. preparing a partial commit using "git add -i/-p",
+ interacting with svn repositories with "git svn"). If you can
+ live without these, use NO_PERL.
- "openssl" library is used by git-imap-send to use IMAP over SSL.
If you don't need it, use NO_OPENSSL.
@@ -122,8 +135,9 @@ Issues of note:
Building and installing the pdf file additionally requires
dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
- The documentation is written for AsciiDoc 7, but "make
- ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
+ The documentation is written for AsciiDoc 7, but by default
+ uses some compatibility wrappers to work on AsciiDoc 8. If you have
+ AsciiDoc 7, try "make ASCIIDOC7=YesPlease".
Alternatively, pre-formatted documentation is available in
"html" and "man" branches of the git repository itself. For
@@ -157,3 +171,36 @@ Issues of note:
It has been reported that docbook-xsl version 1.72 and 1.73 are
buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
+
+ Users attempting to build the documentation on Cygwin may need to ensure
+ that the /etc/xml/catalog file looks something like this:
+
+ <?xml version="1.0"?>
+ <!DOCTYPE catalog PUBLIC
+ "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+ "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd"
+ >
+ <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+ <rewriteURI
+ uriStartString = "http://docbook.sourceforge.net/release/xsl/current"
+ rewritePrefix = "/usr/share/sgml/docbook/xsl-stylesheets"
+ />
+ <rewriteURI
+ uriStartString="http://www.oasis-open.org/docbook/xml/4.5"
+ rewritePrefix="/usr/share/sgml/docbook/xml-dtd-4.5"
+ />
+ </catalog>
+
+ This can be achieved with the following two xmlcatalog commands:
+
+ xmlcatalog --noout \
+ --add rewriteURI \
+ http://docbook.sourceforge.net/release/xsl/current \
+ /usr/share/sgml/docbook/xsl-stylesheets \
+ /etc/xml/catalog
+
+ xmlcatalog --noout \
+ --add rewriteURI \
+ http://www.oasis-open.org/docbook/xml/4.5/xsl/current \
+ /usr/share/sgml/docbook/xml-dtd-4.5 \
+ /etc/xml/catalog
diff --git a/LGPL-2.1 b/LGPL-2.1
new file mode 100644
index 0000000..d38b1b9
--- /dev/null
+++ b/LGPL-2.1
@@ -0,0 +1,511 @@
+
+ While most of this project is under the GPL (see COPYING), the xdiff/
+ library and some libc code from compat/ are licensed under the
+ GNU LGPL, version 2.1 or (at your option) any later version and some
+ other files are under other licenses. Check the individual files to
+ be sure.
+
+----------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/Makefile b/Makefile
index 9aca8a1..4ed7996 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,12 @@ all::
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies BLK_SHA1.
#
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
# Define NO_CURL if you do not have libcurl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
# transports.
@@ -45,11 +51,6 @@ all::
# Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
# d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7).
#
-# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
-# do not support the 'size specifiers' introduced by C99, namely ll, hh,
-# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
-# some C compilers supported these specifiers prior to C99 as an extension.
-#
# Define NO_STRCASESTR if you don't have strcasestr.
#
# Define NO_MEMMEM if you don't have memmem.
@@ -68,6 +69,16 @@ all::
#
# Define NO_MKSTEMPS if you don't have mkstemps in the C library.
#
+# Define NO_STRTOK_R if you don't have strtok_r in the C library.
+#
+# Define NO_FNMATCH if you don't have fnmatch in the C library.
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+#
+# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
+# in the C library.
+#
# Define NO_LIBGEN_H if you don't have libgen.h.
#
# Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -142,6 +153,9 @@ all::
# that tells runtime paths to dynamic libraries;
# "-Wl,-rpath=/path/lib" is used instead.
#
+# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
+# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
+#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -160,13 +174,13 @@ all::
# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
# field that counts the on-disk footprint in 512-byte blocks.
#
-# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
+# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7
#
# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
# (not v1.73 or v1.71).
#
-# Define ASCIIDOC_NO_ROFF if your DocBook XSL escapes raw roff directives
-# (versions 1.72 and later and 1.68.1 and earlier).
+# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives
+# (versions 1.68.1 through v1.72).
#
# Define GNU_ROFF if your target system uses GNU groff. This forces
# apostrophes to be ASCII so that cut&pasting examples to the shell
@@ -209,6 +223,11 @@ all::
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
#
+# Define GETTEXT_POISON if you are debugging the choice of strings marked
+# for translation. In a GETTEXT_POISON build, you can turn all strings marked
+# for translation into gibberish by setting the GIT_GETTEXT_POISON variable
+# (to any value) in your environment.
+#
# Define JSMIN to point to JavaScript minifier that functions as
# a filter to have gitweb.js minified.
#
@@ -267,7 +286,7 @@ STRIP ?= strip
# mandir
# infodir
# htmldir
-# ETC_GITCONFIG (but not sysconfdir)
+# sysconfdir
# can be specified as a relative path some/where/else;
# this is interpreted as relative to $(prefix) and "git" at
# runtime figures out where they are based on the path to the executable.
@@ -283,13 +302,8 @@ sharedir = $(prefix)/share
gitwebdir = $(sharedir)/gitweb
template_dir = share/git-core/templates
htmldir = share/doc/git-doc
-ifeq ($(prefix),/usr)
-sysconfdir = /etc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
-else
-sysconfdir = $(prefix)/etc
-ETC_GITCONFIG = etc/gitconfig
-endif
+ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
lib = lib
# DESTDIR=
pathsep = :
@@ -306,14 +320,14 @@ INSTALL = install
RPMBUILD = rpmbuild
TCL_PATH = tclsh
TCLTK_PATH = wish
+XGETTEXT = xgettext
PTHREAD_LIBS = -lpthread
PTHREAD_CFLAGS =
+GCOV = gcov
export TCL_PATH TCLTK_PATH
-# sparse is architecture-neutral, which means that we need to tell it
-# explicitly what architecture to check for. Fix this up for yours..
-SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
+SPARSE_FLAGS =
@@ -358,7 +372,6 @@ SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
-SCRIPT_SH += git-rebase--interactive.sh
SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.sh
@@ -368,7 +381,11 @@ SCRIPT_SH += git-web--browse.sh
SCRIPT_LIB += git-mergetool--lib
SCRIPT_LIB += git-parse-remote
+SCRIPT_LIB += git-rebase--am
+SCRIPT_LIB += git-rebase--interactive
+SCRIPT_LIB += git-rebase--merge
SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-sh-i18n
SCRIPT_PERL += git-add--interactive.perl
SCRIPT_PERL += git-difftool.perl
@@ -387,18 +404,22 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-instaweb
+ETAGS_TARGET = TAGS
+
# Empty...
EXTRA_PROGRAMS =
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
+PROGRAM_OBJS += daemon.o
PROGRAM_OBJS += fast-import.o
PROGRAM_OBJS += imap-send.o
PROGRAM_OBJS += shell.o
PROGRAM_OBJS += show-index.o
PROGRAM_OBJS += upload-pack.o
PROGRAM_OBJS += http-backend.o
+PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
@@ -408,13 +429,20 @@ TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-index-version
+TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-mktemp
+TEST_PROGRAMS_NEED_X += test-obj-pool
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
TEST_PROGRAMS_NEED_X += test-run-command
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
-TEST_PROGRAMS_NEED_X += test-index-version
+TEST_PROGRAMS_NEED_X += test-string-pool
+TEST_PROGRAMS_NEED_X += test-subprocess
+TEST_PROGRAMS_NEED_X += test-svn-fe
+TEST_PROGRAMS_NEED_X += test-treap
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -468,6 +496,7 @@ export PYTHON_PATH
LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
+VCSSVN_LIB=vcs-svn/lib.a
LIB_H += advice.h
LIB_H += archive.h
@@ -482,6 +511,9 @@ LIB_H += compat/bswap.h
LIB_H += compat/cygwin.h
LIB_H += compat/mingw.h
LIB_H += compat/win32/pthread.h
+LIB_H += compat/win32/syslog.h
+LIB_H += compat/win32/sys/poll.h
+LIB_H += compat/win32/dirent.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
@@ -490,6 +522,7 @@ LIB_H += diff.h
LIB_H += dir.h
LIB_H += exec_cmd.h
LIB_H += fsck.h
+LIB_H += gettext.h
LIB_H += git-compat-util.h
LIB_H += graph.h
LIB_H += grep.h
@@ -500,9 +533,11 @@ LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
LIB_H += mailmap.h
+LIB_H += merge-file.h
LIB_H += merge-recursive.h
LIB_H += notes.h
LIB_H += notes-cache.h
+LIB_H += notes-merge.h
LIB_H += object.h
LIB_H += pack.h
LIB_H += pack-refs.h
@@ -519,10 +554,12 @@ LIB_H += rerere.h
LIB_H += resolve-undo.h
LIB_H += revision.h
LIB_H += run-command.h
+LIB_H += sha1-array.h
LIB_H += sha1-lookup.h
LIB_H += sideband.h
LIB_H += sigchain.h
LIB_H += strbuf.h
+LIB_H += streaming.h
LIB_H += string-list.h
LIB_H += submodule.h
LIB_H += tag.h
@@ -593,6 +630,7 @@ LIB_OBJS += merge-recursive.o
LIB_OBJS += name-hash.o
LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
+LIB_OBJS += notes-merge.o
LIB_OBJS += object.o
LIB_OBJS += pack-check.o
LIB_OBJS += pack-refs.o
@@ -620,6 +658,7 @@ LIB_OBJS += revision.o
LIB_OBJS += run-command.o
LIB_OBJS += server-info.o
LIB_OBJS += setup.o
+LIB_OBJS += sha1-array.o
LIB_OBJS += sha1-lookup.o
LIB_OBJS += sha1_file.o
LIB_OBJS += sha1_name.o
@@ -627,6 +666,7 @@ LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
LIB_OBJS += sigchain.o
LIB_OBJS += strbuf.o
+LIB_OBJS += streaming.o
LIB_OBJS += string-list.o
LIB_OBJS += submodule.o
LIB_OBJS += symlinks.o
@@ -648,6 +688,7 @@ LIB_OBJS += write_or_die.o
LIB_OBJS += ws.o
LIB_OBJS += wt-status.o
LIB_OBJS += xdiff-interface.o
+LIB_OBJS += zlib.o
BUILTIN_OBJS += builtin/add.o
BUILTIN_OBJS += builtin/annotate.o
@@ -714,6 +755,8 @@ BUILTIN_OBJS += builtin/read-tree.o
BUILTIN_OBJS += builtin/receive-pack.o
BUILTIN_OBJS += builtin/reflog.o
BUILTIN_OBJS += builtin/remote.o
+BUILTIN_OBJS += builtin/remote-ext.o
+BUILTIN_OBJS += builtin/remote-fd.o
BUILTIN_OBJS += builtin/replace.o
BUILTIN_OBJS += builtin/rerere.o
BUILTIN_OBJS += builtin/reset.o
@@ -832,6 +875,7 @@ ifeq ($(uname_S),SunOS)
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
ifeq ($(uname_R),5.6)
SOCKLEN_T = int
NO_HSTRERROR = YesPlease
@@ -840,7 +884,6 @@ ifeq ($(uname_S),SunOS)
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
NO_STRLCPY = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
GIT_TEST_CMP = cmp
endif
@@ -851,21 +894,18 @@ ifeq ($(uname_S),SunOS)
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
NO_STRLCPY = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
GIT_TEST_CMP = cmp
endif
ifeq ($(uname_R),5.8)
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
GIT_TEST_CMP = cmp
endif
ifeq ($(uname_R),5.9)
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
GIT_TEST_CMP = cmp
endif
@@ -895,6 +935,7 @@ ifeq ($(uname_O),Cygwin)
X = .exe
COMPAT_OBJS += compat/cygwin.o
UNRELIABLE_FSTAT = UnfortunatelyYes
+ SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
@@ -973,6 +1014,8 @@ ifeq ($(uname_S),IRIX)
# NO_MMAP. If you suspect that your compiler is not affected by this
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
+ NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH = /usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
@@ -991,6 +1034,8 @@ ifeq ($(uname_S),IRIX64)
# NO_MMAP. If you suspect that your compiler is not affected by this
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
+ NO_REGEX = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH=/usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
@@ -1007,6 +1052,7 @@ ifeq ($(uname_S),HP-UX)
NO_UNSETENV = YesPlease
NO_HSTRERROR = YesPlease
NO_SYS_SELECT_H = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
NO_NSEC = YesPlease
ifeq ($(uname_R),B.11.00)
@@ -1035,10 +1081,11 @@ ifeq ($(uname_S),Windows)
NO_UNSETENV = YesPlease
NO_STRCASESTR = YesPlease
NO_STRLCPY = YesPlease
+ NO_STRTOK_R = YesPlease
+ NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
# NEEDS_LIBICONV = YesPlease
NO_ICONV = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
NO_STRTOULL = YesPlease
NO_MKDTEMP = YesPlease
@@ -1047,7 +1094,6 @@ ifeq ($(uname_S),Windows)
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
RUNTIME_PREFIX = YesPlease
- NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
@@ -1058,16 +1104,19 @@ ifeq ($(uname_S),Windows)
NO_CURL = YesPlease
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
+ NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
CFLAGS =
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
- COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
- COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
+ COMPAT_OBJS = compat/msvc.o compat/winansi.o \
+ compat/win32/pthread.o compat/win32/syslog.o \
+ compat/win32/sys/poll.o compat/win32/dirent.o
+ COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
- EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
+ EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
PTHREAD_LIBS =
lib =
ifndef DEBUG
@@ -1079,6 +1128,27 @@ else
endif
X = .exe
endif
+ifeq ($(uname_S),Interix)
+ NO_INITGROUPS = YesPlease
+ NO_IPV6 = YesPlease
+ NO_MEMMEM = YesPlease
+ NO_MKDTEMP = YesPlease
+ NO_STRTOUMAX = YesPlease
+ NO_NSEC = YesPlease
+ NO_MKSTEMPS = YesPlease
+ ifeq ($(uname_R),3.5)
+ NO_INET_NTOP = YesPlease
+ NO_INET_PTON = YesPlease
+ NO_SOCKADDR_STORAGE = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
+ endif
+ ifeq ($(uname_R),5.2)
+ NO_INET_NTOP = YesPlease
+ NO_INET_PTON = YesPlease
+ NO_SOCKADDR_STORAGE = YesPlease
+ NO_FNMATCH_CASEFOLD = YesPlease
+ endif
+endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
NO_PREAD = YesPlease
@@ -1089,17 +1159,17 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_UNSETENV = YesPlease
NO_STRCASESTR = YesPlease
NO_STRLCPY = YesPlease
+ NO_STRTOK_R = YesPlease
+ NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
NEEDS_LIBICONV = YesPlease
OLD_ICONV = YesPlease
- NO_C99_FORMAT = YesPlease
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
RUNTIME_PREFIX = YesPlease
- NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
USE_WIN32_MMAP = YesPlease
@@ -1109,13 +1179,19 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_REGEX = YesPlease
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
- COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
+ ETAGS_TARGET = ETAGS
+ NO_INET_PTON = YesPlease
+ NO_INET_NTOP = YesPlease
+ NO_POSIX_GOODIES = UnfortunatelyYes
+ COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
- COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
- compat/win32/pthread.o
+ COMPAT_OBJS += compat/mingw.o compat/winansi.o \
+ compat/win32/pthread.o compat/win32/syslog.o \
+ compat/win32/sys/poll.o compat/win32/dirent.o
EXTLIBS += -lws2_32
PTHREAD_LIBS =
X = .exe
+ SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir=doc/git/html/
prefix =
@@ -1131,6 +1207,14 @@ endif
-include config.mak.autogen
-include config.mak
+ifndef sysconfdir
+ifeq ($(prefix),/usr)
+sysconfdir = /etc
+else
+sysconfdir = etc
+endif
+endif
+
ifdef CHECK_HEADER_DEPENDENCIES
COMPUTE_HEADER_DEPENDENCIES =
USE_COMPUTED_HEADER_DEPENDENCIES =
@@ -1187,6 +1271,15 @@ ifdef NO_LIBGEN_H
COMPAT_OBJS += compat/basename.o
endif
+ifdef USE_LIBPCRE
+ BASIC_CFLAGS += -DUSE_LIBPCRE
+ ifdef LIBPCREDIR
+ BASIC_CFLAGS += -I$(LIBPCREDIR)/include
+ EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
+ endif
+ EXTLIBS += -lpcre
+endif
+
ifdef NO_CURL
BASIC_CFLAGS += -DNO_CURL
REMOTE_CURL_PRIMARY =
@@ -1227,9 +1320,6 @@ ifdef ZLIB_PATH
endif
EXTLIBS += -lz
-ifndef NO_POSIX_ONLY_PROGRAMS
- PROGRAM_OBJS += daemon.o
-endif
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
@@ -1246,11 +1336,15 @@ else
BLK_SHA1 = 1
OPENSSL_LIBSSL =
endif
+ifdef NO_OPENSSL
+ LIB_4_CRYPTO =
+else
ifdef NEEDS_SSL_WITH_CRYPTO
LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
else
LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
endif
+endif
ifdef NEEDS_LIBICONV
ifdef ICONVDIR
BASIC_CFLAGS += -I$(ICONVDIR)/include
@@ -1287,12 +1381,12 @@ endif
ifdef USE_ST_TIMESPEC
BASIC_CFLAGS += -DUSE_ST_TIMESPEC
endif
+ifdef NO_NORETURN
+ BASIC_CFLAGS += -DNO_NORETURN
+endif
ifdef NO_NSEC
BASIC_CFLAGS += -DNO_NSEC
endif
-ifdef NO_C99_FORMAT
- BASIC_CFLAGS += -DNO_C99_FORMAT
-endif
ifdef SNPRINTF_RETURNS_BOGUS
COMPAT_CFLAGS += -DSNPRINTF_RETURNS_BOGUS
COMPAT_OBJS += compat/snprintf.o
@@ -1304,6 +1398,10 @@ endif
ifdef NO_SYMLINK_HEAD
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
+ifdef GETTEXT_POISON
+ LIB_OBJS += gettext.o
+ BASIC_CFLAGS += -DGETTEXT_POISON
+endif
ifdef NO_STRCASESTR
COMPAT_CFLAGS += -DNO_STRCASESTR
COMPAT_OBJS += compat/strcasestr.o
@@ -1319,6 +1417,21 @@ endif
ifdef NO_STRTOULL
COMPAT_CFLAGS += -DNO_STRTOULL
endif
+ifdef NO_STRTOK_R
+ COMPAT_CFLAGS += -DNO_STRTOK_R
+ COMPAT_OBJS += compat/strtok_r.o
+endif
+ifdef NO_FNMATCH
+ COMPAT_CFLAGS += -Icompat/fnmatch
+ COMPAT_CFLAGS += -DNO_FNMATCH
+ COMPAT_OBJS += compat/fnmatch/fnmatch.o
+else
+ifdef NO_FNMATCH_CASEFOLD
+ COMPAT_CFLAGS += -Icompat/fnmatch
+ COMPAT_CFLAGS += -DNO_FNMATCH_CASEFOLD
+ COMPAT_OBJS += compat/fnmatch/fnmatch.o
+endif
+endif
ifdef NO_SETENV
COMPAT_CFLAGS += -DNO_SETENV
COMPAT_OBJS += compat/setenv.o
@@ -1337,6 +1450,15 @@ endif
ifdef NO_SYS_SELECT_H
BASIC_CFLAGS += -DNO_SYS_SELECT_H
endif
+ifdef NO_SYS_POLL_H
+ BASIC_CFLAGS += -DNO_SYS_POLL_H
+endif
+ifdef NO_INTTYPES_H
+ BASIC_CFLAGS += -DNO_INTTYPES_H
+endif
+ifdef NO_INITGROUPS
+ BASIC_CFLAGS += -DNO_INITGROUPS
+endif
ifdef NO_MMAP
COMPAT_CFLAGS += -DNO_MMAP
COMPAT_OBJS += compat/mmap.o
@@ -1374,9 +1496,11 @@ endif
endif
ifdef NO_INET_NTOP
LIB_OBJS += compat/inet_ntop.o
+ BASIC_CFLAGS += -DNO_INET_NTOP
endif
ifdef NO_INET_PTON
LIB_OBJS += compat/inet_pton.o
+ BASIC_CFLAGS += -DNO_INET_PTON
endif
ifdef NO_ICONV
@@ -1391,6 +1515,10 @@ ifdef NO_DEFLATE_BOUND
BASIC_CFLAGS += -DNO_DEFLATE_BOUND
endif
+ifdef NO_POSIX_GOODIES
+ BASIC_CFLAGS += -DNO_POSIX_GOODIES
+endif
+
ifdef BLK_SHA1
SHA1_HEADER = "block-sha1/sha1.h"
LIB_OBJS += block-sha1/sha1.o
@@ -1448,7 +1576,7 @@ ifdef NO_REGEX
endif
ifdef USE_NED_ALLOCATOR
- COMPAT_CFLAGS += -DUSE_NED_ALLOCATOR -DOVERRIDE_STRDUP -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR -Icompat/nedmalloc
+ COMPAT_CFLAGS += -Icompat/nedmalloc
COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
endif
@@ -1485,6 +1613,9 @@ ifndef V
QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
QUIET_GEN = @echo ' ' GEN $@;
QUIET_LNCP = @echo ' ' LN/CP $@;
+ QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
+ QUIET_GCOV = @echo ' ' GCOV $@;
+ QUIET_SP = @echo ' ' SP $<;
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
$(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1494,14 +1625,15 @@ ifndef V
endif
endif
-ifdef ASCIIDOC8
- export ASCIIDOC8
+ifdef ASCIIDOC7
+ export ASCIIDOC7
endif
# Shell quote (do not use $(call) to accommodate ancient setups);
SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
+ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES))
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
@@ -1579,15 +1711,19 @@ strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
git.o: common-cmds.h
-git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
- '-DGIT_HTML_PATH="$(htmldir_SQ)"'
+git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
+ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_SQ)"'
-git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
+git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-builtin/help.o: common-cmds.h
-builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
+help.sp help.o: common-cmds.h
+
+builtin/help.sp builtin/help.o: common-cmds.h
+builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"'
@@ -1649,33 +1785,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
gitweb:
$(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
-ifdef JSMIN
-GITWEB_PROGRAMS += gitweb/static/gitweb.min.js
-GITWEB_JS = gitweb/static/gitweb.min.js
-else
-GITWEB_JS = gitweb/static/gitweb.js
-endif
-ifdef CSSMIN
-GITWEB_PROGRAMS += gitweb/static/gitweb.min.css
-GITWEB_CSS = gitweb/static/gitweb.min.css
-else
-GITWEB_CSS = gitweb/static/gitweb.css
-endif
-OTHER_PROGRAMS += gitweb/gitweb.cgi $(GITWEB_PROGRAMS)
-gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS)
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-
-ifdef JSMIN
-gitweb/static/gitweb.min.js: gitweb/static/gitweb.js
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-endif # JSMIN
-ifdef CSSMIN
-gitweb/static/gitweb.min.css: gitweb/static/gitweb.css
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-endif # CSSMIN
-
-
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js
+git-instaweb: git-instaweb.sh gitweb
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1739,7 +1849,11 @@ ifndef NO_CURL
endif
XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
xdiff/xmerge.o xdiff/xpatience.o
-OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS)
+VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
+ vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
+VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
+ test-line-buffer.o test-treap.o
+OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -1847,48 +1961,70 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
builtin/bundle.o bundle.o transport.o: bundle.h
builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o: thread-utils.h
+builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
builtin/send-pack.o transport.o: send-pack.h
builtin/log.o builtin/shortlog.o: shortlog.h
builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
-builtin/pack-objects.o: thread-utils.h
+connect.o transport.o http-backend.o: url.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
-http.o http-walker.o http-push.o remote-curl.o: http.h
+http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
xdiff-interface.o $(XDIFF_OBJS): \
xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
+ vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
+ vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
+ vcs-svn/svndump.h
+
+test-svn-fe.o: vcs-svn/svndump.h
endif
-exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
+exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
'-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
'-DBINDIR="$(bindir_relative_SQ)"' \
'-DPREFIX="$(prefix_SQ)"'
-builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \
+builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \
-DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
-config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
+config.sp config.s config.o: EXTRA_CPPFLAGS = \
+ -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
+
+attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
+ -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
-http.s http.o: EXTRA_CPPFLAGS = -DGIT_USER_AGENT='"git/$(GIT_VERSION)"'
+http.sp http.s http.o: EXTRA_CPPFLAGS = \
+ -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
ifdef NO_EXPAT
-http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
+http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
+endif
+
+ifdef NO_REGEX
+compat/regex/regex.sp compat/regex/regex.o: EXTRA_CPPFLAGS = \
+ -DGAWK -DNO_MBSUPPORT
endif
-git-%$X: %.o $(GITLIBS)
+ifdef USE_NED_ALLOCATOR
+compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
+ -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
+endif
+
+git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-git-imap-send$X: imap-send.o $(GITLIBS)
+git-imap-send$X: imap-send.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
+ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
-git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
+git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL)
-git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
+git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
@@ -1898,7 +2034,7 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
ln -s $< $@ 2>/dev/null || \
cp $< $@
-$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
@@ -1908,6 +2044,8 @@ $(LIB_FILE): $(LIB_OBJS)
$(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+$(VCSSVN_LIB): $(VCSSVN_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
doc:
$(MAKE) -C Documentation all
@@ -1924,11 +2062,30 @@ info:
pdf:
$(MAKE) -C Documentation pdf
-TAGS:
- $(RM) TAGS
- $(FIND) . -name '*.[hcS]' -print | xargs etags -a
+XGETTEXT_FLAGS = \
+ --force-po \
+ --add-comments \
+ --msgid-bugs-address="Git Mailing List <git@vger.kernel.org>" \
+ --from-code=UTF-8
+XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
+ --keyword=_ --keyword=N_ --keyword="Q_:1,2"
+XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
+LOCALIZED_C := $(C_OBJ:o=c)
+LOCALIZED_SH := $(SCRIPT_SH)
+
+po/git.pot: $(LOCALIZED_C)
+ $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
+ $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
+ $(LOCALIZED_SH)
+ mv $@+ $@
-tags:
+pot: po/git.pot
+
+$(ETAGS_TARGET): FORCE
+ $(RM) $(ETAGS_TARGET)
+ $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
+
+tags: FORCE
$(RM) tags
$(FIND) . -name '*.[hcS]' -print | xargs ctags -a
@@ -1937,7 +2094,7 @@ cscope:
$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
### Detect prefix changes
-TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
+TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
GIT-CFLAGS: FORCE
@@ -1947,6 +2104,15 @@ GIT-CFLAGS: FORCE
echo "$$FLAGS" >GIT-CFLAGS; \
fi
+TRACK_LDFLAGS = $(subst ','\'',$(ALL_LDFLAGS))
+
+GIT-LDFLAGS: FORCE
+ @FLAGS='$(TRACK_LDFLAGS)'; \
+ if test x"$$FLAGS" != x"`cat GIT-LDFLAGS 2>/dev/null`" ; then \
+ echo 1>&2 " * new link flags"; \
+ echo "$$FLAGS" >GIT-LDFLAGS; \
+ fi
+
# We need to apply sq twice, once to protect from the shell
# that runs GIT-BUILD-OPTIONS, and then again to protect it
# and the first level quoting from the shell that runs "echo".
@@ -1957,6 +2123,7 @@ GIT-BUILD-OPTIONS: FORCE
@echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
+ @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
ifdef GIT_TEST_CMP
@@ -1965,6 +2132,7 @@ endif
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
endif
+ @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
### Detect Tck/Tk interpreter path changes
ifndef NO_TCLTK
@@ -2006,23 +2174,36 @@ test-date$X: date.o ctype.o
test-delta$X: diff-delta.o patch-delta.o
+test-line-buffer$X: vcs-svn/lib.a
+
test-parse-options$X: parse-options.o
+test-string-pool$X: vcs-svn/lib.a
+
+test-svn-fe$X: vcs-svn/lib.a
+
.PRECIOUS: $(TEST_OBJS)
-test-%$X: test-%.o $(GITLIBS)
- $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
check-sha1:: test-sha1$X
./test-sha1.sh
+SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ))
+
+$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE
+ $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
+ $(SPARSE_FLAGS) $<
+
+.PHONY: sparse $(SP_OBJ)
+sparse: $(SP_OBJ)
+
check: common-cmds.h
- if sparse; \
+ @if sparse; \
then \
- for i in *.c; \
- do \
- sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; \
- done; \
+ echo 2>&1 "Use 'make sparse' instead"; \
+ $(MAKE) --no-print-directory sparse; \
else \
echo 2>&1 "Did you mean 'make test'?"; \
exit 1; \
@@ -2075,23 +2256,32 @@ endif
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
{ test "$$bindir/" = "$$execdir/" || \
- { $(RM) "$$execdir/git$X" && \
+ for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
+ $(RM) "$$execdir/$$p" && \
test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
- ln "$$bindir/git$X" "$$execdir/git$X" 2>/dev/null || \
- cp "$$bindir/git$X" "$$execdir/git$X"; } ; } && \
- { for p in $(BUILT_INS); do \
+ ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
+ cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
+ done; \
+ } && \
+ for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
+ $(RM) "$$bindir/$$p" && \
+ ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
+ ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
+ cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
+ done && \
+ for p in $(BUILT_INS); do \
$(RM) "$$execdir/$$p" && \
ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
- done; } && \
- { test x"$(REMOTE_CURL_ALIASES)" = x || \
- { for p in $(REMOTE_CURL_ALIASES); do \
+ done && \
+ remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
+ for p in $$remote_curl_aliases; do \
$(RM) "$$execdir/$$p" && \
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; } ; } && \
+ done && \
./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
install-gitweb:
@@ -2175,15 +2365,16 @@ dist-doc:
distclean: clean
$(RM) configure
+ $(RM) po/git.pot
clean:
- $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
- builtin/*.o $(LIB_FILE) $(XDIFF_LIB)
+ $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
+ builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB)
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS)
$(RM) -r bin-wrappers
$(RM) -r $(dep_dirs)
- $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
+ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
$(RM) -r autom4te.cache
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache
$(RM) -r $(GIT_TARNAME) .doc-tmp-dir
@@ -2203,11 +2394,11 @@ ifndef NO_TCLTK
$(MAKE) -C gitk-git clean
$(MAKE) -C git-gui clean
endif
- $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+ $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
.PHONY: all install clean strip
.PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: FORCE TAGS tags cscope
+.PHONY: FORCE cscope
### Check documentation
#
@@ -2254,6 +2445,7 @@ check-docs::
documented,gitglossary | \
documented,githooks | \
documented,gitrepository-layout | \
+ documented,gitrevisions | \
documented,gittutorial | \
documented,gittutorial-2 | \
documented,git-bisect-lk2009 | \
@@ -2280,11 +2472,18 @@ coverage:
$(MAKE) coverage-build
$(MAKE) coverage-report
+object_dirs := $(sort $(dir $(OBJECTS)))
coverage-clean:
- rm -f *.gcda *.gcno
+ $(RM) $(addsuffix *.gcov,$(object_dirs))
+ $(RM) $(addsuffix *.gcda,$(object_dirs))
+ $(RM) $(addsuffix *.gcno,$(object_dirs))
+ $(RM) coverage-untested-functions
+ $(RM) -r cover_db/
+ $(RM) -r cover_db_html/
COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov
+GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks
coverage-build: coverage-clean
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
@@ -2292,7 +2491,33 @@ coverage-build: coverage-clean
-j1 test
coverage-report:
- gcov -b *.c
+ $(QUIET_GCOV)for dir in $(object_dirs); do \
+ $(GCOV) $(GCOVFLAGS) --object-directory=$$dir $$dir*.c || exit; \
+ done
+
+coverage-untested-functions: coverage-report
grep '^function.*called 0 ' *.c.gcov \
| sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \
- | tee coverage-untested-functions
+ > coverage-untested-functions
+
+cover_db: coverage-report
+ gcov2perl -db cover_db *.gcov
+
+cover_db_html: cover_db
+ cover -report html -outputdir cover_db_html cover_db
+
+### profile feedback build
+#
+.PHONY: profile-all profile-clean
+
+PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1
+PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1
+
+profile-clean:
+ $(RM) $(addsuffix *.gcda,$(object_dirs))
+ $(RM) $(addsuffix *.gcno,$(object_dirs))
+
+profile-all: profile-clean
+ $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all
+ $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test
+ $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all
diff --git a/RelNotes b/RelNotes
index 3da01a5..6372427 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.7.2.txt \ No newline at end of file
+Documentation/RelNotes/1.7.7.txt \ No newline at end of file
diff --git a/abspath.c b/abspath.c
index c91a29c..37287f8 100644
--- a/abspath.c
+++ b/abspath.c
@@ -14,7 +14,14 @@ int is_directory(const char *path)
/* We allow "recursive" symbolic links. Only within reason, though. */
#define MAXDEPTH 5
-const char *make_absolute_path(const char *path)
+/*
+ * Use this to get the real path, i.e. resolve links. If you want an
+ * absolute path but don't mind links, use absolute_path.
+ *
+ * If path is our buffer, then return path, as it's already what the
+ * user wants.
+ */
+const char *real_path(const char *path)
{
static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
char cwd[1024] = "";
@@ -24,12 +31,16 @@ const char *make_absolute_path(const char *path)
char *last_elem = NULL;
struct stat st;
+ /* We've already done it */
+ if (path == buf || path == next_buf)
+ return path;
+
if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
die ("Too long path: %.*s", 60, path);
while (depth--) {
if (!is_directory(buf)) {
- char *last_slash = strrchr(buf, '/');
+ char *last_slash = find_last_dir_sep(buf);
if (last_slash) {
*last_slash = '\0';
last_elem = xstrdup(last_slash + 1);
@@ -54,7 +65,7 @@ const char *make_absolute_path(const char *path)
if (len + strlen(last_elem) + 2 > PATH_MAX)
die ("Too long path name: '%s/%s'",
buf, last_elem);
- if (len && buf[len-1] != '/')
+ if (len && !is_dir_sep(buf[len-1]))
buf[len++] = '/';
strcpy(buf + len, last_elem);
free(last_elem);
@@ -91,7 +102,8 @@ static const char *get_pwd_cwd(void)
pwd = getenv("PWD");
if (pwd && strcmp(pwd, cwd)) {
stat(cwd, &cwd_stat);
- if (!stat(pwd, &pwd_stat) &&
+ if ((cwd_stat.st_dev || cwd_stat.st_ino) &&
+ !stat(pwd, &pwd_stat) &&
pwd_stat.st_dev == cwd_stat.st_dev &&
pwd_stat.st_ino == cwd_stat.st_ino) {
strlcpy(cwd, pwd, PATH_MAX);
@@ -100,7 +112,14 @@ static const char *get_pwd_cwd(void)
return cwd;
}
-const char *make_nonrelative_path(const char *path)
+/*
+ * Use this to get an absolute path from a relative one. If you want
+ * to resolve links, you should use real_path.
+ *
+ * If the path is already absolute, then return path. As the user is
+ * never meant to free the return value, we're safe.
+ */
+const char *absolute_path(const char *path)
{
static char buf[PATH_MAX + 1];
@@ -108,10 +127,14 @@ const char *make_nonrelative_path(const char *path)
if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
} else {
+ size_t len;
+ const char *fmt;
const char *cwd = get_pwd_cwd();
if (!cwd)
die_errno("Cannot determine the current working directory");
- if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
+ len = strlen(cwd);
+ fmt = (len > 0 && is_dir_sep(cwd[len-1])) ? "%s%s" : "%s/%s";
+ if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX)
die("Too long path: %.*s", 60, path);
}
return buf;
diff --git a/aclocal.m4 b/aclocal.m4
index d399de2..f11bc7e 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -13,7 +13,7 @@ AC_DEFUN([TYPE_SOCKLEN_T],
git_cv_socklen_t_equiv=
for arg2 in "struct sockaddr" void; do
for t in int size_t unsigned long "unsigned long"; do
- AC_TRY_COMPILE([
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
#include <sys/types.h>
#include <sys/socket.h>
@@ -21,7 +21,7 @@ AC_DEFUN([TYPE_SOCKLEN_T],
],[
$t len;
getpeername(0,0,&len);
- ],[
+ ])],[
git_cv_socklen_t_equiv="$t"
break 2
])
diff --git a/alias.c b/alias.c
index 372b7d8..eb9f08b 100644
--- a/alias.c
+++ b/alias.c
@@ -22,6 +22,13 @@ char *alias_lookup(const char *alias)
return alias_val;
}
+#define SPLIT_CMDLINE_BAD_ENDING 1
+#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
+static const char *split_cmdline_errors[] = {
+ "cmdline ends with \\",
+ "unclosed quote"
+};
+
int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
@@ -53,7 +60,7 @@ int split_cmdline(char *cmdline, const char ***argv)
if (!c) {
free(*argv);
*argv = NULL;
- return error("cmdline ends with \\");
+ return -SPLIT_CMDLINE_BAD_ENDING;
}
}
cmdline[dst++] = c;
@@ -66,7 +73,7 @@ int split_cmdline(char *cmdline, const char ***argv)
if (quoted) {
free(*argv);
*argv = NULL;
- return error("unclosed quote");
+ return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
}
ALLOC_GROW(*argv, count+1, size);
@@ -75,3 +82,6 @@ int split_cmdline(char *cmdline, const char ***argv)
return count;
}
+const char *split_cmdline_strerror(int split_cmdline_errno) {
+ return split_cmdline_errors[-split_cmdline_errno-1];
+}
diff --git a/alloc.c b/alloc.c
index 6ef6753..aeae55c 100644
--- a/alloc.c
+++ b/alloc.c
@@ -51,19 +51,12 @@ DEFINE_ALLOCATOR(commit, struct commit)
DEFINE_ALLOCATOR(tag, struct tag)
DEFINE_ALLOCATOR(object, union any_object)
-#ifdef NO_C99_FORMAT
-#define SZ_FMT "%u"
-#else
-#define SZ_FMT "%zu"
-#endif
-
static void report(const char *name, unsigned int count, size_t size)
{
- fprintf(stderr, "%10s: %8u (" SZ_FMT " kB)\n", name, count, size);
+ fprintf(stderr, "%10s: %8u (%"PRIuMAX" kB)\n",
+ name, count, (uintmax_t) size);
}
-#undef SZ_FMT
-
#define REPORT(name) \
report(#name, name##_allocs, name##_allocs*sizeof(struct name) >> 10)
diff --git a/archive-tar.c b/archive-tar.c
index cee06ce..20af005 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "tar.h"
#include "archive.h"
+#include "run-command.h"
#define RECORDSIZE (512)
#define BLOCKSIZE (RECORDSIZE * 20)
@@ -13,6 +14,9 @@ static unsigned long offset;
static int tar_umask = 002;
+static int write_tar_filter_archive(const struct archiver *ar,
+ struct archiver_args *args);
+
/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
{
@@ -220,6 +224,67 @@ static int write_global_extended_header(struct archiver_args *args)
return err;
}
+static struct archiver **tar_filters;
+static int nr_tar_filters;
+static int alloc_tar_filters;
+
+static struct archiver *find_tar_filter(const char *name, int len)
+{
+ int i;
+ for (i = 0; i < nr_tar_filters; i++) {
+ struct archiver *ar = tar_filters[i];
+ if (!strncmp(ar->name, name, len) && !ar->name[len])
+ return ar;
+ }
+ return NULL;
+}
+
+static int tar_filter_config(const char *var, const char *value, void *data)
+{
+ struct archiver *ar;
+ const char *dot;
+ const char *name;
+ const char *type;
+ int namelen;
+
+ if (prefixcmp(var, "tar."))
+ return 0;
+ dot = strrchr(var, '.');
+ if (dot == var + 9)
+ return 0;
+
+ name = var + 4;
+ namelen = dot - name;
+ type = dot + 1;
+
+ ar = find_tar_filter(name, namelen);
+ if (!ar) {
+ ar = xcalloc(1, sizeof(*ar));
+ ar->name = xmemdupz(name, namelen);
+ ar->write_archive = write_tar_filter_archive;
+ ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS;
+ ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters);
+ tar_filters[nr_tar_filters++] = ar;
+ }
+
+ if (!strcmp(type, "command")) {
+ if (!value)
+ return config_error_nonbool(var);
+ free(ar->data);
+ ar->data = xstrdup(value);
+ return 0;
+ }
+ if (!strcmp(type, "remote")) {
+ if (git_config_bool(var, value))
+ ar->flags |= ARCHIVER_REMOTE;
+ else
+ ar->flags &= ~ARCHIVER_REMOTE;
+ return 0;
+ }
+
+ return 0;
+}
+
static int git_tar_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "tar.umask")) {
@@ -231,15 +296,15 @@ static int git_tar_config(const char *var, const char *value, void *cb)
}
return 0;
}
- return git_default_config(var, value, cb);
+
+ return tar_filter_config(var, value, cb);
}
-int write_tar_archive(struct archiver_args *args)
+static int write_tar_archive(const struct archiver *ar,
+ struct archiver_args *args)
{
int err = 0;
- git_config(git_tar_config, NULL);
-
if (args->commit_sha1)
err = write_global_extended_header(args);
if (!err)
@@ -248,3 +313,65 @@ int write_tar_archive(struct archiver_args *args)
write_trailer();
return err;
}
+
+static int write_tar_filter_archive(const struct archiver *ar,
+ struct archiver_args *args)
+{
+ struct strbuf cmd = STRBUF_INIT;
+ struct child_process filter;
+ const char *argv[2];
+ int r;
+
+ if (!ar->data)
+ die("BUG: tar-filter archiver called with no filter defined");
+
+ strbuf_addstr(&cmd, ar->data);
+ if (args->compression_level >= 0)
+ strbuf_addf(&cmd, " -%d", args->compression_level);
+
+ memset(&filter, 0, sizeof(filter));
+ argv[0] = cmd.buf;
+ argv[1] = NULL;
+ filter.argv = argv;
+ filter.use_shell = 1;
+ filter.in = -1;
+
+ if (start_command(&filter) < 0)
+ die_errno("unable to start '%s' filter", argv[0]);
+ close(1);
+ if (dup2(filter.in, 1) < 0)
+ die_errno("unable to redirect descriptor");
+ close(filter.in);
+
+ r = write_tar_archive(ar, args);
+
+ close(1);
+ if (finish_command(&filter) != 0)
+ die("'%s' filter reported error", argv[0]);
+
+ strbuf_release(&cmd);
+ return r;
+}
+
+static struct archiver tar_archiver = {
+ "tar",
+ write_tar_archive,
+ ARCHIVER_REMOTE
+};
+
+void init_tar_archiver(void)
+{
+ int i;
+ register_archiver(&tar_archiver);
+
+ tar_filter_config("tar.tgz.command", "gzip -cn", NULL);
+ tar_filter_config("tar.tgz.remote", "true", NULL);
+ tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL);
+ tar_filter_config("tar.tar.gz.remote", "true", NULL);
+ git_config(git_tar_config, NULL);
+ for (i = 0; i < nr_tar_filters; i++) {
+ /* omit any filters that never had a command configured */
+ if (tar_filters[i]->data)
+ register_archiver(tar_filters[i]);
+ }
+}
diff --git a/archive-zip.c b/archive-zip.c
index cf28504..02d1f37 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -90,14 +90,14 @@ static void copy_le32(unsigned char *dest, unsigned int n)
static void *zlib_deflate(void *data, unsigned long size,
int compression_level, unsigned long *compressed_size)
{
- z_stream stream;
+ git_zstream stream;
unsigned long maxsize;
void *buffer;
int result;
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, compression_level);
- maxsize = deflateBound(&stream, size);
+ git_deflate_init(&stream, compression_level);
+ maxsize = git_deflate_bound(&stream, size);
buffer = xmalloc(maxsize);
stream.next_in = data;
@@ -106,7 +106,7 @@ static void *zlib_deflate(void *data, unsigned long size,
stream.avail_out = maxsize;
do {
- result = deflate(&stream, Z_FINISH);
+ result = git_deflate(&stream, Z_FINISH);
} while (result == Z_OK);
if (result != Z_STREAM_END) {
@@ -114,7 +114,7 @@ static void *zlib_deflate(void *data, unsigned long size,
return NULL;
}
- deflateEnd(&stream);
+ git_deflate_end(&stream);
*compressed_size = stream.total_out;
return buffer;
@@ -261,7 +261,8 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time)
*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
}
-int write_zip_archive(struct archiver_args *args)
+static int write_zip_archive(const struct archiver *ar,
+ struct archiver_args *args)
{
int err;
@@ -278,3 +279,14 @@ int write_zip_archive(struct archiver_args *args)
return err;
}
+
+static struct archiver zip_archiver = {
+ "zip",
+ write_zip_archive,
+ ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
+};
+
+void init_zip_archiver(void)
+{
+ register_archiver(&zip_archiver);
+}
diff --git a/archive.c b/archive.c
index d700af3..2a7a28e 100644
--- a/archive.c
+++ b/archive.c
@@ -7,23 +7,22 @@
#include "unpack-trees.h"
static char const * const archive_usage[] = {
- "git archive [options] <tree-ish> [path...]",
+ "git archive [options] <tree-ish> [<path>...]",
"git archive --list",
- "git archive --remote <repo> [--exec <cmd>] [options] <tree-ish> [path...]",
+ "git archive --remote <repo> [--exec <cmd>] [options] <tree-ish> [<path>...]",
"git archive --remote <repo> [--exec <cmd>] --list",
NULL
};
-#define USES_ZLIB_COMPRESSION 1
+static const struct archiver **archivers;
+static int nr_archivers;
+static int alloc_archivers;
-static const struct archiver {
- const char *name;
- write_archive_fn_t write_archive;
- unsigned int flags;
-} archivers[] = {
- { "tar", write_tar_archive },
- { "zip", write_zip_archive, USES_ZLIB_COMPRESSION },
-};
+void register_archiver(struct archiver *ar)
+{
+ ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers);
+ archivers[nr_archivers++] = ar;
+}
static void format_subst(const struct commit *commit,
const char *src, size_t len,
@@ -33,6 +32,7 @@ static void format_subst(const struct commit *commit,
struct strbuf fmt = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.date_mode = DATE_NORMAL;
+ ctx.abbrev = DEFAULT_ABBREV;
if (src == buf->buf)
to_free = strbuf_detach(buf, NULL);
@@ -156,6 +156,7 @@ int write_archive_entries(struct archiver_args *args,
struct archiver_context context;
struct unpack_trees_options opts;
struct tree_desc t;
+ struct pathspec pathspec;
int err;
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
@@ -190,8 +191,10 @@ int write_archive_entries(struct archiver_args *args,
git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
}
- err = read_tree_recursive(args->tree, "", 0, 0, args->pathspec,
+ init_pathspec(&pathspec, args->pathspec);
+ err = read_tree_recursive(args->tree, "", 0, 0, &pathspec,
write_archive_entry, &context);
+ free_pathspec(&pathspec);
if (err == READ_TREE_RECURSIVE)
err = 0;
return err;
@@ -204,9 +207,9 @@ static const struct archiver *lookup_archiver(const char *name)
if (!name)
return NULL;
- for (i = 0; i < ARRAY_SIZE(archivers); i++) {
- if (!strcmp(name, archivers[i].name))
- return &archivers[i];
+ for (i = 0; i < nr_archivers; i++) {
+ if (!strcmp(name, archivers[i]->name))
+ return archivers[i];
}
return NULL;
}
@@ -220,11 +223,14 @@ static int reject_entry(const unsigned char *sha1, const char *base,
static int path_exists(struct tree *tree, const char *path)
{
- const char *pathspec[] = { path, NULL };
-
- if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL))
- return 1;
- return 0;
+ const char *paths[] = { path, NULL };
+ struct pathspec pathspec;
+ int ret;
+
+ init_pathspec(&pathspec, paths);
+ ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
+ free_pathspec(&pathspec);
+ return ret != 0;
}
static void parse_pathspec_arg(const char **pathspec,
@@ -292,9 +298,10 @@ static void parse_treeish_arg(const char **argv,
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) }
static int parse_archive_args(int argc, const char **argv,
- const struct archiver **ar, struct archiver_args *args)
+ const struct archiver **ar, struct archiver_args *args,
+ const char *name_hint, int is_remote)
{
- const char *format = "tar";
+ const char *format = NULL;
const char *base = NULL;
const char *remote = NULL;
const char *exec = NULL;
@@ -313,7 +320,7 @@ static int parse_archive_args(int argc, const char **argv,
"write the archive to this file"),
OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
"read .gitattributes in working directory"),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "report archived files on stderr"),
OPT__COMPR('0', &compression_level, "store only", 0),
OPT__COMPR('1', &compression_level, "compress faster", 1),
OPT__COMPR_HIDDEN('2', &compression_level, 2),
@@ -348,21 +355,27 @@ static int parse_archive_args(int argc, const char **argv,
base = "";
if (list) {
- for (i = 0; i < ARRAY_SIZE(archivers); i++)
- printf("%s\n", archivers[i].name);
+ for (i = 0; i < nr_archivers; i++)
+ if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE)
+ printf("%s\n", archivers[i]->name);
exit(0);
}
+ if (!format && name_hint)
+ format = archive_format_from_filename(name_hint);
+ if (!format)
+ format = "tar";
+
/* We need at least one parameter -- tree-ish */
if (argc < 1)
usage_with_options(archive_usage, opts);
*ar = lookup_archiver(format);
- if (!*ar)
+ if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE)))
die("Unknown archive format '%s'", format);
args->compression_level = Z_DEFAULT_COMPRESSION;
if (compression_level != -1) {
- if ((*ar)->flags & USES_ZLIB_COMPRESSION)
+ if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS)
args->compression_level = compression_level;
else {
die("Argument not supported for format '%s': -%d",
@@ -378,19 +391,55 @@ static int parse_archive_args(int argc, const char **argv,
}
int write_archive(int argc, const char **argv, const char *prefix,
- int setup_prefix)
+ int setup_prefix, const char *name_hint, int remote)
{
+ int nongit = 0;
const struct archiver *ar = NULL;
struct archiver_args args;
- argc = parse_archive_args(argc, argv, &ar, &args);
if (setup_prefix && prefix == NULL)
- prefix = setup_git_directory();
+ prefix = setup_git_directory_gently(&nongit);
+
+ git_config(git_default_config, NULL);
+ init_tar_archiver();
+ init_zip_archiver();
+
+ argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
+ if (nongit) {
+ /*
+ * We know this will die() with an error, so we could just
+ * die ourselves; but its error message will be more specific
+ * than what we could write here.
+ */
+ setup_git_directory();
+ }
parse_treeish_arg(argv, &args, prefix);
parse_pathspec_arg(argv + 1, &args);
- git_config(git_default_config, NULL);
+ return ar->write_archive(ar, &args);
+}
- return ar->write_archive(&args);
+static int match_extension(const char *filename, const char *ext)
+{
+ int prefixlen = strlen(filename) - strlen(ext);
+
+ /*
+ * We need 1 character for the '.', and 1 character to ensure that the
+ * prefix is non-empty (k.e., we don't match .tar.gz with no actual
+ * filename).
+ */
+ if (prefixlen < 2 || filename[prefixlen-1] != '.')
+ return 0;
+ return !strcmp(filename + prefixlen, ext);
+}
+
+const char *archive_format_from_filename(const char *filename)
+{
+ int i;
+
+ for (i = 0; i < nr_archivers; i++)
+ if (match_extension(filename, archivers[i]->name))
+ return archivers[i]->name;
+ return NULL;
}
diff --git a/archive.h b/archive.h
index 038ac35..2b0884f 100644
--- a/archive.h
+++ b/archive.h
@@ -14,17 +14,24 @@ struct archiver_args {
int compression_level;
};
-typedef int (*write_archive_fn_t)(struct archiver_args *);
+#define ARCHIVER_WANT_COMPRESSION_LEVELS 1
+#define ARCHIVER_REMOTE 2
+struct archiver {
+ const char *name;
+ int (*write_archive)(const struct archiver *, struct archiver_args *);
+ unsigned flags;
+ void *data;
+};
+extern void register_archiver(struct archiver *);
-typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
+extern void init_tar_archiver(void);
+extern void init_zip_archiver(void);
-/*
- * Archive-format specific backends.
- */
-extern int write_tar_archive(struct archiver_args *);
-extern int write_zip_archive(struct archiver_args *);
+typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size);
extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry);
-extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix);
+extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote);
+
+const char *archive_format_from_filename(const char *filename);
#endif /* ARCHIVE_H */
diff --git a/attr.c b/attr.c
index 8ba606c..f6b3f7e 100644
--- a/attr.c
+++ b/attr.c
@@ -1,5 +1,6 @@
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+#include "exec_cmd.h"
#include "attr.h"
const char git_attr__true[] = "(builtin)true";
@@ -10,6 +11,8 @@ static const char git_attr__unknown[] = "(builtin)unknown";
#define ATTR__UNSET NULL
#define ATTR__UNKNOWN git_attr__unknown
+static const char *attributes_file;
+
/*
* The basic design decision here is that we are not going to have
* insanely large number of attributes.
@@ -462,6 +465,27 @@ static void drop_attr_stack(void)
}
}
+static const char *git_etc_gitattributes(void)
+{
+ static const char *system_wide;
+ if (!system_wide)
+ system_wide = system_path(ETC_GITATTRIBUTES);
+ return system_wide;
+}
+
+static int git_attr_system(void)
+{
+ return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
+}
+
+static int git_attr_config(const char *var, const char *value, void *dummy)
+{
+ if (!strcmp(var, "core.attributesfile"))
+ return git_config_pathname(&attributes_file, var, value);
+
+ return 0;
+}
+
static void bootstrap_attr_stack(void)
{
if (!attr_stack) {
@@ -472,6 +496,25 @@ static void bootstrap_attr_stack(void)
elem->prev = attr_stack;
attr_stack = elem;
+ if (git_attr_system()) {
+ elem = read_attr_from_file(git_etc_gitattributes(), 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
+ }
+ }
+
+ git_config(git_attr_config, NULL);
+ if (attributes_file) {
+ elem = read_attr_from_file(attributes_file, 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
+ }
+ }
+
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
elem = read_attr(GITATTRIBUTES_FILE, 1);
elem->origin = strdup("");
@@ -499,7 +542,9 @@ static void prepare_attr_stack(const char *path, int dirlen)
/*
* At the bottom of the attribute stack is the built-in
- * set of attribute definitions. Then, contents from
+ * set of attribute definitions, followed by the contents
+ * of $(prefix)/etc/gitattributes and a file specified by
+ * core.attributesfile. Then, contents from
* .gitattribute files from directories closer to the
* root to the ones in deeper directories are pushed
* to the stack. Finally, at the very top of the stack
diff --git a/base85.c b/base85.c
index e459fee..781b575 100644
--- a/base85.c
+++ b/base85.c
@@ -7,9 +7,9 @@
#define say1(a,b) fprintf(stderr, a, b)
#define say2(a,b,c) fprintf(stderr, a, b, c)
#else
-#define say(a) do {} while(0)
-#define say1(a,b) do {} while(0)
-#define say2(a,b,c) do {} while(0)
+#define say(a) do { /* nothing */ } while (0)
+#define say1(a,b) do { /* nothing */ } while (0)
+#define say2(a,b,c) do { /* nothing */ } while (0)
#endif
static const char en85[] = {
diff --git a/bisect.c b/bisect.c
index b556b11..dd7e8ed 100644
--- a/bisect.c
+++ b/bisect.c
@@ -9,13 +9,7 @@
#include "run-command.h"
#include "log-tree.h"
#include "bisect.h"
-
-struct sha1_array {
- unsigned char (*sha1)[20];
- int sha1_nr;
- int sha1_alloc;
- int sorted;
-};
+#include "sha1-array.h"
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
@@ -141,7 +135,8 @@ static void show_list(const char *debug, int counted, int nr,
enum object_type type;
unsigned long size;
char *buf = read_sha1_file(commit->object.sha1, &type, &size);
- char *ep, *sp;
+ const char *subject_start;
+ int subject_len;
fprintf(stderr, "%c%c%c ",
(flags & TREESAME) ? ' ' : 'T',
@@ -156,13 +151,9 @@ static void show_list(const char *debug, int counted, int nr,
fprintf(stderr, " %.*s", 8,
sha1_to_hex(pp->item->object.sha1));
- sp = strstr(buf, "\n\n");
- if (sp) {
- sp += 2;
- for (ep = sp; *ep && *ep != '\n'; ep++)
- ;
- fprintf(stderr, " %.*s", (int)(ep - sp), sp);
- }
+ subject_len = find_commit_subject(buf, &subject_start);
+ if (subject_len)
+ fprintf(stderr, " %.*s", subject_len, subject_start);
fprintf(stderr, "\n");
}
}
@@ -428,22 +419,15 @@ static void argv_array_push_sha1(struct argv_array *array,
argv_array_push(array, strbuf_detach(&buf, NULL));
}
-static void sha1_array_push(struct sha1_array *array,
- const unsigned char *sha1)
-{
- ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc);
- hashcpy(array->sha1[array->sha1_nr++], sha1);
-}
-
static int register_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
if (!strcmp(refname, "bad")) {
current_bad_sha1 = sha1;
} else if (!prefixcmp(refname, "good-")) {
- sha1_array_push(&good_revs, sha1);
+ sha1_array_append(&good_revs, sha1);
} else if (!prefixcmp(refname, "skip-")) {
- sha1_array_push(&skipped_revs, sha1);
+ sha1_array_append(&skipped_revs, sha1);
}
return 0;
@@ -480,41 +464,14 @@ static void read_bisect_paths(struct argv_array *array)
fclose(fp);
}
-static int array_cmp(const void *a, const void *b)
-{
- return hashcmp(a, b);
-}
-
-static void sort_sha1_array(struct sha1_array *array)
-{
- qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp);
-
- array->sorted = 1;
-}
-
-static const unsigned char *sha1_access(size_t index, void *table)
-{
- unsigned char (*array)[20] = table;
- return array[index];
-}
-
-static int lookup_sha1_array(struct sha1_array *array,
- const unsigned char *sha1)
-{
- if (!array->sorted)
- sort_sha1_array(array);
-
- return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access);
-}
-
static char *join_sha1_array_hex(struct sha1_array *array, char delim)
{
struct strbuf joined_hexs = STRBUF_INIT;
int i;
- for (i = 0; i < array->sha1_nr; i++) {
+ for (i = 0; i < array->nr; i++) {
strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i]));
- if (i + 1 < array->sha1_nr)
+ if (i + 1 < array->nr)
strbuf_addch(&joined_hexs, delim);
}
@@ -549,13 +506,13 @@ struct commit_list *filter_skipped(struct commit_list *list,
if (count)
*count = 0;
- if (!skipped_revs.sha1_nr)
+ if (!skipped_revs.nr)
return list;
while (list) {
struct commit_list *next = list->next;
list->next = NULL;
- if (0 <= lookup_sha1_array(&skipped_revs,
+ if (0 <= sha1_array_lookup(&skipped_revs,
list->item->object.sha1)) {
if (skipped_first && !*skipped_first)
*skipped_first = 1;
@@ -650,7 +607,7 @@ static struct commit_list *managed_skipped(struct commit_list *list,
*tried = NULL;
- if (!skipped_revs.sha1_nr)
+ if (!skipped_revs.nr)
return list;
list = filter_skipped(list, tried, 0, &count, &skipped_first);
@@ -675,7 +632,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
/* rev_argv.argv[0] will be ignored by setup_revisions */
argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format);
- for (i = 0; i < good_revs.sha1_nr; i++)
+ for (i = 0; i < good_revs.nr; i++)
argv_array_push_sha1(&rev_argv, good_revs.sha1[i],
good_format);
argv_array_push(&rev_argv, xstrdup("--"));
@@ -775,12 +732,12 @@ static struct commit *get_commit_reference(const unsigned char *sha1)
static struct commit **get_bad_and_good_commits(int *rev_nr)
{
- int len = 1 + good_revs.sha1_nr;
+ int len = 1 + good_revs.nr;
struct commit **rev = xmalloc(len * sizeof(*rev));
int i, n = 0;
rev[n++] = get_commit_reference(current_bad_sha1);
- for (i = 0; i < good_revs.sha1_nr; i++)
+ for (i = 0; i < good_revs.nr; i++)
rev[n++] = get_commit_reference(good_revs.sha1[i]);
*rev_nr = n;
@@ -843,9 +800,9 @@ static void check_merge_bases(void)
const unsigned char *mb = result->item->object.sha1;
if (!hashcmp(mb, current_bad_sha1)) {
handle_bad_merge_base();
- } else if (0 <= lookup_sha1_array(&good_revs, mb)) {
+ } else if (0 <= sha1_array_lookup(&good_revs, mb)) {
continue;
- } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) {
+ } else if (0 <= sha1_array_lookup(&skipped_revs, mb)) {
handle_skipped_merge_base(mb);
} else {
printf("Bisecting: a merge base must be tested\n");
@@ -906,7 +863,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix)
return;
/* Bisecting with no good rev is ok. */
- if (good_revs.sha1_nr == 0)
+ if (good_revs.nr == 0)
return;
/* Check if all good revs are ancestor of the bad rev. */
@@ -971,7 +928,7 @@ int bisect_next_all(const char *prefix)
bisect_common(&revs);
revs.commits = find_bisection(revs.commits, &reaches, &all,
- !!skipped_revs.sha1_nr);
+ !!skipped_revs.nr);
revs.commits = managed_skipped(revs.commits, &tried);
if (!revs.commits) {
diff --git a/branch.c b/branch.c
index 2ab42aa..c0c865a 100644
--- a/branch.c
+++ b/branch.c
@@ -159,7 +159,7 @@ void create_branch(const char *head,
dont_change_ref = 1;
else if (!force)
die("A branch named '%s' already exists.", name);
- else if (!is_bare_repository() && !strcmp(head, name))
+ else if (!is_bare_repository() && head && !strcmp(head, name))
die("Cannot force update the current branch.");
forcing = 1;
}
@@ -175,9 +175,14 @@ void create_branch(const char *head,
die("Cannot setup tracking information; starting point is not a branch.");
break;
case 1:
- /* Unique completion -- good, only if it is a real ref */
- if (explicit_tracking && !strcmp(real_ref, "HEAD"))
- die("Cannot setup tracking information; starting point is not a branch.");
+ /* Unique completion -- good, only if it is a real branch */
+ if (prefixcmp(real_ref, "refs/heads/") &&
+ prefixcmp(real_ref, "refs/remotes/")) {
+ if (explicit_tracking)
+ die("Cannot setup tracking information; starting point is not a branch.");
+ else
+ real_ref = NULL;
+ }
break;
default:
die("Ambiguous object name: '%s'.", start_name);
@@ -217,6 +222,7 @@ void create_branch(const char *head,
void remove_branch_state(void)
{
+ unlink(git_path("CHERRY_PICK_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_RR"));
unlink(git_path("MERGE_MSG"));
diff --git a/branch.h b/branch.h
index eed817a..4026e38 100644
--- a/branch.h
+++ b/branch.h
@@ -22,8 +22,8 @@ void create_branch(const char *head, const char *name, const char *start_name,
void remove_branch_state(void);
/*
- * Configure local branch "local" to merge remote branch "remote"
- * taken from origin "origin".
+ * Configure local branch "local" as downstream to branch "remote"
+ * from remote "origin". Used by git branch --set-upstream.
*/
#define BRANCH_CONFIG_VERBOSE 01
extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote);
diff --git a/builtin.h b/builtin.h
index ed6ee26..0e9da90 100644
--- a/builtin.h
+++ b/builtin.h
@@ -7,17 +7,16 @@
#include "commit.h"
#include "notes.h"
+#define DEFAULT_MERGE_LOG_LEN 20
+
extern const char git_version_string[];
extern const char git_usage_string[];
extern const char git_more_info_string[];
-extern void list_common_cmds_help(void);
-extern const char *help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
-extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
- struct strbuf *out);
-extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out);
-extern int commit_notes(struct notes_tree *t, const char *msg);
+extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ int merge_title, int shortlog_len);
+extern void commit_notes(struct notes_tree *t, const char *msg);
struct notes_rewrite_cfg {
struct notes_tree **trees;
@@ -37,7 +36,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
extern int check_pager_config(const char *cmd);
-extern int textconv_object(const char *path, const unsigned char *sha1, char **buf, unsigned long *buf_size);
+extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@@ -58,6 +57,7 @@ extern int cmd_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_config(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
@@ -109,7 +109,9 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
extern int cmd_remote(int argc, const char **argv, const char *prefix);
-extern int cmd_config(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_ext(int argc, const char **argv, const char *prefix);
+extern int cmd_remote_fd(int argc, const char **argv, const char *prefix);
+extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_reset(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index 17149cf..c59b0c9 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -21,12 +21,32 @@ static const char * const builtin_add_usage[] = {
static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes;
-struct update_callback_data
-{
+struct update_callback_data {
int flags;
int add_errors;
};
+static int fix_unmerged_status(struct diff_filepair *p,
+ struct update_callback_data *data)
+{
+ if (p->status != DIFF_STATUS_UNMERGED)
+ return p->status;
+ if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode)
+ /*
+ * This is not an explicit add request, and the
+ * path is missing from the working tree (deleted)
+ */
+ return DIFF_STATUS_DELETED;
+ else
+ /*
+ * Either an explicit add request, or path exists
+ * in the working tree. An attempt to explicitly
+ * add a path that does not exist in the working tree
+ * will be caught as an error by the caller immediately.
+ */
+ return DIFF_STATUS_MODIFIED;
+}
+
static void update_callback(struct diff_queue_struct *q,
struct diff_options *opt, void *cbdata)
{
@@ -36,35 +56,14 @@ static void update_callback(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
- switch (p->status) {
+ switch (fix_unmerged_status(p, data)) {
default:
- die("unexpected diff status %c", p->status);
- case DIFF_STATUS_UNMERGED:
- /*
- * ADD_CACHE_IGNORE_REMOVAL is unset if "git
- * add -u" is calling us, In such a case, a
- * missing work tree file needs to be removed
- * if there is an unmerged entry at stage #2,
- * but such a diff record is followed by
- * another with DIFF_STATUS_DELETED (and if
- * there is no stage #2, we won't see DELETED
- * nor MODIFIED). We can simply continue
- * either way.
- */
- if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
- continue;
- /*
- * Otherwise, it is "git add path" is asking
- * to explicitly add it; we fall through. A
- * missing work tree file is an error and is
- * caught by add_file_to_index() in such a
- * case.
- */
+ die(_("unexpected diff status %c"), p->status);
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
if (add_file_to_index(&the_index, path, data->flags)) {
if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
- die("updating files failed");
+ die(_("updating files failed"));
data->add_errors++;
}
break;
@@ -74,7 +73,7 @@ static void update_callback(struct diff_queue_struct *q,
if (!(data->flags & ADD_CACHE_PRETEND))
remove_file_from_index(&the_index, path);
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
- printf("remove '%s'\n", path);
+ printf(_("remove '%s'\n"), path);
break;
}
}
@@ -86,12 +85,13 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = pathspec;
+ init_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
data.flags = flags;
data.add_errors = 0;
rev.diffopt.format_callback_data = &data;
+ rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
return !!data.add_errors;
}
@@ -172,7 +172,7 @@ static void treat_gitlinks(const char **pathspec)
/* strip trailing slash */
pathspec[j] = xstrndup(ce->name, len);
else
- die ("Path '%s' is in submodule '%.*s'",
+ die (_("Path '%s' is in submodule '%.*s'"),
pathspec[j], len, ce->name);
}
}
@@ -188,10 +188,10 @@ static void refresh(int verbose, const char **pathspec)
/* nothing */;
seen = xcalloc(specs, 1);
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
- pathspec, seen, "Unstaged changes after refreshing the index:");
+ pathspec, seen, _("Unstaged changes after refreshing the index:"));
for (i = 0; i < specs; i++) {
if (!seen[i])
- die("pathspec '%s' did not match any files", pathspec[i]);
+ die(_("pathspec '%s' did not match any files"), pathspec[i]);
}
free(seen);
}
@@ -205,7 +205,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
for (p = pathspec; *p; p++) {
if (has_symlink_leading_path(*p, strlen(*p))) {
int len = prefix ? strlen(prefix) : 0;
- die("'%s' is beyond a symbolic link", *p + len);
+ die(_("'%s' is beyond a symbolic link"), *p + len);
}
}
}
@@ -242,7 +242,7 @@ int run_add_interactive(const char *revision, const char *patch_mode,
return status;
}
-int interactive_add(int argc, const char **argv, const char *prefix)
+int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
const char **pathspec = NULL;
@@ -253,7 +253,7 @@ int interactive_add(int argc, const char **argv, const char *prefix)
}
return run_add_interactive(NULL,
- patch_interactive ? "--patch" : NULL,
+ patch ? "--patch" : NULL,
pathspec);
}
@@ -272,7 +272,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
if (read_cache() < 0)
- die ("Could not read the index");
+ die (_("Could not read the index"));
init_revisions(&rev, prefix);
rev.diffopt.context = 7;
@@ -281,24 +281,24 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
out = open(file, O_CREAT | O_WRONLY, 0644);
if (out < 0)
- die ("Could not open '%s' for writing.", file);
+ die (_("Could not open '%s' for writing."), file);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
- die ("Could not write patch");
+ die (_("Could not write patch"));
launch_editor(file, NULL, NULL);
if (stat(file, &st))
- die_errno("Could not stat '%s'", file);
+ die_errno(_("Could not stat '%s'"), file);
if (!st.st_size)
- die("Empty patch. Aborted.");
+ die(_("Empty patch. Aborted."));
memset(&child, 0, sizeof(child));
child.git_cmd = 1;
child.argv = apply_argv;
if (run_command(&child))
- die ("Could not apply '%s'", file);
+ die (_("Could not apply '%s'"), file);
unlink(file);
return 0;
@@ -307,30 +307,32 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
static struct lock_file lock_file;
static const char ignore_error[] =
-"The following paths are ignored by one of your .gitignore files:\n";
+N_("The following paths are ignored by one of your .gitignore files:\n");
static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int ignore_add_errors, addremove, intent_to_add;
+static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;
static struct option builtin_add_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__VERBOSE(&verbose),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_GROUP(""),
OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
- OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"),
OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
- OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
+ OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"),
OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
- OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
+ OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"),
OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
+ OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
OPT_END(),
};
static int add_config(const char *var, const char *value, void *cb)
{
- if (!strcasecmp(var, "add.ignore-errors")) {
+ if (!strcmp(var, "add.ignoreerrors") ||
+ !strcmp(var, "add.ignore-errors")) {
ignore_add_errors = git_config_bool(var, value);
return 0;
}
@@ -342,17 +344,17 @@ static int add_files(struct dir_struct *dir, int flags)
int i, exit_status = 0;
if (dir->ignored_nr) {
- fprintf(stderr, ignore_error);
+ fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- fprintf(stderr, "Use -f if you really want to add them.\n");
- die("no files added");
+ fprintf(stderr, _("Use -f if you really want to add them.\n"));
+ die(_("no files added"));
}
for (i = 0; i < dir->nr; i++)
if (add_file_to_cache(dir->entries[i]->name, flags)) {
if (!ignore_add_errors)
- die("adding files failed");
+ die(_("adding files failed"));
exit_status = 1;
}
return exit_status;
@@ -376,7 +378,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (patch_interactive)
add_interactive = 1;
if (add_interactive)
- exit(interactive_add(argc - 1, argv + 1, prefix));
+ exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
if (edit_interactive)
return(edit_patch(argc, argv, prefix));
@@ -384,7 +386,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
argv++;
if (addremove && take_worktree_changes)
- die("-A and -u are mutually incompatible");
+ die(_("-A and -u are mutually incompatible"));
+ if (!show_only && ignore_missing)
+ die(_("Option --ignore-missing can only be used together with --dry-run"));
if ((addremove || take_worktree_changes) && !argc) {
static const char *here[2] = { ".", NULL };
argc = 1;
@@ -404,14 +408,14 @@ int cmd_add(int argc, const char **argv, const char *prefix)
? ADD_CACHE_IGNORE_REMOVAL : 0));
if (require_pathspec && argc == 0) {
- fprintf(stderr, "Nothing specified, nothing added.\n");
- fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+ fprintf(stderr, _("Nothing specified, nothing added.\n"));
+ fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0;
}
pathspec = validate_pathspec(argc, argv, prefix);
if (read_cache() < 0)
- die("index file corrupt");
+ die(_("index file corrupt"));
treat_gitlinks(pathspec);
if (add_new_files) {
@@ -441,9 +445,15 @@ int cmd_add(int argc, const char **argv, const char *prefix)
seen = find_used_pathspec(pathspec);
for (i = 0; pathspec[i]; i++) {
if (!seen[i] && pathspec[i][0]
- && !file_exists(pathspec[i]))
- die("pathspec '%s' did not match any files",
- pathspec[i]);
+ && !file_exists(pathspec[i])) {
+ if (ignore_missing) {
+ int dtype = DT_UNKNOWN;
+ if (excluded(&dir, pathspec[i], &dtype))
+ dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
+ } else
+ die(_("pathspec '%s' did not match any files"),
+ pathspec[i]);
+ }
}
free(seen);
}
@@ -457,7 +467,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ die(_("Unable to write new index file"));
}
return exit_status;
diff --git a/builtin/apply.c b/builtin/apply.c
index 12ef9ea..f2edc52 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -43,6 +43,7 @@ static int apply = 1;
static int apply_in_reverse;
static int apply_with_reject;
static int apply_verbosely;
+static int allow_overlap;
static int no_add;
static const char *fake_ancestor;
static int line_termination = '\n';
@@ -204,6 +205,7 @@ struct line {
unsigned hash : 24;
unsigned flag : 8;
#define LINE_COMMON 1
+#define LINE_PATCHED 2
};
/*
@@ -416,48 +418,210 @@ static char *squash_slash(char *name)
return name;
}
-static char *find_name(const char *line, char *def, int p_value, int terminate)
+static char *find_name_gnu(const char *line, char *def, int p_value)
{
- int len;
- const char *start = NULL;
+ struct strbuf name = STRBUF_INIT;
+ char *cp;
- if (p_value == 0)
- start = line;
+ /*
+ * Proposed "new-style" GNU patch/diff format; see
+ * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+ */
+ if (unquote_c_style(&name, line, NULL)) {
+ strbuf_release(&name);
+ return NULL;
+ }
- if (*line == '"') {
- struct strbuf name = STRBUF_INIT;
+ for (cp = name.buf; p_value; p_value--) {
+ cp = strchr(cp, '/');
+ if (!cp) {
+ strbuf_release(&name);
+ return NULL;
+ }
+ cp++;
+ }
- /*
- * Proposed "new-style" GNU patch/diff format; see
- * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
- */
- if (!unquote_c_style(&name, line, NULL)) {
- char *cp;
+ /* name can later be freed, so we need
+ * to memmove, not just return cp
+ */
+ strbuf_remove(&name, 0, cp - name.buf);
+ free(def);
+ if (root)
+ strbuf_insert(&name, 0, root, root_len);
+ return squash_slash(strbuf_detach(&name, NULL));
+}
- for (cp = name.buf; p_value; p_value--) {
- cp = strchr(cp, '/');
- if (!cp)
- break;
- cp++;
- }
- if (cp) {
- /* name can later be freed, so we need
- * to memmove, not just return cp
- */
- strbuf_remove(&name, 0, cp - name.buf);
- free(def);
- if (root)
- strbuf_insert(&name, 0, root, root_len);
- return squash_slash(strbuf_detach(&name, NULL));
- }
- }
- strbuf_release(&name);
+static size_t sane_tz_len(const char *line, size_t len)
+{
+ const char *tz, *p;
+
+ if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
+ return 0;
+ tz = line + len - strlen(" +0500");
+
+ if (tz[1] != '+' && tz[1] != '-')
+ return 0;
+
+ for (p = tz + 2; p != line + len; p++)
+ if (!isdigit(*p))
+ return 0;
+
+ return line + len - tz;
+}
+
+static size_t tz_with_colon_len(const char *line, size_t len)
+{
+ const char *tz, *p;
+
+ if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
+ return 0;
+ tz = line + len - strlen(" +08:00");
+
+ if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
+ return 0;
+ p = tz + 2;
+ if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+ !isdigit(*p++) || !isdigit(*p++))
+ return 0;
+
+ return line + len - tz;
+}
+
+static size_t date_len(const char *line, size_t len)
+{
+ const char *date, *p;
+
+ if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
+ return 0;
+ p = date = line + len - strlen("72-02-05");
+
+ if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
+ !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
+ !isdigit(*p++) || !isdigit(*p++)) /* Not a date. */
+ return 0;
+
+ if (date - line >= strlen("19") &&
+ isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */
+ date -= strlen("19");
+
+ return line + len - date;
+}
+
+static size_t short_time_len(const char *line, size_t len)
+{
+ const char *time, *p;
+
+ if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
+ return 0;
+ p = time = line + len - strlen(" 07:01:32");
+
+ /* Permit 1-digit hours? */
+ if (*p++ != ' ' ||
+ !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+ !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+ !isdigit(*p++) || !isdigit(*p++)) /* Not a time. */
+ return 0;
+
+ return line + len - time;
+}
+
+static size_t fractional_time_len(const char *line, size_t len)
+{
+ const char *p;
+ size_t n;
+
+ /* Expected format: 19:41:17.620000023 */
+ if (!len || !isdigit(line[len - 1]))
+ return 0;
+ p = line + len - 1;
+
+ /* Fractional seconds. */
+ while (p > line && isdigit(*p))
+ p--;
+ if (*p != '.')
+ return 0;
+
+ /* Hours, minutes, and whole seconds. */
+ n = short_time_len(line, p - line);
+ if (!n)
+ return 0;
+
+ return line + len - p + n;
+}
+
+static size_t trailing_spaces_len(const char *line, size_t len)
+{
+ const char *p;
+
+ /* Expected format: ' ' x (1 or more) */
+ if (!len || line[len - 1] != ' ')
+ return 0;
+
+ p = line + len;
+ while (p != line) {
+ p--;
+ if (*p != ' ')
+ return line + len - (p + 1);
}
- for (;;) {
+ /* All spaces! */
+ return len;
+}
+
+static size_t diff_timestamp_len(const char *line, size_t len)
+{
+ const char *end = line + len;
+ size_t n;
+
+ /*
+ * Posix: 2010-07-05 19:41:17
+ * GNU: 2010-07-05 19:41:17.620000023 -0500
+ */
+
+ if (!isdigit(end[-1]))
+ return 0;
+
+ n = sane_tz_len(line, end - line);
+ if (!n)
+ n = tz_with_colon_len(line, end - line);
+ end -= n;
+
+ n = short_time_len(line, end - line);
+ if (!n)
+ n = fractional_time_len(line, end - line);
+ end -= n;
+
+ n = date_len(line, end - line);
+ if (!n) /* No date. Too bad. */
+ return 0;
+ end -= n;
+
+ if (end == line) /* No space before date. */
+ return 0;
+ if (end[-1] == '\t') { /* Success! */
+ end--;
+ return line + len - end;
+ }
+ if (end[-1] != ' ') /* No space before date. */
+ return 0;
+
+ /* Whitespace damage. */
+ end -= trailing_spaces_len(line, end - line);
+ return line + len - end;
+}
+
+static char *find_name_common(const char *line, char *def, int p_value,
+ const char *end, int terminate)
+{
+ int len;
+ const char *start = NULL;
+
+ if (p_value == 0)
+ start = line;
+ while (line != end) {
char c = *line;
- if (isspace(c)) {
+ if (!end && isspace(c)) {
if (c == '\n')
break;
if (name_terminate(start, line-start, c, terminate))
@@ -497,6 +661,37 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
return squash_slash(xmemdupz(start, len));
}
+static char *find_name(const char *line, char *def, int p_value, int terminate)
+{
+ if (*line == '"') {
+ char *name = find_name_gnu(line, def, p_value);
+ if (name)
+ return name;
+ }
+
+ return find_name_common(line, def, p_value, NULL, terminate);
+}
+
+static char *find_name_traditional(const char *line, char *def, int p_value)
+{
+ size_t len = strlen(line);
+ size_t date_len;
+
+ if (*line == '"') {
+ char *name = find_name_gnu(line, def, p_value);
+ if (name)
+ return name;
+ }
+
+ len = strchrnul(line, '\n') - line;
+ date_len = diff_timestamp_len(line, len);
+ if (!date_len)
+ return find_name_common(line, def, p_value, NULL, TERM_TAB);
+ len -= date_len;
+
+ return find_name_common(line, def, p_value, line + len, 0);
+}
+
static int count_slashes(const char *cp)
{
int cnt = 0;
@@ -519,7 +714,7 @@ static int guess_p_value(const char *nameline)
if (is_dev_null(nameline))
return -1;
- name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
+ name = find_name_traditional(nameline, NULL, 0);
if (!name)
return -1;
cp = strchr(name, '/');
@@ -560,8 +755,8 @@ static int has_epoch_timestamp(const char *nameline)
" "
"[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
" "
- "([-+][0-2][0-9][0-5][0-9])\n";
- const char *timestamp = NULL, *cp;
+ "([-+][0-2][0-9]:?[0-5][0-9])\n";
+ const char *timestamp = NULL, *cp, *colon;
static regex_t *stamp;
regmatch_t m[10];
int zoneoffset;
@@ -591,8 +786,11 @@ static int has_epoch_timestamp(const char *nameline)
return 0;
}
- zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
- zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+ zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
+ if (*colon == ':')
+ zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
+ else
+ zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
if (timestamp[m[3].rm_so] == '-')
zoneoffset = -zoneoffset;
@@ -638,16 +836,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc
if (is_dev_null(first)) {
patch->is_new = 1;
patch->is_delete = 0;
- name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
+ name = find_name_traditional(second, NULL, p_value);
patch->new_name = name;
} else if (is_dev_null(second)) {
patch->is_new = 0;
patch->is_delete = 1;
- name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+ name = find_name_traditional(first, NULL, p_value);
patch->old_name = name;
} else {
- name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
- name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
+ name = find_name_traditional(first, NULL, p_value);
+ name = find_name_traditional(second, name, p_value);
if (has_epoch_timestamp(first)) {
patch->is_new = 1;
patch->is_delete = 0;
@@ -746,28 +944,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch)
static int gitdiff_copysrc(const char *line, struct patch *patch)
{
patch->is_copy = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
+ patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_copydst(const char *line, struct patch *patch)
{
patch->is_copy = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
+ patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamesrc(const char *line, struct patch *patch)
{
patch->is_rename = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
+ patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamedst(const char *line, struct patch *patch)
{
patch->is_rename = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
+ patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -852,7 +1050,7 @@ static char *git_header_name(char *line, int llen)
{
const char *name;
const char *second = NULL;
- size_t len;
+ size_t len, line_len;
line += strlen("diff --git ");
llen -= strlen("diff --git ");
@@ -952,6 +1150,10 @@ static char *git_header_name(char *line, int llen)
* Accept a name only if it shows up twice, exactly the same
* form.
*/
+ second = strchr(name, '\n');
+ if (!second)
+ return NULL;
+ line_len = second - name;
for (len = 0 ; ; len++) {
switch (name[len]) {
default:
@@ -959,15 +1161,11 @@ static char *git_header_name(char *line, int llen)
case '\n':
return NULL;
case '\t': case ' ':
- second = name+len;
- for (;;) {
- char c = *second++;
- if (c == '\n')
- return NULL;
- if (c == '/')
- break;
- }
- if (second[len] == '\n' && !memcmp(name, second, len)) {
+ second = stop_at_slash(name + len, line_len - len);
+ if (!second)
+ return NULL;
+ second++;
+ if (second[len] == '\n' && !strncmp(name, second, len)) {
return xmemdupz(name, len);
}
}
@@ -1436,7 +1634,7 @@ static inline int metadata_changes(struct patch *patch)
static char *inflate_it(const void *data, unsigned long size,
unsigned long inflated_size)
{
- z_stream stream;
+ git_zstream stream;
void *out;
int st;
@@ -1889,7 +2087,8 @@ static int match_fragment(struct image *img,
/* Quick hash check */
for (i = 0; i < preimage_limit; i++)
- if (preimage->line[i].hash != img->line[try_lno + i].hash)
+ if ((img->line[try_lno + i].flag & LINE_PATCHED) ||
+ (preimage->line[i].hash != img->line[try_lno + i].hash))
return 0;
if (preimage_limit == preimage->nr) {
@@ -2232,11 +2431,15 @@ static void update_image(struct image *img,
memcpy(img->line + applied_pos,
postimage->line,
postimage->nr * sizeof(*img->line));
+ if (!allow_overlap)
+ for (i = 0; i < postimage->nr; i++)
+ img->line[applied_pos + i].flag |= LINE_PATCHED;
img->nr = nr;
}
static int apply_one_fragment(struct image *img, struct fragment *frag,
- int inaccurate_eof, unsigned ws_rule)
+ int inaccurate_eof, unsigned ws_rule,
+ int nth_fragment)
{
int match_beginning, match_end;
const char *patch = frag->patch;
@@ -2442,6 +2645,15 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
apply = 0;
}
+ if (apply_verbosely && applied_pos != pos) {
+ int offset = applied_pos - pos;
+ if (apply_in_reverse)
+ offset = 0 - offset;
+ fprintf(stderr,
+ "Hunk #%d succeeded at %d (offset %d lines).\n",
+ nth_fragment, applied_pos + 1, offset);
+ }
+
/*
* Warn if it was necessary to reduce the number
* of context lines.
@@ -2472,6 +2684,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
unsigned long len;
void *dst;
+ if (!fragment)
+ return error("missing binary patch data for '%s'",
+ patch->new_name ?
+ patch->new_name :
+ patch->old_name);
+
/* Binary patch is irreversible without the optional second hunk */
if (apply_in_reverse) {
if (!fragment->next)
@@ -2583,12 +2801,14 @@ static int apply_fragments(struct image *img, struct patch *patch)
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned ws_rule = patch->ws_rule;
unsigned inaccurate_eof = patch->inaccurate_eof;
+ int nth = 0;
if (patch->is_binary)
return apply_binary(img, patch);
while (frag) {
- if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
+ nth++;
+ if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
error("patch failed: %s:%ld", name, frag->oldpos);
if (!apply_with_reject)
return -1;
@@ -2979,8 +3199,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
else if (get_sha1(patch->old_sha1_prefix, sha1))
/* git diff has no index line for mode/type changes */
if (!patch->lines_added && !patch->lines_deleted) {
- if (get_current_sha1(patch->new_name, sha1) ||
- get_current_sha1(patch->old_name, sha1))
+ if (get_current_sha1(patch->old_name, sha1))
die("mode change for %s, which is not "
"in current HEAD", name);
sha1_ptr = sha1;
@@ -3607,11 +3826,11 @@ static int option_parse_directory(const struct option *opt,
return 0;
}
-int cmd_apply(int argc, const char **argv, const char *unused_prefix)
+int cmd_apply(int argc, const char **argv, const char *prefix_)
{
int i;
int errs = 0;
- int is_not_gitdir;
+ int is_not_gitdir = !startup_info->have_repository;
int binary;
int force_apply = 0;
@@ -3671,7 +3890,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
"don't expect at least one line of context"),
OPT_BOOLEAN(0, "reject", &apply_with_reject,
"leave the rejected hunks in corresponding *.rej files"),
- OPT__VERBOSE(&apply_verbosely),
+ OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
+ "allow overlapping hunks"),
+ OPT__VERBOSE(&apply_verbosely, "be verbose"),
OPT_BIT(0, "inaccurate-eof", &options,
"tolerate incorrectly detected missing new-line at the end of file",
INACCURATE_EOF),
@@ -3684,7 +3905,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
OPT_END()
};
- prefix = setup_git_directory_gently(&is_not_gitdir);
+ prefix = prefix_;
prefix_length = prefix ? strlen(prefix) : 0;
git_config(git_apply_config, NULL);
if (apply_default_whitespace)
diff --git a/builtin/archive.c b/builtin/archive.c
index 6a887f5..883c009 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -14,17 +14,18 @@ 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);
+ die_errno(_("could not create archive file '%s'"), output_file);
if (output_fd != 1) {
if (dup2(output_fd, 1) < 0)
- die_errno("could not redirect output");
+ die_errno(_("could not redirect output"));
else
close(output_fd);
}
}
static int run_remote_archiver(int argc, const char **argv,
- const char *remote, const char *exec)
+ const char *remote, const char *exec,
+ const char *name_hint)
{
char buf[LARGE_PACKET_MAX];
int fd[2], i, len, rv;
@@ -33,28 +34,39 @@ static int run_remote_archiver(int argc, const char **argv,
_remote = remote_get(remote);
if (!_remote->url[0])
- die("git archive: Remote with no URL");
+ die(_("git archive: Remote with no URL"));
transport = transport_get(_remote, _remote->url[0]);
transport_connect(transport, "git-upload-archive", exec, fd);
+ /*
+ * Inject a fake --format field at the beginning of the
+ * arguments, with the format inferred from our output
+ * filename. This way explicit --format options can override
+ * it.
+ */
+ if (name_hint) {
+ const char *format = archive_format_from_filename(name_hint);
+ if (format)
+ packet_write(fd[1], "argument --format=%s\n", format);
+ }
for (i = 1; i < argc; i++)
packet_write(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
len = packet_read_line(fd[0], buf, sizeof(buf));
if (!len)
- die("git archive: expected ACK/NAK, got EOF");
+ die(_("git archive: expected ACK/NAK, got EOF"));
if (buf[len-1] == '\n')
buf[--len] = 0;
if (strcmp(buf, "ACK")) {
if (len > 5 && !prefixcmp(buf, "NACK "))
- die("git archive: NACK %s", buf + 5);
- die("git archive: protocol error");
+ die(_("git archive: NACK %s"), buf + 5);
+ die(_("git archive: protocol error"));
}
len = packet_read_line(fd[0], buf, sizeof(buf));
if (len)
- die("git archive: expected a flush");
+ die(_("git archive: expected a flush"));
/* Now, start reading from fd[0] and spit it out to stdout */
rv = recv_sideband("archive", fd[0], 1);
@@ -63,17 +75,6 @@ static int run_remote_archiver(int argc, const char **argv,
return !!rv;
}
-static const char *format_from_name(const char *filename)
-{
- const char *ext = strrchr(filename, '.');
- if (!ext)
- return NULL;
- ext++;
- if (!strcasecmp(ext, "zip"))
- return "--format=zip";
- return NULL;
-}
-
#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
PARSE_OPT_KEEP_ARGV0 | \
PARSE_OPT_KEEP_UNKNOWN | \
@@ -84,7 +85,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
const char *exec = "git-upload-archive";
const char *output = NULL;
const char *remote = NULL;
- const char *format_option = NULL;
struct option local_opts[] = {
OPT_STRING('o', "output", &output, "file",
"write the archive to this file"),
@@ -98,32 +98,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, local_opts, NULL,
PARSE_OPT_KEEP_ALL);
- if (output) {
+ if (output)
create_output_file(output);
- format_option = format_from_name(output);
- }
-
- /*
- * We have enough room in argv[] to muck it in place, because
- * --output must have been given on the original command line
- * if we get to this point, and parse_options() must have eaten
- * it, i.e. we can add back one element to the array.
- *
- * We add a fake --format option at the beginning, with the
- * format inferred from our output filename. This way explicit
- * --format options can override it, and the fake option is
- * inserted before any "--" that might have been given.
- */
- if (format_option) {
- memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
- argv[1] = format_option;
- argv[++argc] = NULL;
- }
if (remote)
- return run_remote_archiver(argc, argv, remote, exec);
+ return run_remote_archiver(argc, argv, remote, exec, output);
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
- return write_archive(argc, argv, prefix, 1);
+ return write_archive(argc, argv, prefix, 1, output, 0);
}
diff --git a/builtin/blame.c b/builtin/blame.c
index 01e62fd..26a5d42 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -41,6 +41,7 @@ static int reverse;
static int blank_boundary;
static int incremental;
static int xdl_opts;
+static int abbrev = -1;
static enum date_mode blame_date_mode = DATE_ISO8601;
static size_t blame_date_width;
@@ -83,6 +84,7 @@ struct origin {
struct commit *commit;
mmfile_t file;
unsigned char blob_sha1[20];
+ unsigned mode;
char path[FLEX_ARRAY];
};
@@ -92,6 +94,7 @@ struct origin {
* Return 1 if the conversion succeeds, 0 otherwise.
*/
int textconv_object(const char *path,
+ unsigned mode,
const unsigned char *sha1,
char **buf,
unsigned long *buf_size)
@@ -100,7 +103,7 @@ int textconv_object(const char *path,
struct userdiff_driver *textconv;
df = alloc_filespec(path);
- fill_filespec(df, sha1, S_IFREG | 0664);
+ fill_filespec(df, sha1, mode);
textconv = get_textconv(df);
if (!textconv) {
free_filespec(df);
@@ -125,7 +128,7 @@ static void fill_origin_blob(struct diff_options *opt,
num_read_blob++;
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size))
+ textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
;
else
file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
@@ -313,21 +316,23 @@ static struct origin *get_origin(struct scoreboard *sb,
* for an origin is also used to pass the blame for the entire file to
* the parent to detect the case where a child's blob is identical to
* that of its parent's.
+ *
+ * This also fills origin->mode for corresponding tree path.
*/
-static int fill_blob_sha1(struct origin *origin)
+static int fill_blob_sha1_and_mode(struct origin *origin)
{
- unsigned mode;
if (!is_null_sha1(origin->blob_sha1))
return 0;
if (get_tree_entry(origin->commit->object.sha1,
origin->path,
- origin->blob_sha1, &mode))
+ origin->blob_sha1, &origin->mode))
goto error_out;
if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
goto error_out;
return 0;
error_out:
hashclr(origin->blob_sha1);
+ origin->mode = S_IFINVALID;
return -1;
}
@@ -360,12 +365,14 @@ static struct origin *find_origin(struct scoreboard *sb,
/*
* If the origin was newly created (i.e. get_origin
* would call make_origin if none is found in the
- * scoreboard), it does not know the blob_sha1,
+ * scoreboard), it does not know the blob_sha1/mode,
* so copy it. Otherwise porigin was in the
- * scoreboard and already knows blob_sha1.
+ * scoreboard and already knows blob_sha1/mode.
*/
- if (porigin->refcnt == 1)
+ if (porigin->refcnt == 1) {
hashcpy(porigin->blob_sha1, cached->blob_sha1);
+ porigin->mode = cached->mode;
+ }
return porigin;
}
/* otherwise it was not very useful; free it */
@@ -400,6 +407,7 @@ static struct origin *find_origin(struct scoreboard *sb,
/* The path is the same as parent */
porigin = get_origin(sb, parent, origin->path);
hashcpy(porigin->blob_sha1, origin->blob_sha1);
+ porigin->mode = origin->mode;
} else {
/*
* Since origin->path is a pathspec, if the parent
@@ -425,6 +433,7 @@ static struct origin *find_origin(struct scoreboard *sb,
case 'M':
porigin = get_origin(sb, parent, origin->path);
hashcpy(porigin->blob_sha1, p->one->sha1);
+ porigin->mode = p->one->mode;
break;
case 'A':
case 'T':
@@ -444,6 +453,7 @@ static struct origin *find_origin(struct scoreboard *sb,
cached = make_origin(porigin->commit, porigin->path);
hashcpy(cached->blob_sha1, porigin->blob_sha1);
+ cached->mode = porigin->mode;
parent->util = cached;
}
return porigin;
@@ -486,6 +496,7 @@ static struct origin *find_rename(struct scoreboard *sb,
!strcmp(p->two->path, origin->path)) {
porigin = get_origin(sb, parent, p->one->path);
hashcpy(porigin->blob_sha1, p->one->sha1);
+ porigin->mode = p->one->mode;
break;
}
}
@@ -1099,6 +1110,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
norigin = get_origin(sb, parent, p->one->path);
hashcpy(norigin->blob_sha1, p->one->sha1);
+ norigin->mode = p->one->mode;
fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
if (!file_p.ptr)
continue;
@@ -1301,8 +1313,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
/*
* Information on commits, used for output.
*/
-struct commit_info
-{
+struct commit_info {
const char *author;
const char *author_mail;
unsigned long author_time;
@@ -1367,7 +1378,7 @@ static void get_ac_line(const char *inbuf, const char *what,
timepos = tmp;
*tmp = 0;
- while (person < tmp && *tmp != ' ')
+ while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
tmp--;
if (tmp <= person)
return;
@@ -1407,7 +1418,8 @@ static void get_commit_info(struct commit *commit,
int detailed)
{
int len;
- char *tmp, *endp, *reencoded, *message;
+ const char *subject;
+ char *reencoded, *message;
static char author_name[1024];
static char author_mail[1024];
static char committer_name[1024];
@@ -1449,22 +1461,13 @@ static void get_commit_info(struct commit *commit,
&ret->committer_time, &ret->committer_tz);
ret->summary = summary_buf;
- tmp = strstr(message, "\n\n");
- if (!tmp) {
- error_out:
+ len = find_commit_subject(message, &subject);
+ if (len && len < sizeof(summary_buf)) {
+ memcpy(summary_buf, subject, len);
+ summary_buf[len] = 0;
+ } else {
sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
- free(reencoded);
- return;
}
- tmp += 2;
- endp = strchr(tmp, '\n');
- if (!endp)
- endp = tmp + strlen(tmp);
- len = endp - tmp;
- if (len >= sizeof(summary_buf) || len == 0)
- goto error_out;
- memcpy(summary_buf, tmp, len);
- summary_buf[len] = 0;
free(reencoded);
}
@@ -1481,13 +1484,14 @@ static void write_filename_info(const char *path)
/*
* Porcelain/Incremental format wants to show a lot of details per
* commit. Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.
+ * the first time each commit appears in the output (unless the
+ * user has specifically asked for us to repeat).
*/
-static int emit_one_suspect_detail(struct origin *suspect)
+static int emit_one_suspect_detail(struct origin *suspect, int repeat)
{
struct commit_info ci;
- if (suspect->commit->object.flags & METAINFO_SHOWN)
+ if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
return 0;
suspect->commit->object.flags |= METAINFO_SHOWN;
@@ -1526,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent)
printf("%s %d %d %d\n",
sha1_to_hex(suspect->commit->object.sha1),
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
- emit_one_suspect_detail(suspect);
+ emit_one_suspect_detail(suspect, 0);
write_filename_info(suspect->path);
maybe_flush_or_die(stdout, "stdout");
}
@@ -1614,9 +1618,20 @@ static const char *format_time(unsigned long time, const char *tz_str,
#define OUTPUT_SHOW_NUMBER 040
#define OUTPUT_SHOW_SCORE 0100
#define OUTPUT_NO_AUTHOR 0200
+#define OUTPUT_SHOW_EMAIL 0400
+#define OUTPUT_LINE_PORCELAIN 01000
-static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+static void emit_porcelain_details(struct origin *suspect, int repeat)
{
+ if (emit_one_suspect_detail(suspect, repeat) ||
+ (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
+ write_filename_info(suspect->path);
+}
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
+ int opt)
+{
+ int repeat = opt & OUTPUT_LINE_PORCELAIN;
int cnt;
const char *cp;
struct origin *suspect = ent->suspect;
@@ -1629,17 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
ent->s_lno + 1,
ent->lno + 1,
ent->num_lines);
- if (emit_one_suspect_detail(suspect) ||
- (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
- write_filename_info(suspect->path);
+ emit_porcelain_details(suspect, repeat);
cp = nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
- if (cnt)
+ if (cnt) {
printf("%s %d %d\n", hex,
ent->s_lno + 1 + cnt,
ent->lno + 1 + cnt);
+ if (repeat)
+ emit_porcelain_details(suspect, 1);
+ }
putchar('\t');
do {
ch = *cp++;
@@ -1667,7 +1683,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
cp = nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
- int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+ int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev;
if (suspect->commit->object.flags & UNINTERESTING) {
if (blank_boundary)
@@ -1679,12 +1695,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
}
printf("%.*s", length, hex);
- if (opt & OUTPUT_ANNOTATE_COMPAT)
- printf("\t(%10s\t%10s\t%d)", ci.author,
+ if (opt & OUTPUT_ANNOTATE_COMPAT) {
+ const char *name;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ printf("\t(%10s\t%10s\t%d)", name,
format_time(ci.author_time, ci.author_tz,
show_raw_time),
ent->lno + 1 + cnt);
- else {
+ } else {
if (opt & OUTPUT_SHOW_SCORE)
printf(" %*d %02d",
max_score_digits, ent->score,
@@ -1697,9 +1718,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
ent->s_lno + 1 + cnt);
if (!(opt & OUTPUT_NO_AUTHOR)) {
- int pad = longest_author - utf8_strwidth(ci.author);
+ const char *name;
+ int pad;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s",
- ci.author, pad, "",
+ name, pad, "",
format_time(ci.author_time,
ci.author_tz,
show_raw_time));
@@ -1741,7 +1768,7 @@ static void output(struct scoreboard *sb, int option)
for (ent = sb->ent; ent; ent = ent->next) {
if (option & OUTPUT_PORCELAIN)
- emit_porcelain(sb, ent);
+ emit_porcelain(sb, ent, option);
else {
emit_other(sb, ent, option);
}
@@ -1837,7 +1864,10 @@ static void find_alignment(struct scoreboard *sb, int *option)
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- num = utf8_strwidth(ci.author);
+ if (*option & OUTPUT_SHOW_EMAIL)
+ num = utf8_strwidth(ci.author_mail);
+ else
+ num = utf8_strwidth(ci.author);
if (longest_author < num)
longest_author = num;
}
@@ -2083,7 +2113,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(read_from, null_sha1, &buf.buf, &buf_len))
+ textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
buf.len = buf_len;
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
@@ -2282,16 +2312,19 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+ OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+ OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+ OPT__ABBREV(&abbrev),
OPT_END()
};
@@ -2306,8 +2339,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
save_commit_buffer = 0;
dashdash_pos = 0;
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
+ parse_options_start(&ctx, argc, argv, prefix, options,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, blame_opt_usage)) {
case PARSE_OPT_HELP:
@@ -2327,6 +2360,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
parse_done:
argc = parse_options_end(&ctx);
+ if (abbrev == -1)
+ abbrev = default_abbrev;
+ /* one more abbrev length is needed for the boundary commit */
+ abbrev++;
+
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
@@ -2376,11 +2414,11 @@ parse_done:
*
* The remaining are:
*
- * (1) if dashdash_pos != 0, its either
+ * (1) if dashdash_pos != 0, it is either
* "blame [revisions] -- <path>" or
* "blame -- <path> <rev>"
*
- * (2) otherwise, its one of the two:
+ * (2) otherwise, it is one of the two:
* "blame [revisions] <path>"
* "blame <path> <rev>"
*
@@ -2463,11 +2501,11 @@ parse_done:
}
else {
o = get_origin(&sb, sb.final, path);
- if (fill_blob_sha1(o))
+ if (fill_blob_sha1_and_mode(o))
die("no such path %s in %s", path, final_commit_name);
if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
- textconv_object(path, o->blob_sha1, (char **) &sb.final_buf,
+ textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
diff --git a/builtin/branch.c b/builtin/branch.c
index 87976f0..3142daa 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -19,7 +19,7 @@
static const char * const builtin_branch_usage[] = {
"git branch [options] [-r | -a] [--merged | --no-merged]",
"git branch [options] [-l] [-f] <branchname> [<start-point>]",
- "git branch [options] [-r] (-d | -D) <branchname>",
+ "git branch [options] [-r] (-d | -D) <branchname>...",
"git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
NULL
};
@@ -133,12 +133,12 @@ static int branch_merged(int kind, const char *name,
if ((head_rev != reference_rev) &&
in_merge_bases(rev, &head_rev, 1) != merged) {
if (merged)
- warning("deleting branch '%s' that has been merged to\n"
- " '%s', but it is not yet merged to HEAD.",
+ warning(_("deleting branch '%s' that has been merged to\n"
+ " '%s', but not yet merged to HEAD."),
name, reference_name);
else
- warning("not deleting branch '%s' that is not yet merged to\n"
- " '%s', even though it is merged to HEAD.",
+ warning(_("not deleting branch '%s' that is not yet merged to\n"
+ " '%s', even though it is merged to HEAD."),
name, reference_name);
}
return merged;
@@ -157,7 +157,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
switch (kinds) {
case REF_REMOTE_BRANCH:
fmt = "refs/remotes/%s";
- remote = "remote ";
+ /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
+ remote = _("remote ");
force = 1;
break;
case REF_LOCAL_BRANCH:
@@ -165,19 +166,19 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
remote = "";
break;
default:
- die("cannot use -a with -d");
+ die(_("cannot use -a with -d"));
}
if (!force) {
head_rev = lookup_commit_reference(head_sha1);
if (!head_rev)
- die("Couldn't look up commit object for HEAD");
+ die(_("Couldn't look up commit object for HEAD"));
}
for (i = 0; i < argc; i++, strbuf_release(&bname)) {
strbuf_branchname(&bname, argv[i]);
if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
- error("Cannot delete the branch '%s' "
- "which you are currently on.", bname.buf);
+ error(_("Cannot delete the branch '%s' "
+ "which you are currently on."), bname.buf);
ret = 1;
continue;
}
@@ -186,7 +187,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
name = xstrdup(mkpath(fmt, bname.buf));
if (!resolve_ref(name, sha1, 1, NULL)) {
- error("%sbranch '%s' not found.",
+ error(_("%sbranch '%s' not found."),
remote, bname.buf);
ret = 1;
continue;
@@ -194,31 +195,31 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
rev = lookup_commit_reference(sha1);
if (!rev) {
- error("Couldn't look up commit object for '%s'", name);
+ error(_("Couldn't look up commit object for '%s'"), name);
ret = 1;
continue;
}
if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
- error("The branch '%s' is not fully merged.\n"
+ error(_("The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, "
- "run 'git branch -D %s'.", bname.buf, bname.buf);
+ "run 'git branch -D %s'."), bname.buf, bname.buf);
ret = 1;
continue;
}
if (delete_ref(name, sha1, 0)) {
- error("Error deleting %sbranch '%s'", remote,
+ error(_("Error deleting %sbranch '%s'"), remote,
bname.buf);
ret = 1;
} else {
struct strbuf buf = STRBUF_INIT;
- printf("Deleted %sbranch %s (was %s).\n", remote,
+ printf(_("Deleted %sbranch %s (was %s).\n"), remote,
bname.buf,
find_unique_abbrev(sha1, DEFAULT_ABBREV));
strbuf_addf(&buf, "branch.%s", bname.buf);
if (git_config_rename_section(buf.buf, NULL) < 0)
- warning("Update of config-file failed");
+ warning(_("Update of config-file failed"));
strbuf_release(&buf);
}
}
@@ -300,7 +301,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
commit = lookup_commit_reference_gently(sha1, 1);
if (!commit) {
- cb->ret = error("branch '%s' does not point at a commit", refname);
+ cb->ret = error(_("branch '%s' does not point at a commit"), refname);
return 0;
}
@@ -313,12 +314,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
(struct object *)commit, refname);
}
- /* Resize buffer */
- if (ref_list->index >= ref_list->alloc) {
- ref_list->alloc = alloc_nr(ref_list->alloc);
- ref_list->list = xrealloc(ref_list->list,
- ref_list->alloc * sizeof(struct ref_item));
- }
+ ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
/* Record the new item */
newitem = &(ref_list->list[ref_list->index++]);
@@ -377,11 +373,11 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
strbuf_addf(stat, "%s: ",
shorten_unambiguous_ref(branch->merge[0]->dst, 0));
if (!ours)
- strbuf_addf(stat, "behind %d] ", theirs);
+ strbuf_addf(stat, _("behind %d] "), theirs);
else if (!theirs)
- strbuf_addf(stat, "ahead %d] ", ours);
+ strbuf_addf(stat, _("ahead %d] "), ours);
else
- strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
+ strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs);
}
static int matches_merge_filter(struct commit *commit)
@@ -395,6 +391,28 @@ static int matches_merge_filter(struct commit *commit)
return (is_merged == (merge_filter == SHOW_MERGED));
}
+static void add_verbose_info(struct strbuf *out, struct ref_item *item,
+ int verbose, int abbrev)
+{
+ struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
+ const char *sub = " **** invalid ref ****";
+ struct commit *commit = item->commit;
+
+ if (commit && !parse_commit(commit)) {
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
+ sub = subject.buf;
+ }
+
+ if (item->kind == REF_LOCAL_BRANCH)
+ fill_tracking_info(&stat, item->name, verbose > 1);
+
+ strbuf_addf(out, " %s %s%s",
+ find_unique_abbrev(item->commit->object.sha1, abbrev),
+ stat.buf, sub);
+ strbuf_release(&stat);
+ strbuf_release(&subject);
+}
+
static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
int abbrev, int current, char *prefix)
{
@@ -435,27 +453,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
if (item->dest)
strbuf_addf(&out, " -> %s", item->dest);
- else if (verbose) {
- struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
- const char *sub = " **** invalid ref ****";
-
- commit = item->commit;
- if (commit && !parse_commit(commit)) {
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &subject, &ctx);
- sub = subject.buf;
- }
-
- if (item->kind == REF_LOCAL_BRANCH)
- fill_tracking_info(&stat, item->name, verbose > 1);
-
- strbuf_addf(&out, " %s %s%s",
- find_unique_abbrev(item->commit->object.sha1, abbrev),
- stat.buf, sub);
- strbuf_release(&stat);
- strbuf_release(&subject);
- }
+ else if (verbose)
+ /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
+ add_verbose_info(&out, item, verbose, abbrev);
printf("%s\n", out.buf);
strbuf_release(&name);
strbuf_release(&out);
@@ -480,7 +480,7 @@ static void show_detached(struct ref_list *ref_list)
if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
struct ref_item item;
- item.name = xstrdup("(no branch)");
+ item.name = xstrdup(_("(no branch)"));
item.len = strlen(item.name);
item.kind = REF_LOCAL_BRANCH;
item.dest = NULL;
@@ -540,7 +540,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
free_ref_list(&ref_list);
if (cb.ret)
- error("some refs could not be read");
+ error(_("some refs could not be read"));
return cb.ret;
}
@@ -553,7 +553,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
int recovery = 0;
if (!oldname)
- die("cannot rename the current branch while not on any.");
+ die(_("cannot rename the current branch while not on any."));
if (strbuf_check_branch_ref(&oldref, oldname)) {
/*
@@ -563,35 +563,35 @@ static void rename_branch(const char *oldname, const char *newname, int force)
if (resolve_ref(oldref.buf, sha1, 1, NULL))
recovery = 1;
else
- die("Invalid branch name: '%s'", oldname);
+ die(_("Invalid branch name: '%s'"), oldname);
}
if (strbuf_check_branch_ref(&newref, newname))
- die("Invalid branch name: '%s'", newname);
+ die(_("Invalid branch name: '%s'"), newname);
if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
- die("A branch named '%s' already exists.", newref.buf + 11);
+ die(_("A branch named '%s' already exists."), newref.buf + 11);
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
- die("Branch rename failed");
+ die(_("Branch rename failed"));
strbuf_release(&logmsg);
if (recovery)
- warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
+ warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
/* no need to pass logmsg here as HEAD didn't really move */
if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
- die("Branch renamed to %s, but HEAD is not updated!", newname);
+ die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
strbuf_release(&oldref);
strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
strbuf_release(&newref);
if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
- die("Branch is renamed, but update of config-file failed");
+ die(_("Branch is renamed, but update of config-file failed"));
strbuf_release(&oldsection);
strbuf_release(&newsection);
}
@@ -606,7 +606,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
if (!arg)
arg = "HEAD";
if (get_sha1(arg, merge_filter_ref))
- die("malformed object name %s", arg);
+ die(_("malformed object name %s"), arg);
return 0;
}
@@ -621,7 +621,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT_GROUP("Generic options"),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose,
+ "show hash and subject, give twice for upstream branch"),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
@@ -651,7 +652,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
- OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
+ OPT__FORCE(&force_create, "force creation (when already exists)"),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
"commit", "print only not merged branches",
@@ -667,6 +668,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_END(),
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_branch_usage, options);
+
git_config(git_branch_config, NULL);
if (branch_use_color == -1)
@@ -676,13 +680,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
head = resolve_ref("HEAD", head_sha1, 0, NULL);
if (!head)
- die("Failed to resolve HEAD as a valid ref.");
+ die(_("Failed to resolve HEAD as a valid ref."));
head = xstrdup(head);
if (!strcmp(head, "HEAD")) {
detached = 1;
} else {
if (prefixcmp(head, "refs/heads/"))
- die("HEAD not found below refs/heads!");
+ die(_("HEAD not found below refs/heads!"));
head += 11;
}
hashcpy(merge_filter_ref, head_sha1);
@@ -702,7 +706,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
rename_branch(argv[0], argv[1], rename > 1);
else if (argc <= 2) {
if (kinds != REF_LOCAL_BRANCH)
- die("-a and -r options to 'git branch' do not make sense with a branch name");
+ die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, track);
} else
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 2006cc5..81046a9 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -12,13 +12,12 @@
static const char builtin_bundle_usage[] =
"git bundle create <file> <git-rev-list args>\n"
" or: git bundle verify <file>\n"
- " or: git bundle list-heads <file> [refname...]\n"
- " or: git bundle unbundle <file> [refname...]";
+ " or: git bundle list-heads <file> [<refname>...]\n"
+ " or: git bundle unbundle <file> [<refname>...]";
int cmd_bundle(int argc, const char **argv, const char *prefix)
{
struct bundle_header header;
- int nongit;
const char *cmd, *bundle_file;
int bundle_fd = -1;
char buffer[PATH_MAX];
@@ -31,7 +30,6 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
argc -= 2;
argv += 2;
- prefix = setup_git_directory_gently(&nongit);
if (prefix && bundle_file[0] != '/') {
snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
bundle_file = buffer;
@@ -46,7 +44,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
close(bundle_fd);
if (verify_bundle(&header, 1))
return 1;
- fprintf(stderr, "%s is okay\n", bundle_file);
+ fprintf(stderr, _("%s is okay\n"), bundle_file);
return 0;
}
if (!strcmp(cmd, "list-heads")) {
@@ -54,12 +52,12 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
return !!list_bundle_refs(&header, argc, argv);
}
if (!strcmp(cmd, "create")) {
- if (nongit)
- die("Need a repository to create a bundle.");
+ if (!startup_info->have_repository)
+ die(_("Need a repository to create a bundle."));
return !!create_bundle(&header, bundle_file, argc, argv);
} else if (!strcmp(cmd, "unbundle")) {
- if (nongit)
- die("Need a repository to unbundle.");
+ if (!startup_info->have_repository)
+ die(_("Need a repository to unbundle."));
return !!unbundle(&header, bundle_fd) ||
list_bundle_refs(&header, argc, argv);
} else
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 76ec3fe..07bd984 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -143,7 +143,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);
- if (!textconv_object(obj_context.path, sha1, &buf, &size))
+ if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size))
die("git cat-file --textconv: unable to run textconv on %s",
obj_name);
break;
@@ -187,6 +187,8 @@ static int batch_one_object(const char *obj_name, int print_contents)
if (type <= 0) {
printf("%s missing\n", obj_name);
fflush(stdout);
+ if (print_contents == BATCH)
+ free(contents);
return 0;
}
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index b106c65..ae3f281 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -33,28 +33,38 @@ static void collapse_slashes(char *dst, const char *src)
*dst = '\0';
}
+static int check_ref_format_branch(const char *arg)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int nongit;
+
+ setup_git_directory_gently(&nongit);
+ if (strbuf_check_branch_ref(&sb, arg))
+ die("'%s' is not a valid branch name", arg);
+ printf("%s\n", sb.buf + 11);
+ return 0;
+}
+
+static int check_ref_format_print(const char *arg)
+{
+ char *refname = xmalloc(strlen(arg) + 1);
+
+ if (check_ref_format(arg))
+ return 1;
+ collapse_slashes(refname, arg);
+ printf("%s\n", refname);
+ return 0;
+}
+
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
{
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_check_ref_format_usage);
- if (argc == 3 && !strcmp(argv[1], "--branch")) {
- struct strbuf sb = STRBUF_INIT;
-
- if (strbuf_check_branch_ref(&sb, argv[2]))
- die("'%s' is not a valid branch name", argv[2]);
- printf("%s\n", sb.buf + 11);
- exit(0);
- }
- if (argc == 3 && !strcmp(argv[1], "--print")) {
- char *refname = xmalloc(strlen(argv[2]) + 1);
-
- if (check_ref_format(argv[2]))
- exit(1);
- collapse_slashes(refname, argv[2]);
- printf("%s\n", refname);
- exit(0);
- }
+ if (argc == 3 && !strcmp(argv[1], "--branch"))
+ return check_ref_format_branch(argv[2]);
+ if (argc == 3 && !strcmp(argv[1], "--print"))
+ return check_ref_format_print(argv[2]);
if (argc != 2)
usage(builtin_check_ref_format_usage);
return !!check_ref_format(argv[1]);
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index a7a5ee1..f1fec24 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -155,7 +155,7 @@ static void checkout_all(const char *prefix, int prefix_length)
}
static const char * const builtin_checkout_index_usage[] = {
- "git checkout-index [options] [--] <file>...",
+ "git checkout-index [options] [--] [<file>...]",
NULL
};
@@ -217,9 +217,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
struct option builtin_checkout_index_options[] = {
OPT_BOOLEAN('a', "all", &all,
"checks out all files in the index"),
- OPT_BOOLEAN('f', "force", &force,
- "forces overwrite of existing files"),
- OPT__QUIET(&quiet),
+ OPT__FORCE(&force, "forces overwrite of existing files"),
+ OPT__QUIET(&quiet,
+ "no warning for existing files and files not in index"),
OPT_BOOLEAN('n', "no-create", &not_new,
"don't checkout new files"),
{ OPTION_CALLBACK, 'u', "index", &newfd, NULL,
@@ -241,6 +241,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_checkout_index_usage,
+ builtin_checkout_index_options);
git_config(git_default_config, NULL);
state.base_dir = "";
prefix_length = prefix ? strlen(prefix) : 0;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1994be9..d647a31 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -18,6 +18,7 @@
#include "xdiff-interface.h"
#include "ll-merge.h"
#include "resolve-undo.h"
+#include "submodule.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
@@ -29,13 +30,19 @@ struct checkout_opts {
int quiet;
int merge;
int force;
+ int force_detach;
int writeout_stage;
int writeout_error;
+ /* not set by parse_options */
+ int branch_exists;
+
const char *new_branch;
+ const char *new_branch_force;
const char *new_orphan_branch;
int new_branch_log;
enum branch_track track;
+ struct diff_options diff_options;
};
static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -72,7 +79,10 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
static int read_tree_some(struct tree *tree, const char **pathspec)
{
- read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+ struct pathspec ps;
+ init_pathspec(&ps, pathspec);
+ read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
+ free_pathspec(&ps);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
@@ -97,9 +107,10 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
return 0;
pos++;
}
- return error("path '%s' does not have %s version",
- ce->name,
- (stage == 2) ? "our" : "their");
+ if (stage == 2)
+ return error(_("path '%s' does not have our version"), ce->name);
+ else
+ return error(_("path '%s' does not have their version"), ce->name);
}
static int check_all_stages(struct cache_entry *ce, int pos)
@@ -110,7 +121,7 @@ static int check_all_stages(struct cache_entry *ce, int pos)
ce_stage(active_cache[pos+1]) != 2 ||
strcmp(active_cache[pos+2]->name, ce->name) ||
ce_stage(active_cache[pos+2]) != 3)
- return error("path '%s' does not have all three versions",
+ return error(_("path '%s' does not have all three versions"),
ce->name);
return 0;
}
@@ -124,9 +135,10 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos,
return checkout_entry(active_cache[pos], state, NULL);
pos++;
}
- return error("path '%s' does not have %s version",
- ce->name,
- (stage == 2) ? "our" : "their");
+ if (stage == 2)
+ return error(_("path '%s' does not have our version"), ce->name);
+ else
+ return error(_("path '%s' does not have their version"), ce->name);
}
static int checkout_merged(int pos, struct checkout *state)
@@ -144,20 +156,24 @@ static int checkout_merged(int pos, struct checkout *state)
ce_stage(active_cache[pos+1]) != 2 ||
strcmp(active_cache[pos+2]->name, path) ||
ce_stage(active_cache[pos+2]) != 3)
- return error("path '%s' does not have all 3 versions", path);
+ return error(_("path '%s' does not have all 3 versions"), path);
read_mmblob(&ancestor, active_cache[pos]->sha1);
read_mmblob(&ours, active_cache[pos+1]->sha1);
read_mmblob(&theirs, active_cache[pos+2]->sha1);
+ /*
+ * NEEDSWORK: re-create conflicts from merges with
+ * merge.renormalize set, too
+ */
status = ll_merge(&result_buf, path, &ancestor, "base",
- &ours, "ours", &theirs, "theirs", 0);
+ &ours, "ours", &theirs, "theirs", NULL);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
if (status < 0 || !result_buf.ptr) {
free(result_buf.ptr);
- return error("path '%s': cannot merge", path);
+ return error(_("path '%s': cannot merge"), path);
}
/*
@@ -174,12 +190,12 @@ static int checkout_merged(int pos, struct checkout *state)
*/
if (write_sha1_file(result_buf.ptr, result_buf.size,
blob_type, sha1))
- die("Unable to add merge result for '%s'", path);
+ die(_("Unable to add merge result for '%s'"), path);
ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
sha1,
path, 2, 0);
if (!ce)
- die("make_cache_entry failed for path '%s'", path);
+ die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL);
return status;
}
@@ -201,7 +217,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
newfd = hold_locked_index(lock_file, 1);
if (read_cache_preload(pathspec) < 0)
- return error("corrupt index file");
+ return error(_("corrupt index file"));
if (source_tree)
read_tree_some(source_tree, pathspec);
@@ -229,14 +245,14 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
if (!ce_stage(ce))
continue;
if (opts->force) {
- warning("path '%s' is unmerged", ce->name);
+ warning(_("path '%s' is unmerged"), ce->name);
} else if (stage) {
errs |= check_stage(stage, ce, pos);
} else if (opts->merge) {
errs |= check_all_stages(ce, pos);
} else {
errs = 1;
- error("path '%s' is unmerged", ce->name);
+ error(_("path '%s' is unmerged"), ce->name);
}
pos = skip_same_name(ce, pos) - 1;
}
@@ -265,7 +281,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock_file))
- die("unable to write new index file");
+ die(_("unable to write new index file"));
resolve_ref("HEAD", rev, 0, &flag);
head = lookup_commit_reference_gently(rev, 1);
@@ -274,25 +290,24 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
return errs;
}
-static void show_local_changes(struct object *head)
+static void show_local_changes(struct object *head, struct diff_options *opts)
{
struct rev_info rev;
/* I think we want full paths, even if we're in a subdirectory. */
init_revisions(&rev, NULL);
- rev.abbrev = 0;
+ rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
if (diff_setup_done(&rev.diffopt) < 0)
- die("diff_setup_done failed");
+ die(_("diff_setup_done failed"));
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
}
-static void describe_detached_head(char *msg, struct commit *commit)
+static void describe_detached_head(const char *msg, struct commit *commit)
{
struct strbuf sb = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
parse_commit(commit);
- pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
fprintf(stderr, "%s %s... %s\n", msg,
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
@@ -356,7 +371,7 @@ static int merge_working_tree(struct checkout_opts *opts,
int newfd = hold_locked_index(lock_file, 1);
if (read_cache_preload(NULL) < 0)
- return error("corrupt index file");
+ return error(_("corrupt index file"));
resolve_undo_clear();
if (opts->force) {
@@ -373,12 +388,12 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.src_index = &the_index;
topts.dst_index = &the_index;
- topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
+ setup_unpack_trees_porcelain(&topts, "checkout");
refresh_cache(REFRESH_QUIET);
if (unmerged_cache()) {
- error("you need to resolve your current index first");
+ error(_("you need to resolve your current index first"));
return 1;
}
@@ -394,7 +409,7 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.dir->exclude_per_dir = ".gitignore";
tree = parse_tree_indirect(old->commit ?
old->commit->object.sha1 :
- (unsigned char *)EMPTY_TREE_SHA1_BIN);
+ EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
tree = parse_tree_indirect(new->commit->object.sha1);
init_tree_desc(&trees[1], tree->buffer, tree->size);
@@ -433,6 +448,13 @@ static int merge_working_tree(struct checkout_opts *opts,
*/
add_files_to_cache(NULL, NULL, 0);
+ /*
+ * NEEDSWORK: carrying over local changes
+ * when branches have different end-of-line
+ * normalization (or clean+smudge rules) is
+ * a pain; plumb in an option to set
+ * o.renormalize?
+ */
init_merge_options(&o);
o.verbosity = 0;
work = write_tree_from_memory(&o);
@@ -453,10 +475,10 @@ static int merge_working_tree(struct checkout_opts *opts,
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock_file))
- die("unable to write new index file");
+ die(_("unable to write new index file"));
if (!opts->force && !opts->quiet)
- show_local_changes(&new->commit->object);
+ show_local_changes(&new->commit->object, &opts->diff_options);
return 0;
}
@@ -502,7 +524,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
temp = log_all_ref_updates;
log_all_ref_updates = 1;
if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
- fprintf(stderr, "Can not do reflog for '%s'\n",
+ fprintf(stderr, _("Can not do reflog for '%s'\n"),
opts->new_orphan_branch);
log_all_ref_updates = temp;
return;
@@ -511,7 +533,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
}
}
else
- create_branch(old->name, opts->new_branch, new->name, 0,
+ create_branch(old->name, opts->new_branch, new->name,
+ opts->new_branch_force ? 1 : 0,
opts->new_branch_log, opts->track);
new->name = opts->new_branch;
setup_branch_path(new);
@@ -523,16 +546,31 @@ static void update_refs_for_switch(struct checkout_opts *opts,
strbuf_addf(&msg, "checkout: moving from %s to %s",
old_desc ? old_desc : "(invalid)", new->name);
- if (new->path) {
+ if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
+ /* Nothing to do. */
+ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */
+ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+ REF_NODEREF, DIE_ON_ERR);
+ if (!opts->quiet) {
+ if (old->path && advice_detached_head)
+ detach_advice(old->path, new->name);
+ describe_detached_head(_("HEAD is now at"), new->commit);
+ }
+ } else if (new->path) { /* Switch branches. */
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
- if (old->path && !strcmp(new->path, old->path))
- fprintf(stderr, "Already on '%s'\n",
+ if (old->path && !strcmp(new->path, old->path)) {
+ fprintf(stderr, _("Already on '%s'\n"),
new->name);
- else
- fprintf(stderr, "Switched to%s branch '%s'\n",
- opts->new_branch ? " a new" : "",
+ } else if (opts->new_branch) {
+ if (opts->branch_exists)
+ fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
+ else
+ fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name);
+ } else {
+ fprintf(stderr, _("Switched to branch '%s'\n"),
new->name);
+ }
}
if (old->path && old->name) {
char log_file[PATH_MAX], ref_file[PATH_MAX];
@@ -542,21 +580,135 @@ static void update_refs_for_switch(struct checkout_opts *opts,
if (!file_exists(ref_file) && file_exists(log_file))
remove_path(log_file);
}
- } else if (strcmp(new->name, "HEAD")) {
- update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
- REF_NODEREF, DIE_ON_ERR);
- if (!opts->quiet) {
- if (old->path && advice_detached_head)
- detach_advice(old->path, new->name);
- describe_detached_head("HEAD is now at", new->commit);
- }
}
remove_branch_state();
strbuf_release(&msg);
- if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ if (!opts->quiet &&
+ (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
report_tracking(new);
}
+struct rev_list_args {
+ int argc;
+ int alloc;
+ const char **argv;
+};
+
+static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
+{
+ ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
+ args->argv[args->argc++] = s;
+}
+
+static int add_one_ref_to_rev_list_arg(const char *refname,
+ const unsigned char *sha1,
+ int flags,
+ void *cb_data)
+{
+ add_one_rev_list_arg(cb_data, refname);
+ return 0;
+}
+
+static int clear_commit_marks_from_one_ref(const char *refname,
+ const unsigned char *sha1,
+ int flags,
+ void *cb_data)
+{
+ struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ if (commit)
+ clear_commit_marks(commit, -1);
+ return 0;
+}
+
+static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
+{
+ parse_commit(commit);
+ strbuf_addstr(sb, " ");
+ strbuf_addstr(sb,
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ strbuf_addch(sb, ' ');
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
+ strbuf_addch(sb, '\n');
+}
+
+#define ORPHAN_CUTOFF 4
+static void suggest_reattach(struct commit *commit, struct rev_info *revs)
+{
+ struct commit *c, *last = NULL;
+ struct strbuf sb = STRBUF_INIT;
+ int lost = 0;
+ while ((c = get_revision(revs)) != NULL) {
+ if (lost < ORPHAN_CUTOFF)
+ describe_one_orphan(&sb, c);
+ last = c;
+ lost++;
+ }
+ if (ORPHAN_CUTOFF < lost) {
+ int more = lost - ORPHAN_CUTOFF;
+ if (more == 1)
+ describe_one_orphan(&sb, last);
+ else
+ strbuf_addf(&sb, _(" ... and %d more.\n"), more);
+ }
+
+ fprintf(stderr,
+ Q_(
+ /* The singular version */
+ "Warning: you are leaving %d commit behind, "
+ "not connected to\n"
+ "any of your branches:\n\n"
+ "%s\n",
+ /* The plural version */
+ "Warning: you are leaving %d commits behind, "
+ "not connected to\n"
+ "any of your branches:\n\n"
+ "%s\n",
+ /* Give ngettext() the count */
+ lost),
+ lost,
+ sb.buf);
+ strbuf_release(&sb);
+
+ if (advice_detached_head)
+ fprintf(stderr,
+ _(
+ "If you want to keep them by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch new_branch_name %s\n\n"),
+ sha1_to_hex(commit->object.sha1));
+}
+
+/*
+ * We are about to leave commit that was at the tip of a detached
+ * HEAD. If it is not reachable from any ref, this is the last chance
+ * for the user to do so without resorting to reflog.
+ */
+static void orphaned_commit_warning(struct commit *commit)
+{
+ struct rev_list_args args = { 0, 0, NULL };
+ struct rev_info revs;
+
+ add_one_rev_list_arg(&args, "(internal)");
+ add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
+ add_one_rev_list_arg(&args, "--not");
+ for_each_ref(add_one_ref_to_rev_list_arg, &args);
+ add_one_rev_list_arg(&args, "--");
+ add_one_rev_list_arg(&args, NULL);
+
+ init_revisions(&revs, NULL);
+ if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
+ die(_("internal error: only -- alone should have been left"));
+ if (prepare_revision_walk(&revs))
+ die(_("internal error in revision walk"));
+ if (!(commit->object.flags & UNINTERESTING))
+ suggest_reattach(commit, &revs);
+ else
+ describe_detached_head(_("Previous HEAD position was"), commit);
+
+ clear_commit_marks(commit, -1);
+ for_each_ref(clear_commit_marks_from_one_ref, NULL);
+}
+
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
{
int ret = 0;
@@ -564,10 +716,12 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
unsigned char rev[20];
int flag;
memset(&old, 0, sizeof(old));
- old.path = resolve_ref("HEAD", rev, 0, &flag);
+ old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
old.commit = lookup_commit_reference_gently(rev, 1);
- if (!(flag & REF_ISSYMREF))
+ if (!(flag & REF_ISSYMREF)) {
+ free((char *)old.path);
old.path = NULL;
+ }
if (old.path && !prefixcmp(old.path, "refs/heads/"))
old.name = old.path + strlen("refs/heads/");
@@ -576,7 +730,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
new->name = "HEAD";
new->commit = old.commit;
if (!new->commit)
- die("You are on a branch yet to be born");
+ die(_("You are on a branch yet to be born"));
parse_commit(new->commit);
}
@@ -584,23 +738,28 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
if (ret)
return ret;
- /*
- * If we were on a detached HEAD, but have now moved to
- * a new commit, we want to mention the old commit once more
- * to remind the user that it might be lost.
- */
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
- describe_detached_head("Previous HEAD position was", old.commit);
+ orphaned_commit_warning(old.commit);
update_refs_for_switch(opts, &old, new);
ret = post_checkout_hook(old.commit, new->commit, 1);
+ free((char *)old.path);
return ret || opts->writeout_error;
}
static int git_checkout_config(const char *var, const char *value, void *cb)
{
- return git_xmerge_config(var, value, cb);
+ if (!strcmp(var, "diff.ignoresubmodules")) {
+ struct checkout_opts *opts = cb;
+ handle_ignore_submodules_arg(&opts->diff_options, value);
+ return 0;
+ }
+
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+
+ return git_xmerge_config(var, value, NULL);
}
static int interactive_checkout(const char *revision, const char **pathspec,
@@ -645,29 +804,145 @@ static const char *unique_tracking_name(const char *name)
return NULL;
}
+static int parse_branchname_arg(int argc, const char **argv,
+ int dwim_new_local_branch_ok,
+ struct branch_info *new,
+ struct tree **source_tree,
+ unsigned char rev[20],
+ const char **new_branch)
+{
+ int argcount = 0;
+ unsigned char branch_rev[20];
+ const char *arg;
+ int has_dash_dash;
+
+ /*
+ * case 1: git checkout <ref> -- [<paths>]
+ *
+ * <ref> must be a valid tree, everything after the '--' must be
+ * a path.
+ *
+ * case 2: git checkout -- [<paths>]
+ *
+ * everything after the '--' must be paths.
+ *
+ * case 3: git checkout <something> [<paths>]
+ *
+ * With no paths, if <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
+ *
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote-tracking branch.
+ *
+ * Otherwise <something> shall not be ambiguous.
+ * - If it's *only* a reference, treat it like case (1).
+ * - If it's only a path, treat it like case (2).
+ * - else: fail.
+ *
+ */
+ if (!argc)
+ return 0;
+
+ if (!strcmp(argv[0], "--")) /* case (2) */
+ return 1;
+
+ arg = argv[0];
+ has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
+ if (get_sha1_mb(arg, rev)) {
+ if (has_dash_dash) /* case (1) */
+ die(_("invalid reference: %s"), arg);
+ if (dwim_new_local_branch_ok &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ return argcount;
+ *new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ } else {
+ return argcount;
+ }
+ }
+
+ /* we can't end up being in (2) anymore, eat the argument */
+ argcount++;
+ argv++;
+ argc--;
+
+ new->name = arg;
+ setup_branch_path(new);
+
+ if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
+ resolve_ref(new->path, branch_rev, 1, NULL))
+ hashcpy(rev, branch_rev);
+ else
+ new->path = NULL; /* not an existing branch */
+
+ new->commit = lookup_commit_reference_gently(rev, 1);
+ if (!new->commit) {
+ /* not a commit */
+ *source_tree = parse_tree_indirect(rev);
+ } else {
+ parse_commit(new->commit);
+ *source_tree = new->commit->tree;
+ }
+
+ if (!*source_tree) /* case (1): want a tree */
+ die(_("reference is not a tree: %s"), arg);
+ if (!has_dash_dash) {/* case (3 -> 1) */
+ /*
+ * Do not complain the most common case
+ * git checkout branch
+ * even if there happen to be a file called 'branch';
+ * it would be extremely annoying.
+ */
+ if (argc)
+ verify_non_filename(NULL, arg);
+ } else {
+ argcount++;
+ argv++;
+ argc--;
+ }
+
+ return argcount;
+}
+
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct checkout_opts opts;
unsigned char rev[20];
- const char *arg;
struct branch_info new;
struct tree *source_tree = NULL;
char *conflict_style = NULL;
int patch_mode = 0;
int dwim_new_local_branch = 1;
struct option options[] = {
- OPT__QUIET(&opts.quiet),
- OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
- OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
- OPT_SET_INT('t', "track", &opts.track, "track",
+ OPT__QUIET(&opts.quiet, "suppress progress reporting"),
+ OPT_STRING('b', NULL, &opts.new_branch, "branch",
+ "create and checkout a new branch"),
+ OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
+ "create/reset and checkout a branch"),
+ OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
+ OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
+ OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
BRANCH_TRACK_EXPLICIT),
OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
- OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files",
2),
- OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files",
3),
- OPT_BOOLEAN('f', "force", &opts.force, "force"),
- OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+ OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
+ OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
OPT_STRING(0, "conflict", &conflict_style, "style",
"conflict style (merge or diff3)"),
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
@@ -676,42 +951,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
OPT_END(),
};
- int has_dash_dash;
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
- git_config(git_checkout_config, NULL);
+ gitmodules_config();
+ git_config(git_checkout_config, &opts);
opts.track = BRANCH_TRACK_UNSPECIFIED;
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ /* we can assume from now on new_branch = !new_branch_force */
+ if (opts.new_branch && opts.new_branch_force)
+ die(_("-B cannot be used with -b"));
+
+ /* copy -B over to -b, so that we can just check the latter */
+ if (opts.new_branch_force)
+ opts.new_branch = opts.new_branch_force;
+
if (patch_mode && (opts.track > 0 || opts.new_branch
- || opts.new_branch_log || opts.merge || opts.force))
- die ("--patch is incompatible with all other options");
+ || opts.new_branch_log || opts.merge || opts.force
+ || opts.force_detach))
+ die (_("--patch is incompatible with all other options"));
+
+ if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
+ die(_("--detach cannot be used with -b/-B/--orphan"));
+ if (opts.force_detach && 0 < opts.track)
+ die(_("--detach cannot be used with -t"));
/* --track without -b should DWIM */
if (0 < opts.track && !opts.new_branch) {
const char *argv0 = argv[0];
if (!argc || !strcmp(argv0, "--"))
- die ("--track needs a branch name");
+ die (_("--track needs a branch name"));
if (!prefixcmp(argv0, "refs/"))
argv0 += 5;
if (!prefixcmp(argv0, "remotes/"))
argv0 += 8;
argv0 = strchr(argv0, '/');
if (!argv0 || !argv0[1])
- die ("Missing branch name; try -b");
+ die (_("Missing branch name; try -b"));
opts.new_branch = argv0 + 1;
}
if (opts.new_orphan_branch) {
if (opts.new_branch)
- die("--orphan and -b are mutually exclusive");
+ die(_("--orphan and -b|-B are mutually exclusive"));
if (opts.track > 0)
- die("--orphan cannot be used with -t");
+ die(_("--orphan cannot be used with -t"));
opts.new_branch = opts.new_orphan_branch;
}
@@ -721,108 +1010,33 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
}
if (opts.force && opts.merge)
- die("git checkout: -f and -m are incompatible");
+ die(_("git checkout: -f and -m are incompatible"));
/*
- * case 1: git checkout <ref> -- [<paths>]
- *
- * <ref> must be a valid tree, everything after the '--' must be
- * a path.
- *
- * case 2: git checkout -- [<paths>]
- *
- * everything after the '--' must be paths.
- *
- * case 3: git checkout <something> [<paths>]
+ * Extract branch name from command line arguments, so
+ * all that is left is pathspecs.
*
- * With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it. As a special case,
- * if <something> is A...B (missing A or B means HEAD but you can
- * omit at most one side), and if there is a unique merge base
- * between A and B, A...B names that merge base.
- *
- * With no paths, if <something> is _not_ a commit, no -t nor -b
- * was given, and there is a tracking branch whose name is
- * <something> in one and only one remote, then this is a short-hand
- * to fork local <something> from that remote tracking branch.
+ * Handle
*
- * Otherwise <something> shall not be ambiguous.
- * - If it's *only* a reference, treat it like case (1).
- * - If it's only a path, treat it like case (2).
- * - else: fail.
+ * 1) git checkout <tree> -- [<paths>]
+ * 2) git checkout -- [<paths>]
+ * 3) git checkout <something> [<paths>]
*
+ * including "last branch" syntax and DWIM-ery for names of
+ * remote branches, erroring out for invalid or ambiguous cases.
*/
if (argc) {
- if (!strcmp(argv[0], "--")) { /* case (2) */
- argv++;
- argc--;
- goto no_reference;
- }
-
- arg = argv[0];
- has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
-
- if (!strcmp(arg, "-"))
- arg = "@{-1}";
-
- if (get_sha1_mb(arg, rev)) {
- if (has_dash_dash) /* case (1) */
- die("invalid reference: %s", arg);
- if (!patch_mode &&
- dwim_new_local_branch &&
- opts.track == BRANCH_TRACK_UNSPECIFIED &&
- !opts.new_branch &&
- !check_filename(NULL, arg) &&
- argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
- goto no_reference;
- opts.new_branch = arg;
- arg = remote;
- /* DWIMmed to create local branch */
- }
- else
- goto no_reference;
- }
-
- /* we can't end up being in (2) anymore, eat the argument */
- argv++;
- argc--;
-
- new.name = arg;
- if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
- setup_branch_path(&new);
-
- if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
- resolve_ref(new.path, rev, 1, NULL))
- ;
- else
- new.path = NULL;
- parse_commit(new.commit);
- source_tree = new.commit->tree;
- } else
- source_tree = parse_tree_indirect(rev);
-
- if (!source_tree) /* case (1): want a tree */
- die("reference is not a tree: %s", arg);
- if (!has_dash_dash) {/* case (3 -> 1) */
- /*
- * Do not complain the most common case
- * git checkout branch
- * even if there happen to be a file called 'branch';
- * it would be extremely annoying.
- */
- if (argc)
- verify_non_filename(NULL, arg);
- }
- else {
- argv++;
- argc--;
- }
+ int dwim_ok =
+ !patch_mode &&
+ dwim_new_local_branch &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch;
+ int n = parse_branchname_arg(argc, argv, dwim_ok,
+ &new, &source_tree, rev, &opts.new_branch);
+ argv += n;
+ argc -= n;
}
-no_reference:
-
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
opts.track = git_branch_track;
@@ -830,7 +1044,7 @@ no_reference:
const char **pathspec = get_pathspec(prefix, argv);
if (!pathspec)
- die("invalid path specification");
+ die(_("invalid path specification"));
if (patch_mode)
return interactive_checkout(new.name, pathspec, &opts);
@@ -838,14 +1052,17 @@ no_reference:
/* Checkout paths */
if (opts.new_branch) {
if (argc == 1) {
- die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+ die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]);
} else {
- die("git checkout: updating paths is incompatible with switching branches.");
+ die(_("git checkout: updating paths is incompatible with switching branches."));
}
}
+ if (opts.force_detach)
+ die(_("git checkout: --detach does not take a path argument"));
+
if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
- die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+ die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."));
return checkout_paths(source_tree, pathspec, &opts);
}
@@ -856,18 +1073,22 @@ no_reference:
if (opts.new_branch) {
struct strbuf buf = STRBUF_INIT;
if (strbuf_check_branch_ref(&buf, opts.new_branch))
- die("git checkout: we do not like '%s' as a branch name.",
+ die(_("git checkout: we do not like '%s' as a branch name."),
opts.new_branch);
- if (!get_sha1(buf.buf, rev))
- die("git checkout: branch %s already exists", opts.new_branch);
+ if (ref_exists(buf.buf)) {
+ opts.branch_exists = 1;
+ if (!opts.new_branch_force)
+ die(_("git checkout: branch %s already exists"),
+ opts.new_branch);
+ }
strbuf_release(&buf);
}
if (new.name && !new.commit) {
- die("Cannot switch branch to a non-commit.");
+ die(_("Cannot switch branch to a non-commit."));
}
if (opts.writeout_stage)
- die("--ours/--theirs is incompatible with switching branches.");
+ die(_("--ours/--theirs is incompatible with switching branches."));
return switch_branches(&opts, &new);
}
diff --git a/builtin/clean.c b/builtin/clean.c
index fac64e6..75697f7 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -10,12 +10,13 @@
#include "cache.h"
#include "dir.h"
#include "parse-options.h"
+#include "string-list.h"
#include "quote.h"
static int force = -1; /* unset */
static const char *const builtin_clean_usage[] = {
- "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
+ "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...",
NULL
};
@@ -26,24 +27,34 @@ static int git_clean_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+static int exclude_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct string_list *exclude_list = opt->value;
+ string_list_append(exclude_list, arg);
+ return 0;
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i;
int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
- int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
+ int ignored_only = 0, config_set = 0, errors = 0;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf directory = STRBUF_INIT;
struct dir_struct dir;
static const char **pathspec;
struct strbuf buf = STRBUF_INIT;
+ struct string_list exclude_list = STRING_LIST_INIT_NODUP;
const char *qname;
char *seen = NULL;
struct option options[] = {
- OPT__QUIET(&quiet),
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force"),
+ OPT__QUIET(&quiet, "do not print names of files removed"),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__FORCE(&force, "force"),
OPT_BOOLEAN('d', NULL, &remove_directories,
"remove whole directories"),
+ { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
+ "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb },
OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
OPT_BOOLEAN('X', NULL, &ignored_only,
"remove only ignored files"),
@@ -64,11 +75,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_SHOW_IGNORED;
if (ignored && ignored_only)
- die("-x and -X cannot be used together");
-
- if (!show_only && !force)
- die("clean.requireForce %s to true and neither -n nor -f given; "
- "refusing to clean", config_set ? "set" : "defaults");
+ die(_("-x and -X cannot be used together"));
+
+ if (!show_only && !force) {
+ if (config_set)
+ die(_("clean.requireForce set to true and neither -n nor -f given; "
+ "refusing to clean"));
+ else
+ die(_("clean.requireForce defaults to true and neither -n nor -f given; "
+ "refusing to clean"));
+ }
if (force > 1)
rm_flags = 0;
@@ -76,11 +92,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
if (read_cache() < 0)
- die("index file corrupt");
+ die(_("index file corrupt"));
if (!ignored)
setup_standard_excludes(&dir);
+ for (i = 0; i < exclude_list.nr; i++)
+ add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list);
+
pathspec = get_pathspec(prefix, argv);
fill_directory(&dir, pathspec);
@@ -124,7 +143,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (pathspec) {
memset(seen, 0, argc > 0 ? argc : 1);
matches = match_pathspec(pathspec, ent->name, len,
- baselen, seen);
+ 0, seen);
}
if (S_ISDIR(st.st_mode)) {
@@ -132,20 +151,20 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
if (show_only && (remove_directories ||
(matches == MATCHED_EXACTLY))) {
- printf("Would remove %s\n", qname);
+ printf(_("Would remove %s\n"), qname);
} else if (remove_directories ||
(matches == MATCHED_EXACTLY)) {
if (!quiet)
- printf("Removing %s\n", qname);
+ printf(_("Removing %s\n"), qname);
if (remove_dir_recursively(&directory,
rm_flags) != 0) {
- warning("failed to remove '%s'", qname);
+ warning(_("failed to remove %s"), qname);
errors++;
}
} else if (show_only) {
- printf("Would not remove %s\n", qname);
+ printf(_("Would not remove %s\n"), qname);
} else {
- printf("Not removing %s\n", qname);
+ printf(_("Not removing %s\n"), qname);
}
strbuf_reset(&directory);
} else {
@@ -153,13 +172,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
continue;
qname = quote_path_relative(ent->name, -1, &buf, prefix);
if (show_only) {
- printf("Would remove %s\n", qname);
+ printf(_("Would remove %s\n"), qname);
continue;
} else if (!quiet) {
- printf("Removing %s\n", qname);
+ printf(_("Removing %s\n"), qname);
}
if (unlink(ent->name) != 0) {
- warning("failed to remove '%s'", qname);
+ warning(_("failed to remove %s"), qname);
errors++;
}
}
@@ -167,5 +186,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
free(seen);
strbuf_release(&directory);
+ string_list_clear(&exclude_list, 0);
return (errors != 0);
}
diff --git a/builtin/clone.c b/builtin/clone.c
index efb1e6f..a15784a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -8,7 +8,7 @@
* Clone a repository into a different directory that does not yet exist.
*/
-#include "cache.h"
+#include "builtin.h"
#include "parse-options.h"
#include "fetch-pack.h"
#include "refs.h"
@@ -42,9 +42,11 @@ static int option_local, option_no_hardlinks, option_shared, option_recursive;
static char *option_template, *option_reference, *option_depth;
static char *option_origin = NULL;
static char *option_branch = NULL;
+static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
static int option_progress;
+static struct string_list option_config;
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
@@ -66,8 +68,10 @@ static struct option builtin_clone_options[] = {
"setup as shared repository"),
OPT_BOOLEAN(0, "recursive", &option_recursive,
"initialize submodules in the clone"),
- OPT_STRING(0, "template", &option_template, "path",
- "path the template repository"),
+ OPT_BOOLEAN(0, "recurse-submodules", &option_recursive,
+ "initialize submodules in the clone"),
+ OPT_STRING(0, "template", &option_template, "template-directory",
+ "directory from which templates will be used"),
OPT_STRING(0, "reference", &option_reference, "repo",
"reference repository"),
OPT_STRING('o', "origin", &option_origin, "branch",
@@ -78,7 +82,10 @@ static struct option builtin_clone_options[] = {
"path to git-upload-pack on the remote"),
OPT_STRING(0, "depth", &option_depth, "depth",
"create a shallow clone of that depth"),
-
+ OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
+ "separate git dir from working tree"),
+ OPT_STRING_LIST('c', "config", &option_config, "key=value",
+ "set config inside the new repository"),
OPT_END()
};
@@ -98,7 +105,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
path = mkpath("%s%s", repo, suffix[i]);
if (is_directory(path)) {
*is_bundle = 0;
- return xstrdup(make_nonrelative_path(path));
+ return xstrdup(absolute_path(path));
}
}
@@ -107,7 +114,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
path = mkpath("%s%s", repo, bundle_suffix[i]);
if (!stat(path, &st) && S_ISREG(st.st_mode)) {
*is_bundle = 1;
- return xstrdup(make_nonrelative_path(path));
+ return xstrdup(absolute_path(path));
}
}
@@ -201,12 +208,12 @@ static void setup_reference(const char *repo)
struct transport *transport;
const struct ref *extra;
- ref_git = make_absolute_path(option_reference);
+ ref_git = real_path(option_reference);
if (is_directory(mkpath("%s/.git/objects", ref_git)))
ref_git = mkpath("%s/.git", ref_git);
else if (!is_directory(mkpath("%s/objects", ref_git)))
- die("reference repository '%s' is not a local directory.",
+ die(_("reference repository '%s' is not a local directory."),
option_reference);
ref_git_copy = xstrdup(ref_git);
@@ -233,15 +240,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
dir = opendir(src->buf);
if (!dir)
- die_errno("failed to open '%s'", src->buf);
+ die_errno(_("failed to open '%s'"), src->buf);
if (mkdir(dest->buf, 0777)) {
if (errno != EEXIST)
- die_errno("failed to create directory '%s'", dest->buf);
+ die_errno(_("failed to create directory '%s'"), dest->buf);
else if (stat(dest->buf, &buf))
- die_errno("failed to stat '%s'", dest->buf);
+ die_errno(_("failed to stat '%s'"), dest->buf);
else if (!S_ISDIR(buf.st_mode))
- die("%s exists and is not a directory", dest->buf);
+ die(_("%s exists and is not a directory"), dest->buf);
}
strbuf_addch(src, '/');
@@ -255,7 +262,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
strbuf_setlen(dest, dest_len);
strbuf_addstr(dest, de->d_name);
if (stat(src->buf, &buf)) {
- warning ("failed to stat %s\n", src->buf);
+ warning (_("failed to stat %s\n"), src->buf);
continue;
}
if (S_ISDIR(buf.st_mode)) {
@@ -265,16 +272,16 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
}
if (unlink(dest->buf) && errno != ENOENT)
- die_errno("failed to unlink '%s'", dest->buf);
+ die_errno(_("failed to unlink '%s'"), dest->buf);
if (!option_no_hardlinks) {
if (!link(src->buf, dest->buf))
continue;
if (option_local)
- die_errno("failed to create link '%s'", dest->buf);
+ die_errno(_("failed to create link '%s'"), dest->buf);
option_no_hardlinks = 1;
}
if (copy_file_with_time(dest->buf, src->buf, 0666))
- die_errno("failed to copy file to '%s'", dest->buf);
+ die_errno(_("failed to copy file to '%s'"), dest->buf);
}
closedir(dir);
}
@@ -303,7 +310,7 @@ static const struct ref *clone_local(const char *src_repo,
ret = transport_get_remote_refs(transport);
transport_disconnect(transport);
if (0 <= option_verbosity)
- printf("done.\n");
+ printf(_("done.\n"));
return ret;
}
@@ -359,9 +366,25 @@ static void write_remote_refs(const struct ref *local_refs)
clear_extra_refs();
}
+static int write_one_config(const char *key, const char *value, void *data)
+{
+ return git_config_set_multivar(key, value ? value : "true", "^$", 0);
+}
+
+static void write_config(struct string_list *config)
+{
+ int i;
+
+ for (i = 0; i < config->nr; i++) {
+ if (git_config_parse_parameter(config->items[i].string,
+ write_one_config, NULL) < 0)
+ die("unable to write parameters to config file");
+ }
+}
+
int cmd_clone(int argc, const char **argv, const char *prefix)
{
- int is_bundle = 0;
+ int is_bundle = 0, is_local;
struct stat buf;
const char *repo_name, *repo, *work_tree, *git_dir;
char *path, *dir;
@@ -381,15 +404,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
junk_pid = getpid();
+ packet_trace_identity("clone");
argc = parse_options(argc, argv, prefix, builtin_clone_options,
builtin_clone_usage, 0);
if (argc > 2)
- usage_msg_opt("Too many arguments.",
+ usage_msg_opt(_("Too many arguments."),
builtin_clone_usage, builtin_clone_options);
if (argc == 0)
- usage_msg_opt("You must specify a repository to clone.",
+ usage_msg_opt(_("You must specify a repository to clone."),
builtin_clone_usage, builtin_clone_options);
if (option_mirror)
@@ -397,7 +421,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_bare) {
if (option_origin)
- die("--bare and --origin %s options are incompatible.",
+ die(_("--bare and --origin %s options are incompatible."),
option_origin);
option_no_checkout = 1;
}
@@ -409,11 +433,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
path = get_repo_path(repo_name, &is_bundle);
if (path)
- repo = xstrdup(make_nonrelative_path(repo_name));
+ repo = xstrdup(absolute_path(repo_name));
else if (!strchr(repo_name, ':'))
- repo = xstrdup(make_absolute_path(repo_name));
+ die(_("repository '%s' does not exist"), repo_name);
else
repo = repo_name;
+ is_local = path && !is_bundle;
+ if (is_local && option_depth)
+ warning(_("--depth is ignored in local clones; use file:// instead."));
if (argc == 2)
dir = xstrdup(argv[1]);
@@ -423,8 +450,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
dest_exists = !stat(dir, &buf);
if (dest_exists && !is_empty_dir(dir))
- die("destination path '%s' already exists and is not "
- "an empty directory.", dir);
+ die(_("destination path '%s' already exists and is not "
+ "an empty directory."), dir);
strbuf_addf(&reflog_msg, "clone: from %s", repo);
@@ -433,7 +460,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
else {
work_tree = getenv("GIT_WORK_TREE");
if (work_tree && !stat(work_tree, &buf))
- die("working tree '%s' already exists.", work_tree);
+ die(_("working tree '%s' already exists."), work_tree);
}
if (option_bare || work_tree)
@@ -446,10 +473,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (!option_bare) {
junk_work_tree = work_tree;
if (safe_create_leading_directories_const(work_tree) < 0)
- die_errno("could not create leading directories of '%s'",
+ die_errno(_("could not create leading directories of '%s'"),
work_tree);
if (!dest_exists && mkdir(work_tree, 0755))
- die_errno("could not create work tree dir '%s'.",
+ die_errno(_("could not create work tree dir '%s'."),
work_tree);
set_git_work_tree(work_tree);
}
@@ -460,13 +487,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
if (safe_create_leading_directories_const(git_dir) < 0)
- die("could not create leading directories of '%s'", git_dir);
- set_git_dir(make_absolute_path(git_dir));
+ die(_("could not create leading directories of '%s'"), git_dir);
- if (0 <= option_verbosity)
- printf("Cloning into %s%s...\n",
- option_bare ? "bare repository " : "", dir);
+ set_git_dir_init(git_dir, real_git_dir, 0);
+ if (real_git_dir)
+ git_dir = real_git_dir;
+
+ if (0 <= option_verbosity) {
+ if (option_bare)
+ printf(_("Cloning into bare repository %s...\n"), dir);
+ else
+ printf(_("Cloning into %s...\n"), dir);
+ }
init_db(option_template, INIT_DB_QUIET);
+ write_config(&option_config);
/*
* At this point, the config exists, so we do not need the
@@ -514,7 +548,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_reset(&value);
- if (path && !is_bundle) {
+ if (is_local) {
refs = clone_local(path, git_dir);
mapped_refs = wanted_peer_refs(refs, refspec);
} else {
@@ -522,7 +556,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport = transport_get(remote, remote->url[0]);
if (!transport->get_refs_list || !transport->fetch)
- die("Don't know how to clone %s", transport->url);
+ die(_("Don't know how to clone %s"), transport->url);
transport_set_option(transport, TRANS_OPT_KEEP, "yes");
@@ -561,8 +595,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_release(&head);
if (!our_head_points_at) {
- warning("Remote branch %s not found in "
- "upstream %s, using HEAD instead",
+ warning(_("Remote branch %s not found in "
+ "upstream %s, using HEAD instead"),
option_branch, option_origin);
our_head_points_at = remote_head_points_at;
}
@@ -571,7 +605,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
our_head_points_at = remote_head_points_at;
}
else {
- warning("You appear to have cloned an empty repository.");
+ warning(_("You appear to have cloned an empty repository."));
our_head_points_at = NULL;
remote_head_points_at = NULL;
remote_head = NULL;
@@ -613,8 +647,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
} else {
/* Nothing to checkout out */
if (!option_no_checkout)
- warning("remote HEAD refers to nonexistent ref, "
- "unable to checkout.\n");
+ warning(_("remote HEAD refers to nonexistent ref, "
+ "unable to checkout.\n"));
option_no_checkout = 1;
}
@@ -650,7 +684,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(lock_file))
- die("unable to write new index file");
+ die(_("unable to write new index file"));
err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
sha1_to_hex(our_head_points_at->old_sha1), "1",
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 87f0591..d083795 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -9,7 +9,7 @@
#include "builtin.h"
#include "utf8.h"
-static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
+static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
@@ -56,10 +56,12 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
if (strbuf_read(&buffer, 0, 0) < 0)
die_errno("git commit-tree: failed to read");
- if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
- printf("%s\n", sha1_to_hex(commit_sha1));
- return 0;
- }
- else
+ if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+ strbuf_release(&buffer);
return 1;
+ }
+
+ printf("%s\n", sha1_to_hex(commit_sha1));
+ strbuf_release(&buffer);
+ return 0;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index c101f00..e1af9b1 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -25,6 +25,7 @@
#include "rerere.h"
#include "unpack-trees.h"
#include "quote.h"
+#include "submodule.h"
static const char * const builtin_commit_usage[] = {
"git commit [options] [--] <filepattern>...",
@@ -37,25 +38,33 @@ static const char * const builtin_status_usage[] = {
};
static const char implicit_ident_advice[] =
-"Your name and email address were configured automatically based\n"
+N_("Your name and email address were configured automatically based\n"
"on your username and hostname. Please check that they are accurate.\n"
"You can suppress this message by setting them explicitly:\n"
"\n"
" git config --global user.name \"Your Name\"\n"
" git config --global user.email you@example.com\n"
"\n"
-"If the identity used for this commit is wrong, you can fix it with:\n"
+"After doing this, you may fix the identity used for this commit with:\n"
"\n"
-" git commit --amend --author='Your Name <you@example.com>'\n";
+" git commit --amend --reset-author\n");
static const char empty_amend_advice[] =
-"You asked to amend the most recent commit, but doing so would make\n"
+N_("You asked to amend the most recent commit, but doing so would make\n"
"it empty. You can repeat your command with --allow-empty, or you can\n"
-"remove the commit entirely with \"git reset HEAD^\".\n";
+"remove the commit entirely with \"git reset HEAD^\".\n");
+
+static const char empty_cherry_pick_advice[] =
+N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
+"If you wish to commit it anyway, use:\n"
+"\n"
+" git commit --allow-empty\n"
+"\n"
+"Otherwise, please use 'git reset'\n");
static unsigned char head_sha1[20];
-static char *use_message_buffer;
+static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */
static struct lock_file false_lock; /* used only for partial commits */
@@ -67,9 +76,14 @@ static enum {
static const char *logfile, *force_author;
static const char *template_file;
+/*
+ * The _message variables are commit names from which to take
+ * the commit message and/or authorship.
+ */
+static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
-static char *author_name, *author_email, *author_date;
-static int all, edit_flag, also, interactive, only, amend, signoff;
+static char *fixup_message, *squash_message;
+static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
@@ -87,7 +101,8 @@ static enum {
} cleanup_mode;
static char *cleanup_arg;
-static int use_editor = 1, initial_commit, in_merge, include_status = 1;
+static enum commit_whence whence;
+static int use_editor = 1, initial_commit, include_status = 1;
static int show_ignored_in_status;
static const char *only_include_assumed;
static struct strbuf message;
@@ -113,16 +128,18 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
}
static struct option builtin_commit_options[] = {
- OPT__QUIET(&quiet),
- OPT__VERBOSE(&verbose),
+ OPT__QUIET(&quiet, "suppress summary after successful commit"),
+ OPT__VERBOSE(&verbose, "show diff in commit message template"),
OPT_GROUP("Commit message options"),
- OPT_FILENAME('F', "file", &logfile, "read log from file"),
- OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
- OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
- OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
- OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+ OPT_FILENAME('F', "file", &logfile, "read message from file"),
+ OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
+ OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
+ OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
+ OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
+ OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
+ OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
+ OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
@@ -135,6 +152,7 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"),
OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
@@ -142,7 +160,7 @@ static struct option builtin_commit_options[] = {
STATUS_FORMAT_SHORT),
OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+ "machine-readable output", STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
@@ -160,6 +178,36 @@ static struct option builtin_commit_options[] = {
OPT_END()
};
+static void determine_whence(struct wt_status *s)
+{
+ if (file_exists(git_path("MERGE_HEAD")))
+ whence = FROM_MERGE;
+ else if (file_exists(git_path("CHERRY_PICK_HEAD")))
+ whence = FROM_CHERRY_PICK;
+ else
+ whence = FROM_COMMIT;
+ if (s)
+ s->whence = whence;
+}
+
+static const char *whence_s(void)
+{
+ char *s = "";
+
+ switch (whence) {
+ case FROM_COMMIT:
+ break;
+ case FROM_MERGE:
+ s = "merge";
+ break;
+ case FROM_CHERRY_PICK:
+ s = "cherry-pick";
+ break;
+ }
+
+ return s;
+}
+
static void rollback_index_files(void)
{
switch (commit_style) {
@@ -240,7 +288,7 @@ static void add_remove_files(struct string_list *list)
if (!lstat(p->string, &st)) {
if (add_to_cache(p->string, &st, 0))
- die("updating files failed");
+ die(_("updating files failed"));
} else
remove_file_from_cache(p->string);
}
@@ -267,7 +315,7 @@ static void create_base_index(void)
opts.fn = oneway_merge;
tree = parse_tree_indirect(head_sha1);
if (!tree)
- die("failed to unpack HEAD tree object");
+ die(_("failed to unpack HEAD tree object"));
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts))
@@ -289,24 +337,44 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
int fd;
struct string_list partial;
const char **pathspec = NULL;
+ char *old_index_env = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
- if (interactive) {
- if (interactive_add(argc, argv, prefix) != 0)
- die("interactive add failed");
- if (read_cache_preload(NULL) < 0)
- die("index file corrupt");
- commit_style = COMMIT_AS_IS;
- return get_index_file();
- }
if (*argv)
pathspec = get_pathspec(prefix, argv);
if (read_cache_preload(pathspec) < 0)
- die("index file corrupt");
+ die(_("index file corrupt"));
+
+ if (interactive) {
+ fd = hold_locked_index(&index_lock, 1);
+
+ refresh_cache_or_die(refresh_flags);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die(_("unable to create temporary index"));
+
+ old_index_env = getenv(INDEX_ENVIRONMENT);
+ setenv(INDEX_ENVIRONMENT, index_lock.filename, 1);
+
+ if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
+ die(_("interactive add failed"));
+
+ if (old_index_env && *old_index_env)
+ setenv(INDEX_ENVIRONMENT, old_index_env, 1);
+ else
+ unsetenv(INDEX_ENVIRONMENT);
+
+ discard_cache();
+ read_cache_from(index_lock.filename);
+
+ commit_style = COMMIT_NORMAL;
+ return index_lock.filename;
+ }
/*
* Non partial, non as-is commit.
@@ -326,7 +394,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
refresh_cache_or_die(refresh_flags);
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock))
- die("unable to write new_index file");
+ die(_("unable to write new_index file"));
commit_style = COMMIT_NORMAL;
return index_lock.filename;
}
@@ -343,9 +411,13 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
if (!pathspec || !*pathspec) {
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock))
- die("unable to write new_index file");
+ if (active_cache_changed) {
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock))
+ die(_("unable to write new_index file"));
+ } else {
+ rollback_lock_file(&index_lock);
+ }
commit_style = COMMIT_AS_IS;
return get_index_file();
}
@@ -371,8 +443,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/
commit_style = COMMIT_PARTIAL;
- if (in_merge)
- die("cannot do a partial commit during a merge.");
+ if (whence != FROM_COMMIT)
+ die(_("cannot do a partial commit during a %s."), whence_s());
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
@@ -381,14 +453,14 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
discard_cache();
if (read_cache() < 0)
- die("cannot read the index");
+ die(_("cannot read the index"));
fd = hold_locked_index(&index_lock, 1);
add_remove_files(&partial);
refresh_cache(REFRESH_QUIET);
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock))
- die("unable to write new_index file");
+ die(_("unable to write new_index file"));
fd = hold_lock_file_for_update(&false_lock,
git_path("next-index-%"PRIuMAX,
@@ -401,7 +473,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&false_lock))
- die("unable to write temporary index file");
+ die(_("unable to write temporary index file"));
discard_cache();
read_cache_from(false_lock.filename);
@@ -448,13 +520,13 @@ static int is_a_merge(const unsigned char *sha1)
{
struct commit *commit = lookup_commit(sha1);
if (!commit || parse_commit(commit))
- die("could not parse HEAD commit");
+ die(_("could not parse HEAD commit"));
return !!(commit->parents && commit->parents->next);
}
static const char sign_off_header[] = "Signed-off-by: ";
-static void determine_author_info(void)
+static void determine_author_info(struct strbuf *author_ident)
{
char *name, *email, *date;
@@ -462,18 +534,18 @@ static void determine_author_info(void)
email = getenv("GIT_AUTHOR_EMAIL");
date = getenv("GIT_AUTHOR_DATE");
- if (use_message && !renew_authorship) {
+ if (author_message) {
const char *a, *lb, *rb, *eol;
- a = strstr(use_message_buffer, "\nauthor ");
+ a = strstr(author_message_buffer, "\nauthor ");
if (!a)
- die("invalid commit: %s", use_message);
+ die(_("invalid commit: %s"), author_message);
lb = strchrnul(a + strlen("\nauthor "), '<');
rb = strchrnul(lb, '>');
eol = strchrnul(rb, '\n');
if (!*lb || !*rb || !*eol)
- die("invalid commit: %s", use_message);
+ die(_("invalid commit: %s"), author_message);
if (lb == a + strlen("\nauthor "))
/* \nauthor <foo@example.com> */
@@ -491,17 +563,15 @@ static void determine_author_info(void)
const char *rb = strchr(force_author, '>');
if (!lb || !rb)
- die("malformed --author parameter");
+ die(_("malformed --author parameter"));
name = xstrndup(force_author, lb - force_author);
email = xstrndup(lb + 2, rb - (lb + 2));
}
if (force_date)
date = force_date;
-
- author_name = name;
- author_email = email;
- author_date = date;
+ strbuf_addstr(author_ident, fmt_ident(name, email, date,
+ IDENT_ERROR_ON_NO_NAME));
}
static int ends_rfc2822_footer(struct strbuf *sb)
@@ -545,68 +615,123 @@ static int ends_rfc2822_footer(struct strbuf *sb)
return 1;
}
+static char *cut_ident_timestamp_part(char *string)
+{
+ char *ket = strrchr(string, '>');
+ if (!ket || ket[1] != ' ')
+ die(_("Malformed ident string: '%s'"), string);
+ *++ket = '\0';
+ return ket;
+}
+
static int prepare_to_commit(const char *index_file, const char *prefix,
- struct wt_status *s)
+ struct wt_status *s,
+ struct strbuf *author_ident)
{
struct stat statbuf;
+ struct strbuf committer_ident = STRBUF_INIT;
int commitable, saved_color_setting;
struct strbuf sb = STRBUF_INIT;
char *buffer;
- FILE *fp;
const char *hook_arg1 = NULL;
const char *hook_arg2 = NULL;
int ident_shown = 0;
+ int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
+ if (squash_message) {
+ /*
+ * Insert the proper subject line before other commit
+ * message options add their content.
+ */
+ if (use_message && !strcmp(use_message, squash_message))
+ strbuf_addstr(&sb, "squash! ");
+ else {
+ struct pretty_print_context ctx = {0};
+ struct commit *c;
+ c = lookup_commit_reference_by_name(squash_message);
+ if (!c)
+ die(_("could not lookup commit %s"), squash_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(c, "squash! %s\n\n", &sb,
+ &ctx);
+ }
+ }
+
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) {
if (isatty(0))
- fprintf(stderr, "(reading log message from standard input)\n");
+ fprintf(stderr, _("(reading log message from standard input)\n"));
if (strbuf_read(&sb, 0, 0) < 0)
- die_errno("could not read log from standard input");
+ die_errno(_("could not read log from standard input"));
hook_arg1 = "message";
} else if (logfile) {
if (strbuf_read_file(&sb, logfile, 0) < 0)
- die_errno("could not read log file '%s'",
+ die_errno(_("could not read log file '%s'"),
logfile);
hook_arg1 = "message";
} else if (use_message) {
buffer = strstr(use_message_buffer, "\n\n");
if (!buffer || buffer[2] == '\0')
- die("commit has empty message");
+ die(_("commit has empty message"));
strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
+ } else if (fixup_message) {
+ struct pretty_print_context ctx = {0};
+ struct commit *commit;
+ commit = lookup_commit_reference_by_name(fixup_message);
+ if (!commit)
+ die(_("could not lookup commit %s"), fixup_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(commit, "fixup! %s\n\n",
+ &sb, &ctx);
+ hook_arg1 = "message";
} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
- die_errno("could not read MERGE_MSG");
+ die_errno(_("could not read MERGE_MSG"));
hook_arg1 = "merge";
} else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
- die_errno("could not read SQUASH_MSG");
+ die_errno(_("could not read SQUASH_MSG"));
hook_arg1 = "squash";
- } else if (template_file && !stat(template_file, &statbuf)) {
+ } else if (template_file) {
if (strbuf_read_file(&sb, template_file, 0) < 0)
- die_errno("could not read '%s'", template_file);
+ die_errno(_("could not read '%s'"), template_file);
hook_arg1 = "template";
+ clean_message_contents = 0;
}
/*
- * This final case does not modify the template message,
- * it just sets the argument to the prepare-commit-msg hook.
+ * The remaining cases don't modify the template message, but
+ * just set the argument(s) to the prepare-commit-msg hook.
*/
- else if (in_merge)
+ else if (whence == FROM_MERGE)
hook_arg1 = "merge";
+ else if (whence == FROM_CHERRY_PICK) {
+ hook_arg1 = "commit";
+ hook_arg2 = "CHERRY_PICK_HEAD";
+ }
- fp = fopen(git_path(commit_editmsg), "w");
- if (fp == NULL)
- die_errno("could not open '%s'", git_path(commit_editmsg));
+ if (squash_message) {
+ /*
+ * If squash_commit was used for the commit subject,
+ * then we're possibly hijacking other commit log options.
+ * Reset the hook args to tell the real story.
+ */
+ hook_arg1 = "message";
+ hook_arg2 = "";
+ }
- if (cleanup_mode != CLEANUP_NONE)
+ s->fp = fopen(git_path(commit_editmsg), "w");
+ if (s->fp == NULL)
+ die_errno(_("could not open '%s'"), git_path(commit_editmsg));
+
+ if (clean_message_contents)
stripspace(&sb, 0);
if (signoff) {
@@ -627,77 +752,81 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_release(&sob);
}
- if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
- die_errno("could not write commit template");
+ if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
+ die_errno(_("could not write commit template"));
strbuf_release(&sb);
- determine_author_info();
+ /* This checks and barfs if author is badly specified */
+ determine_author_info(author_ident);
/* This checks if committer ident is explicitly given */
- git_committer_info(0);
+ strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) {
- char *author_ident;
- const char *committer_ident;
-
- if (in_merge)
- fprintf(fp,
- "#\n"
- "# It looks like you may be committing a MERGE.\n"
- "# If this is not correct, please remove the file\n"
- "# %s\n"
- "# and try again.\n"
- "#\n",
- git_path("MERGE_HEAD"));
-
- fprintf(fp,
- "\n"
- "# Please enter the commit message for your changes.");
+ char *ai_tmp, *ci_tmp;
+ if (whence != FROM_COMMIT)
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ _("\n"
+ "It looks like you may be committing a %s.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n"
+ ""),
+ whence_s(),
+ git_path(whence == FROM_MERGE
+ ? "MERGE_HEAD"
+ : "CHERRY_PICK_HEAD"));
+
+ fprintf(s->fp, "\n");
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."));
if (cleanup_mode == CLEANUP_ALL)
- fprintf(fp,
- " Lines starting\n"
- "# with '#' will be ignored, and an empty"
- " message aborts the commit.\n");
+ status_printf_more(s, GIT_COLOR_NORMAL,
+ _(" Lines starting\n"
+ "with '#' will be ignored, and an empty"
+ " message aborts the commit.\n"));
else /* CLEANUP_SPACE, that is. */
- fprintf(fp,
- " Lines starting\n"
- "# with '#' will be kept; you may remove them"
+ status_printf_more(s, GIT_COLOR_NORMAL,
+ _(" Lines starting\n"
+ "with '#' will be kept; you may remove them"
" yourself if you want to.\n"
- "# An empty message aborts the commit.\n");
+ "An empty message aborts the commit.\n"));
if (only_include_assumed)
- fprintf(fp, "# %s\n", only_include_assumed);
-
- author_ident = xstrdup(fmt_name(author_name, author_email));
- committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
- getenv("GIT_COMMITTER_EMAIL"));
- if (strcmp(author_ident, committer_ident))
- fprintf(fp,
- "%s"
- "# Author: %s\n",
- ident_shown++ ? "" : "#\n",
- author_ident);
- free(author_ident);
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ "%s", only_include_assumed);
+
+ ai_tmp = cut_ident_timestamp_part(author_ident->buf);
+ ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
+ if (strcmp(author_ident->buf, committer_ident.buf))
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ _("%s"
+ "Author: %s"),
+ ident_shown++ ? "" : "\n",
+ author_ident->buf);
if (!user_ident_sufficiently_given())
- fprintf(fp,
- "%s"
- "# Committer: %s\n",
- ident_shown++ ? "" : "#\n",
- committer_ident);
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ _("%s"
+ "Committer: %s"),
+ ident_shown++ ? "" : "\n",
+ committer_ident.buf);
if (ident_shown)
- fprintf(fp, "#\n");
+ status_printf_ln(s, GIT_COLOR_NORMAL, "");
saved_color_setting = s->use_color;
s->use_color = 0;
- commitable = run_status(fp, index_file, prefix, 1, s);
+ commitable = run_status(s->fp, index_file, prefix, 1, s);
s->use_color = saved_color_setting;
+
+ *ai_tmp = ' ';
+ *ci_tmp = ' ';
} else {
unsigned char sha1[20];
const char *parent = "HEAD";
if (!active_nr && read_cache() < 0)
- die("Cannot read index");
+ die(_("Cannot read index"));
if (amend)
parent = "HEAD^1";
@@ -707,14 +836,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
else
commitable = index_differs_from(parent, 0);
}
+ strbuf_release(&committer_ident);
- fclose(fp);
+ fclose(s->fp);
- if (!commitable && !in_merge && !allow_empty &&
+ /*
+ * Reject an attempt to record a non-merge empty commit without
+ * explicit --allow-empty. In the cherry-pick case, it may be
+ * empty due to conflict resolution, which the user should okay.
+ */
+ if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(head_sha1))) {
run_status(stdout, index_file, prefix, 0, s);
if (amend)
- fputs(empty_amend_advice, stderr);
+ fputs(_(empty_amend_advice), stderr);
+ else if (whence == FROM_CHERRY_PICK)
+ fputs(_(empty_cherry_pick_advice), stderr);
return 0;
}
@@ -729,7 +866,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
active_cache_tree = cache_tree();
if (cache_tree_update(active_cache_tree,
active_cache, active_nr, 0, 0) < 0) {
- error("Error building trees");
+ error(_("Error building trees"));
return 0;
}
@@ -744,7 +881,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
if (launch_editor(git_path(commit_editmsg), NULL, env)) {
fprintf(stderr,
- "Please supply the message using either -m or -F option.\n");
+ _("Please supply the message using either -m or -F option.\n"));
exit(1);
}
}
@@ -824,7 +961,7 @@ static const char *find_author_by_nickname(const char *name)
format_commit_message(commit, "%an <%ae>", &buf, &ctx);
return strbuf_detach(&buf, NULL);
}
- die("No existing author found with '%s'", name);
+ die(_("No existing author found with '%s'"), name);
}
@@ -839,7 +976,29 @@ static void handle_untracked_files_arg(struct wt_status *s)
else if (!strcmp(untracked_files_arg, "all"))
s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
- die("Invalid untracked files mode '%s'", untracked_files_arg);
+ die(_("Invalid untracked files mode '%s'"), untracked_files_arg);
+}
+
+static const char *read_commit_message(const char *name)
+{
+ const char *out_enc, *out;
+ struct commit *commit;
+
+ commit = lookup_commit_reference_by_name(name);
+ if (!commit)
+ die(_("could not lookup commit %s"), name);
+ out_enc = get_commit_output_encoding();
+ out = logmsg_reencode(commit, out_enc);
+
+ /*
+ * If we failed to reencode the buffer, just copy it
+ * byte for byte so the user can try to fix it up.
+ * This also handles the case where input and output
+ * encodings are identical.
+ */
+ if (out == NULL)
+ out = xstrdup(commit->buffer);
+ return out;
}
static int parse_and_validate_options(int argc, const char *argv[],
@@ -856,9 +1015,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
force_author = find_author_by_nickname(force_author);
if (force_author && renew_authorship)
- die("Using both --reset-author and --author does not make sense");
+ die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || message.len || use_message)
+ if (logfile || message.len || use_message || fixup_message)
use_editor = 0;
if (edit_flag)
use_editor = 1;
@@ -870,72 +1029,52 @@ static int parse_and_validate_options(int argc, const char *argv[],
/* Sanity check options */
if (amend && initial_commit)
- die("You have nothing to amend.");
- if (amend && in_merge)
- die("You are in the middle of a merge -- cannot amend.");
-
+ die(_("You have nothing to amend."));
+ if (amend && whence != FROM_COMMIT)
+ die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+ if (fixup_message && squash_message)
+ die(_("Options --squash and --fixup cannot be used together"));
if (use_message)
f++;
if (edit_message)
f++;
+ if (fixup_message)
+ f++;
if (logfile)
f++;
if (f > 1)
- die("Only one of -c/-C/-F can be used.");
+ die(_("Only one of -c/-C/-F/--fixup can be used."));
if (message.len && f > 0)
- die("Option -m cannot be combined with -c/-C/-F.");
+ die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
if (edit_message)
use_message = edit_message;
- if (amend && !use_message)
+ if (amend && !use_message && !fixup_message)
use_message = "HEAD";
- if (!use_message && renew_authorship)
- die("--reset-author can be used only with -C, -c or --amend.");
+ if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
+ die(_("--reset-author can be used only with -C, -c or --amend."));
if (use_message) {
- unsigned char sha1[20];
- static char utf8[] = "UTF-8";
- const char *out_enc;
- char *enc, *end;
- struct commit *commit;
-
- if (get_sha1(use_message, sha1))
- die("could not lookup commit %s", use_message);
- commit = lookup_commit_reference(sha1);
- if (!commit || parse_commit(commit))
- die("could not parse commit %s", use_message);
-
- enc = strstr(commit->buffer, "\nencoding");
- if (enc) {
- end = strchr(enc + 10, '\n');
- enc = xstrndup(enc + 10, end - (enc + 10));
- } else {
- enc = utf8;
+ use_message_buffer = read_commit_message(use_message);
+ if (!renew_authorship) {
+ author_message = use_message;
+ author_message_buffer = use_message_buffer;
}
- out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
- if (strcmp(out_enc, enc))
- use_message_buffer =
- reencode_string(commit->buffer, out_enc, enc);
-
- /*
- * If we failed to reencode the buffer, just copy it
- * byte for byte so the user can try to fix it up.
- * This also handles the case where input and output
- * encodings are identical.
- */
- if (use_message_buffer == NULL)
- use_message_buffer = xstrdup(commit->buffer);
- if (enc != utf8)
- free(enc);
+ }
+ if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+ author_message = "CHERRY_PICK_HEAD";
+ author_message_buffer = read_commit_message(author_message);
}
+ if (patch_interactive)
+ interactive = 1;
+
if (!!also + !!only + !!all + !!interactive > 1)
- die("Only one of --include/--only/--all/--interactive can be used.");
+ die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
if (argc == 0 && (also || (only && !amend)))
- die("No paths with --include/--only does not make sense.");
+ die(_("No paths with --include/--only does not make sense."));
if (argc == 0 && only && amend)
- only_include_assumed = "Clever... amending the last one with dirty index.";
+ only_include_assumed = _("Clever... amending the last one with dirty index.");
if (argc > 0 && !also && !only)
- only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+ only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths...");
if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
else if (!strcmp(cleanup_arg, "verbatim"))
@@ -945,14 +1084,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
else if (!strcmp(cleanup_arg, "strip"))
cleanup_mode = CLEANUP_ALL;
else
- die("Invalid cleanup mode %s", cleanup_arg);
+ die(_("Invalid cleanup mode %s"), cleanup_arg);
handle_untracked_files_arg(s);
if (all && argc > 0)
- die("Paths with -a does not make sense.");
- else if (interactive && argc > 0)
- die("Paths with --interactive does not make sense.");
+ die(_("Paths with -a does not make sense."));
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
@@ -979,6 +1116,8 @@ static int parse_status_slot(const char *var, int offset)
{
if (!strcasecmp(var+offset, "header"))
return WT_STATUS_HEADER;
+ if (!strcasecmp(var+offset, "branch"))
+ return WT_STATUS_ONBRANCH;
if (!strcasecmp(var+offset, "updated")
|| !strcasecmp(var+offset, "added"))
return WT_STATUS_UPDATED;
@@ -1031,7 +1170,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
else if (!strcmp(v, "all"))
s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
- return error("Invalid untracked files mode '%s'", v);
+ return error(_("Invalid untracked files mode '%s'"), v);
return 0;
}
return git_diff_ui_config(k, v, NULL);
@@ -1043,13 +1182,13 @@ int cmd_status(int argc, const char **argv, const char *prefix)
int fd;
unsigned char sha1[20];
static struct option builtin_status_options[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_SET_INT('s', "short", &status_format,
"show status concisely", STATUS_FORMAT_SHORT),
OPT_BOOLEAN('b', "branch", &status_show_branch,
"show branch information"),
OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format",
+ "machine-readable output",
STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
@@ -1065,15 +1204,20 @@ int cmd_status(int argc, const char **argv, const char *prefix)
OPT_END(),
};
- if (null_termination && status_format == STATUS_FORMAT_LONG)
- status_format = STATUS_FORMAT_PORCELAIN;
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_status_usage, builtin_status_options);
wt_status_prepare(&s);
+ gitmodules_config();
git_config(git_status_config, &s);
- in_merge = file_exists(git_path("MERGE_HEAD"));
+ determine_whence(&s);
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
+
+ if (null_termination && status_format == STATUS_FORMAT_LONG)
+ status_format = STATUS_FORMAT_PORCELAIN;
+
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
@@ -1084,14 +1228,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
- if (0 <= fd) {
- if (!write_cache(fd, active_cache, active_nr))
- commit_locked_index(&index_lock);
- rollback_lock_file(&index_lock);
- }
+ if (0 <= fd)
+ update_index_if_able(&the_index, &index_lock);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
- s.in_merge = in_merge;
s.ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(&s);
@@ -1131,9 +1271,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
commit = lookup_commit(sha1);
if (!commit)
- die("couldn't look up newly created commit");
+ die(_("couldn't look up newly created commit"));
if (!commit || parse_commit(commit))
- die("could not parse newly created commit");
+ die(_("could not parse newly created commit"));
strbuf_addstr(&format, "format:%h] %s");
@@ -1148,7 +1288,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
strbuf_addbuf_percentquote(&format, &committer_ident);
if (advice_implicit_identity) {
strbuf_addch(&format, '\n');
- strbuf_addstr(&format, implicit_ident_advice);
+ strbuf_addstr(&format, _(implicit_ident_advice));
}
}
strbuf_release(&author_ident);
@@ -1157,7 +1297,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- rev.abbrev = 0;
rev.diff = 1;
rev.diffopt.output_format =
DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
@@ -1167,7 +1306,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
get_commit_format(format.buf, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = 1;
- rev.diffopt.rename_limit = 100;
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
@@ -1175,9 +1313,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
!prefixcmp(head, "refs/heads/") ?
head + 11 :
!strcmp(head, "HEAD") ?
- "detached HEAD" :
+ _("detached HEAD") :
head,
- initial_commit ? " (root-commit)" : "");
+ initial_commit ? _(" (root-commit)") : "");
if (!log_tree_commit(&rev, commit)) {
rev.always_show_header = 1;
@@ -1239,6 +1377,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
int cmd_commit(int argc, const char **argv, const char *prefix)
{
struct strbuf sb = STRBUF_INIT;
+ struct strbuf author_ident = STRBUF_INIT;
const char *index_file, *reflog_msg;
char *nl, *p;
unsigned char commit_sha1[20];
@@ -1248,10 +1387,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
int allow_fast_forward = 1;
struct wt_status s;
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_commit_usage, builtin_commit_options);
+
wt_status_prepare(&s);
git_config(git_commit_config, &s);
- in_merge = file_exists(git_path("MERGE_HEAD"));
- s.in_merge = in_merge;
+ determine_whence(&s);
if (s.use_color == -1)
s.use_color = git_use_color_default;
@@ -1266,7 +1407,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
/* Set up everything for writing the commit object. This includes
running hooks, writing the trees, and interacting with the user. */
- if (!prepare_to_commit(index_file, prefix, &s)) {
+ if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) {
rollback_index_files();
return 1;
}
@@ -1284,11 +1425,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
reflog_msg = "commit (amend)";
commit = lookup_commit(head_sha1);
if (!commit || parse_commit(commit))
- die("could not parse HEAD commit");
+ die(_("could not parse HEAD commit"));
for (c = commit->parents; c; c = c->next)
pptr = &commit_list_insert(c->item, pptr)->next;
- } else if (in_merge) {
+ } else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT;
FILE *fp;
@@ -1297,19 +1438,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
fp = fopen(git_path("MERGE_HEAD"), "r");
if (fp == NULL)
- die_errno("could not open '%s' for reading",
+ die_errno(_("could not open '%s' for reading"),
git_path("MERGE_HEAD"));
while (strbuf_getline(&m, fp, '\n') != EOF) {
unsigned char sha1[20];
if (get_sha1_hex(m.buf, sha1) < 0)
- die("Corrupt MERGE_HEAD file (%s)", m.buf);
+ die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
}
fclose(fp);
strbuf_release(&m);
if (!stat(git_path("MERGE_MODE"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
- die_errno("could not read MERGE_MODE");
+ die_errno(_("could not read MERGE_MODE"));
if (!strcmp(sb.buf, "no-ff"))
allow_fast_forward = 0;
}
@@ -1317,7 +1458,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
parents = reduce_heads(parents);
} else {
if (!reflog_msg)
- reflog_msg = "commit";
+ reflog_msg = (whence == FROM_CHERRY_PICK)
+ ? "commit (cherry-pick)"
+ : "commit";
pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
}
@@ -1326,7 +1469,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
int saved_errno = errno;
rollback_index_files();
- die("could not read commit message: %s", strerror(saved_errno));
+ die(_("could not read commit message: %s"), strerror(saved_errno));
}
/* Truncate the message just before the diff, if any. */
@@ -1340,16 +1483,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
if (message_is_empty(&sb) && !allow_empty_message) {
rollback_index_files();
- fprintf(stderr, "Aborting commit due to empty commit message.\n");
+ fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
exit(1);
}
if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
- fmt_ident(author_name, author_email, author_date,
- IDENT_ERROR_ON_NO_NAME))) {
+ author_ident.buf)) {
rollback_index_files();
- die("failed to write commit object");
+ die(_("failed to write commit object"));
}
+ strbuf_release(&author_ident);
ref_lock = lock_any_ref_for_update("HEAD",
initial_commit ? NULL : head_sha1,
@@ -1365,22 +1508,23 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (!ref_lock) {
rollback_index_files();
- die("cannot lock HEAD ref");
+ die(_("cannot lock HEAD ref"));
}
if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
rollback_index_files();
- die("cannot update HEAD ref");
+ die(_("cannot update HEAD ref"));
}
+ unlink(git_path("CHERRY_PICK_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("MERGE_MODE"));
unlink(git_path("SQUASH_MSG"));
if (commit_index_files())
- die ("Repository has been updated, but unable to write\n"
+ die (_("Repository has been updated, but unable to write\n"
"new_index file. Check that disk is not full or quota is\n"
- "not exceeded, and then \"git reset HEAD\" to recover.");
+ "not exceeded, and then \"git reset HEAD\" to recover."));
rerere(0);
run_hook(get_index_file(), "post-commit", NULL);
diff --git a/builtin/config.c b/builtin/config.c
index f3d1660..211e118 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -20,7 +20,7 @@ static char delim = '=';
static char key_delim = ' ';
static char term = '\n';
-static int use_global_config, use_system_config;
+static int use_global_config, use_system_config, use_local_config;
static const char *given_config_file;
static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
@@ -51,7 +51,8 @@ static struct option builtin_config_options[] = {
OPT_GROUP("Config file location"),
OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
- OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+ OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"),
+ OPT_STRING('f', "file", &given_config_file, "file", "use given config file"),
OPT_GROUP("Action"),
OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
@@ -152,7 +153,6 @@ static int show_config(const char *key_, const char *value_, void *cb)
static int get_value(const char *key_, const char *regex_)
{
int ret = -1;
- char *tl;
char *global = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
@@ -160,24 +160,38 @@ static int get_value(const char *key_, const char *regex_)
if (!local) {
const char *home = getenv("HOME");
local = repo_config = git_pathdup("config");
- if (git_config_global() && home)
+ if (home)
global = xstrdup(mkpath("%s/.gitconfig", home));
if (git_config_system())
system_wide = git_etc_gitconfig();
}
- key = xstrdup(key_);
- for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
- *tl = tolower(*tl);
- for (tl=key; *tl && *tl != '.'; ++tl)
- *tl = tolower(*tl);
-
if (use_key_regexp) {
+ char *tl;
+
+ /*
+ * NEEDSWORK: this naive pattern lowercasing obviously does not
+ * work for more complex patterns like "^[^.]*Foo.*bar".
+ * Perhaps we should deprecate this altogether someday.
+ */
+
+ key = xstrdup(key_);
+ for (tl = key + strlen(key) - 1;
+ tl >= key && *tl != '.';
+ tl--)
+ *tl = tolower(*tl);
+ for (tl = key; *tl && *tl != '.'; tl++)
+ *tl = tolower(*tl);
+
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ free(key);
goto free_strings;
}
+ } else {
+ if (git_config_parse_key(key_, &key, NULL))
+ goto free_strings;
}
if (regex_) {
@@ -330,11 +344,10 @@ static int get_colorbool(int print)
return get_colorbool_found ? 0 : 1;
}
-int cmd_config(int argc, const char **argv, const char *unused_prefix)
+int cmd_config(int argc, const char **argv, const char *prefix)
{
- int nongit;
+ int nongit = !startup_info->have_repository;
char *value;
- const char *prefix = setup_git_directory_gently(&nongit);
config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
@@ -342,7 +355,7 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix)
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- if (use_global_config + use_system_config + !!given_config_file > 1) {
+ if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
error("only one config file at a time.");
usage_with_options(builtin_config_usage, builtin_config_options);
}
@@ -358,6 +371,8 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix)
}
else if (use_system_config)
config_exclusive_filename = git_etc_gitconfig();
+ else if (use_local_config)
+ config_exclusive_filename = git_pathdup("config");
else if (given_config_file) {
if (!is_absolute_path(given_config_file) && prefix)
config_exclusive_filename = prefix_filename(prefix,
@@ -421,9 +436,14 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix)
NULL, NULL);
}
else if (actions == ACTION_SET) {
+ int ret;
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- return git_config_set(argv[0], value);
+ ret = git_config_set(argv[0], value);
+ if (ret == CONFIG_NOTHING_SET)
+ error("cannot overwrite multiple values with a single value\n"
+ " Use a regexp, --add or --set-all to change %s.", argv[0]);
+ return ret;
}
else if (actions == ACTION_SET_ALL) {
check_argc(argc, 2, 3);
@@ -498,3 +518,9 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix)
return 0;
}
+
+int cmd_repo_config(int argc, const char **argv, const char *prefix)
+{
+ fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n");
+ return cmd_config(argc, argv, prefix);
+}
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index 2bdd8eb..c37cb98 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -79,7 +79,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
off_t loose_size = 0;
struct option opts[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_END(),
};
diff --git a/builtin/describe.c b/builtin/describe.c
index 43caff2..66fc291 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -6,6 +6,7 @@
#include "exec_cmd.h"
#include "parse-options.h"
#include "diff.h"
+#include "hash.h"
#define SEEN (1u<<0)
#define MAX_TAGS (FLAG_BITS - 1)
@@ -20,9 +21,10 @@ static int debug; /* Display lots of verbose info */
static int all; /* Any valid ref can be used */
static int tags; /* Allow lightweight tags */
static int longformat;
-static int abbrev = DEFAULT_ABBREV;
+static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
-static int found_names;
+static struct hash_table names;
+static int have_util;
static const char *pattern;
static int always;
static const char *dirty;
@@ -34,16 +36,44 @@ static const char *diff_index_args[] = {
struct commit_name {
+ struct commit_name *next;
+ unsigned char peeled[20];
struct tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
unsigned char sha1[20];
- char path[FLEX_ARRAY]; /* more */
+ const char *path;
};
static const char *prio_names[] = {
"head", "lightweight", "annotated",
};
+static inline unsigned int hash_sha1(const unsigned char *sha1)
+{
+ unsigned int hash;
+ memcpy(&hash, sha1, sizeof(hash));
+ return hash;
+}
+
+static inline struct commit_name *find_commit_name(const unsigned char *peeled)
+{
+ struct commit_name *n = lookup_hash(hash_sha1(peeled), &names);
+ while (n && !!hashcmp(peeled, n->peeled))
+ n = n->next;
+ return n;
+}
+
+static int set_util(void *chain, void *data)
+{
+ struct commit_name *n;
+ for (n = chain; n; n = n->next) {
+ struct commit *c = lookup_commit_reference_gently(n->peeled, 1);
+ if (c)
+ c->util = n;
+ }
+ return 0;
+}
+
static int replace_name(struct commit_name *e,
int prio,
const unsigned char *sha1,
@@ -78,31 +108,36 @@ static int replace_name(struct commit_name *e,
}
static void add_to_known_names(const char *path,
- struct commit *commit,
+ const unsigned char *peeled,
int prio,
const unsigned char *sha1)
{
- struct commit_name *e = commit->util;
+ struct commit_name *e = find_commit_name(peeled);
struct tag *tag = NULL;
if (replace_name(e, prio, sha1, &tag)) {
- size_t len = strlen(path)+1;
- free(e);
- e = xmalloc(sizeof(struct commit_name) + len);
+ if (!e) {
+ void **pos;
+ e = xmalloc(sizeof(struct commit_name));
+ hashcpy(e->peeled, peeled);
+ pos = insert_hash(hash_sha1(peeled), e, &names);
+ if (pos) {
+ e->next = *pos;
+ *pos = e;
+ } else {
+ e->next = NULL;
+ }
+ }
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
hashcpy(e->sha1, sha1);
- memcpy(e->path, path, len);
- commit->util = e;
+ e->path = path;
}
- found_names = 1;
}
static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
int might_be_tag = !prefixcmp(path, "refs/tags/");
- struct commit *commit;
- struct object *object;
unsigned char peeled[20];
int is_tag, prio;
@@ -110,16 +145,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
return 0;
if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
- commit = lookup_commit_reference_gently(peeled, 1);
- if (!commit)
- return 0;
- is_tag = !!hashcmp(sha1, commit->object.sha1);
+ is_tag = !!hashcmp(sha1, peeled);
} else {
- commit = lookup_commit_reference_gently(sha1, 1);
- object = parse_object(sha1);
- if (!commit || !object)
- return 0;
- is_tag = object->type == OBJ_TAG;
+ hashcpy(peeled, sha1);
+ is_tag = 0;
}
/* If --all, then any refs are used.
@@ -142,7 +171,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
if (!prio)
return 0;
}
- add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
+ add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
return 0;
}
@@ -189,7 +218,7 @@ static unsigned long finish_depth_computation(
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
- insert_by_date(p, list);
+ commit_list_insert_by_date(p, list);
p->object.flags |= c->object.flags;
parents = parents->next;
}
@@ -202,13 +231,13 @@ static void display_name(struct commit_name *n)
if (n->prio == 2 && !n->tag) {
n->tag = lookup_tag(n->sha1);
if (!n->tag || parse_tag(n->tag))
- die("annotated tag %s not available", n->path);
+ die(_("annotated tag %s not available"), n->path);
}
if (n->tag && !n->name_checked) {
if (!n->tag->tag)
- die("annotated tag %s has no embedded name", n->path);
+ die(_("annotated tag %s has no embedded name"), n->path);
if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
- warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
+ warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
n->name_checked = 1;
}
@@ -235,12 +264,12 @@ static void describe(const char *arg, int last_one)
unsigned int unannotated_cnt = 0;
if (get_sha1(arg, sha1))
- die("Not a valid object name %s", arg);
+ die(_("Not a valid object name %s"), arg);
cmit = lookup_commit_reference(sha1);
if (!cmit)
- die("%s is not a valid '%s' object", arg, commit_type);
+ die(_("%s is not a valid '%s' object"), arg, commit_type);
- n = cmit->util;
+ n = find_commit_name(cmit->object.sha1);
if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
@@ -255,9 +284,14 @@ static void describe(const char *arg, int last_one)
}
if (!max_candidates)
- die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
+ die(_("no tag exactly matches '%s'"), sha1_to_hex(cmit->object.sha1));
if (debug)
- fprintf(stderr, "searching to describe %s\n", arg);
+ fprintf(stderr, _("searching to describe %s\n"), arg);
+
+ if (!have_util) {
+ for_each_hash(&names, set_util, NULL);
+ have_util = 1;
+ }
list = NULL;
cmit->object.flags = SEEN;
@@ -292,7 +326,7 @@ static void describe(const char *arg, int last_one)
}
if (annotated_cnt && !list) {
if (debug)
- fprintf(stderr, "finished search at %s\n",
+ fprintf(stderr, _("finished search at %s\n"),
sha1_to_hex(c->object.sha1));
break;
}
@@ -300,7 +334,7 @@ static void describe(const char *arg, int last_one)
struct commit *p = parents->item;
parse_commit(p);
if (!(p->object.flags & SEEN))
- insert_by_date(p, &list);
+ commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
}
@@ -316,19 +350,19 @@ static void describe(const char *arg, int last_one)
return;
}
if (unannotated_cnt)
- die("No annotated tags can describe '%s'.\n"
- "However, there were unannotated tags: try --tags.",
+ die(_("No annotated tags can describe '%s'.\n"
+ "However, there were unannotated tags: try --tags."),
sha1_to_hex(sha1));
else
- die("No tags can describe '%s'.\n"
- "Try --always, or create some tags.",
+ die(_("No tags can describe '%s'.\n"
+ "Try --always, or create some tags."),
sha1_to_hex(sha1));
}
qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
if (gave_up_on) {
- insert_by_date(gave_up_on, &list);
+ commit_list_insert_by_date(gave_up_on, &list);
seen_commits--;
}
seen_commits += finish_depth_computation(&list, &all_matches[0]);
@@ -341,11 +375,11 @@ static void describe(const char *arg, int last_one)
prio_names[t->name->prio],
t->depth, t->name->path);
}
- fprintf(stderr, "traversed %lu commits\n", seen_commits);
+ fprintf(stderr, _("traversed %lu commits\n"), seen_commits);
if (gave_up_on) {
fprintf(stderr,
- "more than %i tags found; listed %i most recent\n"
- "gave up search at %s\n",
+ _("more than %i tags found; listed %i most recent\n"
+ "gave up search at %s\n"),
max_candidates, max_candidates,
sha1_to_hex(gave_up_on->object.sha1));
}
@@ -386,7 +420,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
OPT_END(),
};
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, describe_usage, 0);
+ if (abbrev < 0)
+ abbrev = DEFAULT_ABBREV;
+
if (max_candidates < 0)
max_candidates = 0;
else if (max_candidates > MAX_TAGS)
@@ -395,7 +433,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
save_commit_buffer = 0;
if (longformat && abbrev == 0)
- die("--long is incompatible with --abbrev=0");
+ die(_("--long is incompatible with --abbrev=0"));
if (contains) {
const char **args = xmalloc((7 + argc) * sizeof(char *));
@@ -418,16 +456,17 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
return cmd_name_rev(i + argc, args, prefix);
}
- for_each_ref(get_name, NULL);
- if (!found_names && !always)
- die("No names found, cannot describe anything.");
+ init_hash(&names);
+ for_each_rawref(get_name, NULL);
+ if (!names.nr && !always)
+ die(_("No names found, cannot describe anything."));
if (argc == 0) {
if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
dirty = NULL;
describe("HEAD", 1);
} else if (dirty) {
- die("--dirty is incompatible with committishes");
+ die(_("--dirty is incompatible with committishes"));
} else {
while (argc-- > 0) {
describe(*argv++, argc == 0);
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 5b64011..46085f8 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -8,6 +8,7 @@
#include "commit.h"
#include "revision.h"
#include "builtin.h"
+#include "submodule.h"
static const char diff_files_usage[] =
"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
@@ -20,6 +21,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
unsigned options = 0;
init_revisions(&rev, prefix);
+ gitmodules_config();
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
rev.abbrev = 0;
@@ -59,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
- if (read_cache_preload(rev.diffopt.paths) < 0) {
+ if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 0483749..2eb32bd 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -3,6 +3,7 @@
#include "commit.h"
#include "revision.h"
#include "builtin.h"
+#include "submodule.h"
static const char diff_cache_usage[] =
"git diff-index [-m] [--cached] "
@@ -17,6 +18,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
int result;
init_revisions(&rev, prefix);
+ gitmodules_config();
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
rev.abbrev = 0;
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index 3c78bda..be6417d 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -3,6 +3,7 @@
#include "commit.h"
#include "log-tree.h"
#include "builtin.h"
+#include "submodule.h"
static struct rev_info log_tree_opt;
@@ -112,6 +113,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
int read_stdin = 0;
init_revisions(opt, prefix);
+ gitmodules_config();
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
opt->abbrev = 0;
opt->diff = 1;
@@ -161,6 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
}
if (read_stdin) {
+ int saved_nrl = 0;
+ int saved_dcctc = 0;
+
if (opt->diffopt.detect_rename)
opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
DIFF_SETUP_USE_CACHE);
@@ -171,9 +176,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
fputs(line, stdout);
fflush(stdout);
}
- else
+ else {
diff_tree_stdin(line);
+ if (saved_nrl < opt->diffopt.needed_rename_limit)
+ saved_nrl = opt->diffopt.needed_rename_limit;
+ if (opt->diffopt.degraded_cc_to_c)
+ saved_dcctc = 1;
+ }
}
+ opt->diffopt.degraded_cc_to_c = saved_dcctc;
+ opt->diffopt.needed_rename_limit = saved_nrl;
}
return diff_result_code(&opt->diffopt, 0);
diff --git a/builtin/diff.c b/builtin/diff.c
index ffcdd05..69cd5ee 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -13,6 +13,7 @@
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
+#include "submodule.h"
struct blobinfo {
unsigned char sha1[20];
@@ -21,7 +22,7 @@ struct blobinfo {
};
static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
+"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
@@ -70,9 +71,9 @@ static int builtin_diff_b_f(struct rev_info *revs,
usage(builtin_diff_usage);
if (lstat(path, &st))
- die_errno("failed to stat '%s'", path);
+ die_errno(_("failed to stat '%s'"), path);
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
- die("'%s': not a regular file or symlink", path);
+ die(_("'%s': not a regular file or symlink"), path);
diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
@@ -134,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs,
revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1)
usage(builtin_diff_usage);
- if (read_cache_preload(revs->diffopt.paths) < 0) {
+ if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
@@ -181,6 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs,
hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
diff_tree_combined(parent[0], parent + 1, ents - 1,
revs->dense_combined_merges, revs);
+ free(parent);
return 0;
}
@@ -196,17 +198,11 @@ static void refresh_index_quietly(void)
discard_cache();
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
-
- if (active_cache_changed &&
- !write_cache(fd, active_cache, active_nr))
- commit_locked_index(lock_file);
-
- rollback_lock_file(lock_file);
+ update_index_if_able(&the_index, lock_file);
}
static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
{
- int result;
unsigned int options = 0;
while (1 < argc && argv[1][0] == '-') {
@@ -221,7 +217,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
else if (!strcmp(argv[1], "-h"))
usage(builtin_diff_usage);
else
- return error("invalid option: %s", argv[1]);
+ return error(_("invalid option: %s"), argv[1]);
argv++; argc--;
}
@@ -236,12 +232,11 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
- if (read_cache_preload(revs->diffopt.paths) < 0) {
+ if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
perror("read_cache_preload");
return -1;
}
- result = run_diff_files(revs, options);
- return diff_result_code(&revs->diffopt, result);
+ return run_diff_files(revs, options);
}
int cmd_diff(int argc, const char **argv, const char *prefix)
@@ -279,6 +274,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
*/
prefix = setup_git_directory_gently(&nongit);
+ gitmodules_config();
git_config(git_diff_ui_config, NULL);
if (diff_use_color_default == -1)
@@ -297,12 +293,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
if (nongit)
- die("Not a git repository");
+ die(_("Not a git repository"));
argc = setup_revisions(argc, argv, &rev, NULL);
if (!rev.diffopt.output_format) {
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
if (diff_setup_done(&rev.diffopt) < 0)
- die("diff_setup_done failed");
+ die(_("diff_setup_done failed"));
}
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
@@ -328,8 +324,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
else if (!strcmp(arg, "--cached") ||
!strcmp(arg, "--staged")) {
add_head_to_pending(&rev);
- if (!rev.pending.nr)
- die("No HEAD commit to compare with (yet)");
+ if (!rev.pending.nr) {
+ struct tree *tree;
+ tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN);
+ add_pending_object(&rev, &tree->object, "HEAD");
+ }
break;
}
}
@@ -344,12 +343,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
obj = parse_object(obj->sha1);
obj = deref_tag(obj, NULL, 0);
if (!obj)
- die("invalid object '%s' given.", name);
+ die(_("invalid object '%s' given."), name);
if (obj->type == OBJ_COMMIT)
obj = &((struct commit *)obj)->tree->object;
if (obj->type == OBJ_TREE) {
if (ARRAY_SIZE(ent) <= ents)
- die("more than %d trees given: '%s'",
+ die(_("more than %d trees given: '%s'"),
(int) ARRAY_SIZE(ent), name);
obj->flags |= flags;
ent[ents].item = obj;
@@ -359,7 +358,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
- die("more than two blobs given: '%s'", name);
+ die(_("more than two blobs given: '%s'"), name);
hashcpy(blob[blobs].sha1, obj->sha1);
blob[blobs].name = name;
blob[blobs].mode = list->mode;
@@ -367,16 +366,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
continue;
}
- die("unhandled object '%s' given.", name);
+ die(_("unhandled object '%s' given."), name);
}
- if (rev.prune_data) {
- const char **pathspec = rev.prune_data;
- while (*pathspec) {
- if (!path)
- path = *pathspec;
- paths++;
- pathspec++;
- }
+ if (rev.prune_data.nr) {
+ if (!path)
+ path = rev.prune_data.items[0].match;
+ paths += rev.prune_data.nr;
}
/*
@@ -407,17 +402,19 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
result = builtin_diff_index(&rev, argc, argv);
else if (ents == 2)
result = builtin_diff_tree(&rev, argc, argv, ent);
- else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
- /* diff A...B where there is one sane merge base between
- * A and B. We have ent[0] == merge-base, ent[1] == A,
- * and ent[2] == B. Show diff between the base and B.
+ else if (ent[0].item->flags & UNINTERESTING) {
+ /*
+ * diff A...B where there is at least one merge base
+ * between A and B. We have ent[0] == merge-base,
+ * ent[ents-2] == A, and ent[ents-1] == B. Show diff
+ * between the base and B. Note that we pick one
+ * merge base at random if there are more than one.
*/
- ent[1] = ent[2];
+ ent[1] = ent[ents-1];
result = builtin_diff_tree(&rev, argc, argv, ent);
- }
- else
+ } else
result = builtin_diff_combined(&rev, argc, argv,
- ent, ents);
+ ent, ents);
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 9fe25ff..daf1945 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -27,6 +27,7 @@ static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
static int fake_missing_tagger;
static int no_data;
+static int full_tree;
static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset)
@@ -147,10 +148,47 @@ static void handle_object(const unsigned char *sha1)
free(buf);
}
+static int depth_first(const void *a_, const void *b_)
+{
+ const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+ const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+ const char *name_a, *name_b;
+ int len_a, len_b, len;
+ int cmp;
+
+ name_a = a->one ? a->one->path : a->two->path;
+ name_b = b->one ? b->one->path : b->two->path;
+
+ len_a = strlen(name_a);
+ len_b = strlen(name_b);
+ len = (len_a < len_b) ? len_a : len_b;
+
+ /* strcmp will sort 'd' before 'd/e', we want 'd/e' before 'd' */
+ cmp = memcmp(name_a, name_b, len);
+ if (cmp)
+ return cmp;
+ cmp = len_b - len_a;
+ if (cmp)
+ return cmp;
+ /*
+ * Move 'R'ename entries last so that all references of the file
+ * appear in the output before it is renamed (e.g., when a file
+ * was copied and renamed in the same commit).
+ */
+ return (a->status == 'R') - (b->status == 'R');
+}
+
static void show_filemodify(struct diff_queue_struct *q,
struct diff_options *options, void *data)
{
int i;
+
+ /*
+ * Handle files below a directory first, in case they are all deleted
+ * and the directory changes to a file or symlink.
+ */
+ qsort(q->queue, q->nr, sizeof(q->queue[0]), depth_first);
+
for (i = 0; i < q->nr; i++) {
struct diff_filespec *ospec = q->queue[i]->one;
struct diff_filespec *spec = q->queue[i]->two;
@@ -241,7 +279,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
message += 2;
if (commit->parents &&
- get_object_mark(&commit->parents->item->object) != 0) {
+ get_object_mark(&commit->parents->item->object) != 0 &&
+ !full_tree) {
parse_commit(commit->parents->item);
diff_tree_sha1(commit->parents->item->tree->object.sha1,
commit->tree->object.sha1, "", &rev->diffopt);
@@ -281,6 +320,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
i++;
}
+ if (full_tree)
+ printf("deleteall\n");
log_tree_diff_flush(rev);
rev->diffopt.output_format = saved_output_format;
@@ -565,8 +606,8 @@ static void import_marks(char *input_file)
int cmd_fast_export(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
- struct object_array commits = { 0, 0, NULL };
- struct string_list extra_refs = { NULL, 0, 0, 0 };
+ struct object_array commits = OBJECT_ARRAY_INIT;
+ struct string_list extra_refs = STRING_LIST_INIT_NODUP;
struct commit *commit;
char *export_filename = NULL, *import_filename = NULL;
struct option options[] = {
@@ -578,12 +619,14 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
"select handling of tags that tag filtered objects",
parse_opt_tag_of_filtered_mode),
- OPT_STRING(0, "export-marks", &export_filename, "FILE",
+ OPT_STRING(0, "export-marks", &export_filename, "file",
"Dump marks to this file"),
- OPT_STRING(0, "import-marks", &import_filename, "FILE",
+ OPT_STRING(0, "import-marks", &import_filename, "file",
"Import marks from this file"),
OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
"Fake a tagger when tags lack one"),
+ OPT_BOOLEAN(0, "full-tree", &full_tree,
+ "Output full tree for each commit"),
{ OPTION_NEGBIT, 0, "data", &no_data, NULL,
"Skip output of blob data",
PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
@@ -608,6 +651,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (import_filename)
import_marks(import_filename);
+ if (import_filename && revs.prune_data.nr)
+ full_tree = 1;
+
get_tags_and_duplicates(&revs.pending, &extra_refs);
if (prepare_revision_walk(&revs))
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index dbd8b7b..4367984 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "refs.h"
#include "pkt-line.h"
#include "commit.h"
@@ -9,11 +9,13 @@
#include "fetch-pack.h"
#include "remote.h"
#include "run-command.h"
+#include "transport.h"
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
static int unpack_limit = 100;
static int prefer_ofs_delta = 1;
+static int no_done = 0;
static struct fetch_pack_args args = {
/* .uploadpack = */ "git-upload-pack",
};
@@ -47,7 +49,7 @@ static void rev_list_push(struct commit *commit, int mark)
if (parse_commit(commit))
return;
- insert_by_date(commit, &rev_list);
+ commit_list_insert_by_date(commit, &rev_list);
if (!(commit->object.flags & COMMON))
non_common_revs++;
@@ -217,14 +219,40 @@ static void send_request(int fd, struct strbuf *buf)
safe_write(fd, buf->buf, buf->len);
}
+static void insert_one_alternate_ref(const struct ref *ref, void *unused)
+{
+ rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
+}
+
+static void insert_alternate_refs(void)
+{
+ for_each_alternate_ref(insert_one_alternate_ref, NULL);
+}
+
+#define INITIAL_FLUSH 16
+#define PIPESAFE_FLUSH 32
+#define LARGE_FLUSH 1024
+
+static int next_flush(int count)
+{
+ int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH;
+
+ if (count < flush_limit)
+ count <<= 1;
+ else
+ count += flush_limit;
+ return count;
+}
+
static int find_common(int fd[2], unsigned char *result_sha1,
struct ref *refs)
{
int fetching;
- int count = 0, flushes = 0, retval;
+ int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval;
const unsigned char *sha1;
unsigned in_vain = 0;
int got_continue = 0;
+ int got_ready = 0;
struct strbuf req_buf = STRBUF_INIT;
size_t state_len = 0;
@@ -235,6 +263,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
marked = 1;
for_each_ref(rev_list_insert_ref, NULL);
+ insert_alternate_refs();
fetching = 0;
for ( ; refs ; refs = refs->next) {
@@ -262,6 +291,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
struct strbuf c = STRBUF_INIT;
if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed");
if (multi_ack == 1) strbuf_addstr(&c, " multi_ack");
+ if (no_done) strbuf_addstr(&c, " no-done");
if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k");
if (use_sideband == 1) strbuf_addstr(&c, " side-band");
if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
@@ -332,19 +362,20 @@ static int find_common(int fd[2], unsigned char *result_sha1,
if (args.verbose)
fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
in_vain++;
- if (!(31 & ++count)) {
+ if (flush_at <= ++count) {
int ack;
packet_buf_flush(&req_buf);
send_request(fd[1], &req_buf);
strbuf_setlen(&req_buf, state_len);
flushes++;
+ flush_at = next_flush(count);
/*
* We keep one window "ahead" of the other side, and
* will wait for an ACK only on the next one
*/
- if (!args.stateless_rpc && count == 32)
+ if (!args.stateless_rpc && count == INITIAL_FLUSH)
continue;
consume_shallow_list(fd[0]);
@@ -379,6 +410,10 @@ static int find_common(int fd[2], unsigned char *result_sha1,
retval = 0;
in_vain = 0;
got_continue = 1;
+ if (ack == ACK_ready) {
+ rev_list = NULL;
+ got_ready = 1;
+ }
break;
}
}
@@ -392,8 +427,10 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
}
done:
- packet_buf_write(&req_buf, "done\n");
- send_request(fd[1], &req_buf);
+ if (!got_ready || !no_done) {
+ packet_buf_write(&req_buf, "done\n");
+ send_request(fd[1], &req_buf);
+ }
if (args.verbose)
fprintf(stderr, "done\n");
if (retval != 0) {
@@ -435,8 +472,10 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag,
}
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
- commit->object.flags |= COMPLETE;
- insert_by_date(commit, &complete);
+ if (!(commit->object.flags & COMPLETE)) {
+ commit->object.flags |= COMPLETE;
+ commit_list_insert_by_date(commit, &complete);
+ }
}
return 0;
}
@@ -696,6 +735,12 @@ static struct ref *do_fetch_pack(int fd[2],
if (args.verbose)
fprintf(stderr, "Server supports multi_ack_detailed\n");
multi_ack = 2;
+ if (server_supports("no-done")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports no-done\n");
+ if (args.stateless_rpc)
+ no_done = 1;
+ }
}
else if (server_supports("multi_ack")) {
if (args.verbose)
@@ -804,6 +849,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
char **pack_lockfile_ptr = NULL;
struct child_process *conn;
+ packet_trace_identity("fetch-pack");
+
nr_heads = 0;
heads = NULL;
for (i = 1; i < argc; i++) {
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 6eb1dfe..93c9938 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -12,11 +12,12 @@
#include "parse-options.h"
#include "sigchain.h"
#include "transport.h"
+#include "submodule.h"
static const char * const builtin_fetch_usage[] = {
"git fetch [<options>] [<repository> [<refspec>...]]",
"git fetch [<options>] <group>",
- "git fetch --multiple [<options>] [<repository> | <group>]...",
+ "git fetch --multiple [<options>] [(<repository> | <group>)...]",
"git fetch --all [<options>]",
NULL
};
@@ -28,12 +29,28 @@ enum {
};
static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress;
+static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int tags = TAGS_DEFAULT;
static const char *depth;
static const char *upload_pack;
static struct strbuf default_rla = STRBUF_INIT;
static struct transport *transport;
+static const char *submodule_prefix = "";
+static const char *recurse_submodules_default;
+
+static int option_parse_recurse_submodules(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ recurse_submodules = RECURSE_SUBMODULES_OFF;
+ } else {
+ if (arg)
+ recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg);
+ else
+ recurse_submodules = RECURSE_SUBMODULES_ON;
+ }
+ return 0;
+}
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
@@ -41,10 +58,9 @@ static struct option builtin_fetch_options[] = {
"fetch from all remotes"),
OPT_BOOLEAN('a', "append", &append,
"append to .git/FETCH_HEAD instead of overwriting"),
- OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ OPT_STRING(0, "upload-pack", &upload_pack, "path",
"path to upload pack on remote end"),
- OPT_BOOLEAN('f', "force", &force,
- "force overwrite of local branch"),
+ OPT__FORCE(&force, "force overwrite of local branch"),
OPT_BOOLEAN('m', "multiple", &multiple,
"fetch from multiple remotes"),
OPT_SET_INT('t', "tags", &tags,
@@ -52,15 +68,23 @@ static struct option builtin_fetch_options[] = {
OPT_SET_INT('n', NULL, &tags,
"do not fetch all tags (--no-tags)", TAGS_UNSET),
OPT_BOOLEAN('p', "prune", &prune,
- "prune tracking branches no longer on remote"),
+ "prune remote-tracking branches no longer on remote"),
+ { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand",
+ "control recursive fetching of submodules",
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules },
OPT_BOOLEAN(0, "dry-run", &dry_run,
"dry run"),
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
- OPT_STRING(0, "depth", &depth, "DEPTH",
+ OPT_STRING(0, "depth", &depth, "depth",
"deepen history of shallow clone"),
+ { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
+ "prepend this to submodule path output", PARSE_OPT_HIDDEN },
+ { OPTION_STRING, 0, "recurse-submodules-default",
+ &recurse_submodules_default, NULL,
+ "default mode for recursion", PARSE_OPT_HIDDEN },
OPT_END()
};
@@ -98,7 +122,7 @@ static void add_merge_config(struct ref **head,
continue;
/*
- * Not fetched to a tracking branch? We need to fetch
+ * Not fetched to a remote-tracking branch? We need to fetch
* it anyway to allow this branch's "branch.$name.merge"
* to be honored by 'git pull', but we do not have to
* fail if branch.$name.merge is misconfigured to point
@@ -146,7 +170,10 @@ static struct ref *get_ref_map(struct transport *transport,
struct remote *remote = transport->remote;
struct branch *branch = branch_get(NULL);
int has_merge = branch_has_merge_config(branch);
- if (remote && (remote->fetch_refspec_nr || has_merge)) {
+ if (remote &&
+ (remote->fetch_refspec_nr ||
+ /* Note: has_merge implies non-NULL branch->remote_name */
+ (has_merge && !strcmp(branch->remote_name, remote->name)))) {
for (i = 0; i < remote->fetch_refspec_nr; i++) {
get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
if (remote->fetch[i].dst &&
@@ -160,6 +187,8 @@ static struct ref *get_ref_map(struct transport *transport,
* if the remote we're fetching from is the same
* as given in branch.<name>.remote, we add the
* ref given in branch.<name>.merge, too.
+ *
+ * Note: has_merge implies non-NULL branch->remote_name
*/
if (has_merge &&
!strcmp(branch->remote_name, remote->name))
@@ -167,7 +196,7 @@ static struct ref *get_ref_map(struct transport *transport,
} else {
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
- die("Couldn't find remote ref HEAD");
+ die(_("Couldn't find remote ref HEAD"));
ref_map->merge = 1;
tail = &ref_map->next;
}
@@ -220,12 +249,12 @@ static int update_local_ref(struct ref *ref,
*display = 0;
type = sha1_object_info(ref->new_sha1, NULL);
if (type < 0)
- die("object %s not found", sha1_to_hex(ref->new_sha1));
+ die(_("object %s not found"), sha1_to_hex(ref->new_sha1));
if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
if (verbosity > 0)
sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
- "[up to date]", REFCOL_WIDTH, remote,
+ _("[up to date]"), REFCOL_WIDTH, remote,
pretty_ref);
return 0;
}
@@ -238,8 +267,8 @@ static int update_local_ref(struct ref *ref,
* If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty...
*/
- sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
- TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ sprintf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"),
+ TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
pretty_ref);
return 1;
}
@@ -249,8 +278,8 @@ static int update_local_ref(struct ref *ref,
int r;
r = s_update_ref("updating tag", ref, 0);
sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
- TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
- pretty_ref, r ? " (unable to update local ref)" : "");
+ TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote,
+ pretty_ref, r ? _(" (unable to update local ref)") : "");
return r;
}
@@ -262,17 +291,20 @@ static int update_local_ref(struct ref *ref,
int r;
if (!strncmp(ref->name, "refs/tags/", 10)) {
msg = "storing tag";
- what = "[new tag]";
+ what = _("[new tag]");
}
else {
msg = "storing head";
- what = "[new branch]";
+ what = _("[new branch]");
+ if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
+ (recurse_submodules != RECURSE_SUBMODULES_ON))
+ check_for_new_submodule_commits(ref->new_sha1);
}
r = s_update_ref(msg, ref, 0);
sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
- r ? " (unable to update local ref)" : "");
+ r ? _(" (unable to update local ref)") : "");
return r;
}
@@ -282,10 +314,13 @@ static int update_local_ref(struct ref *ref,
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "..");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
+ (recurse_submodules != RECURSE_SUBMODULES_ON))
+ check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref("fast-forward", ref, 1);
sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
- pretty_ref, r ? " (unable to update local ref)" : "");
+ pretty_ref, r ? _(" (unable to update local ref)") : "");
return r;
} else if (force || ref->force) {
char quickref[84];
@@ -293,16 +328,19 @@ static int update_local_ref(struct ref *ref,
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "...");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
+ (recurse_submodules != RECURSE_SUBMODULES_ON))
+ check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref("forced-update", ref, 1);
sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
pretty_ref,
- r ? "unable to update local ref" : "forced update");
+ r ? _("unable to update local ref") : _("forced update"));
return r;
} else {
- sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
- TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
- pretty_ref);
+ sprintf(display, "! %-*s %-*s -> %s %s",
+ TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
+ pretty_ref, _("(non-fast-forward)"));
return 1;
}
}
@@ -320,7 +358,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
fp = fopen(filename, "a");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error(_("cannot open %s: %s\n"), filename, strerror(errno));
if (raw_url)
url = transport_anonymize_url(raw_url);
@@ -354,7 +392,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
what = rm->name + 10;
}
else if (!prefixcmp(rm->name, "refs/remotes/")) {
- kind = "remote branch";
+ kind = "remote-tracking branch";
what = rm->name + 13;
}
else {
@@ -398,7 +436,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
REFCOL_WIDTH, *what ? what : "HEAD");
if (*note) {
if (verbosity >= 0 && !shown_url) {
- fprintf(stderr, "From %.*s\n",
+ fprintf(stderr, _("From %.*s\n"),
url_len, url);
shown_url = 1;
}
@@ -409,9 +447,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
free(url);
fclose(fp);
if (rc & STORE_REF_ERROR_DF_CONFLICT)
- error("some local refs could not be updated; try running\n"
+ error(_("some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting "
- "branches", remote_name);
+ "branches"), remote_name);
return rc;
}
@@ -459,7 +497,7 @@ static int quickfetch(struct ref *ref_map)
err = start_command(&revlist);
if (err) {
- error("could not run rev-list");
+ error(_("could not run rev-list"));
return err;
}
@@ -473,14 +511,14 @@ static int quickfetch(struct ref *ref_map)
if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
write_str_in_full(revlist.in, "\n") < 0) {
if (errno != EPIPE && errno != EINVAL)
- error("failed write to rev-list: %s", strerror(errno));
+ error(_("failed write to rev-list: %s"), strerror(errno));
err = -1;
break;
}
}
if (close(revlist.in)) {
- error("failed to close rev-list's stdin: %s", strerror(errno));
+ error(_("failed to close rev-list's stdin: %s"), strerror(errno));
err = -1;
}
@@ -507,16 +545,16 @@ static int prune_refs(struct transport *transport, struct ref *ref_map)
int result = 0;
struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
const char *dangling_msg = dry_run
- ? " (%s will become dangling)\n"
- : " (%s has become dangling)\n";
+ ? _(" (%s will become dangling)\n")
+ : _(" (%s has become dangling)\n");
for (ref = stale_refs; ref; ref = ref->next) {
if (!dry_run)
result |= delete_ref(ref->name, NULL, 0);
if (verbosity >= 0) {
fprintf(stderr, " x %-*s %-*s -> %s\n",
- TRANSPORT_SUMMARY_WIDTH, "[deleted]",
- REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+ TRANSPORT_SUMMARY_WIDTH, _("[deleted]"),
+ REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
warn_dangling_symref(stderr, dangling_msg, ref->name);
}
}
@@ -544,40 +582,14 @@ static int will_fetch(struct ref **head, const unsigned char *sha1)
return 0;
}
-struct tag_data {
- struct ref **head;
- struct ref ***tail;
-};
-
-static int add_to_tail(struct string_list_item *item, void *cb_data)
-{
- struct tag_data *data = (struct tag_data *)cb_data;
- struct ref *rm = NULL;
-
- /* We have already decided to ignore this item */
- if (!item->util)
- return 0;
-
- rm = alloc_ref(item->string);
- rm->peer_ref = alloc_ref(item->string);
- hashcpy(rm->old_sha1, item->util);
-
- **data->tail = rm;
- *data->tail = &rm->next;
-
- return 0;
-}
-
static void find_non_local_tags(struct transport *transport,
struct ref **head,
struct ref ***tail)
{
- struct string_list existing_refs = { NULL, 0, 0, 0 };
- struct string_list remote_refs = { NULL, 0, 0, 0 };
- struct tag_data data;
+ struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ struct string_list remote_refs = STRING_LIST_INIT_NODUP;
const struct ref *ref;
struct string_list_item *item = NULL;
- data.head = head; data.tail = tail;
for_each_ref(add_existing, &existing_refs);
for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
@@ -631,10 +643,20 @@ static void find_non_local_tags(struct transport *transport,
item->util = NULL;
/*
- * For all the tags in the remote_refs string list, call
- * add_to_tail to add them to the list of refs to be fetched
+ * For all the tags in the remote_refs string list,
+ * add them to the list of refs to be fetched
*/
- for_each_string_list(&remote_refs, add_to_tail, &data);
+ for_each_string_list_item(item, &remote_refs) {
+ /* Unless we have already decided to ignore this item... */
+ if (item->util)
+ {
+ struct ref *rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+ **tail = rm;
+ *tail = &rm->next;
+ }
+ }
string_list_clear(&remote_refs, 0);
}
@@ -649,8 +671,8 @@ static void check_not_current_branch(struct ref *ref_map)
for (; ref_map; ref_map = ref_map->next)
if (ref_map->peer_ref && !strcmp(current_branch->refname,
ref_map->peer_ref->name))
- die("Refusing to fetch into current branch %s "
- "of non-bare repository", current_branch->refname);
+ die(_("Refusing to fetch into current branch %s "
+ "of non-bare repository"), current_branch->refname);
}
static int truncate_fetch_head(void)
@@ -659,7 +681,7 @@ static int truncate_fetch_head(void)
FILE *fp = fopen(filename, "w");
if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
+ return error(_("cannot open %s: %s\n"), filename, strerror(errno));
fclose(fp);
return 0;
}
@@ -667,7 +689,7 @@ static int truncate_fetch_head(void)
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
- struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list existing_refs = STRING_LIST_INIT_NODUP;
struct string_list_item *peer_item = NULL;
struct ref *ref_map;
struct ref *rm;
@@ -675,13 +697,15 @@ static int do_fetch(struct transport *transport,
for_each_ref(add_existing, &existing_refs);
- if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
- tags = TAGS_SET;
- if (transport->remote->fetch_tags == -1)
- tags = TAGS_UNSET;
+ if (tags == TAGS_DEFAULT) {
+ if (transport->remote->fetch_tags == 2)
+ tags = TAGS_SET;
+ if (transport->remote->fetch_tags == -1)
+ tags = TAGS_UNSET;
+ }
if (!transport->get_refs_list || !transport->fetch)
- die("Don't know how to fetch from %s", transport->url);
+ die(_("Don't know how to fetch from %s"), transport->url);
/* if not appending, truncate FETCH_HEAD */
if (!append && !dry_run) {
@@ -735,10 +759,10 @@ static void set_option(const char *name, const char *value)
{
int r = transport_set_option(transport, name, value);
if (r < 0)
- die("Option \"%s\" value \"%s\" is not valid for %s",
+ die(_("Option \"%s\" value \"%s\" is not valid for %s"),
name, value, transport->url);
if (r > 0)
- warning("Option \"%s\" is ignored for %s\n",
+ warning(_("Option \"%s\" is ignored for %s\n"),
name, transport->url);
}
@@ -793,28 +817,38 @@ static int add_remote_or_group(const char *name, struct string_list *list)
return 1;
}
-static int fetch_multiple(struct string_list *list)
+static void add_options_to_argv(int *argc, const char **argv)
{
- int i, result = 0;
- const char *argv[11] = { "fetch", "--append" };
- int argc = 2;
-
if (dry_run)
- argv[argc++] = "--dry-run";
+ argv[(*argc)++] = "--dry-run";
if (prune)
- argv[argc++] = "--prune";
+ argv[(*argc)++] = "--prune";
if (update_head_ok)
- argv[argc++] = "--update-head-ok";
+ argv[(*argc)++] = "--update-head-ok";
if (force)
- argv[argc++] = "--force";
+ argv[(*argc)++] = "--force";
if (keep)
- argv[argc++] = "--keep";
+ argv[(*argc)++] = "--keep";
+ if (recurse_submodules == RECURSE_SUBMODULES_ON)
+ argv[(*argc)++] = "--recurse-submodules";
+ else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
+ argv[(*argc)++] = "--recurse-submodules=on-demand";
if (verbosity >= 2)
- argv[argc++] = "-v";
+ argv[(*argc)++] = "-v";
if (verbosity >= 1)
- argv[argc++] = "-v";
+ argv[(*argc)++] = "-v";
else if (verbosity < 0)
- argv[argc++] = "-q";
+ argv[(*argc)++] = "-q";
+
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+ int i, result = 0;
+ const char *argv[12] = { "fetch", "--append" };
+ int argc = 2;
+
+ add_options_to_argv(&argc, argv);
if (!append && !dry_run) {
int errcode = truncate_fetch_head();
@@ -827,9 +861,9 @@ static int fetch_multiple(struct string_list *list)
argv[argc] = name;
argv[argc + 1] = NULL;
if (verbosity >= 0)
- printf("Fetching %s\n", name);
+ printf(_("Fetching %s\n"), name);
if (run_command_v_opt(argv, RUN_GIT_CMD)) {
- error("Could not fetch %s", name);
+ error(_("Could not fetch %s"), name);
result = 1;
}
}
@@ -841,11 +875,13 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
{
int i;
static const char **refs = NULL;
+ struct refspec *refspec;
int ref_nr = 0;
int exit_code;
if (!remote)
- die("Where do you want to fetch from today?");
+ die(_("No remote repository specified. Please, specify either a URL or a\n"
+ "remote name from which new revisions should be fetched."));
transport = transport_get(remote, NULL);
transport_set_verbosity(transport, verbosity, progress);
@@ -864,7 +900,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
char *ref;
i++;
if (i >= argc)
- die("You need to specify a tag name.");
+ die(_("You need to specify a tag name."));
ref = xmalloc(strlen(argv[i]) * 2 + 22);
strcpy(ref, "refs/tags/");
strcat(ref, argv[i]);
@@ -880,8 +916,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
- exit_code = do_fetch(transport,
- parse_fetch_refspec(ref_nr, refs), ref_nr);
+ refspec = parse_fetch_refspec(ref_nr, refs);
+ exit_code = do_fetch(transport, refspec, ref_nr);
+ free(refspec);
transport_disconnect(transport);
transport = NULL;
return exit_code;
@@ -890,10 +927,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
int cmd_fetch(int argc, const char **argv, const char *prefix)
{
int i;
- struct string_list list = { NULL, 0, 0, 0 };
+ struct string_list list = STRING_LIST_INIT_NODUP;
struct remote *remote;
int result = 0;
+ packet_trace_identity("fetch");
+
/* Record the command line for the reflog */
strbuf_addstr(&default_rla, "fetch");
for (i = 1; i < argc; i++)
@@ -904,9 +943,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (all) {
if (argc == 1)
- die("fetch --all does not take a repository argument");
+ die(_("fetch --all does not take a repository argument"));
else if (argc > 1)
- die("fetch --all does not make sense with refspecs");
+ die(_("fetch --all does not make sense with refspecs"));
(void) for_each_remote(get_one_remote_for_fetch, &list);
result = fetch_multiple(&list);
} else if (argc == 0) {
@@ -917,7 +956,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
/* All arguments are assumed to be remotes or groups */
for (i = 0; i < argc; i++)
if (!add_remote_or_group(argv[i], &list))
- die("No such remote or remote group: %s", argv[i]);
+ die(_("No such remote or remote group: %s"), argv[i]);
result = fetch_multiple(&list);
} else {
/* Single remote or group */
@@ -925,7 +964,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (list.nr > 1) {
/* More than one remote */
if (argc > 1)
- die("Fetching a group and specifying refspecs does not make sense");
+ die(_("Fetching a group and specifying refspecs does not make sense"));
result = fetch_multiple(&list);
} else {
/* Zero or one remotes */
@@ -934,6 +973,22 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
}
+ if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
+ const char *options[10];
+ int num_options = 0;
+ if (recurse_submodules_default) {
+ int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
+ set_config_fetch_recurse_submodules(arg);
+ }
+ gitmodules_config();
+ git_config(submodule_config, NULL);
+ add_options_to_argv(&num_options, options);
+ result = fetch_populated_submodules(num_options, options,
+ submodule_prefix,
+ recurse_submodules,
+ verbosity < 0);
+ }
+
/* All names were strdup()ed or strndup()ed */
list.strdup_strings = 1;
string_list_clear(&list, 0);
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index bc3c5e6..7581632 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -7,21 +7,22 @@
#include "string-list.h"
static const char * const fmt_merge_msg_usage[] = {
- "git fmt-merge-msg [--log|--no-log] [--file <file>]",
+ "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
NULL
};
-static int merge_summary;
+static int shortlog_len;
static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
{
- static int found_merge_log = 0;
- if (!strcmp("merge.log", key)) {
- found_merge_log = 1;
- merge_summary = git_config_bool(key, value);
+ if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
+ int is_bool;
+ shortlog_len = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool && shortlog_len < 0)
+ return error("%s: negative length %s", key, value);
+ if (is_bool && shortlog_len)
+ shortlog_len = DEFAULT_MERGE_LOG_LEN;
}
- if (!found_merge_log && !strcmp("merge.summary", key))
- merge_summary = git_config_bool(key, value);
return 0;
}
@@ -30,7 +31,7 @@ struct src_data {
int head_status;
};
-void init_src_data(struct src_data *data)
+static void init_src_data(struct src_data *data)
{
data->branch.strdup_strings = 1;
data->tag.strdup_strings = 1;
@@ -38,8 +39,8 @@ void init_src_data(struct src_data *data)
data->generic.strdup_strings = 1;
}
-static struct string_list srcs = { NULL, 0, 0, 1 };
-static struct string_list origins = { NULL, 0, 0, 1 };
+static struct string_list srcs = STRING_LIST_INIT_DUP;
+static struct string_list origins = STRING_LIST_INIT_DUP;
static int handle_line(char *line)
{
@@ -99,8 +100,8 @@ static int handle_line(char *line)
origin = line;
string_list_append(&src_data->tag, origin + 4);
src_data->head_status |= 2;
- } else if (!prefixcmp(line, "remote branch ")) {
- origin = line + 14;
+ } else if (!prefixcmp(line, "remote-tracking branch ")) {
+ origin = line + strlen("remote-tracking branch ");
string_list_append(&src_data->r_branch, origin);
src_data->head_status |= 2;
} else {
@@ -146,7 +147,7 @@ static void shortlog(const char *name, unsigned char *sha1,
int i, count = 0;
struct commit *commit;
struct object *branch;
- struct string_list subjects = { NULL, 0, 0, 1 };
+ struct string_list subjects = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
@@ -232,7 +233,7 @@ static void do_fmt_merge_msg_title(struct strbuf *out,
if (src_data->r_branch.nr) {
strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("remote branch ", "remote branches ",
+ print_joined("remote-tracking branch ", "remote-tracking branches ",
&src_data->r_branch, out);
}
if (src_data->tag.nr) {
@@ -255,9 +256,9 @@ static void do_fmt_merge_msg_title(struct strbuf *out,
strbuf_addf(out, " into %s\n", current_branch);
}
-static int do_fmt_merge_msg(int merge_title, int merge_summary,
- struct strbuf *in, struct strbuf *out) {
- int limit = 20, i = 0, pos = 0;
+static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
+ struct strbuf *out, int shortlog_len) {
+ int i = 0, pos = 0;
unsigned char head_sha1[20];
const char *current_branch;
@@ -288,7 +289,7 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary,
if (merge_title)
do_fmt_merge_msg_title(out, current_branch);
- if (merge_summary) {
+ if (shortlog_len) {
struct commit *head;
struct rev_info rev;
@@ -303,27 +304,30 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary,
for (i = 0; i < origins.nr; i++)
shortlog(origins.items[i].string, origins.items[i].util,
- head, &rev, limit, out);
+ head, &rev, shortlog_len, out);
}
return 0;
}
-int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
- return do_fmt_merge_msg(1, merge_summary, in, out);
-}
-
-int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) {
- return do_fmt_merge_msg(0, 1, in, out);
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ int merge_title, int shortlog_len) {
+ return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
+ const char *message = NULL;
struct option options[] = {
- OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),
- { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL,
+ { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
+ "populate log with at most <n> entries from shortlog",
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
+ { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
"alias for --log (deprecated)",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
+ DEFAULT_MERGE_LOG_LEN },
+ OPT_STRING('m', "message", &message, "text",
+ "use <text> as start of message"),
OPT_FILENAME('F', "file", &inpath, "file to read from"),
OPT_END()
};
@@ -337,6 +341,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
0);
if (argc > 0)
usage_with_options(fmt_merge_msg_usage, options);
+ if (message && !shortlog_len) {
+ char nl = '\n';
+ write_in_full(STDOUT_FILENO, message, strlen(message));
+ write_in_full(STDOUT_FILENO, &nl, 1);
+ return 0;
+ }
+ if (shortlog_len < 0)
+ die("Negative --log=%d", shortlog_len);
if (inpath && strcmp(inpath, "-")) {
in = fopen(inpath, "r");
@@ -346,7 +358,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (strbuf_read(&input, fileno(in), 0) < 0)
die_errno("could not read input file");
- ret = fmt_merge_msg(merge_summary, &input, &output);
+
+ if (message)
+ strbuf_addstr(&output, message);
+ ret = fmt_merge_msg(&input, &output,
+ message ? 0 : 1,
+ shortlog_len);
+
if (ret)
return ret;
write_in_full(STDOUT_FILENO, output.buf, output.len);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index a2b28c6..89e75c6 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -228,7 +228,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object
v->s = s;
}
else if (!strcmp(name, "objectname:short")) {
- v->s = find_unique_abbrev(obj->sha1, DEFAULT_ABBREV);
+ v->s = xstrdup(find_unique_abbrev(obj->sha1,
+ DEFAULT_ABBREV));
}
}
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 0929c7f..5ae0366 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -74,7 +74,13 @@ static int mark_object(struct object *obj, int type, void *data)
{
struct object *parent = data;
+ /*
+ * The only case data is NULL or type is OBJ_ANY is when
+ * mark_object_reachable() calls us. All the callers of
+ * that function has non-NULL obj hence ...
+ */
if (!obj) {
+ /* ... these references to parent->fld are safe here */
printf("broken link from %7s %s\n",
typename(parent->type), sha1_to_hex(parent->sha1));
printf("broken link from %7s %s\n",
@@ -84,6 +90,7 @@ static int mark_object(struct object *obj, int type, void *data)
}
if (type != OBJ_ANY && obj->type != type)
+ /* ... and the reference to parent is safe here */
objerror(parent, "wrong object type in link");
if (obj->flags & REACHABLE)
@@ -109,7 +116,7 @@ static void mark_object_reachable(struct object *obj)
mark_object(obj, OBJ_ANY, NULL);
}
-static int traverse_one_object(struct object *obj, struct object *parent)
+static int traverse_one_object(struct object *obj)
{
int result;
struct tree *tree = NULL;
@@ -133,12 +140,11 @@ static int traverse_reachable(void)
int result = 0;
while (pending.nr) {
struct object_array_entry *entry;
- struct object *obj, *parent;
+ struct object *obj;
entry = pending.objects + --pending.nr;
obj = entry->item;
- parent = (struct object *) entry->name;
- result |= traverse_one_object(obj, parent);
+ result |= traverse_one_object(obj);
}
return !!result;
}
@@ -385,10 +391,20 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino)
sha1_list.nr = ++nr;
}
+static inline int is_loose_object_file(struct dirent *de,
+ char *name, unsigned char *sha1)
+{
+ if (strlen(de->d_name) != 38)
+ return 0;
+ memcpy(name + 2, de->d_name, 39);
+ return !get_sha1_hex(name, sha1);
+}
+
static void fsck_dir(int i, char *path)
{
DIR *dir = opendir(path);
struct dirent *de;
+ char name[100];
if (!dir)
return;
@@ -396,17 +412,13 @@ static void fsck_dir(int i, char *path)
if (verbose)
fprintf(stderr, "Checking directory %s\n", path);
+ sprintf(name, "%02x", i);
while ((de = readdir(dir)) != NULL) {
- char name[100];
unsigned char sha1[20];
if (is_dot_or_dotdot(de->d_name))
continue;
- if (strlen(de->d_name) == 38) {
- sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, 39);
- if (get_sha1_hex(name, sha1) < 0)
- break;
+ if (is_loose_object_file(de, name, sha1)) {
add_sha1_list(sha1, DIRENT_SORT_HINT(de));
continue;
}
@@ -556,8 +568,8 @@ static int fsck_cache_tree(struct cache_tree *it)
sha1_to_hex(it->sha1));
return 1;
}
- mark_object_reachable(obj);
obj->used = 1;
+ mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, "non-tree in cache-tree");
}
@@ -572,7 +584,7 @@ static char const * const fsck_usage[] = {
};
static struct option fsck_opts[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
diff --git a/builtin/gc.c b/builtin/gc.c
index c304638..0498094 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -60,7 +60,7 @@ static int gc_config(const char *var, const char *value, void *cb)
if (value && strcmp(value, "now")) {
unsigned long now = approxidate("now");
if (approxidate(value) >= now)
- return error("Invalid %s: '%s'", var, value);
+ return error(_("Invalid %s: '%s'"), var, value);
}
return git_config_string(&prune_expire, var, value);
}
@@ -75,7 +75,7 @@ static void append_option(const char **cmd, const char *opt, int max_length)
;
if (i + 2 >= max_length)
- die("Too many options specified");
+ die(_("Too many options specified"));
cmd[i++] = opt;
cmd[i] = NULL;
}
@@ -100,7 +100,7 @@ static int too_many_loose_objects(void)
return 0;
if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
- warning("insanely long object directory %.*s", 50, objdir);
+ warning(_("insanely long object directory %.*s"), 50, objdir);
return 0;
}
dir = opendir(path);
@@ -180,7 +180,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
char buf[80];
struct option builtin_gc_options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "suppress progress reporting"),
{ OPTION_STRING, 0, "prune", &prune_expire, "date",
"prune unreferenced objects",
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
@@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_gc_usage, builtin_gc_options);
+
git_config(gc_config, NULL);
if (pack_refs < 0)
@@ -216,13 +219,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
*/
if (!need_to_gc())
return 0;
- fprintf(stderr,
- "Auto packing the repository for optimum performance.%s\n",
- quiet
- ? ""
- : (" You may also\n"
- "run \"git gc\" manually. See "
- "\"git help gc\" for more information."));
+ if (quiet)
+ fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
+ else
+ fprintf(stderr,
+ _("Auto packing the repository for optimum performance. You may also\n"
+ "run \"git gc\" manually. See "
+ "\"git help gc\" for more information.\n"));
} else
append_option(argv_repack,
prune_expire && !strcmp(prune_expire, "now")
@@ -248,8 +251,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return error(FAILED_RUN, argv_rerere[0]);
if (auto_gc && too_many_loose_objects())
- warning("There are too many unreachable loose objects; "
- "run 'git prune' to remove them.");
+ warning(_("There are too many unreachable loose objects; "
+ "run 'git prune' to remove them."));
return 0;
}
diff --git a/builtin/grep.c b/builtin/grep.c
index 232cd1c..cccf8da 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,14 +17,10 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static char const * const grep_usage[] = {
- "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
+ "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
NULL
};
@@ -44,8 +40,7 @@ enum work_type {WORK_SHA1, WORK_FILE};
* threads. The producer adds struct work_items to 'todo' and the
* consumers pick work items from the same array.
*/
-struct work_item
-{
+struct work_item {
enum work_type type;
char *name;
@@ -98,8 +93,7 @@ static pthread_cond_t cond_write;
/* Signalled when we are finished with everything. */
static pthread_cond_t cond_result;
-static int print_hunk_marks_between_files;
-static int printed_something;
+static int skip_first_line;
static void add_work(enum work_type type, char *name, void *id)
{
@@ -165,10 +159,20 @@ static void work_done(struct work_item *w)
todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
w = &todo[todo_done];
if (w->out.len) {
- if (print_hunk_marks_between_files && printed_something)
- write_or_die(1, "--\n", 3);
- write_or_die(1, w->out.buf, w->out.len);
- printed_something = 1;
+ const char *p = w->out.buf;
+ size_t len = w->out.len;
+
+ /* Skip the leading hunk mark of the first file. */
+ if (skip_first_line) {
+ while (len) {
+ len--;
+ if (*p++ == '\n')
+ break;
+ }
+ skip_first_line = 0;
+ }
+
+ write_or_die(1, p, len);
}
free(w->name);
free(w->identifier);
@@ -249,7 +253,7 @@ static void start_threads(struct grep_opt *opt)
err = pthread_create(&threads[i], NULL, run, o);
if (err)
- die("grep: failed to create thread: %s",
+ die(_("grep: failed to create thread: %s"),
strerror(err));
}
}
@@ -307,6 +311,19 @@ static int grep_config(const char *var, const char *value, void *cb)
default: return 0;
}
+ if (!strcmp(var, "grep.extendedregexp")) {
+ if (git_config_bool(var, value))
+ opt->regflags |= REG_EXTENDED;
+ else
+ opt->regflags &= ~REG_EXTENDED;
+ return 0;
+ }
+
+ if (!strcmp(var, "grep.linenumber")) {
+ opt->linenum = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "color.grep"))
opt->color = git_config_colorbool(var, value, -1);
else if (!strcmp(var, "color.grep.context"))
@@ -333,106 +350,6 @@ static int grep_config(const char *var, const char *value, void *cb)
return 0;
}
-/*
- * Return non-zero if max_depth is negative or path has no more then max_depth
- * slashes.
- */
-static int accept_subdir(const char *path, int max_depth)
-{
- if (max_depth < 0)
- return 1;
-
- while ((path = strchr(path, '/')) != NULL) {
- max_depth--;
- if (max_depth < 0)
- return 0;
- path++;
- }
- return 1;
-}
-
-/*
- * Return non-zero if name is a subdirectory of match and is not too deep.
- */
-static int is_subdir(const char *name, int namelen,
- const char *match, int matchlen, int max_depth)
-{
- if (matchlen > namelen || strncmp(name, match, matchlen))
- return 0;
-
- if (name[matchlen] == '\0') /* exact match */
- return 1;
-
- if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
- return accept_subdir(name + matchlen + 1, max_depth);
-
- return 0;
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-static int pathspec_matches(const char **paths, const char *name, int max_depth)
-{
- int namelen, i;
- if (!paths || !*paths)
- return accept_subdir(name, max_depth);
- namelen = strlen(name);
- for (i = 0; paths[i]; i++) {
- const char *match = paths[i];
- int matchlen = strlen(match);
- const char *cp, *meta;
-
- if (is_subdir(name, namelen, match, matchlen, max_depth))
- return 1;
- if (!fnmatch(match, name, 0))
- return 1;
- if (name[namelen-1] != '/')
- continue;
-
- /* We are being asked if the directory ("name") is worth
- * descending into.
- *
- * Find the longest leading directory name that does
- * not have metacharacter in the pathspec; the name
- * we are looking at must overlap with that directory.
- */
- for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
- char ch = *cp;
- if (ch == '*' || ch == '[' || ch == '?') {
- meta = cp;
- break;
- }
- }
- if (!meta)
- meta = cp; /* fully literal */
-
- if (namelen <= meta - match) {
- /* Looking at "Documentation/" and
- * the pattern says "Documentation/howto/", or
- * "Documentation/diff*.txt". The name we
- * have should match prefix.
- */
- if (!memcmp(match, name, namelen))
- return 1;
- continue;
- }
-
- if (meta - match < namelen) {
- /* Looking at "Documentation/howto/" and
- * the pattern says "Documentation/h*";
- * match up to "Do.../h"; this avoids descending
- * into "Documentation/technical/".
- */
- if (!memcmp(match, name, meta - match))
- return 1;
- continue;
- }
- }
- return 0;
-}
-
static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
void *data;
@@ -454,7 +371,7 @@ static void *load_sha1(const unsigned char *sha1, unsigned long *size,
void *data = lock_and_read_sha1_file(sha1, &type, size);
if (!data)
- error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+ error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
return data;
}
@@ -505,21 +422,21 @@ static void *load_file(const char *filename, size_t *sz)
if (lstat(filename, &st) < 0) {
err_ret:
if (errno != ENOENT)
- error("'%s': %s", filename, strerror(errno));
- return 0;
+ error(_("'%s': %s"), filename, strerror(errno));
+ return NULL;
}
if (!S_ISREG(st.st_mode))
- return 0;
+ return NULL;
*sz = xsize_t(st.st_size);
i = open(filename, O_RDONLY);
if (i < 0)
goto err_ret;
data = xmalloc(*sz + 1);
if (st.st_size != read_in_full(i, data, *sz)) {
- error("'%s': short read %s", filename, strerror(errno));
+ error(_("'%s': short read %s"), filename, strerror(errno));
close(i);
free(data);
- return 0;
+ return NULL;
}
close(i);
data[*sz] = 0;
@@ -578,14 +495,14 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
argv[path_list->nr] = NULL;
if (prefix && chdir(prefix))
- die("Failed to chdir: %s", prefix);
+ die(_("Failed to chdir: %s"), prefix);
status = run_command_v_opt(argv, RUN_USING_SHELL);
if (status)
exit(status);
free(argv);
}
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
{
int hit = 0;
int nr;
@@ -595,7 +512,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode))
continue;
- if (!pathspec_matches(paths, ce->name, opt->max_depth))
+ if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
continue;
/*
* If CE_VALID is on, we assume worktree file and its cache entry
@@ -622,44 +539,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
return hit;
}
-static int grep_tree(struct grep_opt *opt, const char **paths,
- struct tree_desc *tree,
- const char *tree_name, const char *base)
+static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
+ struct tree_desc *tree, struct strbuf *base, int tn_len)
{
- int len;
- int hit = 0;
+ int hit = 0, match = 0;
struct name_entry entry;
- char *down;
- int tn_len = strlen(tree_name);
- struct strbuf pathbuf;
-
- strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
- if (tn_len) {
- strbuf_add(&pathbuf, tree_name, tn_len);
- strbuf_addch(&pathbuf, ':');
- tn_len = pathbuf.len;
- }
- strbuf_addstr(&pathbuf, base);
- len = pathbuf.len;
+ int old_baselen = base->len;
while (tree_entry(tree, &entry)) {
int te_len = tree_entry_len(entry.path, entry.sha1);
- pathbuf.len = len;
- strbuf_add(&pathbuf, entry.path, te_len);
-
- if (S_ISDIR(entry.mode))
- /* Match "abc/" against pathspec to
- * decide if we want to descend into "abc"
- * directory.
- */
- strbuf_addch(&pathbuf, '/');
-
- down = pathbuf.buf + tn_len;
- if (!pathspec_matches(paths, down, opt->max_depth))
- ;
- else if (S_ISREG(entry.mode))
- hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+
+ if (match != 2) {
+ match = tree_entry_interesting(&entry, base, tn_len, pathspec);
+ if (match < 0)
+ break;
+ if (match == 0)
+ continue;
+ }
+
+ strbuf_add(base, entry.path, te_len);
+
+ if (S_ISREG(entry.mode)) {
+ hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
+ }
else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
@@ -668,20 +570,23 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
data = lock_and_read_sha1_file(entry.sha1, &type, &size);
if (!data)
- die("unable to read tree (%s)",
+ die(_("unable to read tree (%s)"),
sha1_to_hex(entry.sha1));
+
+ strbuf_addch(base, '/');
init_tree_desc(&sub, data, size);
- hit |= grep_tree(opt, paths, &sub, tree_name, down);
+ hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
free(data);
}
+ strbuf_setlen(base, old_baselen);
+
if (hit && opt->status_only)
break;
}
- strbuf_release(&pathbuf);
return hit;
}
-static int grep_object(struct grep_opt *opt, const char **paths,
+static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct object *obj, const char *name)
{
if (obj->type == OBJ_BLOB)
@@ -690,20 +595,30 @@ static int grep_object(struct grep_opt *opt, const char **paths,
struct tree_desc tree;
void *data;
unsigned long size;
- int hit;
+ struct strbuf base;
+ int hit, len;
+
data = read_object_with_reference(obj->sha1, tree_type,
&size, NULL);
if (!data)
- die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+ die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
+
+ len = name ? strlen(name) : 0;
+ strbuf_init(&base, PATH_MAX + len + 1);
+ if (len) {
+ strbuf_add(&base, name, len);
+ strbuf_addch(&base, ':');
+ }
init_tree_desc(&tree, data, size);
- hit = grep_tree(opt, paths, &tree, name, "");
+ hit = grep_tree(opt, pathspec, &tree, &base, base.len);
+ strbuf_release(&base);
free(data);
return hit;
}
- die("unable to grep from object of type %s", typename(obj->type));
+ die(_("unable to grep from object of type %s"), typename(obj->type));
}
-static int grep_objects(struct grep_opt *opt, const char **paths,
+static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
const struct object_array *list)
{
unsigned int i;
@@ -713,7 +628,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
for (i = 0; i < nr; i++) {
struct object *real_obj;
real_obj = deref_tag(list->objects[i].item, NULL, 0);
- if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+ if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
hit = 1;
if (opt->status_only)
break;
@@ -722,7 +637,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
return hit;
}
-static int grep_directory(struct grep_opt *opt, const char **paths)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
{
struct dir_struct dir;
int i, hit = 0;
@@ -730,8 +645,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths)
memset(&dir, 0, sizeof(dir));
setup_standard_excludes(&dir);
- fill_directory(&dir, paths);
+ fill_directory(&dir, pathspec->raw);
for (i = 0; i < dir.nr; i++) {
+ const char *name = dir.entries[i]->name;
+ int namelen = strlen(name);
+ if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL))
+ continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
break;
@@ -752,7 +671,7 @@ static int context_callback(const struct option *opt, const char *arg,
}
value = strtol(arg, (char **)&endp, 10);
if (*endp) {
- return error("switch `%c' expects a numerical value",
+ return error(_("switch `%c' expects a numerical value"),
opt->short_name);
}
grep_opt->pre_context = grep_opt->post_context = value;
@@ -762,13 +681,14 @@ static int context_callback(const struct option *opt, const char *arg,
static int file_callback(const struct option *opt, const char *arg, int unset)
{
struct grep_opt *grep_opt = opt->value;
+ int from_stdin = !strcmp(arg, "-");
FILE *patterns;
int lno = 0;
struct strbuf sb = STRBUF_INIT;
- patterns = fopen(arg, "r");
+ patterns = from_stdin ? stdin : fopen(arg, "r");
if (!patterns)
- die_errno("cannot open '%s'", arg);
+ die_errno(_("cannot open '%s'"), arg);
while (strbuf_getline(&sb, patterns, '\n') == 0) {
char *s;
size_t len;
@@ -780,7 +700,8 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
s = strbuf_detach(&sb, &len);
append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
}
- fclose(patterns);
+ if (!from_stdin)
+ fclose(patterns);
strbuf_release(&sb);
return 0;
}
@@ -834,12 +755,22 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
int external_grep_allowed__ignored;
const char *show_in_pager = NULL, *default_pager = "dummy";
struct grep_opt opt;
- struct object_array list = { 0, 0, NULL };
+ struct object_array list = OBJECT_ARRAY_INIT;
const char **paths = NULL;
- struct string_list path_list = { NULL, 0, 0, 0 };
+ struct pathspec pathspec;
+ struct string_list path_list = STRING_LIST_INIT_NODUP;
int i;
int dummy;
- int nongit = 0, use_index = 1;
+ int use_index = 1;
+ enum {
+ pattern_type_unspecified = 0,
+ pattern_type_bre,
+ pattern_type_ere,
+ pattern_type_fixed,
+ pattern_type_pcre,
+ };
+ int pattern_type = pattern_type_unspecified;
+
struct option options[] = {
OPT_BOOLEAN(0, "cached", &cached,
"search in index instead of in the work tree"),
@@ -861,15 +792,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
"descend at most <depth> levels", PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""),
- OPT_BIT('E', "extended-regexp", &opt.regflags,
- "use extended POSIX regular expressions", REG_EXTENDED),
- OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
- "use basic POSIX regular expressions (default)",
- REG_EXTENDED),
- OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
- "interpret patterns as fixed strings"),
+ OPT_SET_INT('E', "extended-regexp", &pattern_type,
+ "use extended POSIX regular expressions",
+ pattern_type_ere),
+ OPT_SET_INT('G', "basic-regexp", &pattern_type,
+ "use basic POSIX regular expressions (default)",
+ pattern_type_bre),
+ OPT_SET_INT('F', "fixed-strings", &pattern_type,
+ "interpret patterns as fixed strings",
+ pattern_type_fixed),
+ OPT_SET_INT('P', "perl-regexp", &pattern_type,
+ "use Perl-compatible regular expressions",
+ pattern_type_pcre),
OPT_GROUP(""),
- OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
+ OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"),
OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
OPT_NEGBIT(0, "full-name", &opt.relative,
@@ -886,6 +822,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN('c', "count", &opt.count,
"show the number of matches instead of matching lines"),
OPT__COLOR(&opt.color, "highlight matches"),
+ OPT_BOOLEAN(0, "break", &opt.file_break,
+ "print empty line between matches from different files"),
+ OPT_BOOLEAN(0, "heading", &opt.heading,
+ "show filename only once above matches from same file"),
OPT_GROUP(""),
OPT_CALLBACK('C', NULL, &opt, "n",
"show <n> context lines before and after matches",
@@ -915,8 +855,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
close_callback },
- OPT_BOOLEAN('q', "quiet", &opt.status_only,
- "indicate hit with exit status without output"),
+ OPT__QUIET(&opt.status_only,
+ "indicate hit with exit status without output"),
OPT_BOOLEAN(0, "all-match", &opt.all_match,
"show only matches from files that match all patterns"),
OPT_GROUP(""),
@@ -930,8 +870,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_END()
};
- prefix = setup_git_directory_gently(&nongit);
-
/*
* 'git grep -h', unlike 'git grep -h <pattern>', is a request
* to show usage information and exit.
@@ -975,8 +913,30 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION |
PARSE_OPT_NO_INTERNAL_HELP);
+ switch (pattern_type) {
+ case pattern_type_fixed:
+ opt.fixed = 1;
+ opt.pcre = 0;
+ break;
+ case pattern_type_bre:
+ opt.fixed = 0;
+ opt.pcre = 0;
+ opt.regflags &= ~REG_EXTENDED;
+ break;
+ case pattern_type_ere:
+ opt.fixed = 0;
+ opt.pcre = 0;
+ opt.regflags |= REG_EXTENDED;
+ break;
+ case pattern_type_pcre:
+ opt.fixed = 0;
+ opt.pcre = 1;
+ break;
+ default:
+ break; /* nothing */
+ }
- if (use_index && nongit)
+ if (use_index && !startup_info->have_repository)
/* die the same way as if we did it at the beginning */
setup_git_directory();
@@ -1001,6 +961,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (show_in_pager == default_pager)
show_in_pager = git_pager(1);
if (show_in_pager) {
+ opt.color = 0;
opt.name_only = 1;
opt.null_following_name = 1;
opt.output_priv = &path_list;
@@ -1010,19 +971,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
if (!opt.pattern_list)
- die("no pattern given.");
+ die(_("no pattern given."));
if (!opt.fixed && opt.ignore_case)
opt.regflags |= REG_ICASE;
- if ((opt.regflags != REG_NEWLINE) && opt.fixed)
- die("cannot mix --fixed-strings and regexp");
#ifndef NO_PTHREADS
if (online_cpus() == 1 || !grep_threads_ok(&opt))
use_threads = 0;
if (use_threads) {
- if (opt.pre_context || opt.post_context)
- print_hunk_marks_between_files = 1;
+ if (opt.pre_context || opt.post_context || opt.file_break)
+ skip_first_line = 1;
start_threads(&opt);
}
#else
@@ -1039,7 +998,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!get_sha1(arg, sha1)) {
struct object *object = parse_object(sha1);
if (!object)
- die("bad object %s", arg);
+ die(_("bad object %s"), arg);
add_object_array(object, arg, &list);
continue;
}
@@ -1057,16 +1016,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
verify_filename(prefix, argv[j]);
}
- if (i < argc)
- paths = get_pathspec(prefix, argv + i);
- else if (prefix) {
- paths = xcalloc(2, sizeof(const char *));
- paths[0] = prefix;
- paths[1] = NULL;
- }
+ paths = get_pathspec(prefix, argv + i);
+ init_pathspec(&pathspec, paths);
+ pathspec.max_depth = opt.max_depth;
+ pathspec.recursive = 1;
if (show_in_pager && (cached || list.nr))
- die("--open-files-in-pager only works on the worktree");
+ die(_("--open-files-in-pager only works on the worktree"));
if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) {
const char *pager = path_list.items[0].string;
@@ -1091,19 +1047,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!use_index) {
if (cached)
- die("--cached cannot be used with --no-index.");
+ die(_("--cached cannot be used with --no-index."));
if (list.nr)
- die("--no-index cannot be used with revs.");
- hit = grep_directory(&opt, paths);
+ die(_("--no-index cannot be used with revs."));
+ hit = grep_directory(&opt, &pathspec);
} else if (!list.nr) {
if (!cached)
setup_work_tree();
- hit = grep_cache(&opt, paths, cached);
+ hit = grep_cache(&opt, &pathspec, cached);
} else {
if (cached)
- die("both --cached and trees are given.");
- hit = grep_objects(&opt, paths, &list);
+ die(_("both --cached and trees are given."));
+ hit = grep_objects(&opt, &pathspec, &list);
}
if (use_threads)
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 080af1a..33911fd 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -4,7 +4,7 @@
* Copyright (C) Linus Torvalds, 2005
* Copyright (C) Junio C Hamano, 2005
*/
-#include "cache.h"
+#include "builtin.h"
#include "blob.h"
#include "quote.h"
#include "parse-options.h"
@@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path
{
struct stat st;
unsigned char sha1[20];
+ unsigned flags = (HASH_FORMAT_CHECK |
+ (write_object ? HASH_WRITE_OBJECT : 0));
+
if (fstat(fd, &st) < 0 ||
- index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+ index_fd(sha1, fd, &st, type_from_string(type), path, flags))
die(write_object
? "Unable to add %s to database"
: "Unable to hash %s", path);
diff --git a/builtin/help.c b/builtin/help.c
index a9836b00..61ff798 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -120,7 +120,7 @@ static void exec_woman_emacs(const char *path, const char *page)
if (!path)
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
- execlp(path, "emacsclient", "-e", man_page.buf, NULL);
+ execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
}
@@ -148,7 +148,7 @@ static void exec_man_konqueror(const char *path, const char *page)
} else
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
- execlp(path, filename, "newTab", man_page.buf, NULL);
+ execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
}
@@ -157,7 +157,7 @@ static void exec_man_man(const char *path, const char *page)
{
if (!path)
path = "man";
- execlp(path, "man", page, NULL);
+ execlp(path, "man", page, (char *)NULL);
warning("failed to exec '%s': %s", path, strerror(errno));
}
@@ -165,7 +165,7 @@ static void exec_man_cmd(const char *cmd, const char *page)
{
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
- execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
+ execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
warning("failed to exec '%s': %s", cmd, strerror(errno));
}
@@ -372,7 +372,7 @@ static void show_info_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
- execlp("info", "info", "gitman", page, NULL);
+ execlp("info", "info", "gitman", page, (char *)NULL);
die("no info viewer handled the request");
}
@@ -398,7 +398,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
#ifndef open_html
static void open_html(const char *path)
{
- execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+ execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
}
#endif
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index a89ae83..0945adb 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "delta.h"
#include "pack.h"
#include "csum-file.h"
@@ -11,15 +11,16 @@
#include "exec_cmd.h"
static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [{ --keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
-struct object_entry
-{
+struct object_entry {
struct pack_idx_entry idx;
unsigned long size;
unsigned int hdr_size;
enum object_type type;
enum object_type real_type;
+ unsigned delta_depth;
+ int base_object_no;
};
union delta_base {
@@ -44,8 +45,7 @@ struct base_data {
#define FLAG_LINK (1u<<20)
#define FLAG_CHECKED (1u<<21)
-struct delta_entry
-{
+struct delta_entry {
union delta_base base;
int obj_no;
};
@@ -68,6 +68,7 @@ static struct progress *progress;
static unsigned char input_buffer[4096];
static unsigned int input_offset, input_len;
static off_t consumed_bytes;
+static unsigned deepest_delta;
static git_SHA_CTX input_ctx;
static uint32_t input_crc32;
static int input_fd, output_fd, pack_fd;
@@ -161,7 +162,7 @@ static void use(int bytes)
input_offset += bytes;
/* make sure off_t is sufficiently large not to wrap */
- if (consumed_bytes > consumed_bytes + bytes)
+ if (signed_add_overflows(consumed_bytes, bytes))
die("pack too large for current definition of off_t");
consumed_bytes += bytes;
}
@@ -209,7 +210,7 @@ static void parse_pack_header(void)
static NORETURN void bad_object(unsigned long offset, const char *format,
...) __attribute__((format (printf, 2, 3)));
-static void bad_object(unsigned long offset, const char *format, ...)
+static NORETURN void bad_object(unsigned long offset, const char *format, ...)
{
va_list params;
char buf[1024];
@@ -267,7 +268,7 @@ static void unlink_base_data(struct base_data *c)
static void *unpack_entry_data(unsigned long offset, unsigned long size)
{
int status;
- z_stream stream;
+ git_zstream stream;
void *buf = xmalloc(size);
memset(&stream, 0, sizeof(stream));
@@ -296,7 +297,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
void *data;
obj->idx.offset = consumed_bytes;
- input_crc32 = crc32(0, Z_NULL, 0);
+ input_crc32 = crc32(0, NULL, 0);
p = fill(1);
c = *p;
@@ -357,7 +358,7 @@ static void *get_data_from_pack(struct object_entry *obj)
off_t from = obj[0].idx.offset + obj[0].hdr_size;
unsigned long len = obj[1].idx.offset - from;
unsigned char *data, *inbuf;
- z_stream stream;
+ git_zstream stream;
int status;
data = xmalloc(obj->size);
@@ -391,7 +392,18 @@ static void *get_data_from_pack(struct object_entry *obj)
return data;
}
-static int find_delta(const union delta_base *base)
+static int compare_delta_bases(const union delta_base *base1,
+ const union delta_base *base2,
+ enum object_type type1,
+ enum object_type type2)
+{
+ int cmp = type1 - type2;
+ if (cmp)
+ return cmp;
+ return memcmp(base1, base2, UNION_BASE_SZ);
+}
+
+static int find_delta(const union delta_base *base, enum object_type type)
{
int first = 0, last = nr_deltas;
@@ -400,7 +412,8 @@ static int find_delta(const union delta_base *base)
struct delta_entry *delta = &deltas[next];
int cmp;
- cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+ cmp = compare_delta_bases(base, &delta->base,
+ type, objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -413,9 +426,10 @@ static int find_delta(const union delta_base *base)
}
static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index)
+ int *first_index, int *last_index,
+ enum object_type type)
{
- int first = find_delta(base);
+ int first = find_delta(base, type);
int last = first;
int end = nr_deltas - 1;
@@ -485,12 +499,17 @@ static void sha1_object(const void *data, unsigned long size,
}
}
+static int is_delta_type(enum object_type type)
+{
+ return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
static void *get_base_data(struct base_data *c)
{
if (!c->data) {
struct object_entry *obj = c->obj;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ if (is_delta_type(obj->type)) {
void *base = get_base_data(c->base);
void *raw = get_data_from_pack(obj);
c->data = patch_delta(
@@ -517,6 +536,10 @@ static void resolve_delta(struct object_entry *delta_obj,
void *base_data, *delta_data;
delta_obj->real_type = base->obj->real_type;
+ delta_obj->delta_depth = base->obj->delta_depth + 1;
+ if (deepest_delta < delta_obj->delta_depth)
+ deepest_delta = delta_obj->delta_depth;
+ delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
result->obj = delta_obj;
@@ -543,11 +566,13 @@ static void find_unresolved_deltas(struct base_data *base,
union delta_base base_spec;
hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec, &ref_first, &ref_last);
+ find_delta_children(&base_spec,
+ &ref_first, &ref_last, OBJ_REF_DELTA);
memset(&base_spec, 0, sizeof(base_spec));
base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec, &ofs_first, &ofs_last);
+ find_delta_children(&base_spec,
+ &ofs_first, &ofs_last, OBJ_OFS_DELTA);
}
if (ref_last == -1 && ofs_last == -1) {
@@ -559,24 +584,24 @@ static void find_unresolved_deltas(struct base_data *base,
for (i = ref_first; i <= ref_last; i++) {
struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_REF_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ref_last && ofs_last == -1)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
+ struct base_data result;
+
+ assert(child->real_type == OBJ_REF_DELTA);
+ resolve_delta(child, base, &result);
+ if (i == ref_last && ofs_last == -1)
+ free_base_data(base);
+ find_unresolved_deltas(&result, base);
}
for (i = ofs_first; i <= ofs_last; i++) {
struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_OFS_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ofs_last)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
+ struct base_data result;
+
+ assert(child->real_type == OBJ_OFS_DELTA);
+ resolve_delta(child, base, &result);
+ if (i == ofs_last)
+ free_base_data(base);
+ find_unresolved_deltas(&result, base);
}
unlink_base_data(base);
@@ -586,7 +611,11 @@ static int compare_delta_entry(const void *a, const void *b)
{
const struct delta_entry *delta_a = a;
const struct delta_entry *delta_b = b;
- return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+
+ /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
+ return compare_delta_bases(&delta_a->base, &delta_b->base,
+ objects[delta_a->obj_no].type,
+ objects[delta_b->obj_no].type);
}
/* Parse all objects and return the pack content SHA1 hash */
@@ -610,7 +639,7 @@ static void parse_pack_objects(unsigned char *sha1)
struct object_entry *obj = &objects[i];
void *data = unpack_raw_entry(obj, &delta->base);
obj->real_type = obj->type;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ if (is_delta_type(obj->type)) {
nr_deltas++;
delta->obj_no = i;
delta++;
@@ -657,7 +686,7 @@ static void parse_pack_objects(unsigned char *sha1)
struct object_entry *obj = &objects[i];
struct base_data base_obj;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+ if (is_delta_type(obj->type))
continue;
base_obj.obj = obj;
base_obj.data = NULL;
@@ -668,26 +697,26 @@ static void parse_pack_objects(unsigned char *sha1)
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
{
- z_stream stream;
+ git_zstream stream;
int status;
unsigned char outbuf[4096];
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
+ git_deflate_init(&stream, zlib_compression_level);
stream.next_in = in;
stream.avail_in = size;
do {
stream.next_out = outbuf;
stream.avail_out = sizeof(outbuf);
- status = deflate(&stream, Z_FINISH);
+ status = git_deflate(&stream, Z_FINISH);
sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out);
} while (status == Z_OK);
if (status != Z_STREAM_END)
die("unable to deflate appended object (%d)", status);
size = stream.total_out;
- deflateEnd(&stream);
+ git_deflate_end(&stream);
return size;
}
@@ -861,48 +890,148 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
static int git_index_pack_config(const char *k, const char *v, void *cb)
{
+ struct pack_idx_option *opts = cb;
+
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
- die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
+ opts->version = git_config_int(k, v);
+ if (opts->version > 2)
+ die("bad pack.indexversion=%"PRIu32, opts->version);
return 0;
}
return git_default_config(k, v, cb);
}
+static int cmp_uint32(const void *a_, const void *b_)
+{
+ uint32_t a = *((uint32_t *)a_);
+ uint32_t b = *((uint32_t *)b_);
+
+ return (a < b) ? -1 : (a != b);
+}
+
+static void read_v2_anomalous_offsets(struct packed_git *p,
+ struct pack_idx_option *opts)
+{
+ const uint32_t *idx1, *idx2;
+ uint32_t i;
+
+ /* The address of the 4-byte offset table */
+ idx1 = (((const uint32_t *)p->index_data)
+ + 2 /* 8-byte header */
+ + 256 /* fan out */
+ + 5 * p->num_objects /* 20-byte SHA-1 table */
+ + p->num_objects /* CRC32 table */
+ );
+
+ /* The address of the 8-byte offset table */
+ idx2 = idx1 + p->num_objects;
+
+ for (i = 0; i < p->num_objects; i++) {
+ uint32_t off = ntohl(idx1[i]);
+ if (!(off & 0x80000000))
+ continue;
+ off = off & 0x7fffffff;
+ if (idx2[off * 2])
+ continue;
+ /*
+ * The real offset is ntohl(idx2[off * 2]) in high 4
+ * octets, and ntohl(idx2[off * 2 + 1]) in low 4
+ * octets. But idx2[off * 2] is Zero!!!
+ */
+ ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc);
+ opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]);
+ }
+
+ if (1 < opts->anomaly_nr)
+ qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32);
+}
+
+static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
+{
+ struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
+
+ if (!p)
+ die("Cannot open existing pack file '%s'", pack_name);
+ if (open_pack_index(p))
+ die("Cannot open existing pack idx file for '%s'", pack_name);
+
+ /* Read the attributes from the existing idx file */
+ opts->version = p->index_version;
+
+ if (opts->version == 2)
+ read_v2_anomalous_offsets(p, opts);
+
+ /*
+ * Get rid of the idx file as we do not need it anymore.
+ * NEEDSWORK: extract this bit from free_pack_by_name() in
+ * sha1_file.c, perhaps? It shouldn't matter very much as we
+ * know we haven't installed this pack (hence we never have
+ * read anything from it).
+ */
+ close_pack_index(p);
+ free(p);
+}
+
+static void show_pack_info(int stat_only)
+{
+ int i, baseobjects = nr_objects - nr_deltas;
+ unsigned long *chain_histogram = NULL;
+
+ if (deepest_delta)
+ chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+
+ if (is_delta_type(obj->type))
+ chain_histogram[obj->delta_depth - 1]++;
+ if (stat_only)
+ continue;
+ printf("%s %-6s %lu %lu %"PRIuMAX,
+ sha1_to_hex(obj->idx.sha1),
+ typename(obj->real_type), obj->size,
+ (unsigned long)(obj[1].idx.offset - obj->idx.offset),
+ (uintmax_t)obj->idx.offset);
+ if (is_delta_type(obj->type)) {
+ struct object_entry *bobj = &objects[obj->base_object_no];
+ printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+ }
+ putchar('\n');
+ }
+
+ if (baseobjects)
+ printf("non delta: %d object%s\n",
+ baseobjects, baseobjects > 1 ? "s" : "");
+ for (i = 0; i < deepest_delta; i++) {
+ if (!chain_histogram[i])
+ continue;
+ printf("chain length = %d: %lu object%s\n",
+ i + 1,
+ chain_histogram[i],
+ chain_histogram[i] > 1 ? "s" : "");
+ }
+}
+
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0;
+ int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
const char *curr_pack, *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
char *index_name_buf = NULL, *keep_name_buf = NULL;
struct pack_idx_entry **idx_objects;
+ struct pack_idx_option opts;
unsigned char pack_sha1[20];
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
- /*
- * We wish to read the repository's config file if any, and
- * for that it is necessary to call setup_git_directory_gently().
- * However if the cwd was inside .git/objects/pack/ then we need
- * to go back there or all the pack name arguments will be wrong.
- * And in that case we cannot rely on any prefix returned by
- * setup_git_directory_gently() either.
- */
- {
- char cwd[PATH_MAX+1];
- int nongit;
-
- if (!getcwd(cwd, sizeof(cwd)-1))
- die("Unable to get current working directory");
- setup_git_directory_gently(&nongit);
- git_config(git_index_pack_config, NULL);
- if (chdir(cwd))
- die("Cannot come back to cwd");
- }
+ read_replace_refs = 0;
+
+ reset_pack_idx_option(&opts);
+ git_config(git_index_pack_config, &opts);
+ if (prefix && chdir(prefix))
+ die("Cannot come back to cwd");
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -914,6 +1043,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
fix_thin_pack = 1;
} else if (!strcmp(arg, "--strict")) {
strict = 1;
+ } else if (!strcmp(arg, "--verify")) {
+ verify = 1;
+ } else if (!strcmp(arg, "--verify-stat")) {
+ verify = 1;
+ stat = 1;
+ } else if (!strcmp(arg, "--verify-stat-only")) {
+ verify = 1;
+ stat = 1;
+ stat_only = 1;
} else if (!strcmp(arg, "--keep")) {
keep_msg = "";
} else if (!prefixcmp(arg, "--keep=")) {
@@ -939,12 +1077,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
index_name = argv[++i];
} else if (!prefixcmp(arg, "--index-version=")) {
char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
+ opts.version = strtoul(arg + 16, &c, 10);
+ if (opts.version > 2)
die("bad %s", arg);
if (*c == ',')
- pack_idx_off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_off32_limit & 0x80000000)
+ opts.off32_limit = strtoul(c+1, &c, 0);
+ if (*c || opts.off32_limit & 0x80000000)
die("bad %s", arg);
} else
usage(index_pack_usage);
@@ -980,11 +1118,17 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
strcpy(keep_name_buf + len - 5, ".keep");
keep_name = keep_name_buf;
}
+ if (verify) {
+ if (!index_name)
+ die("--verify with no packfile name given");
+ read_idx_option(&opts, index_name);
+ opts.flags |= WRITE_IDX_VERIFY;
+ }
curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
- deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+ objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
+ deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
parse_pack_objects(pack_sha1);
if (nr_deltas == nr_resolved_deltas) {
stop_progress(&progress);
@@ -1024,16 +1168,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (strict)
check_objects();
+ if (stat)
+ show_pack_info(stat_only);
+
idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
for (i = 0; i < nr_objects; i++)
idx_objects[i] = &objects[i].idx;
- curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+ curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1);
free(idx_objects);
- final(pack_name, curr_pack,
- index_name, curr_index,
- keep_name, keep_msg,
- pack_sha1);
+ if (!verify)
+ final(pack_name, curr_pack,
+ index_name, curr_index,
+ keep_name, keep_msg,
+ pack_sha1);
+ else
+ close(input_fd);
free(objects);
free(index_name_buf);
free(keep_name_buf);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 0271285..025aa47 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -21,6 +21,7 @@
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
static const char *init_db_template_dir;
+static const char *git_link;
static void safe_create_dir(const char *dir, int share)
{
@@ -31,7 +32,7 @@ static void safe_create_dir(const char *dir, int share)
}
}
else if (share && adjust_shared_perm(dir))
- die("Could not make %s writable by group", dir);
+ die(_("Could not make %s writable by group"), dir);
}
static void copy_templates_1(char *path, int baselen,
@@ -58,25 +59,25 @@ static void copy_templates_1(char *path, int baselen,
namelen = strlen(de->d_name);
if ((PATH_MAX <= baselen + namelen) ||
(PATH_MAX <= template_baselen + namelen))
- die("insanely long template name %s", de->d_name);
+ die(_("insanely long template name %s"), de->d_name);
memcpy(path + baselen, de->d_name, namelen+1);
memcpy(template + template_baselen, de->d_name, namelen+1);
if (lstat(path, &st_git)) {
if (errno != ENOENT)
- die_errno("cannot stat '%s'", path);
+ die_errno(_("cannot stat '%s'"), path);
}
else
exists = 1;
if (lstat(template, &st_template))
- die_errno("cannot stat template '%s'", template);
+ die_errno(_("cannot stat template '%s'"), template);
if (S_ISDIR(st_template.st_mode)) {
DIR *subdir = opendir(template);
int baselen_sub = baselen + namelen;
int template_baselen_sub = template_baselen + namelen;
if (!subdir)
- die_errno("cannot opendir '%s'", template);
+ die_errno(_("cannot opendir '%s'"), template);
path[baselen_sub++] =
template[template_baselen_sub++] = '/';
path[baselen_sub] =
@@ -93,20 +94,20 @@ static void copy_templates_1(char *path, int baselen,
int len;
len = readlink(template, lnk, sizeof(lnk));
if (len < 0)
- die_errno("cannot readlink '%s'", template);
+ die_errno(_("cannot readlink '%s'"), template);
if (sizeof(lnk) <= len)
- die("insanely long symlink %s", template);
+ die(_("insanely long symlink %s"), template);
lnk[len] = 0;
if (symlink(lnk, path))
- die_errno("cannot symlink '%s' '%s'", lnk, path);
+ die_errno(_("cannot symlink '%s' '%s'"), lnk, path);
}
else if (S_ISREG(st_template.st_mode)) {
if (copy_file(path, template, st_template.st_mode))
- die_errno("cannot copy '%s' to '%s'", template,
+ die_errno(_("cannot copy '%s' to '%s'"), template,
path);
}
else
- error("ignoring template %s", template);
+ error(_("ignoring template %s"), template);
}
}
@@ -129,7 +130,7 @@ static void copy_templates(const char *template_dir)
return;
template_len = strlen(template_dir);
if (PATH_MAX <= (template_len+strlen("/config")))
- die("insanely long template path %s", template_dir);
+ die(_("insanely long template path %s"), template_dir);
strcpy(template_path, template_dir);
if (template_path[template_len-1] != '/') {
template_path[template_len++] = '/';
@@ -137,7 +138,7 @@ static void copy_templates(const char *template_dir)
}
dir = opendir(template_path);
if (!dir) {
- warning("templates not found %s", template_dir);
+ warning(_("templates not found %s"), template_dir);
return;
}
@@ -150,8 +151,8 @@ static void copy_templates(const char *template_dir)
if (repository_format_version &&
repository_format_version != GIT_REPO_VERSION) {
- warning("not copying templates of "
- "a wrong format version %d from '%s'",
+ warning(_("not copying templates of "
+ "a wrong format version %d from '%s'"),
repository_format_version,
template_dir);
closedir(dir);
@@ -188,7 +189,7 @@ static int create_default_files(const char *template_path)
int filemode;
if (len > sizeof(path)-50)
- die("insane git directory %s", git_dir);
+ die(_("insane git directory %s"), git_dir);
memcpy(path, git_dir, len);
if (len && path[len-1] != '/')
@@ -294,13 +295,84 @@ static int create_default_files(const char *template_path)
return reinit;
}
+static void create_object_directory(void)
+{
+ const char *object_directory = get_object_directory();
+ int len = strlen(object_directory);
+ char *path = xmalloc(len + 40);
+
+ memcpy(path, object_directory, len);
+
+ safe_create_dir(object_directory, 1);
+ strcpy(path+len, "/pack");
+ safe_create_dir(path, 1);
+ strcpy(path+len, "/info");
+ safe_create_dir(path, 1);
+
+ free(path);
+}
+
+int set_git_dir_init(const char *git_dir, const char *real_git_dir,
+ int exist_ok)
+{
+ if (real_git_dir) {
+ struct stat st;
+
+ if (!exist_ok && !stat(git_dir, &st))
+ die(_("%s already exists"), git_dir);
+
+ if (!exist_ok && !stat(real_git_dir, &st))
+ die(_("%s already exists"), real_git_dir);
+
+ /*
+ * make sure symlinks are resolved because we'll be
+ * moving the target repo later on in separate_git_dir()
+ */
+ git_link = xstrdup(real_path(git_dir));
+ }
+ else {
+ real_git_dir = real_path(git_dir);
+ git_link = NULL;
+ }
+ set_git_dir(real_path(real_git_dir));
+ return 0;
+}
+
+static void separate_git_dir(const char *git_dir)
+{
+ struct stat st;
+ FILE *fp;
+
+ if (!stat(git_link, &st)) {
+ const char *src;
+
+ if (S_ISREG(st.st_mode))
+ src = read_gitfile_gently(git_link);
+ else if (S_ISDIR(st.st_mode))
+ src = git_link;
+ else
+ die(_("unable to handle file type %d"), st.st_mode);
+
+ if (rename(src, git_dir))
+ die_errno(_("unable to move %s to %s"), src, git_dir);
+ }
+
+ fp = fopen(git_link, "w");
+ if (!fp)
+ die(_("Could not create git link %s"), git_link);
+ fprintf(fp, "gitdir: %s\n", git_dir);
+ fclose(fp);
+}
+
int init_db(const char *template_dir, unsigned int flags)
{
- const char *sha1_dir;
- char *path;
- int len, reinit;
+ int reinit;
+ const char *git_dir = get_git_dir();
- safe_create_dir(get_git_dir(), 0);
+ if (git_link)
+ separate_git_dir(git_dir);
+
+ safe_create_dir(git_dir, 0);
init_is_bare_repository = is_bare_repository();
@@ -313,16 +385,7 @@ int init_db(const char *template_dir, unsigned int flags)
reinit = create_default_files(template_dir);
- sha1_dir = get_object_directory();
- len = strlen(sha1_dir);
- path = xmalloc(len + 40);
- memcpy(path, sha1_dir, len);
-
- safe_create_dir(sha1_dir, 1);
- strcpy(path+len, "/pack");
- safe_create_dir(path, 1);
- strcpy(path+len, "/info");
- safe_create_dir(path, 1);
+ create_object_directory();
if (shared_repository) {
char buf[10];
@@ -346,11 +409,16 @@ int init_db(const char *template_dir, unsigned int flags)
}
if (!(flags & INIT_DB_QUIET)) {
- const char *git_dir = get_git_dir();
int len = strlen(git_dir);
- printf("%s%s Git repository in %s%s\n",
- reinit ? "Reinitialized existing" : "Initialized empty",
- shared_repository ? " shared" : "",
+
+ /*
+ * TRANSLATORS: The first '%s' is either "Reinitialized
+ * existing" or "Initialized empty", the second " shared" or
+ * "", and the last '%s%s' is the verbatim directory name.
+ */
+ printf(_("%s%s Git repository in %s%s\n"),
+ reinit ? _("Reinitialized existing") : _("Initialized empty"),
+ shared_repository ? _(" shared") : "",
git_dir, len && git_dir[len-1] != '/' ? "/" : "");
}
@@ -369,7 +437,7 @@ static int guess_repository_type(const char *git_dir)
if (!strcmp(".", git_dir))
return 1;
if (!getcwd(cwd, sizeof(cwd)))
- die_errno("cannot tell cwd");
+ die_errno(_("cannot tell cwd"));
if (!strcmp(git_dir, cwd))
return 1;
/*
@@ -408,11 +476,13 @@ static const char *const init_db_usage[] = {
int cmd_init_db(int argc, const char **argv, const char *prefix)
{
const char *git_dir;
+ const char *real_git_dir = NULL;
+ const char *work_tree;
const char *template_dir = NULL;
unsigned int flags = 0;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, "template-directory",
- "provide the directory from which templates will be used"),
+ "directory from which templates will be used"),
OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
"create a bare repository", 1),
{ OPTION_CALLBACK, 0, "shared", &init_shared_repository,
@@ -420,11 +490,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
"specify that the git repository is to be shared amongst several users",
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
+ OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
+ "separate git dir from working tree"),
OPT_END()
};
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
+ if (real_git_dir && !is_absolute_path(real_git_dir))
+ real_git_dir = xstrdup(real_path(real_git_dir));
+
if (argc == 1) {
int mkdir_tried = 0;
retry:
@@ -443,18 +518,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
errno = EEXIST;
/* fallthru */
case -1:
- die_errno("cannot mkdir %s", argv[0]);
+ die_errno(_("cannot mkdir %s"), argv[0]);
break;
default:
break;
}
shared_repository = saved;
if (mkdir(argv[0], 0777) < 0)
- die_errno("cannot mkdir %s", argv[0]);
+ die_errno(_("cannot mkdir %s"), argv[0]);
mkdir_tried = 1;
goto retry;
}
- die_errno("cannot chdir to %s", argv[0]);
+ die_errno(_("cannot chdir to %s"), argv[0]);
}
} else if (0 < argc) {
usage(init_db_usage[0]);
@@ -474,10 +549,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
* without --bare. Catch the error early.
*/
git_dir = getenv(GIT_DIR_ENVIRONMENT);
- if ((!git_dir || is_bare_repository_cfg == 1)
- && getenv(GIT_WORK_TREE_ENVIRONMENT))
- die("%s (or --work-tree=<directory>) not allowed without "
- "specifying %s (or --git-dir=<directory>)",
+ work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
+ die(_("%s (or --work-tree=<directory>) not allowed without "
+ "specifying %s (or --git-dir=<directory>)"),
GIT_WORK_TREE_ENVIRONMENT,
GIT_DIR_ENVIRONMENT);
@@ -491,25 +566,31 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
is_bare_repository_cfg = guess_repository_type(git_dir);
if (!is_bare_repository_cfg) {
- if (git_dir) {
- const char *git_dir_parent = strrchr(git_dir, '/');
- if (git_dir_parent) {
- char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
- git_work_tree_cfg = xstrdup(make_absolute_path(rel));
- free(rel);
- }
+ const char *git_dir_parent = strrchr(git_dir, '/');
+ if (git_dir_parent) {
+ char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
+ git_work_tree_cfg = xstrdup(real_path(rel));
+ free(rel);
}
if (!git_work_tree_cfg) {
git_work_tree_cfg = xcalloc(PATH_MAX, 1);
if (!getcwd(git_work_tree_cfg, PATH_MAX))
- die_errno ("Cannot access current working directory");
+ die_errno (_("Cannot access current working directory"));
}
+ if (work_tree)
+ set_git_work_tree(real_path(work_tree));
+ else
+ set_git_work_tree(git_work_tree_cfg);
if (access(get_git_work_tree(), X_OK))
- die_errno ("Cannot access work tree '%s'",
+ die_errno (_("Cannot access work tree '%s'"),
get_git_work_tree());
}
+ else {
+ if (work_tree)
+ set_git_work_tree(real_path(work_tree));
+ }
- set_git_dir(make_absolute_path(git_dir));
+ set_git_dir_init(git_dir, real_git_dir, 1);
return init_db(template_dir, flags);
}
diff --git a/builtin/log.c b/builtin/log.c
index 08b8722..5c2af59 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -23,14 +23,18 @@
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
+static int default_abbrev_commit;
static int default_show_root = 1;
static int decoration_style;
+static int decoration_given;
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
-static const char * const builtin_log_usage =
+static const char * const builtin_log_usage[] = {
"git log [<options>] [<since>..<until>] [[--] <path>...]\n"
- " or: git show [options] <object>...";
+ " or: git show [options] <object>...",
+ NULL
+};
static int parse_decoration_style(const char *var, const char *value)
{
@@ -49,33 +53,66 @@ static int parse_decoration_style(const char *var, const char *value)
return -1;
}
-static void cmd_log_init(int argc, const char **argv, const char *prefix,
- struct rev_info *rev, struct setup_revision_opt *opt)
+static int decorate_callback(const struct option *opt, const char *arg, int unset)
{
- int i;
- int decoration_given = 0;
- struct userformat_want w;
+ if (unset)
+ decoration_style = 0;
+ else if (arg)
+ decoration_style = parse_decoration_style("command line", arg);
+ else
+ decoration_style = DECORATE_SHORT_REFS;
+ if (decoration_style < 0)
+ die("invalid --decorate option: %s", arg);
+
+ decoration_given = 1;
+
+ return 0;
+}
+
+static void cmd_log_init_defaults(struct rev_info *rev)
+{
rev->abbrev = DEFAULT_ABBREV;
rev->commit_format = CMIT_FMT_DEFAULT;
if (fmt_pretty)
get_commit_format(fmt_pretty, rev);
rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+ rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
if (default_date_mode)
rev->date_mode = parse_date_format(default_date_mode);
+}
+
+static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
+ struct rev_info *rev, struct setup_revision_opt *opt)
+{
+ struct userformat_want w;
+ int quiet = 0, source = 0;
+
+ const struct option builtin_log_options[] = {
+ OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"),
+ OPT_BOOLEAN(0, "source", &source, "show source"),
+ { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options",
+ PARSE_OPT_OPTARG, decorate_callback},
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_log_options, builtin_log_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
- /*
- * Check for -h before setup_revisions(), or "git log -h" will
- * fail when run without a git directory.
- */
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(builtin_log_usage);
argc = setup_revisions(argc, argv, rev, opt);
+ if (quiet)
+ rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT;
+
+ /* Any arguments at this point are not recognized */
+ if (argc > 1)
+ die("unrecognized argument: %s", argv[1]);
memset(&w, 0, sizeof(w));
userformat_find_requirements(NULL, &w);
@@ -89,42 +126,36 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
rev->always_show_header = 0;
if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
rev->always_show_header = 0;
- if (rev->diffopt.nr_paths != 1)
+ if (rev->diffopt.pathspec.nr != 1)
usage("git logs can only follow renames on one pathname at a time");
}
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--decorate")) {
- decoration_style = DECORATE_SHORT_REFS;
- decoration_given = 1;
- } else if (!prefixcmp(arg, "--decorate=")) {
- const char *v = skip_prefix(arg, "--decorate=");
- decoration_style = parse_decoration_style(arg, v);
- if (decoration_style < 0)
- die("invalid --decorate option: %s", arg);
- decoration_given = 1;
- } else if (!strcmp(arg, "--no-decorate")) {
+
+ if (source)
+ rev->show_source = 1;
+
+ if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
+ /*
+ * "log --pretty=raw" is special; ignore UI oriented
+ * configuration variables such as decoration.
+ */
+ if (!decoration_given)
decoration_style = 0;
- } else if (!strcmp(arg, "--source")) {
- rev->show_source = 1;
- } else if (!strcmp(arg, "-h")) {
- usage(builtin_log_usage);
- } else
- die("unrecognized argument: %s", arg);
+ if (!rev->abbrev_commit_given)
+ rev->abbrev_commit = 0;
}
- /*
- * defeat log.decorate configuration interacting with --pretty=raw
- * from the command line.
- */
- if (!decoration_given && rev->pretty_given
- && rev->commit_format == CMIT_FMT_RAW)
- decoration_style = 0;
-
if (decoration_style) {
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+ setup_pager();
+}
+
+static void cmd_log_init(int argc, const char **argv, const char *prefix,
+ struct rev_info *rev, struct setup_revision_opt *opt)
+{
+ cmd_log_init_defaults(rev);
+ cmd_log_init_finish(argc, argv, prefix, rev, opt);
}
/*
@@ -152,7 +183,7 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr)
if (rev->commit_format != CMIT_FMT_ONELINE)
putchar(rev->diffopt.line_termination);
}
- printf("Final output: %d %s\n", nr, stage);
+ printf(_("Final output: %d %s\n"), nr, stage);
}
static struct itimerval early_output_timer;
@@ -246,12 +277,14 @@ static void finish_early_output(struct rev_info *rev)
static int cmd_log_walk(struct rev_info *rev)
{
struct commit *commit;
+ int saved_nrl = 0;
+ int saved_dcctc = 0;
if (rev->early_output)
setup_early_output(rev);
if (prepare_revision_walk(rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
if (rev->early_output)
finish_early_output(rev);
@@ -262,7 +295,13 @@ static int cmd_log_walk(struct rev_info *rev)
* retain that state information if replacing rev->diffopt in this loop
*/
while ((commit = get_revision(rev)) != NULL) {
- log_tree_commit(rev, commit);
+ if (!log_tree_commit(rev, commit) &&
+ rev->max_count >= 0)
+ /*
+ * We decremented max_count in get_revision,
+ * but we didn't actually show the commit.
+ */
+ rev->max_count++;
if (!rev->reflog_info) {
/* we allow cycles in reflog ancestry */
free(commit->buffer);
@@ -270,7 +309,14 @@ static int cmd_log_walk(struct rev_info *rev)
}
free_commit_list(commit->parents);
commit->parents = NULL;
+ if (saved_nrl < rev->diffopt.needed_rename_limit)
+ saved_nrl = rev->diffopt.needed_rename_limit;
+ if (rev->diffopt.degraded_cc_to_c)
+ saved_dcctc = 1;
}
+ rev->diffopt.degraded_cc_to_c = saved_dcctc;
+ rev->diffopt.needed_rename_limit = saved_nrl;
+
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
return 02;
@@ -284,6 +330,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
return git_config_string(&fmt_pretty, var, value);
if (!strcmp(var, "format.subjectprefix"))
return git_config_string(&fmt_patch_subject_prefix, var, value);
+ if (!strcmp(var, "log.abbrevcommit")) {
+ default_abbrev_commit = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "log.date"))
return git_config_string(&default_date_mode, var, value);
if (!strcmp(var, "log.decorate")) {
@@ -326,10 +376,11 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
static void show_tagger(char *buf, int len, struct rev_info *rev)
{
struct strbuf out = STRBUF_INIT;
+ struct pretty_print_context pp = {0};
- pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
- git_log_output_encoding ?
- git_log_output_encoding: git_commit_encoding);
+ pp.fmt = rev->commit_format;
+ pp.date_mode = rev->date_mode;
+ pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding());
printf("%s", out.buf);
strbuf_release(&out);
}
@@ -343,7 +394,7 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
int offset = 0;
if (!buf)
- return error("Could not read object %s", sha1_to_hex(sha1));
+ return error(_("Could not read object %s"), sha1_to_hex(sha1));
if (show_tag_object)
while (offset < size && buf[offset] != '\n') {
@@ -390,6 +441,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct object_array_entry *objects;
struct setup_revision_opt opt;
+ struct pathspec match_all;
int i, count, ret = 0;
git_config(git_log_config, NULL);
@@ -397,6 +449,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
if (diff_use_color_default == -1)
diff_use_color_default = git_use_color_default;
+ init_pathspec(&match_all, NULL);
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
@@ -430,7 +483,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
break;
o = parse_object(t->tagged->sha1);
if (!o)
- ret = error("Could not read object %s",
+ ret = error(_("Could not read object %s"),
sha1_to_hex(t->tagged->sha1));
objects[i].item = o;
i--;
@@ -443,7 +496,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
+ read_tree_recursive((struct tree *)o, "", 0, 0, &match_all,
show_tree_object, NULL);
rev.shown_one = 1;
break;
@@ -454,7 +507,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
ret = cmd_log_walk(&rev);
break;
default:
- ret = error("Unknown type: %d", o->type);
+ ret = error(_("Unknown type: %d"), o->type);
}
}
free(objects);
@@ -476,26 +529,15 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
init_revisions(&rev, prefix);
init_reflog_walk(&rev.reflog_info);
- rev.abbrev_commit = 1;
rev.verbose_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
- cmd_log_init(argc, argv, prefix, &rev, &opt);
-
- /*
- * This means that we override whatever commit format the user gave
- * on the cmd line. Sad, but cmd_log_init() currently doesn't
- * allow us to set a different default.
- */
+ cmd_log_init_defaults(&rev);
+ rev.abbrev_commit = 1;
rev.commit_format = CMIT_FMT_ONELINE;
rev.use_terminator = 1;
rev.always_show_header = 1;
-
- /*
- * We get called through "git reflog", so unlike the other log
- * routines, we need to set up our pager manually..
- */
- setup_pager();
+ cmd_log_init_finish(argc, argv, prefix, &rev, &opt);
return cmd_log_walk(&rev);
}
@@ -560,7 +602,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "format.headers")) {
if (!value)
- die("format.headers without value");
+ die(_("format.headers without value"));
add_header(value);
return 0;
}
@@ -623,7 +665,7 @@ static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int outdir_offset;
-static int reopen_stdout(struct commit *commit, struct rev_info *rev)
+static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
int suffix_len = strlen(fmt_patch_suffix) + 1;
@@ -632,18 +674,18 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev)
strbuf_addstr(&filename, output_directory);
if (filename.len >=
PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
- return error("name of output directory is too long");
+ return error(_("name of output directory is too long"));
if (filename.buf[filename.len - 1] != '/')
strbuf_addch(&filename, '/');
}
get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
- if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
+ if (!quiet)
fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
if (freopen(filename.buf, "w", stdout) == NULL)
- return error("Cannot open patch file %s", filename.buf);
+ return error(_("Cannot open patch file %s"), filename.buf);
strbuf_release(&filename);
return 0;
@@ -657,7 +699,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
unsigned flags1, flags2;
if (rev->pending.nr != 2)
- die("Need exactly one range.");
+ die(_("Need exactly one range."));
o1 = rev->pending.objects[0].item;
flags1 = o1->flags;
@@ -665,7 +707,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
flags2 = o2->flags;
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
- die("Not a range.");
+ die(_("Not a range."));
init_patch_ids(ids);
@@ -676,7 +718,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
add_pending_object(&check_rev, o1, "o1");
add_pending_object(&check_rev, o2, "o2");
if (prepare_revision_walk(&check_rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
while ((commit = get_revision(&check_rev)) != NULL) {
/* ignore merges */
@@ -702,7 +744,7 @@ static void gen_message_id(struct rev_info *info, char *base)
const char *email_end = strrchr(committer, '>');
struct strbuf buf = STRBUF_INIT;
if (!email_start || !email_end || email_start > email_end - 1)
- die("Could not extract email from committer identity.");
+ die(_("Could not extract email from committer identity."));
strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
(unsigned long) time(NULL),
(int)(email_end - email_start - 1), email_start + 1);
@@ -718,13 +760,12 @@ static void print_signature(void)
static void make_cover_letter(struct rev_info *rev, int use_stdout,
int numbered, int numbered_files,
struct commit *origin,
- int nr, struct commit **list, struct commit *head)
+ int nr, struct commit **list, struct commit *head,
+ int quiet)
{
const char *committer;
- const char *subject_start = NULL;
const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
const char *msg;
- const char *extra_headers = rev->extra_headers;
struct shortlog log;
struct strbuf sb = STRBUF_INIT;
int i;
@@ -732,9 +773,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
struct diff_options opts;
int need_8bit_cte = 0;
struct commit *commit = NULL;
+ struct pretty_print_context pp = {0};
if (rev->commit_format != CMIT_FMT_EMAIL)
- die("Cover letter needs email format");
+ die(_("Cover letter needs email format"));
committer = git_committer_info(0);
@@ -754,7 +796,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
sha1_to_hex(head->object.sha1), committer, committer);
}
- if (!use_stdout && reopen_stdout(commit, rev))
+ if (!use_stdout && reopen_stdout(commit, rev, quiet))
return;
if (commit) {
@@ -763,7 +805,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
free(commit);
}
- log_write_email_headers(rev, head, &subject_start, &extra_headers,
+ log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
&need_8bit_cte);
for (i = 0; !need_8bit_cte && i < nr; i++)
@@ -771,11 +813,11 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
need_8bit_cte = 1;
msg = body;
- pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
- encoding);
- pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
- encoding, need_8bit_cte);
- pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+ pp.fmt = CMIT_FMT_EMAIL;
+ pp.date_mode = DATE_RFC2822;
+ pp_user_info(&pp, NULL, &sb, committer, encoding);
+ pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
+ pp_remainder(&pp, &msg, &sb, 0);
printf("%s\n", sb.buf);
strbuf_release(&sb);
@@ -827,7 +869,7 @@ static const char *clean_message_id(const char *msg_id)
m++;
}
if (!z)
- die("insane in-reply-to: %s", msg_id);
+ die(_("insane in-reply-to: %s"), msg_id);
if (++z == m)
return a;
return xmemdupz(a, z - a);
@@ -900,7 +942,7 @@ static int output_directory_callback(const struct option *opt, const char *arg,
{
const char **dir = (const char **)opt->value;
if (*dir)
- die("Two output directories?");
+ die(_("Two output directories?"));
*dir = arg;
return 0;
}
@@ -995,6 +1037,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
char *add_signoff = NULL;
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
+ int quiet = 0;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
"use [PATCH n/m] even with a single patch",
@@ -1050,6 +1093,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, thread_callback },
OPT_STRING(0, "signature", &signature, "signature",
"add a signature"),
+ OPT_BOOLEAN(0, "quiet", &quiet,
+ "don't print the patch filenames"),
OPT_END()
};
@@ -1061,8 +1106,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.commit_format = CMIT_FMT_EMAIL;
rev.verbose_header = 1;
rev.diff = 1;
- rev.combine_merges = 0;
- rev.ignore_merges = 1;
+ rev.max_parents = 1;
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
rev.subject_prefix = fmt_patch_subject_prefix;
memset(&s_r_opt, 0, sizeof(s_r_opt));
@@ -1089,7 +1133,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
endpos = strchr(committer, '>');
if (!endpos)
- die("bogus committer info %s", committer);
+ die(_("bogus committer info %s"), committer);
add_signoff = xmemdupz(committer, endpos - committer + 1);
}
@@ -1134,20 +1178,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
numbered = 0;
if (numbered && keep_subject)
- die ("-n and -k are mutually exclusive.");
+ die (_("-n and -k are mutually exclusive."));
if (keep_subject && subject_prefix)
- die ("--subject-prefix and -k are mutually exclusive.");
+ die (_("--subject-prefix and -k are mutually exclusive."));
+ rev.preserve_subject = keep_subject;
argc = setup_revisions(argc, argv, &rev, &s_r_opt);
if (argc > 1)
- die ("unrecognized argument: %s", argv[1]);
+ die (_("unrecognized argument: %s"), argv[1]);
if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
- die("--name-only does not make sense");
+ die(_("--name-only does not make sense"));
if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
- die("--name-status does not make sense");
+ die(_("--name-status does not make sense"));
if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
- die("--check does not make sense");
+ die(_("--check does not make sense"));
if (!use_patch_format &&
(!rev.diffopt.output_format ||
@@ -1165,12 +1210,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!use_stdout)
output_directory = set_outdir(prefix, output_directory);
+ else
+ setup_pager();
if (output_directory) {
if (use_stdout)
- die("standard output, or directory, which one?");
+ die(_("standard output, or directory, which one?"));
if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
- die_errno("Could not create directory '%s'",
+ die_errno(_("Could not create directory '%s'"),
output_directory);
}
@@ -1224,7 +1271,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
realstdout = xfdopen(xdup(1), "w");
if (prepare_revision_walk(&rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
rev.boundary = 1;
while ((commit = get_revision(&rev)) != NULL) {
if (commit->object.flags & BOUNDARY) {
@@ -1233,10 +1280,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
continue;
}
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
if (ignore_if_in_upstream &&
has_commit_patch_id(commit, &ids))
continue;
@@ -1262,7 +1305,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, use_stdout, numbered, numbered_files,
- origin, nr, list, head);
+ origin, nr, list, head, quiet);
total++;
start_number--;
}
@@ -1308,8 +1351,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
- &rev))
- die("Failed to create output files");
+ &rev, quiet))
+ die(_("Failed to create output files"));
shown = log_tree_commit(&rev, commit);
free(commit->buffer);
commit->buffer = NULL;
@@ -1361,6 +1404,22 @@ static const char * const cherry_usage[] = {
NULL
};
+static void print_commit(char sign, struct commit *commit, int verbose,
+ int abbrev)
+{
+ if (!verbose) {
+ printf("%c %s\n", sign,
+ find_unique_abbrev(commit->object.sha1, abbrev));
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);
+ printf("%c %s %s\n", sign,
+ find_unique_abbrev(commit->object.sha1, abbrev),
+ buf.buf);
+ strbuf_release(&buf);
+ }
+}
+
int cmd_cherry(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -1375,7 +1434,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT__ABBREV(&abbrev),
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "be verbose"),
OPT_END()
};
@@ -1396,9 +1455,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
if (!current_branch || !current_branch->merge
|| !current_branch->merge[0]
|| !current_branch->merge[0]->dst) {
- fprintf(stderr, "Could not find a tracked"
+ fprintf(stderr, _("Could not find a tracked"
" remote branch, please"
- " specify <upstream> manually.\n");
+ " specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options);
}
@@ -1412,9 +1471,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
if (add_pending_commit(head, &revs, 0))
- die("Unknown commit %s", head);
+ die(_("Unknown commit %s"), head);
if (add_pending_commit(upstream, &revs, UNINTERESTING))
- die("Unknown commit %s", upstream);
+ die(_("Unknown commit %s"), upstream);
/* Don't say anything if head and upstream are the same. */
if (revs.pending.nr == 2) {
@@ -1426,11 +1485,11 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
get_patch_ids(&revs, &ids, prefix);
if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
- die("Unknown commit %s", limit);
+ die(_("Unknown commit %s"), limit);
/* reverse the list of commits */
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
while ((commit = get_revision(&revs)) != NULL) {
/* ignore merges */
if (commit->parents && commit->parents->next)
@@ -1445,22 +1504,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
commit = list->item;
if (has_commit_patch_id(commit, &ids))
sign = '-';
-
- if (verbose) {
- struct strbuf buf = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &buf, &ctx);
- printf("%c %s %s\n", sign,
- find_unique_abbrev(commit->object.sha1, abbrev),
- buf.buf);
- strbuf_release(&buf);
- }
- else {
- printf("%c %s\n", sign,
- find_unique_abbrev(commit->object.sha1, abbrev));
- }
-
+ print_commit(sign, commit, verbose, abbrev);
list = list->next;
}
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 1b9b8a8..1570123 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -25,6 +25,7 @@ static int show_modified;
static int show_killed;
static int show_valid_bit;
static int line_terminator = '\n';
+static int debug_mode;
static const char *prefix;
static int max_prefix_len;
@@ -162,35 +163,41 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
ce_stage(ce));
}
write_name(ce->name, ce_namelen(ce));
-}
-
-static int show_one_ru(struct string_list_item *item, void *cbdata)
-{
- const char *path = item->string;
- struct resolve_undo_info *ui = item->util;
- int i, len;
-
- len = strlen(path);
- if (len < max_prefix_len)
- return 0; /* outside of the prefix */
- if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
- return 0; /* uninterested */
- for (i = 0; i < 3; i++) {
- if (!ui->mode[i])
- continue;
- printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
- find_unique_abbrev(ui->sha1[i], abbrev),
- i + 1);
- write_name(path, len);
+ if (debug_mode) {
+ printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
+ printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
+ printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
+ printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
+ printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
}
- return 0;
}
static void show_ru_info(void)
{
+ struct string_list_item *item;
+
if (!the_index.resolve_undo)
return;
- for_each_string_list(the_index.resolve_undo, show_one_ru, NULL);
+
+ for_each_string_list_item(item, the_index.resolve_undo) {
+ const char *path = item->string;
+ struct resolve_undo_info *ui = item->util;
+ int i, len;
+
+ len = strlen(path);
+ if (len < max_prefix_len)
+ continue; /* outside of the prefix */
+ if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
+ continue; /* uninterested */
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+ find_unique_abbrev(ui->sha1[i], abbrev),
+ i + 1);
+ write_name(path, len);
+ }
+ }
}
static void show_files(struct dir_struct *dir)
@@ -331,7 +338,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
{
struct tree *tree;
unsigned char sha1[20];
- const char **match;
+ struct pathspec pathspec;
struct cache_entry *last_stage0 = NULL;
int i;
@@ -353,10 +360,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
static const char *(matchbuf[2]);
matchbuf[0] = prefix;
matchbuf[1] = NULL;
- match = matchbuf;
+ init_pathspec(&pathspec, matchbuf);
+ pathspec.items[0].use_wildcard = 0;
} else
- match = NULL;
- if (read_tree(tree, 1, match))
+ init_pathspec(&pathspec, NULL);
+ if (read_tree(tree, 1, &pathspec))
die("unable to read tree entries %s", tree_name);
for (i = 0; i < active_nr; i++) {
@@ -417,7 +425,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
}
static const char * const ls_files_usage[] = {
- "git ls-files [options] [<file>]*",
+ "git ls-files [options] [<file>...]",
NULL
};
@@ -519,9 +527,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
"pretend that paths removed since <tree-ish> are still present"),
OPT__ABBREV(&abbrev),
+ OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"),
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(ls_files_usage, builtin_ls_files_options);
+
memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix)
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 34480cf..1022309 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -5,7 +5,7 @@
static const char ls_remote_usage[] =
"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n"
-" [-q|--quiet] [<repository> [<refs>...]]";
+" [-q|--quiet] [--exit-code] [<repository> [<refs>...]]";
/*
* Is there one among the list of patterns that match the tail part
@@ -32,9 +32,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
{
int i;
const char *dest = NULL;
- int nongit;
unsigned flags = 0;
+ int get_url = 0;
int quiet = 0;
+ int status = 0;
const char *uploadpack = NULL;
const char **pattern = NULL;
@@ -42,8 +43,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
struct transport *transport;
const struct ref *ref;
- setup_git_directory_gently(&nongit);
-
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -72,6 +71,15 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
quiet = 1;
continue;
}
+ if (!strcmp("--get-url", arg)) {
+ get_url = 1;
+ continue;
+ }
+ if (!strcmp("--exit-code", arg)) {
+ /* return this code if no refs are reported */
+ status = 2;
+ continue;
+ }
usage(ls_remote_usage);
}
dest = arg;
@@ -97,6 +105,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
}
if (!remote->url_nr)
die("remote %s has no configured URL", dest);
+
+ if (get_url) {
+ printf("%s\n", *remote->url);
+ return 0;
+ }
+
transport = transport_get(remote, NULL);
if (uploadpack != NULL)
transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
@@ -113,6 +127,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
if (!tail_match(pattern, ref->name))
continue;
printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ status = 0; /* we found something */
}
- return 0;
+ return status;
}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index dc86b0d..f08c5b0 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -19,12 +19,12 @@ static int line_termination = '\n';
#define LS_SHOW_SIZE 16
static int abbrev;
static int ls_options;
-static const char **pathspec;
+static struct pathspec pathspec;
static int chomp_prefix;
static const char *ls_tree_prefix;
static const char * const ls_tree_usage[] = {
- "git ls-tree [<options>] <tree-ish> [path...]",
+ "git ls-tree [<options>] <tree-ish> [<path>...]",
NULL
};
@@ -35,7 +35,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
if (ls_options & LS_RECURSIVE)
return 1;
- s = pathspec;
+ s = pathspec.raw;
if (!s)
return 0;
@@ -52,6 +52,8 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
speclen = strlen(spec);
if (speclen <= len)
continue;
+ if (spec[len] && spec[len] != '/')
+ continue;
if (memcmp(pathname, spec, len))
continue;
return 1;
@@ -118,7 +120,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
{
unsigned char sha1[20];
struct tree *tree;
- int full_tree = 0;
+ int i, full_tree = 0;
const struct option ls_tree_options[] = {
OPT_BIT('d', NULL, &ls_options, "only show trees",
LS_TREE_ONLY),
@@ -164,11 +166,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
if (get_sha1(argv[0], sha1))
die("Not a valid object name %s", argv[0]);
- pathspec = get_pathspec(prefix, argv + 1);
+ init_pathspec(&pathspec, get_pathspec(prefix, argv + 1));
+ for (i = 0; i < pathspec.nr; i++)
+ pathspec.items[i].use_wildcard = 0;
+ pathspec.has_wildcard = 0;
tree = parse_tree_indirect(sha1);
if (!tree)
die("not a tree object");
- read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL);
+ read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL);
return 0;
}
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 2320d98..bfb32b7 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -400,7 +400,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
break;
if (strbuf_getline(&continuation, in, '\n'))
break;
- continuation.buf[0] = '\n';
+ continuation.buf[0] = ' ';
strbuf_rtrim(&continuation);
strbuf_addbuf(line, &continuation);
}
@@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
*/
git_config(git_mailinfo_config, NULL);
- def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+ def_charset = get_commit_output_encoding();
metainfo_charset = def_charset;
while (1 < argc && argv[1][0] == '-') {
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index e4560da..2d43278 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -10,7 +10,7 @@
#include "strbuf.h"
static const char git_mailsplit_usage[] =
-"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]";
+"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]";
static int is_from_line(const char *line, int len)
{
@@ -137,7 +137,7 @@ static int split_maildir(const char *maildir, const char *dir,
char name[PATH_MAX];
int ret = -1;
int i;
- struct string_list list = {NULL, 0, 0, 1};
+ struct string_list list = STRING_LIST_INIT_DUP;
if (populate_maildir_list(&list, maildir) < 0)
goto out;
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 54e7ec2..4f30f1b 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -24,6 +24,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
static const char * const merge_base_usage[] = {
"git merge-base [-a|--all] <commit> <commit>...",
+ "git merge-base [-a|--all] --octopus <commit>...",
+ "git merge-base --independent <commit>...",
NULL
};
@@ -41,21 +43,58 @@ static struct commit *get_commit_reference(const char *arg)
return r;
}
+static int handle_octopus(int count, const char **args, int reduce, int show_all)
+{
+ struct commit_list *revs = NULL;
+ struct commit_list *result;
+ int i;
+
+ if (reduce)
+ show_all = 1;
+
+ for (i = count - 1; i >= 0; i--)
+ commit_list_insert(get_commit_reference(args[i]), &revs);
+
+ result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs);
+
+ if (!result)
+ return 1;
+
+ while (result) {
+ printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ if (!show_all)
+ return 0;
+ result = result->next;
+ }
+
+ return 0;
+}
+
int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
struct commit **rev;
int rev_nr = 0;
int show_all = 0;
+ int octopus = 0;
+ int reduce = 0;
struct option options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"),
+ OPT_BOOLEAN('a', "all", &show_all, "output all common ancestors"),
+ OPT_BOOLEAN(0, "octopus", &octopus, "find ancestors for a single n-way merge"),
+ OPT_BOOLEAN(0, "independent", &reduce, "list revs not reachable from others"),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
- if (argc < 2)
+ if (!octopus && !reduce && argc < 2)
usage_with_options(merge_base_usage, options);
+ if (reduce && (show_all || octopus))
+ die("--independent cannot be used with other options");
+
+ if (octopus || reduce)
+ return handle_octopus(argc, argv, reduce, show_all);
+
rev = xmalloc(argc * sizeof(*rev));
while (argc-- > 0)
rev[rev_nr++] = get_commit_reference(*argv++);
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index b8e9e5b..237abd3 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -28,7 +28,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
xmparam_t xmp = {{0}};
int ret = 0, i = 0, to_stdout = 0;
int quiet = 0;
- int nongit;
+ int prefixlen = 0;
struct option options[] = {
OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3),
@@ -40,7 +40,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
XDL_MERGE_FAVOR_UNION),
OPT_INTEGER(0, "marker-size", &xmp.marker_size,
"for conflicts, use this marker size"),
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "do not warn about conflicts"),
OPT_CALLBACK('L', NULL, names, "name",
"set labels for file1/orig_file/file2", &label_cb),
OPT_END(),
@@ -50,8 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
xmp.style = 0;
xmp.favor = 0;
- prefix = setup_git_directory_gently(&nongit);
- if (!nongit) {
+ if (startup_info->have_repository) {
/* Read the configuration file */
git_config(git_xmerge_config, NULL);
if (0 <= git_xmerge_style)
@@ -67,10 +66,14 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
"%s\n", strerror(errno));
}
+ if (prefix)
+ prefixlen = strlen(prefix);
+
for (i = 0; i < 3; i++) {
+ const char *fname = prefix_filename(prefix, prefixlen, argv[i]);
if (!names[i])
names[i] = argv[i];
- if (read_mmfile(mmfs + i, argv[i]))
+ if (read_mmfile(mmfs + i, fname))
return -1;
if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
return error("Cannot merge binary files: %s\n",
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index 2c4cf5e..2338832 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -1,6 +1,5 @@
-#include "cache.h"
+#include "builtin.h"
#include "run-command.h"
-#include "exec_cmd.h"
static const char *pgm;
static int one_shot, quiet;
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index d8875d5..3a64f5d 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -1,7 +1,11 @@
-#include "cache.h"
+#include "builtin.h"
#include "commit.h"
#include "tag.h"
#include "merge-recursive.h"
+#include "xdiff-interface.h"
+
+static const char builtin_merge_recursive_usage[] =
+ "git %s <base>... -- <head> <remote> ...";
static const char *better_branch_name(const char *branch)
{
@@ -29,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
o.subtree_shift = "";
if (argc < 4)
- usagef("%s <base>... -- <head> <remote> ...", argv[0]);
+ usagef(builtin_merge_recursive_usage, argv[0]);
for (i = 1; i < argc; ++i) {
const char *arg = argv[i];
@@ -37,15 +41,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (!prefixcmp(arg, "--")) {
if (!arg[2])
break;
- if (!strcmp(arg+2, "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(arg+2, "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(arg+2, "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(arg+2, "subtree="))
- o.subtree_shift = arg + 10;
- else
+ if (parse_merge_opt(&o, arg + 2))
die("Unknown option %s", arg);
continue;
}
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index fc00d79..897a563 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -1,8 +1,9 @@
-#include "cache.h"
+#include "builtin.h"
#include "tree-walk.h"
#include "xdiff-interface.h"
#include "blob.h"
#include "exec_cmd.h"
+#include "merge-file.h"
static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
static int resolve_directories = 1;
@@ -54,12 +55,11 @@ static const char *explanation(struct merge_list *entry)
return "removed in remote";
}
-extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
-
static void *result(struct merge_list *entry, unsigned long *size)
{
enum object_type type;
struct blob *base, *our, *their;
+ const char *path = entry->path;
if (!entry->stage)
return read_sha1_file(entry->blob->object.sha1, &type, size);
@@ -76,7 +76,7 @@ static void *result(struct merge_list *entry, unsigned long *size)
their = NULL;
if (entry)
their = entry->blob;
- return merge_file(entry->path, base, our, their, size);
+ return merge_file(path, base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
diff --git a/builtin/merge.c b/builtin/merge.c
index 37ce4f5..325891e 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -25,6 +25,7 @@
#include "help.h"
#include "merge-recursive.h"
#include "resolve-undo.h"
+#include "remote.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -37,12 +38,13 @@ struct strategy {
};
static const char * const builtin_merge_usage[] = {
- "git merge [options] <remote>...",
- "git merge [options] <msg> HEAD <remote>",
+ "git merge [options] [<commit>...]",
+ "git merge [options] <msg> HEAD <commit>",
+ "git merge --abort",
NULL
};
-static int show_diffstat = 1, option_log, squash;
+static int show_diffstat = 1, shortlog_len, squash;
static int option_commit = 1, allow_fast_forward = 1;
static int fast_forward_only;
static int allow_trivial = 1, have_message;
@@ -54,8 +56,13 @@ static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts;
static size_t xopts_nr, xopts_alloc;
static const char *branch;
+static char *branch_mergeoptions;
+static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
+static int abort_current_merge;
+static int show_progress = -1;
+static int default_to_upstream;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -78,7 +85,7 @@ static int option_parse_message(const struct option *opt,
strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
have_message = 1;
} else
- return error("switch `m' requires a value");
+ return error(_("switch `m' requires a value"));
return 0;
}
@@ -115,13 +122,13 @@ static struct strategy *get_strategy(const char *name)
exclude_cmds(&main_cmds, &not_strategies);
}
if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
- fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
- fprintf(stderr, "Available strategies are:");
+ fprintf(stderr, _("Could not find merge strategy '%s'.\n"), name);
+ fprintf(stderr, _("Available strategies are:"));
for (i = 0; i < main_cmds.cnt; i++)
fprintf(stderr, " %s", main_cmds.names[i]->name);
fprintf(stderr, ".\n");
if (other_cmds.cnt) {
- fprintf(stderr, "Available custom strategies are:");
+ fprintf(stderr, _("Available custom strategies are:"));
for (i = 0; i < other_cmds.cnt; i++)
fprintf(stderr, " %s", other_cmds.names[i]->name);
fprintf(stderr, ".\n");
@@ -131,6 +138,7 @@ static struct strategy *get_strategy(const char *name)
ret = xcalloc(1, sizeof(struct strategy));
ret->name = xstrdup(name);
+ ret->attr = NO_TRIVIAL;
return ret;
}
@@ -175,8 +183,9 @@ static struct option builtin_merge_options[] = {
OPT_BOOLEAN(0, "stat", &show_diffstat,
"show a diffstat at the end of the merge"),
OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
- OPT_BOOLEAN(0, "log", &option_log,
- "add list of one-line log to merge commit message"),
+ { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
+ "add (at most <n>) entries from shortlog to merge commit message",
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
OPT_BOOLEAN(0, "squash", &squash,
"create a single commit instead of doing a merge"),
OPT_BOOLEAN(0, "commit", &option_commit,
@@ -191,9 +200,12 @@ static struct option builtin_merge_options[] = {
OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
"option for selected merge strategy", option_parse_x),
OPT_CALLBACK('m', "message", &merge_msg, "message",
- "message to be used for the merge commit (if any)",
+ "merge commit message (for a non-fast-forward merge)",
option_parse_message),
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "abort", &abort_current_merge,
+ "abort the current in-progress merge"),
+ OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
OPT_END()
};
@@ -218,17 +230,35 @@ static void save_state(void)
cp.git_cmd = 1;
if (start_command(&cp))
- die("could not run stash.");
+ die(_("could not run stash."));
len = strbuf_read(&buffer, cp.out, 1024);
close(cp.out);
if (finish_command(&cp) || len < 0)
- die("stash failed");
+ die(_("stash failed"));
else if (!len)
return;
strbuf_setlen(&buffer, buffer.len-1);
if (get_sha1(buffer.buf, stash))
- die("not a valid object: %s", buffer.buf);
+ die(_("not a valid object: %s"), buffer.buf);
+}
+
+static void read_empty(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[7];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "-m";
+ args[i++] = "-u";
+ args[i++] = EMPTY_TREE_SHA1_HEX;
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die(_("read-tree failed"));
}
static void reset_hard(unsigned const char *sha1, int verbose)
@@ -245,7 +275,7 @@ static void reset_hard(unsigned const char *sha1, int verbose)
args[i] = NULL;
if (run_command_v_opt(args, RUN_GIT_CMD))
- die("read-tree failed");
+ die(_("read-tree failed"));
}
static void restore_state(void)
@@ -274,7 +304,7 @@ static void restore_state(void)
static void finish_up_to_date(const char *msg)
{
if (verbosity >= 0)
- printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg);
drop_save();
}
@@ -287,10 +317,10 @@ static void squash_message(void)
int fd;
struct pretty_print_context ctx = {0};
- printf("Squash commit -- not updating HEAD\n");
+ printf(_("Squash commit -- not updating HEAD\n"));
fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
if (fd < 0)
- die_errno("Could not write to '%s'", git_path("SQUASH_MSG"));
+ die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
init_revisions(&rev, NULL);
rev.ignore_merges = 1;
@@ -305,22 +335,23 @@ static void squash_message(void)
setup_revisions(0, NULL, &rev, NULL);
if (prepare_revision_walk(&rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
ctx.abbrev = rev.abbrev;
ctx.date_mode = rev.date_mode;
+ ctx.fmt = rev.commit_format;
strbuf_addstr(&out, "Squashed commit of the following:\n");
while ((commit = get_revision(&rev)) != NULL) {
strbuf_addch(&out, '\n');
strbuf_addf(&out, "commit %s\n",
sha1_to_hex(commit->object.sha1));
- pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+ pretty_print_commit(&ctx, commit, &out);
}
if (write(fd, out.buf, out.len) < 0)
- die_errno("Writing SQUASH_MSG");
+ die_errno(_("Writing SQUASH_MSG"));
if (close(fd))
- die_errno("Finishing SQUASH_MSG");
+ die_errno(_("Finishing SQUASH_MSG"));
strbuf_release(&out);
}
@@ -340,7 +371,7 @@ static void finish(const unsigned char *new_head, const char *msg)
squash_message();
} else {
if (verbosity >= 0 && !merge_msg.len)
- printf("No merge message -- not updating HEAD\n");
+ printf(_("No merge message -- not updating HEAD\n"));
else {
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD",
@@ -362,7 +393,7 @@ static void finish(const unsigned char *new_head, const char *msg)
if (diff_use_color_default > 0)
DIFF_OPT_SET(&opts, COLOR_DIFF);
if (diff_setup_done(&opts) < 0)
- die("diff_setup_done failed");
+ die(_("diff_setup_done failed"));
diff_tree_sha1(head, new_head, "", &opts);
diffcore_std(&opts);
diff_flush(&opts);
@@ -391,7 +422,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
memset(branch_head, 0, sizeof(branch_head));
remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
if (!remote_head)
- die("'%s' does not point to a commit", remote);
+ die(_("'%s' does not point to a commit"), remote);
if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
if (!prefixcmp(found_ref, "refs/heads/")) {
@@ -400,7 +431,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
goto cleanup;
}
if (!prefixcmp(found_ref, "refs/remotes/")) {
- strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
+ strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
}
@@ -437,7 +468,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_addstr(&truname, "refs/heads/");
strbuf_addstr(&truname, remote);
strbuf_setlen(&truname, truname.len - len);
- if (resolve_ref(truname.buf, buf_sha, 0, NULL)) {
+ if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
sha1_to_hex(remote_head->sha1),
@@ -456,7 +487,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
fp = fopen(git_path("FETCH_HEAD"), "r");
if (!fp)
- die_errno("could not open '%s' for reading",
+ die_errno(_("could not open '%s' for reading"),
git_path("FETCH_HEAD"));
strbuf_getline(&line, fp, '\n');
fclose(fp);
@@ -474,25 +505,34 @@ cleanup:
strbuf_release(&bname);
}
+static void parse_branch_merge_options(char *bmo)
+{
+ const char **argv;
+ int argc;
+
+ if (!bmo)
+ return;
+ argc = split_cmdline(bmo, &argv);
+ if (argc < 0)
+ die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+ split_cmdline_strerror(argc));
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ argv[0] = "branch.*.mergeoptions";
+ parse_options(argc, argv, NULL, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(argv);
+}
+
static int git_merge_config(const char *k, const char *v, void *cb)
{
if (branch && !prefixcmp(k, "branch.") &&
!prefixcmp(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
- const char **argv;
- int argc;
- char *buf;
-
- buf = xstrdup(v);
- argc = split_cmdline(buf, &argv);
- if (argc < 0)
- die("Bad branch.%s.mergeoptions string", branch);
- argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
- memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
- argc++;
- parse_options(argc, argv, NULL, builtin_merge_options,
- builtin_merge_usage, 0);
- free(buf);
+ free(branch_mergeoptions);
+ branch_mergeoptions = xstrdup(v);
+ return 0;
}
if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
@@ -501,8 +541,29 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return git_config_string(&pull_twohead, k, v);
else if (!strcmp(k, "pull.octopus"))
return git_config_string(&pull_octopus, k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
- option_log = git_config_bool(k, v);
+ else if (!strcmp(k, "merge.renormalize"))
+ option_renormalize = git_config_bool(k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
+ int is_bool;
+ shortlog_len = git_config_bool_or_int(k, v, &is_bool);
+ if (!is_bool && shortlog_len < 0)
+ return error(_("%s: negative length %s"), k, v);
+ if (is_bool && shortlog_len)
+ shortlog_len = DEFAULT_MERGE_LOG_LEN;
+ return 0;
+ } else if (!strcmp(k, "merge.ff")) {
+ int boolval = git_config_maybe_bool(k, v);
+ if (0 <= boolval) {
+ allow_fast_forward = boolval;
+ } else if (v && !strcmp(v, "only")) {
+ allow_fast_forward = 1;
+ fast_forward_only = 1;
+ } /* do not barf on values from future versions of git */
+ return 0;
+ } else if (!strcmp(k, "merge.defaulttoupstream")) {
+ default_to_upstream = git_config_bool(k, v);
+ return 0;
+ }
return git_diff_ui_config(k, v, cb);
}
@@ -545,10 +606,19 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head,
static void write_tree_trivial(unsigned char *sha1)
{
if (write_cache_as_tree(sha1, 0, NULL))
- die("git write-tree failed to write a tree");
+ die(_("git write-tree failed to write a tree"));
+}
+
+static const char *merge_argument(struct commit *commit)
+{
+ if (commit)
+ return sha1_to_hex(commit->object.sha1);
+ else
+ return EMPTY_TREE_SHA1_HEX;
}
-int try_merge_command(const char *strategy, struct commit_list *common,
+int try_merge_command(const char *strategy, size_t xopts_nr,
+ const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes)
{
const char **args;
@@ -567,11 +637,11 @@ int try_merge_command(const char *strategy, struct commit_list *common,
args[i++] = s;
}
for (j = common; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i++] = "--";
args[i++] = head_arg;
for (j = remotes; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i] = NULL;
ret = run_command_v_opt(args, RUN_GIT_CMD);
strbuf_release(&buf);
@@ -586,7 +656,7 @@ int try_merge_command(const char *strategy, struct commit_list *common,
free(args);
discard_cache();
if (read_cache() < 0)
- die("failed to read the cache");
+ die(_("failed to read the cache"));
resolve_undo_clear();
return ret;
@@ -603,7 +673,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(lock)))
- return error("Unable to write index.");
+ return error(_("Unable to write index."));
rollback_lock_file(lock);
if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
@@ -616,7 +686,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
struct commit_list *j;
if (remoteheads->next) {
- error("Not handling anything other than two heads merge.");
+ error(_("Not handling anything other than two heads merge."));
return 2;
}
@@ -624,18 +694,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
if (!strcmp(strategy, "subtree"))
o.subtree_shift = "";
- for (x = 0; x < xopts_nr; x++) {
- if (!strcmp(xopts[x], "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(xopts[x], "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(xopts[x], "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(xopts[x], "subtree="))
- o.subtree_shift = xopts[x]+8;
- else
- die("Unknown option for merge-recursive: -X%s", xopts[x]);
- }
+ o.renormalize = option_renormalize;
+ o.show_rename_progress =
+ show_progress == -1 ? isatty(2) : show_progress;
+
+ for (x = 0; x < xopts_nr; x++)
+ if (parse_merge_opt(&o, xopts[x]))
+ die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
o.branch1 = head_arg;
o.branch2 = remoteheads->item->util;
@@ -649,11 +714,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(lock)))
- die ("unable to write %s", get_index_file());
+ die (_("unable to write %s"), get_index_file());
rollback_lock_file(lock);
return clean ? 0 : 1;
} else {
- return try_merge_command(strategy, common, head_arg, remoteheads);
+ return try_merge_command(strategy, xopts_nr, xopts,
+ common, head_arg, remoteheads);
}
}
@@ -704,7 +770,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote
opts.verbose_update = 1;
opts.merge = 1;
opts.fn = twoway_merge;
- opts.msgs = get_porcelain_error_msgs();
+ setup_unpack_trees_porcelain(&opts, "merge");
trees[nr_trees] = parse_tree_indirect(head);
if (!trees[nr_trees++])
@@ -720,7 +786,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote
return -1;
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(lock_file))
- die("unable to write new index file");
+ die(_("unable to write new index file"));
return 0;
}
@@ -768,17 +834,44 @@ static void add_strategies(const char *string, unsigned attr)
}
+static void write_merge_msg(void)
+{
+ int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"),
+ git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
+ die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
+ close(fd);
+}
+
+static void read_merge_msg(void)
+{
+ strbuf_reset(&merge_msg);
+ if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
+ die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
+}
+
+static void run_prepare_commit_msg(void)
+{
+ write_merge_msg();
+ run_hook(get_index_file(), "prepare-commit-msg",
+ git_path("MERGE_MSG"), "merge", NULL, NULL);
+ read_merge_msg();
+}
+
static int merge_trivial(void)
{
unsigned char result_tree[20], result_commit[20];
struct commit_list *parent = xmalloc(sizeof(*parent));
write_tree_trivial(result_tree);
- printf("Wonderful.\n");
+ printf(_("Wonderful.\n"));
parent->item = lookup_commit(head);
parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item;
parent->next->next = NULL;
+ run_prepare_commit_msg();
commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
finish(result_commit, "In-index merge");
drop_save();
@@ -808,6 +901,7 @@ static int finish_automerge(struct commit_list *common,
}
free_commit_list(remoteheads);
strbuf_addch(&merge_msg, '\n');
+ run_prepare_commit_msg();
commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
finish(result_commit, buf.buf);
@@ -816,14 +910,14 @@ static int finish_automerge(struct commit_list *common,
return 0;
}
-static int suggest_conflicts(void)
+static int suggest_conflicts(int renormalizing)
{
FILE *fp;
int pos;
fp = fopen(git_path("MERGE_MSG"), "a");
if (!fp)
- die_errno("Could not open '%s' for writing",
+ die_errno(_("Could not open '%s' for writing"),
git_path("MERGE_MSG"));
fprintf(fp, "\nConflicts:\n");
for (pos = 0; pos < active_nr; pos++) {
@@ -839,8 +933,8 @@ static int suggest_conflicts(void)
}
fclose(fp);
rerere(allow_rerere_auto);
- printf("Automatic merge failed; "
- "fix conflicts and then commit the result.\n");
+ printf(_("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n"));
return 1;
}
@@ -854,7 +948,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv)
return NULL;
second_token = lookup_commit_reference_gently(second_sha1, 0);
if (!second_token)
- die("'%s' is not a commit", argv[1]);
+ die(_("'%s' is not a commit"), argv[1]);
if (hashcmp(second_token->object.sha1, head))
return NULL;
}
@@ -884,6 +978,35 @@ static int evaluate_result(void)
return cnt;
}
+/*
+ * Pretend as if the user told us to merge with the tracking
+ * branch we have for the upstream of the current branch
+ */
+static int setup_with_upstream(const char ***argv)
+{
+ struct branch *branch = branch_get(NULL);
+ int i;
+ const char **args;
+
+ if (!branch)
+ die(_("No current branch."));
+ if (!branch->remote)
+ die(_("No remote for the current branch."));
+ if (!branch->merge_nr)
+ die(_("No default upstream defined for the current branch."));
+
+ args = xcalloc(branch->merge_nr + 1, sizeof(char *));
+ for (i = 0; i < branch->merge_nr; i++) {
+ if (!branch->merge[i]->dst)
+ die(_("No remote tracking branch for %s from %s"),
+ branch->merge[i]->src, branch->remote_name);
+ args[i] = branch->merge[i]->dst;
+ }
+ args[i] = NULL;
+ *argv = args;
+ return i;
+}
+
int cmd_merge(int argc, const char **argv, const char *prefix)
{
unsigned char result_tree[20];
@@ -895,22 +1018,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- if (read_cache_unmerged()) {
- die_resolve_conflict("merge");
- }
- if (file_exists(git_path("MERGE_HEAD"))) {
- /*
- * There is no unmerged entry, don't advise 'git
- * add/rm <file>', just 'git commit'.
- */
- if (advice_resolve_conflict)
- die("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge.");
- else
- die("You have not concluded your merge (MERGE_HEAD exists).");
- }
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_merge_usage, builtin_merge_options);
- resolve_undo_clear();
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
@@ -927,20 +1037,66 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (diff_use_color_default == -1)
diff_use_color_default = git_use_color_default;
+ if (branch_mergeoptions)
+ parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+
+ if (verbosity < 0 && show_progress == -1)
+ show_progress = 0;
+
+ if (abort_current_merge) {
+ int nargc = 2;
+ const char *nargv[] = {"reset", "--merge", NULL};
+
+ if (!file_exists(git_path("MERGE_HEAD")))
+ die(_("There is no merge to abort (MERGE_HEAD missing)."));
+
+ /* Invoke 'git reset --merge' */
+ return cmd_reset(nargc, nargv, prefix);
+ }
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("merge");
+
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge."));
+ else
+ die(_("You have not concluded your merge (MERGE_HEAD exists)."));
+ }
+ if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+ if (advice_resolve_conflict)
+ die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+ "Please, commit your changes before you can merge."));
+ else
+ die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."));
+ }
+ resolve_undo_clear();
+
if (verbosity < 0)
show_diffstat = 0;
if (squash) {
if (!allow_fast_forward)
- die("You cannot combine --squash with --no-ff.");
+ die(_("You cannot combine --squash with --no-ff."));
option_commit = 0;
}
if (!allow_fast_forward && fast_forward_only)
- die("You cannot combine --no-ff with --ff-only.");
+ die(_("You cannot combine --no-ff with --ff-only."));
+ if (!abort_current_merge) {
+ if (!argc && default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else if (argc == 1 && !strcmp(argv[0], "-"))
+ argv[0] = "@{-1}";
+ }
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
@@ -967,19 +1123,19 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* We do the same for "git pull".
*/
if (argc != 1)
- die("Can merge only exactly one commit into "
- "empty head");
+ die(_("Can merge only exactly one commit into "
+ "empty head"));
if (squash)
- die("Squash commit into empty head not supported yet");
+ die(_("Squash commit into empty head not supported yet"));
if (!allow_fast_forward)
- die("Non-fast-forward commit does not make sense into "
- "an empty head");
+ die(_("Non-fast-forward commit does not make sense into "
+ "an empty head"));
remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
if (!remote_head)
- die("%s - not something we can merge", argv[0]);
+ die(_("%s - not something we can merge"), argv[0]);
+ read_empty(remote_head->sha1, 0);
update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
DIE_ON_ERR);
- reset_hard(remote_head->sha1, 0);
return 0;
} else {
struct strbuf merge_names = STRBUF_INIT;
@@ -998,14 +1154,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++)
merge_name(argv[i], &merge_names);
- if (have_message && option_log)
- fmt_merge_msg_shortlog(&merge_names, &merge_msg);
- else if (!have_message)
- fmt_merge_msg(option_log, &merge_names, &merge_msg);
-
-
- if (!(have_message && !option_log) && merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len-1);
+ if (!have_message || shortlog_len) {
+ fmt_merge_msg(&merge_names, &merge_msg, !have_message,
+ shortlog_len);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len - 1);
+ }
}
if (head_invalid || !argc)
@@ -1024,7 +1178,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
if (!o)
- die("%s - not something we can merge", argv[i]);
+ die(_("%s - not something we can merge"), argv[i]);
commit = lookup_commit(o->sha1);
commit->util = (void *)argv[i];
remotes = &commit_list_insert(commit, remotes)->next;
@@ -1082,7 +1236,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
if (verbosity >= 0)
- printf("Updating %s..%s\n",
+ printf(_("Updating %s..%s\n"),
hex,
find_unique_abbrev(remoteheads->item->object.sha1,
DEFAULT_ABBREV));
@@ -1116,11 +1270,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (allow_trivial && !fast_forward_only) {
/* See if it is really trivial. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
- printf("Trying really trivial in-index merge...\n");
+ printf(_("Trying really trivial in-index merge...\n"));
if (!read_tree_trivial(common->item->object.sha1,
head, remoteheads->item->object.sha1))
return merge_trivial();
- printf("Nope.\n");
+ printf(_("Nope.\n"));
}
} else {
/*
@@ -1153,7 +1307,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (fast_forward_only)
- die("Not possible to fast-forward, aborting.");
+ die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
@@ -1179,11 +1333,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < use_strategies_nr; i++) {
int ret;
if (i) {
- printf("Rewinding the tree to pristine...\n");
+ printf(_("Rewinding the tree to pristine...\n"));
restore_state();
}
if (use_strategies_nr != 1)
- printf("Trying merge strategy %s...\n",
+ printf(_("Trying merge strategy %s...\n"),
use_strategies[i]->name);
/*
* Remember which strategy left the state in the working
@@ -1244,17 +1398,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
restore_state();
if (use_strategies_nr > 1)
fprintf(stderr,
- "No merge strategy handled the merge.\n");
+ _("No merge strategy handled the merge.\n"));
else
- fprintf(stderr, "Merge with strategy %s failed.\n",
+ fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
return 2;
} else if (best_strategy == wt_strategy)
; /* We already have its result in the working tree. */
else {
- printf("Rewinding the tree to pristine...\n");
+ printf(_("Rewinding the tree to pristine...\n"));
restore_state();
- printf("Using the %s to prepare resolving by hand.\n",
+ printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy);
try_merge_strategy(best_strategy, common, head_arg);
}
@@ -1270,36 +1424,29 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
sha1_to_hex(j->item->object.sha1));
fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
if (fd < 0)
- die_errno("Could not open '%s' for writing",
+ die_errno(_("Could not open '%s' for writing"),
git_path("MERGE_HEAD"));
if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
+ die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
close(fd);
strbuf_addch(&merge_msg, '\n');
- fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
- merge_msg.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
- close(fd);
+ write_merge_msg();
fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
- die_errno("Could not open '%s' for writing",
+ die_errno(_("Could not open '%s' for writing"),
git_path("MERGE_MODE"));
strbuf_reset(&buf);
if (!allow_fast_forward)
strbuf_addf(&buf, "no-ff");
if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MODE"));
+ die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
close(fd);
}
if (merge_was_ok) {
- fprintf(stderr, "Automatic merge went well; "
- "stopped before committing as requested\n");
+ fprintf(stderr, _("Automatic merge went well; "
+ "stopped before committing as requested\n"));
return 0;
} else
- return suggest_conflicts();
+ return suggest_conflicts(option_renormalize);
}
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 1cb0f3f..640ab64 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -1,6 +1,5 @@
-#include "cache.h"
+#include "builtin.h"
#include "tag.h"
-#include "exec_cmd.h"
/*
* A signature file has a very simple fixed format: four lines
@@ -24,8 +23,8 @@ static int verify_object(const unsigned char *sha1, const char *expected_type)
int ret = -1;
enum object_type type;
unsigned long size;
- const unsigned char *repl;
- void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+ void *buffer = read_sha1_file(sha1, &type, &size);
+ const unsigned char *repl = lookup_replace_object(sha1);
if (buffer) {
if (type == type_from_string(expected_type))
@@ -35,12 +34,6 @@ static int verify_object(const unsigned char *sha1, const char *expected_type)
return ret;
}
-#ifdef NO_C99_FORMAT
-#define PD_FMT "%d"
-#else
-#define PD_FMT "%td"
-#endif
-
static int verify_tag(char *buffer, unsigned long size)
{
int typelen;
@@ -70,15 +63,18 @@ static int verify_tag(char *buffer, unsigned long size)
/* Verify tag-line */
tag_line = strchr(type_line, '\n');
if (!tag_line)
- return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
+ return error("char%"PRIuMAX": could not find next \"\\n\"",
+ (uintmax_t) (type_line - buffer));
tag_line++;
if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
- return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
+ return error("char%"PRIuMAX": no \"tag \" found",
+ (uintmax_t) (tag_line - buffer));
/* Get the actual type */
typelen = tag_line - type_line - strlen("type \n");
if (typelen >= sizeof(type))
- return error("char" PD_FMT ": type too long", type_line+5 - buffer);
+ return error("char%"PRIuMAX": type too long",
+ (uintmax_t) (type_line+5 - buffer));
memcpy(type, type_line+5, typelen);
type[typelen] = 0;
@@ -95,15 +91,16 @@ static int verify_tag(char *buffer, unsigned long size)
break;
if (c > ' ')
continue;
- return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
+ return error("char%"PRIuMAX": could not verify tag name",
+ (uintmax_t) (tag_line - buffer));
}
/* Verify the tagger line */
tagger_line = tag_line;
if (memcmp(tagger_line, "tagger ", 7))
- return error("char" PD_FMT ": could not find \"tagger \"",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": could not find \"tagger \"",
+ (uintmax_t) (tagger_line - buffer));
/*
* Check for correct form for name and email
@@ -115,44 +112,42 @@ static int verify_tag(char *buffer, unsigned long size)
if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
strpbrk(tagger_line, "<>\n") != lb+1 ||
strpbrk(lb+2, "><\n ") != rb)
- return error("char" PD_FMT ": malformed tagger field",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": malformed tagger field",
+ (uintmax_t) (tagger_line - buffer));
/* Check for author name, at least one character, space is acceptable */
if (lb == tagger_line)
- return error("char" PD_FMT ": missing tagger name",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": missing tagger name",
+ (uintmax_t) (tagger_line - buffer));
/* timestamp, 1 or more digits followed by space */
tagger_line = rb + 2;
if (!(len = strspn(tagger_line, "0123456789")))
- return error("char" PD_FMT ": missing tag timestamp",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": missing tag timestamp",
+ (uintmax_t) (tagger_line - buffer));
tagger_line += len;
if (*tagger_line != ' ')
- return error("char" PD_FMT ": malformed tag timestamp",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": malformed tag timestamp",
+ (uintmax_t) (tagger_line - buffer));
tagger_line++;
/* timezone, 5 digits [+-]hhmm, max. 1400 */
if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
strspn(tagger_line+1, "0123456789") == 4 &&
tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
- return error("char" PD_FMT ": malformed tag timezone",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": malformed tag timezone",
+ (uintmax_t) (tagger_line - buffer));
tagger_line += 6;
/* Verify the blank line separating the header from the body */
if (*tagger_line != '\n')
- return error("char" PD_FMT ": trailing garbage in tag header",
- tagger_line - buffer);
+ return error("char%"PRIuMAX": trailing garbage in tag header",
+ (uintmax_t) (tagger_line - buffer));
/* The actual stuff afterwards we don't care about.. */
return 0;
}
-#undef PD_FMT
-
int cmd_mktag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
diff --git a/builtin/mv.c b/builtin/mv.c
index 38574b8..40f33ca 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -55,15 +55,15 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
int i, newfd;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__FORCE(&force, "force move/rename even if target exists"),
OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
OPT_END(),
};
const char **source, **destination, **dest_path;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
- struct string_list src_for_dst = {NULL, 0, 0, 0};
+ struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
git_config(git_default_config, NULL);
@@ -74,7 +74,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
newfd = hold_locked_index(&lock_file, 1);
if (read_cache() < 0)
- die("index file corrupt");
+ die(_("index file corrupt"));
source = copy_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
@@ -100,17 +100,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
const char *bad = NULL;
if (show_only)
- printf("Checking rename of '%s' to '%s'\n", src, dst);
+ printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
length = strlen(src);
if (lstat(src, &st) < 0)
- bad = "bad source";
+ bad = _("bad source");
else if (!strncmp(src, dst, length) &&
(dst[length] == 0 || dst[length] == '/')) {
- bad = "can not move directory into itself";
+ bad = _("can not move directory into itself");
} else if ((src_is_dir = S_ISDIR(st.st_mode))
&& lstat(dst, &st) == 0)
- bad = "cannot move directory over file";
+ bad = _("cannot move directory over file");
else if (src_is_dir) {
const char *src_w_slash = add_slash(src);
int len_w_slash = length + 1;
@@ -120,7 +120,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
first = cache_name_pos(src_w_slash, len_w_slash);
if (first >= 0)
- die ("Huh? %.*s is in index?",
+ die (_("Huh? %.*s is in index?"),
len_w_slash, src_w_slash);
first = -1 - first;
@@ -132,7 +132,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
free((char *)src_w_slash);
if (last - first < 1)
- bad = "source directory is empty";
+ bad = _("source directory is empty");
else {
int j, dst_len;
@@ -163,22 +163,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
argc += last - first;
}
} else if (cache_name_pos(src, length) < 0)
- bad = "not under version control";
+ bad = _("not under version control");
else if (lstat(dst, &st) == 0) {
- bad = "destination exists";
+ bad = _("destination exists");
if (force) {
/*
* only files can overwrite each other:
* check both source and destination
*/
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
- warning("%s; will overwrite!", bad);
+ warning(_("%s; will overwrite!"), bad);
bad = NULL;
} else
- bad = "Cannot overwrite";
+ bad = _("Cannot overwrite");
}
} else if (string_list_has_string(&src_for_dst, dst))
- bad = "multiple sources for the same target";
+ bad = _("multiple sources for the same target");
else
string_list_insert(&src_for_dst, dst);
@@ -193,7 +193,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
i--;
}
} else
- die ("%s, source=%s, destination=%s",
+ die (_("%s, source=%s, destination=%s"),
bad, src, dst);
}
}
@@ -203,10 +203,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
enum update_mode mode = modes[i];
int pos;
if (show_only || verbose)
- printf("Renaming %s to %s\n", src, dst);
+ printf(_("Renaming %s to %s\n"), src, dst);
if (!show_only && mode != INDEX &&
rename(src, dst) < 0 && !ignore_errors)
- die_errno ("renaming '%s' failed", src);
+ die_errno (_("renaming '%s' failed"), src);
if (mode == WORKING_DIRECTORY)
continue;
@@ -220,7 +220,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ die(_("Unable to write new index file"));
}
return 0;
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 06a38ac..31f5c1c 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -220,7 +220,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
- struct object_array revs = { 0, 0, NULL };
+ struct object_array revs = OBJECT_ARRAY_INIT;
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
struct name_ref_data data = { 0, 0, NULL };
struct option opts[] = {
diff --git a/builtin/notes.c b/builtin/notes.c
index 190005f..f8e437d 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -17,6 +17,7 @@
#include "run-command.h"
#include "parse-options.h"
#include "string-list.h"
+#include "notes-merge.h"
static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] [list [<object>]]",
@@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes [--ref <notes_ref>] edit [<object>]",
"git notes [--ref <notes_ref>] show [<object>]",
- "git notes [--ref <notes_ref>] remove [<object>]",
+ "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
+ "git notes merge --commit [-v | -q]",
+ "git notes merge --abort [-v | -q]",
+ "git notes [--ref <notes_ref>] remove [<object>...]",
"git notes [--ref <notes_ref>] prune [-n | -v]",
+ "git notes [--ref <notes_ref>] get-ref",
NULL
};
@@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = {
NULL
};
+static const char * const git_notes_merge_usage[] = {
+ "git notes merge [<options>] <notes_ref>",
+ "git notes merge --commit [<options>]",
+ "git notes merge --abort [<options>]",
+ NULL
+};
+
static const char * const git_notes_remove_usage[] = {
"git notes remove [<object>]",
NULL
@@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = {
NULL
};
+static const char * const git_notes_get_ref_usage[] = {
+ "git notes get-ref",
+ NULL
+};
+
static const char note_template[] =
"\n"
"#\n"
@@ -119,13 +136,13 @@ static void write_commented_object(int fd, const unsigned char *object)
show.err = 0;
show.git_cmd = 1;
if (start_command(&show))
- die("unable to start 'show' for object '%s'",
+ die(_("unable to start 'show' for object '%s'"),
sha1_to_hex(object));
/* Open the output as FILE* so strbuf_getline() can be used. */
show_out = xfdopen(show.out, "r");
if (show_out == NULL)
- die_errno("can't fdopen 'show' output fd");
+ die_errno(_("can't fdopen 'show' output fd"));
/* Prepend "# " to each output line and write result to 'fd' */
while (strbuf_getline(&buf, show_out, '\n') != EOF) {
@@ -135,10 +152,10 @@ static void write_commented_object(int fd, const unsigned char *object)
}
strbuf_release(&buf);
if (fclose(show_out))
- die_errno("failed to close pipe to 'show' for object '%s'",
+ die_errno(_("failed to close pipe to 'show' for object '%s'"),
sha1_to_hex(object));
if (finish_command(&show))
- die("failed to finish 'show' for object '%s'",
+ die(_("failed to finish 'show' for object '%s'"),
sha1_to_hex(object));
}
@@ -155,7 +172,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
path = git_pathdup("NOTES_EDITMSG");
fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0)
- die_errno("could not create file '%s'", path);
+ die_errno(_("could not create file '%s'"), path);
if (msg->given)
write_or_die(fd, msg->buf.buf, msg->buf.len);
@@ -169,8 +186,8 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
strbuf_reset(&(msg->buf));
if (launch_editor(path, &(msg->buf), NULL)) {
- die("Please supply the note contents using either -m" \
- " or -F option");
+ die(_("Please supply the note contents using either -m" \
+ " or -F option"));
}
stripspace(&(msg->buf), 1);
}
@@ -190,14 +207,14 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
}
if (!msg->buf.len) {
- fprintf(stderr, "Removing note for object %s\n",
+ fprintf(stderr, _("Removing note for object %s\n"),
sha1_to_hex(object));
hashclr(result);
} else {
if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {
- error("unable to write note object");
+ error(_("unable to write note object"));
if (path)
- error("The note contents has been left in %s",
+ error(_("The note contents has been left in %s"),
path);
exit(128);
}
@@ -231,9 +248,9 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)
strbuf_addch(&(msg->buf), '\n');
if (!strcmp(arg, "-")) {
if (strbuf_read(&(msg->buf), 0, 1024) < 0)
- die_errno("cannot read '%s'", arg);
+ die_errno(_("cannot read '%s'"), arg);
} else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)
- die_errno("could not open or read '%s'", arg);
+ die_errno(_("could not open or read '%s'"), arg);
stripspace(&(msg->buf), 0);
msg->given = 1;
@@ -252,10 +269,10 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
strbuf_addch(&(msg->buf), '\n');
if (get_sha1(arg, object))
- die("Failed to resolve '%s' as a valid ref.", arg);
+ die(_("Failed to resolve '%s' as a valid ref."), arg);
if (!(buf = read_sha1_file(object, &type, &len)) || !len) {
free(buf);
- die("Failed to read object '%s'.", arg);;
+ die(_("Failed to read object '%s'."), arg);;
}
strbuf_add(&(msg->buf), buf, len);
free(buf);
@@ -271,18 +288,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, unset);
}
-int commit_notes(struct notes_tree *t, const char *msg)
+void commit_notes(struct notes_tree *t, const char *msg)
{
- struct commit_list *parent;
- unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
struct strbuf buf = STRBUF_INIT;
+ unsigned char commit_sha1[20];
if (!t)
t = &default_notes_tree;
if (!t->initialized || !t->ref || !*t->ref)
- die("Cannot commit uninitialized/unreferenced notes tree");
+ die(_("Cannot commit uninitialized/unreferenced notes tree"));
if (!t->dirty)
- return 0; /* don't have to commit an unchanged tree */
+ return; /* don't have to commit an unchanged tree */
/* Prepare commit message and reflog message */
strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
@@ -290,27 +306,10 @@ int commit_notes(struct notes_tree *t, const char *msg)
if (buf.buf[buf.len - 1] != '\n')
strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
- /* Convert notes tree to tree object */
- if (write_notes_tree(t, tree_sha1))
- die("Failed to write current notes tree to database");
-
- /* Create new commit for the tree object */
- if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
- parent = xmalloc(sizeof(*parent));
- parent->item = lookup_commit(prev_commit);
- parent->next = NULL;
- } else {
- hashclr(prev_commit);
- parent = NULL;
- }
- if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
- die("Failed to commit notes tree to database");
-
- /* Update notes ref with new commit */
- update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
+ create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+ update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
strbuf_release(&buf);
- return 0;
}
combine_notes_fn parse_combine_notes_fn(const char *v)
@@ -321,6 +320,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
return combine_notes_ignore;
else if (!strcasecmp(v, "concatenate"))
return combine_notes_concatenate;
+ else if (!strcasecmp(v, "cat_sort_uniq"))
+ return combine_notes_cat_sort_uniq;
else
return NULL;
}
@@ -336,7 +337,7 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb)
config_error_nonbool(k);
c->combine = parse_combine_notes_fn(v);
if (!c->combine) {
- error("Bad notes.rewriteMode value: '%s'", v);
+ error(_("Bad notes.rewriteMode value: '%s'"), v);
return 1;
}
return 0;
@@ -346,8 +347,8 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb)
if (!prefixcmp(v, "refs/notes/"))
string_list_add_refs_by_glob(c->refs, v);
else
- warning("Refusing to rewrite notes in %s"
- " (outside of refs/notes/)", v);
+ warning(_("Refusing to rewrite notes in %s"
+ " (outside of refs/notes/)"), v);
return 0;
}
@@ -371,8 +372,10 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
c->mode_from_env = 1;
c->combine = parse_combine_notes_fn(rewrite_mode_env);
if (!c->combine)
- error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
- " value: '%s'", rewrite_mode_env);
+ /* TRANSLATORS: The first %s is the name of the
+ environment variable, the second %s is its value */
+ error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
+ rewrite_mode_env);
}
if (rewrite_refs_env) {
c->refs_from_env = 1;
@@ -412,7 +415,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
free(c);
}
-int notes_copy_from_stdin(int force, const char *rewrite_cmd)
+static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
{
struct strbuf buf = STRBUF_INIT;
struct notes_rewrite_cfg *c = NULL;
@@ -435,13 +438,13 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
split = strbuf_split(&buf, ' ');
if (!split[0] || !split[1])
- die("Malformed input line: '%s'.", buf.buf);
+ die(_("Malformed input line: '%s'."), buf.buf);
strbuf_rtrim(split[0]);
strbuf_rtrim(split[1]);
if (get_sha1(split[0]->buf, from_obj))
- die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+ die(_("Failed to resolve '%s' as a valid ref."), split[0]->buf);
if (get_sha1(split[1]->buf, to_obj))
- die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+ die(_("Failed to resolve '%s' as a valid ref."), split[1]->buf);
if (rewrite_cmd)
err = copy_note_for_rewrite(c, from_obj, to_obj);
@@ -450,7 +453,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
combine_notes_overwrite);
if (err) {
- error("Failed to copy notes from '%s' to '%s'",
+ error(_("Failed to copy notes from '%s' to '%s'"),
split[0]->buf, split[1]->buf);
ret = 1;
}
@@ -494,20 +497,20 @@ static int list(int argc, const char **argv, const char *prefix)
git_notes_list_usage, 0);
if (1 < argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_list_usage, options);
}
t = init_notes_check("list");
if (argc) {
if (get_sha1(argv[0], object))
- die("Failed to resolve '%s' as a valid ref.", argv[0]);
+ die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
note = get_note(t, object);
if (note) {
puts(sha1_to_hex(note));
retval = 0;
} else
- retval = error("No note found for object %s.",
+ retval = error(_("No note found for object %s."),
sha1_to_hex(object));
} else
retval = for_each_note(t, 0, list_each_note, NULL);
@@ -516,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix)
return retval;
}
+static int append_edit(int argc, const char **argv, const char *prefix);
+
static int add(int argc, const char **argv, const char *prefix)
{
int retval = 0, force = 0;
@@ -526,46 +531,58 @@ static int add(int argc, const char **argv, const char *prefix)
const unsigned char *note;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+ { OPTION_CALLBACK, 'm', "message", &msg, "msg",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+ { OPTION_CALLBACK, 'F', "file", &msg, "file",
"note contents in a file", PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
"reuse and edit specified note object", PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
- OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+ OPT__FORCE(&force, "replace existing notes"),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
- 0);
+ PARSE_OPT_KEEP_ARGV0);
- if (1 < argc) {
- error("too many parameters");
+ if (2 < argc) {
+ error(_("too many parameters"));
usage_with_options(git_notes_add_usage, options);
}
- object_ref = argc ? argv[0] : "HEAD";
+ object_ref = argc > 1 ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ die(_("Failed to resolve '%s' as a valid ref."), object_ref);
t = init_notes_check("add");
note = get_note(t, object);
if (note) {
if (!force) {
- retval = error("Cannot add notes. Found existing notes "
+ if (!msg.given) {
+ /*
+ * Redirect to "edit" subcommand.
+ *
+ * We only end up here if none of -m/-F/-c/-C
+ * or -f are given. The original args are
+ * therefore still in argv[0-1].
+ */
+ argv[0] = "edit";
+ free_notes(t);
+ return append_edit(argc, argv, prefix);
+ }
+ retval = error(_("Cannot add notes. Found existing notes "
"for object %s. Use '-f' to overwrite "
- "existing notes", sha1_to_hex(object));
+ "existing notes"), sha1_to_hex(object));
goto out;
}
- fprintf(stderr, "Overwriting existing notes for object %s\n",
+ fprintf(stderr, _("Overwriting existing notes for object %s\n"),
sha1_to_hex(object));
}
@@ -573,8 +590,8 @@ static int add(int argc, const char **argv, const char *prefix)
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", "add");
@@ -594,7 +611,7 @@ static int copy(int argc, const char **argv, const char *prefix)
struct notes_tree *t;
const char *rewrite_cmd = NULL;
struct option options[] = {
- OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+ OPT__FORCE(&force, "replace existing notes"),
OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
"load rewriting config for <command> (implies "
@@ -607,7 +624,7 @@ static int copy(int argc, const char **argv, const char *prefix)
if (from_stdin || rewrite_cmd) {
if (argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_copy_usage, options);
} else {
return notes_copy_from_stdin(force, rewrite_cmd);
@@ -615,45 +632,46 @@ static int copy(int argc, const char **argv, const char *prefix)
}
if (argc < 2) {
- error("too few parameters");
+ error(_("too few parameters"));
usage_with_options(git_notes_copy_usage, options);
}
if (2 < argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_copy_usage, options);
}
if (get_sha1(argv[0], from_obj))
- die("Failed to resolve '%s' as a valid ref.", argv[0]);
+ die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ die(_("Failed to resolve '%s' as a valid ref."), object_ref);
t = init_notes_check("copy");
note = get_note(t, object);
if (note) {
if (!force) {
- retval = error("Cannot copy notes. Found existing "
+ retval = error(_("Cannot copy notes. Found existing "
"notes for object %s. Use '-f' to "
- "overwrite existing notes",
+ "overwrite existing notes"),
sha1_to_hex(object));
goto out;
}
- fprintf(stderr, "Overwriting existing notes for object %s\n",
+ fprintf(stderr, _("Overwriting existing notes for object %s\n"),
sha1_to_hex(object));
}
from_note = get_note(t, from_obj);
if (!from_note) {
- retval = error("Missing notes on source object %s. Cannot "
- "copy.", sha1_to_hex(from_obj));
+ retval = error(_("Missing notes on source object %s. Cannot "
+ "copy."), sha1_to_hex(from_obj));
goto out;
}
- add_note(t, object, from_note, combine_notes_overwrite);
+ if (add_note(t, object, from_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
commit_notes(t, "Notes added by 'git notes copy'");
out:
free_notes(t);
@@ -670,16 +688,16 @@ static int append_edit(int argc, const char **argv, const char *prefix)
const char * const *usage;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+ { OPTION_CALLBACK, 'm', "message", &msg, "msg",
"note contents as a string", PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+ { OPTION_CALLBACK, 'F', "file", &msg, "file",
"note contents in a file", PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
"reuse and edit specified note object", PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+ { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
"reuse specified note object", PARSE_OPT_NONEG,
parse_reuse_arg},
OPT_END()
@@ -691,19 +709,19 @@ static int append_edit(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0);
if (2 < argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(usage, options);
}
if (msg.given && edit)
- fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
+ fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n"
- "Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
+ "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"));
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ die(_("Failed to resolve '%s' as a valid ref."), object_ref);
t = init_notes_check(argv[0]);
note = get_note(t, object);
@@ -712,8 +730,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
@@ -738,20 +756,20 @@ static int show(int argc, const char **argv, const char *prefix)
0);
if (1 < argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_show_usage, options);
}
object_ref = argc ? argv[0] : "HEAD";
if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ die(_("Failed to resolve '%s' as a valid ref."), object_ref);
t = init_notes_check("show");
note = get_note(t, object);
if (!note)
- retval = error("No note found for object %s.",
+ retval = error(_("No note found for object %s."),
sha1_to_hex(object));
else {
const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
@@ -761,36 +779,236 @@ static int show(int argc, const char **argv, const char *prefix)
return retval;
}
-static int remove_cmd(int argc, const char **argv, const char *prefix)
+static int merge_abort(struct notes_merge_options *o)
+{
+ int ret = 0;
+
+ /*
+ * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
+ * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
+ */
+
+ if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+ ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
+ if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+ ret += error("Failed to delete ref NOTES_MERGE_REF");
+ if (notes_merge_abort(o))
+ ret += error("Failed to remove 'git notes merge' worktree");
+ return ret;
+}
+
+static int merge_commit(struct notes_merge_options *o)
+{
+ struct strbuf msg = STRBUF_INIT;
+ unsigned char sha1[20], parent_sha1[20];
+ struct notes_tree *t;
+ struct commit *partial;
+ struct pretty_print_context pretty_ctx;
+
+ /*
+ * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
+ * and target notes ref from .git/NOTES_MERGE_REF.
+ */
+
+ if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+ die("Failed to read ref NOTES_MERGE_PARTIAL");
+ else if (!(partial = lookup_commit_reference(sha1)))
+ die("Could not find commit from NOTES_MERGE_PARTIAL.");
+ else if (parse_commit(partial))
+ die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+
+ if (partial->parents)
+ hashcpy(parent_sha1, partial->parents->item->object.sha1);
+ else
+ hashclr(parent_sha1);
+
+ t = xcalloc(1, sizeof(struct notes_tree));
+ init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
+
+ o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+ if (!o->local_ref)
+ die("Failed to resolve NOTES_MERGE_REF");
+
+ if (notes_merge_commit(o, t, partial, sha1))
+ die("Failed to finalize notes merge");
+
+ /* Reuse existing commit message in reflog message */
+ memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+ format_commit_message(partial, "%s", &msg, &pretty_ctx);
+ strbuf_trim(&msg);
+ strbuf_insert(&msg, 0, "notes: ", 7);
+ update_ref(msg.buf, o->local_ref, sha1,
+ is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+ 0, DIE_ON_ERR);
+
+ free_notes(t);
+ strbuf_release(&msg);
+ return merge_abort(o);
+}
+
+static int merge(int argc, const char **argv, const char *prefix)
{
+ struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
+ unsigned char result_sha1[20];
+ struct notes_tree *t;
+ struct notes_merge_options o;
+ int do_merge = 0, do_commit = 0, do_abort = 0;
+ int verbosity = 0, result;
+ const char *strategy = NULL;
struct option options[] = {
+ OPT_GROUP("General options"),
+ OPT__VERBOSITY(&verbosity),
+ OPT_GROUP("Merge options"),
+ OPT_STRING('s', "strategy", &strategy, "strategy",
+ "resolve notes conflicts using the given strategy "
+ "(manual/ours/theirs/union/cat_sort_uniq)"),
+ OPT_GROUP("Committing unmerged notes"),
+ { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+ "finalize notes merge by committing unmerged notes",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_GROUP("Aborting notes merge resolution"),
+ { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+ "abort notes merge",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
OPT_END()
};
- const char *object_ref;
- struct notes_tree *t;
- unsigned char object[20];
argc = parse_options(argc, argv, prefix, options,
- git_notes_remove_usage, 0);
+ git_notes_merge_usage, 0);
- if (1 < argc) {
+ if (strategy || do_commit + do_abort == 0)
+ do_merge = 1;
+ if (do_merge + do_commit + do_abort != 1) {
+ error("cannot mix --commit, --abort or -s/--strategy");
+ usage_with_options(git_notes_merge_usage, options);
+ }
+
+ if (do_merge && argc != 1) {
+ error("Must specify a notes ref to merge");
+ usage_with_options(git_notes_merge_usage, options);
+ } else if (!do_merge && argc) {
error("too many parameters");
- usage_with_options(git_notes_remove_usage, options);
+ usage_with_options(git_notes_merge_usage, options);
}
- object_ref = argc ? argv[0] : "HEAD";
+ init_notes_merge_options(&o);
+ o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
+
+ if (do_abort)
+ return merge_abort(&o);
+ if (do_commit)
+ return merge_commit(&o);
+
+ o.local_ref = default_notes_ref();
+ strbuf_addstr(&remote_ref, argv[0]);
+ expand_notes_ref(&remote_ref);
+ o.remote_ref = remote_ref.buf;
+
+ if (strategy) {
+ if (!strcmp(strategy, "manual"))
+ o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
+ else if (!strcmp(strategy, "ours"))
+ o.strategy = NOTES_MERGE_RESOLVE_OURS;
+ else if (!strcmp(strategy, "theirs"))
+ o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
+ else if (!strcmp(strategy, "union"))
+ o.strategy = NOTES_MERGE_RESOLVE_UNION;
+ else if (!strcmp(strategy, "cat_sort_uniq"))
+ o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
+ else {
+ error("Unknown -s/--strategy: %s", strategy);
+ usage_with_options(git_notes_merge_usage, options);
+ }
+ }
- if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ t = init_notes_check("merge");
+
+ strbuf_addf(&msg, "notes: Merged notes from %s into %s",
+ remote_ref.buf, default_notes_ref());
+ strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
+
+ result = notes_merge(&o, t, result_sha1);
+
+ if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
+ /* Update default notes ref with new commit */
+ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ else { /* Merge has unresolved conflicts */
+ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
+ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
+ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+ die("Failed to store link to current notes ref (%s)",
+ default_notes_ref());
+ printf("Automatic notes merge failed. Fix conflicts in %s and "
+ "commit the result with 'git notes merge --commit', or "
+ "abort the merge with 'git notes merge --abort'.\n",
+ git_path(NOTES_MERGE_WORKTREE));
+ }
- t = init_notes_check("remove");
+ free_notes(t);
+ strbuf_release(&remote_ref);
+ strbuf_release(&msg);
+ return result < 0; /* return non-zero on conflicts */
+}
+
+#define IGNORE_MISSING 1
+
+static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag)
+{
+ int status;
+ unsigned char sha1[20];
+ if (get_sha1(name, sha1))
+ return error(_("Failed to resolve '%s' as a valid ref."), name);
+ status = remove_note(t, sha1);
+ if (status)
+ fprintf(stderr, _("Object %s has no note\n"), name);
+ else
+ fprintf(stderr, _("Removing note for object %s\n"), name);
+ return (flag & IGNORE_MISSING) ? 0 : status;
+}
+
+static int remove_cmd(int argc, const char **argv, const char *prefix)
+{
+ unsigned flag = 0;
+ int from_stdin = 0;
+ struct option options[] = {
+ OPT_BIT(0, "ignore-missing", &flag,
+ "attempt to remove non-existent note is not an error",
+ IGNORE_MISSING),
+ OPT_BOOLEAN(0, "stdin", &from_stdin,
+ "read object names from the standard input"),
+ OPT_END()
+ };
+ struct notes_tree *t;
+ int retval = 0;
- fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object));
- remove_note(t, object);
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_remove_usage, 0);
- commit_notes(t, "Notes removed by 'git notes remove'");
+ t = init_notes_check("remove");
+
+ if (!argc && !from_stdin) {
+ retval = remove_one_note(t, "HEAD", flag);
+ } else {
+ while (*argv) {
+ retval |= remove_one_note(t, *argv, flag);
+ argv++;
+ }
+ }
+ if (from_stdin) {
+ struct strbuf sb = STRBUF_INIT;
+ while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
+ strbuf_rtrim(&sb);
+ retval |= remove_one_note(t, sb.buf, flag);
+ }
+ strbuf_release(&sb);
+ }
+ if (!retval)
+ commit_notes(t, "Notes removed by 'git notes remove'");
free_notes(t);
- return 0;
+ return retval;
}
static int prune(int argc, const char **argv, const char *prefix)
@@ -798,8 +1016,8 @@ static int prune(int argc, const char **argv, const char *prefix)
struct notes_tree *t;
int show_only = 0, verbose = 0;
struct option options[] = {
- OPT_BOOLEAN('n', NULL, &show_only, "do not remove, show only"),
- OPT_BOOLEAN('v', NULL, &verbose, "report pruned notes"),
+ OPT__DRY_RUN(&show_only, "do not remove, show only"),
+ OPT__VERBOSE(&verbose, "report pruned notes"),
OPT_END()
};
@@ -807,7 +1025,7 @@ static int prune(int argc, const char **argv, const char *prefix)
0);
if (argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_prune_usage, options);
}
@@ -821,6 +1039,21 @@ static int prune(int argc, const char **argv, const char *prefix)
return 0;
}
+static int get_ref(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = { OPT_END() };
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_get_ref_usage, 0);
+
+ if (argc) {
+ error("too many parameters");
+ usage_with_options(git_notes_get_ref_usage, options);
+ }
+
+ puts(default_notes_ref());
+ return 0;
+}
+
int cmd_notes(int argc, const char **argv, const char *prefix)
{
int result;
@@ -837,13 +1070,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
if (override_notes_ref) {
struct strbuf sb = STRBUF_INIT;
- if (!prefixcmp(override_notes_ref, "refs/notes/"))
- /* we're happy */;
- else if (!prefixcmp(override_notes_ref, "notes/"))
- strbuf_addstr(&sb, "refs/");
- else
- strbuf_addstr(&sb, "refs/notes/");
strbuf_addstr(&sb, override_notes_ref);
+ expand_notes_ref(&sb);
setenv("GIT_NOTES_REF", sb.buf, 1);
strbuf_release(&sb);
}
@@ -858,12 +1086,16 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
result = append_edit(argc, argv, prefix);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv, prefix);
+ else if (!strcmp(argv[0], "merge"))
+ result = merge(argc, argv, prefix);
else if (!strcmp(argv[0], "remove"))
result = remove_cmd(argc, argv, prefix);
else if (!strcmp(argv[0], "prune"))
result = prune(argc, argv, prefix);
+ else if (!strcmp(argv[0], "get-ref"))
+ result = get_ref(argc, argv, prefix);
else {
- result = error("Unknown subcommand: %s", argv[0]);
+ result = error(_("Unknown subcommand: %s"), argv[0]);
usage_with_options(git_notes_usage, options);
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 0e81673..84e6daf 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -16,22 +16,18 @@
#include "list-objects.h"
#include "progress.h"
#include "refs.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static const char pack_usage[] =
- "git pack-objects [{ -q | --progress | --all-progress }]\n"
+ "git pack-objects [ -q | --progress | --all-progress ]\n"
" [--all-progress-implied]\n"
- " [--max-pack-size=N] [--local] [--incremental]\n"
- " [--window=N] [--window-memory=N] [--depth=N]\n"
+ " [--max-pack-size=<n>] [--local] [--incremental]\n"
+ " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
" [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
- " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n"
+ " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
" [--reflog] [--stdout | base-name] [--include-tag]\n"
- " [--keep-unreachable | --unpack-unreachable \n"
- " [<ref-list | <object-list]";
+ " [--keep-unreachable | --unpack-unreachable]\n"
+ " [< ref-list | < object-list]";
struct object_entry {
struct pack_idx_entry idx;
@@ -74,6 +70,7 @@ static int local;
static int incremental;
static int ignore_packed_keep;
static int allow_ofs_delta;
+static struct pack_idx_option pack_idx_opts;
static const char *base_name;
static int progress = 1;
static int window = 10;
@@ -130,13 +127,13 @@ static void *get_delta(struct object_entry *entry)
static unsigned long do_compress(void **pptr, unsigned long size)
{
- z_stream stream;
+ git_zstream stream;
void *in, *out;
unsigned long maxsize;
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, pack_compression_level);
- maxsize = deflateBound(&stream, size);
+ git_deflate_init(&stream, pack_compression_level);
+ maxsize = git_deflate_bound(&stream, size);
in = *pptr;
out = xmalloc(maxsize);
@@ -146,9 +143,9 @@ static unsigned long do_compress(void **pptr, unsigned long size)
stream.avail_in = size;
stream.next_out = out;
stream.avail_out = maxsize;
- while (deflate(&stream, Z_FINISH) == Z_OK)
+ while (git_deflate(&stream, Z_FINISH) == Z_OK)
; /* nothing */
- deflateEnd(&stream);
+ git_deflate_end(&stream);
free(in);
return stream.total_out;
@@ -164,7 +161,7 @@ static int check_pack_inflate(struct packed_git *p,
off_t len,
unsigned long expect)
{
- z_stream stream;
+ git_zstream stream;
unsigned char fakebuf[4096], *in;
int st;
@@ -191,12 +188,12 @@ static void copy_pack_data(struct sha1file *f,
off_t len)
{
unsigned char *in;
- unsigned int avail;
+ unsigned long avail;
while (len) {
in = use_pack(p, w_curs, offset, &avail);
if (avail > len)
- avail = (unsigned int)len;
+ avail = (unsigned long)len;
sha1write(f, in, avail);
offset += avail;
len -= avail;
@@ -431,7 +428,7 @@ static int write_one(struct sha1file *f,
written_list[nr_written++] = &e->idx;
/* make sure off_t is sufficiently large not to wrap */
- if (*offset > *offset + size)
+ if (signed_add_overflows(*offset, size))
die("pack too large for current definition of off_t");
*offset += size;
return 1;
@@ -497,8 +494,8 @@ static void write_pack_file(void)
const char *idx_tmp_name;
char tmpname[PATH_MAX];
- idx_tmp_name = write_idx_file(NULL, written_list,
- nr_written, sha1);
+ idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
+ &pack_idx_opts, sha1);
snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
base_name, sha1_to_hex(sha1));
@@ -998,7 +995,7 @@ static void check_object(struct object_entry *entry)
const unsigned char *base_ref = NULL;
struct object_entry *base_entry;
unsigned long used, used_0;
- unsigned int avail;
+ unsigned long avail;
off_t ofs;
unsigned char *buf, c;
@@ -1146,8 +1143,12 @@ static void get_object_details(void)
sorted_by_offset[i] = objects + i;
qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
- for (i = 0; i < nr_objects; i++)
- check_object(sorted_by_offset[i]);
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *entry = sorted_by_offset[i];
+ check_object(entry);
+ if (big_file_threshold <= entry->size)
+ entry->no_try_delta = 1;
+ }
free(sorted_by_offset);
}
@@ -1298,9 +1299,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
read_lock();
src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
read_unlock();
- if (!src->data)
+ if (!src->data) {
+ if (src_entry->preferred_base) {
+ static int warned = 0;
+ if (!warned++)
+ warning("object %s cannot be read",
+ sha1_to_hex(src_entry->idx.sha1));
+ /*
+ * Those objects are not included in the
+ * resulting pack. Be resilient and ignore
+ * them if they can't be read, in case the
+ * pack could be created nevertheless.
+ */
+ return 0;
+ }
die("object %s cannot be read",
sha1_to_hex(src_entry->idx.sha1));
+ }
if (sz != src_size)
die("object %s inconsistent object length (%lu vs %lu)",
sha1_to_hex(src_entry->idx.sha1), sz, src_size);
@@ -1529,7 +1544,7 @@ static void try_to_free_from_threads(size_t size)
read_unlock();
}
-try_to_free_t old_try_to_free_routine;
+static try_to_free_t old_try_to_free_routine;
/*
* The main thread waits on the condition that (at least) one of the workers
@@ -1870,10 +1885,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
return 0;
}
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
+ pack_idx_opts.version = git_config_int(k, v);
+ if (pack_idx_opts.version > 2)
die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
+ pack_idx_opts.version);
return 0;
}
if (!strcmp(k, "pack.packsizelimit")) {
@@ -2120,6 +2135,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
rp_ac = 2;
+ reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
pack_compression_level = core_compression_level;
@@ -2264,12 +2280,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
if (!prefixcmp(arg, "--index-version=")) {
char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
+ pack_idx_opts.version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_opts.version > 2)
die("bad %s", arg);
if (*c == ',')
- pack_idx_off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_off32_limit & 0x80000000)
+ pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_opts.off32_limit & 0x80000000)
die("bad %s", arg);
continue;
}
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index 41e1615..f5c6afc 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -6,8 +6,7 @@
*
*/
-#include "cache.h"
-#include "exec_cmd.h"
+#include "builtin.h"
#define BLKSIZE 512
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index 091860b..39a9d89 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "parse-options.h"
#include "pack-refs.h"
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 5125300..f821eb3 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -1,5 +1,4 @@
-#include "cache.h"
-#include "exec_cmd.h"
+#include "builtin.h"
static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
{
@@ -57,7 +56,7 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
return 1;
}
-int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
{
static char line[1000];
int patchlen = 0, found_next = 0;
@@ -73,6 +72,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
p += 7;
else if (!memcmp(line, "From ", 5))
p += 5;
+ else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
+ continue;
if (!get_sha1_hex(p, next_sha1)) {
found_next = 1;
diff --git a/builtin/prune.c b/builtin/prune.c
index 81f915ec..e65690b 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -125,10 +125,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
const struct option options[] = {
- OPT_BOOLEAN('n', NULL, &show_only,
- "do not remove, show only"),
- OPT_BOOLEAN('v', NULL, &verbose,
- "report pruned objects"),
+ OPT__DRY_RUN(&show_only, "do not remove, show only"),
+ OPT__VERBOSE(&verbose, "report pruned objects"),
OPT_DATE(0, "expire", &expire,
"expire objects older than <time>"),
OPT_END()
diff --git a/builtin/push.c b/builtin/push.c
index f4358b9..9cebf9e 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -22,13 +22,13 @@ static int progress;
static const char **refspec;
static int refspec_nr;
+static int refspec_alloc;
static void add_refspec(const char *ref)
{
- int nr = refspec_nr + 1;
- refspec = xrealloc(refspec, nr * sizeof(char *));
- refspec[nr-1] = ref;
- refspec_nr = nr;
+ refspec_nr++;
+ ALLOC_GROW(refspec, refspec_nr, refspec_alloc);
+ refspec[refspec_nr-1] = ref;
}
static void set_refspecs(const char **refs, int nr)
@@ -40,7 +40,7 @@ static void set_refspecs(const char **refs, int nr)
char *tag;
int len;
if (nr <= ++i)
- die("tag shorthand without <tag>");
+ die(_("tag shorthand without <tag>"));
len = strlen(refs[i]) + 11;
if (deleterefs) {
tag = xmalloc(len+1);
@@ -59,28 +59,38 @@ static void set_refspecs(const char **refs, int nr)
strcat(delref, ref);
ref = delref;
} else if (deleterefs)
- die("--delete only accepts plain target ref names");
+ die(_("--delete only accepts plain target ref names"));
add_refspec(ref);
}
}
-static void setup_push_tracking(void)
+static void setup_push_upstream(struct remote *remote)
{
struct strbuf refspec = STRBUF_INIT;
struct branch *branch = branch_get(NULL);
if (!branch)
- die("You are not currently on a branch.");
+ die(_("You are not currently on a branch.\n"
+ "To push the history leading to the current (detached HEAD)\n"
+ "state now, use\n"
+ "\n"
+ " git push %s HEAD:<name-of-remote-branch>\n"),
+ remote->name);
if (!branch->merge_nr || !branch->merge)
- die("The current branch %s is not tracking anything.",
+ 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,
branch->name);
if (branch->merge_nr != 1)
- die("The current branch %s is tracking multiple branches, "
- "refusing to push.", branch->name);
+ die(_("The current branch %s has multiple upstream branches, "
+ "refusing to push."), branch->name);
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
}
-static void setup_default_push_refspecs(void)
+static void setup_default_push_refspecs(struct remote *remote)
{
switch (push_default) {
default:
@@ -88,8 +98,8 @@ static void setup_default_push_refspecs(void)
add_refspec(":");
break;
- case PUSH_DEFAULT_TRACKING:
- setup_push_tracking();
+ case PUSH_DEFAULT_UPSTREAM:
+ setup_push_upstream(remote);
break;
case PUSH_DEFAULT_CURRENT:
@@ -97,8 +107,8 @@ static void setup_default_push_refspecs(void)
break;
case PUSH_DEFAULT_NOTHING:
- die("You didn't specify any refspecs to push, and "
- "push.default is \"nothing\".");
+ die(_("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\"."));
break;
}
}
@@ -117,11 +127,11 @@ static int push_with_options(struct transport *transport, int flags)
transport_set_option(transport, TRANS_OPT_THIN, "yes");
if (verbosity > 0)
- fprintf(stderr, "Pushing to %s\n", transport->url);
+ fprintf(stderr, _("Pushing to %s\n"), transport->url);
err = transport_push(transport, refspec_nr, refspec, flags,
&nonfastforward);
if (err != 0)
- error("failed to push some refs to '%s'", transport->url);
+ error(_("failed to push some refs to '%s'"), transport->url);
err |= transport_disconnect(transport);
@@ -129,9 +139,9 @@ static int push_with_options(struct transport *transport, int flags)
return 0;
if (nonfastforward && advice_push_nonfastforward) {
- fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n"
- "Merge the remote changes before pushing again. See the 'Note about\n"
- "fast-forwards' section of 'git push --help' for details.\n");
+ fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
+ "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
+ "'Note about fast-forwards' section of 'git push --help' for details.\n"));
}
return 1;
@@ -146,8 +156,15 @@ static int do_push(const char *repo, int flags)
if (!remote) {
if (repo)
- die("bad repository '%s'", repo);
- die("No destination configured to push to.");
+ die(_("bad repository '%s'"), repo);
+ die(_("No configured push destination.\n"
+ "Either specify the URL from the command-line or configure a remote repository using\n"
+ "\n"
+ " git remote add <name> <url>\n"
+ "\n"
+ "and then push using the remote name\n"
+ "\n"
+ " git push <name>\n"));
}
if (remote->mirror)
@@ -155,19 +172,19 @@ static int do_push(const char *repo, int flags)
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
- return error("--all and --tags are incompatible");
- return error("--all can't be combined with refspecs");
+ return error(_("--all and --tags are incompatible"));
+ return error(_("--all can't be combined with refspecs"));
}
if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
- return error("--mirror and --tags are incompatible");
- return error("--mirror can't be combined with refspecs");
+ return error(_("--mirror and --tags are incompatible"));
+ return error(_("--mirror can't be combined with refspecs"));
}
if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
(TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
- return error("--all and --mirror are incompatible");
+ return error(_("--all and --mirror are incompatible"));
}
if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
@@ -175,7 +192,7 @@ static int do_push(const char *repo, int flags)
refspec = remote->push_refspec;
refspec_nr = remote->push_refspec_nr;
} else if (!(flags & TRANSPORT_PUSH_MIRROR))
- setup_default_push_refspecs();
+ setup_default_push_refspecs(remote);
}
errs = 0;
if (remote->pushurl_nr) {
@@ -228,13 +245,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ packet_trace_identity("push");
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
- die("--delete is incompatible with --all, --mirror and --tags");
+ die(_("--delete is incompatible with --all, --mirror and --tags"));
if (deleterefs && argc < 2)
- die("--delete doesn't make sense without any refs");
+ die(_("--delete doesn't make sense without any refs"));
if (tags)
add_refspec("refs/tags/*");
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 8bdcab1..df6c4c8 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -16,6 +16,7 @@
#include "resolve-undo.h"
static int nr_trees;
+static int read_empty;
static struct tree *trees[MAX_UNPACK_TREES];
static int list_tree(unsigned char *sha1)
@@ -32,7 +33,7 @@ static int list_tree(unsigned char *sha1)
}
static const char * const read_tree_usage[] = {
- "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+ "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])",
NULL
};
@@ -103,10 +104,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
struct unpack_trees_options opts;
int prefix_set = 0;
const struct option read_tree_options[] = {
- { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
- "write resulting index to <FILE>",
+ { OPTION_CALLBACK, 0, "index-output", NULL, "file",
+ "write resulting index to <file>",
PARSE_OPT_NONEG, index_output_cb },
- OPT__VERBOSE(&opts.verbose_update),
+ OPT_SET_INT(0, "empty", &read_empty,
+ "only empty the index", 1),
+ OPT__VERBOSE(&opts.verbose_update, "be verbose"),
OPT_GROUP("Merging"),
OPT_SET_INT('m', NULL, &opts.merge,
"perform a merge in addition to a read", 1),
@@ -127,6 +130,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
PARSE_OPT_NONEG, exclude_per_directory_cb },
OPT_SET_INT('i', NULL, &opts.index_only,
"don't check the working tree after merging", 1),
+ OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"),
OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
"skip applying sparse checkout filter", 1),
OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
@@ -166,6 +170,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
die("failed to unpack tree object %s", arg);
stage++;
}
+ if (nr_trees == 0 && !read_empty)
+ warning("read-tree: emptying the index with no arguments is deprecated; use --empty");
+ else if (nr_trees > 0 && read_empty)
+ die("passing trees as arguments contradicts --empty");
+
if (1 < opts.index_only + opts.update)
die("-u and -i at the same time makes no sense");
if ((opts.update||opts.index_only) && !opts.merge)
@@ -211,7 +220,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
if (unpack_trees(nr_trees, t, &opts))
return 128;
- if (opts.debug_unpack)
+ if (opts.debug_unpack || opts.dry_run)
return 0; /* do not write the index out */
/*
@@ -219,14 +228,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
* "-m ent" or "--reset ent" form), we can obtain a fully
* valid cache-tree because the index must match exactly
* what came from the tree.
- *
- * The same holds true if we are switching between two trees
- * using read-tree -m A B. The index must match B after that.
*/
if (nr_trees == 1 && !opts.prefix)
prime_cache_tree(&active_cache_tree, trees[0]);
- else if (nr_trees == 2 && opts.merge)
- prime_cache_tree(&active_cache_tree, trees[1]);
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index d634b5a..e1a687a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
@@ -10,6 +10,7 @@
#include "remote.h"
#include "transport.h"
#include "string-list.h"
+#include "sha1-array.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@ -530,7 +531,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
static void check_aliased_updates(struct command *commands)
{
struct command *cmd;
- struct string_list ref_list = { NULL, 0, 0, 0 };
+ struct string_list ref_list = STRING_LIST_INIT_NODUP;
for (cmd = commands; cmd; cmd = cmd->next) {
struct string_list_item *item =
@@ -731,43 +732,23 @@ static int delete_only(struct command *commands)
return 1;
}
-static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
{
- char *other;
- size_t len;
- struct remote *remote;
- struct transport *transport;
- const struct ref *extra;
-
- e->name[-1] = '\0';
- other = xstrdup(make_absolute_path(e->base));
- e->name[-1] = '/';
- len = strlen(other);
-
- while (other[len-1] == '/')
- other[--len] = '\0';
- if (len < 8 || memcmp(other + len - 8, "/objects", 8))
- return 0;
- /* Is this a git repository with refs? */
- memcpy(other + len - 8, "/refs", 6);
- if (!is_directory(other))
- return 0;
- other[len - 8] = '\0';
- remote = remote_get(other);
- transport = transport_get(remote, other);
- for (extra = transport_get_remote_refs(transport);
- extra;
- extra = extra->next) {
- add_extra_ref(".have", extra->old_sha1, 0);
- }
- transport_disconnect(transport);
- free(other);
- return 0;
+ add_extra_ref(".have", sha1, 0);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+ struct sha1_array *sa = data;
+ sha1_array_append(sa, ref->old_sha1);
}
static void add_alternate_refs(void)
{
- foreach_alt_odb(add_refs_from_alternate, NULL);
+ struct sha1_array sa = SHA1_ARRAY_INIT;
+ for_each_alternate_ref(collect_one_alternate_ref, &sa);
+ sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
+ sha1_array_clear(&sa);
}
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
@@ -778,6 +759,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
char *dir = NULL;
struct command *commands;
+ packet_trace_identity("receive-pack");
+
argv++;
for (i = 1; i < argc; i++) {
const char *arg = *argv++;
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
new file mode 100644
index 0000000..692c834
--- /dev/null
+++ b/builtin/remote-ext.c
@@ -0,0 +1,242 @@
+#include "builtin.h"
+#include "transport.h"
+#include "run-command.h"
+
+/*
+ * URL syntax:
+ * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
+ * Special characters:
+ * '% ': Literal space in argument.
+ * '%%': Literal percent sign.
+ * '%S': Name of service (git-upload-pack/git-upload-archive/
+ * git-receive-pack.
+ * '%s': Same as \s, but with possible git- prefix stripped.
+ * '%G': Only allowed as first 'character' of argument. Do not pass this
+ * Argument to command, instead send this as name of repository
+ * in in-line git://-style request (also activates sending this
+ * style of request).
+ * '%V': Only allowed as first 'character' of argument. Used in
+ * conjunction with '%G': Do not pass this argument to command,
+ * instead send this as vhost in git://-style request (note: does
+ * not activate sending git:// style request).
+ */
+
+static char *git_req;
+static char *git_req_vhost;
+
+static char *strip_escapes(const char *str, const char *service,
+ const char **next)
+{
+ size_t rpos = 0;
+ int escape = 0;
+ char special = 0;
+ size_t psoff = 0;
+ struct strbuf ret = STRBUF_INIT;
+
+ /* Calculate prefix length for \s and lengths for \s and \S */
+ if (!strncmp(service, "git-", 4))
+ psoff = 4;
+
+ /* Pass the service to command. */
+ setenv("GIT_EXT_SERVICE", service, 1);
+ setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
+
+ /* Scan the length of argument. */
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ case 's':
+ case 'S':
+ break;
+ case 'G':
+ case 'V':
+ special = str[rpos];
+ if (rpos == 1)
+ break;
+ /* Fall-through to error. */
+ default:
+ die("Bad remote-ext placeholder '%%%c'.",
+ str[rpos]);
+ }
+ escape = 0;
+ } else
+ escape = (str[rpos] == '%');
+ rpos++;
+ }
+ if (escape && !str[rpos])
+ die("remote-ext command has incomplete placeholder");
+ *next = str + rpos;
+ if (**next == ' ')
+ ++*next; /* Skip over space */
+
+ /*
+ * Do the actual placeholder substitution. The string will be short
+ * enough not to overflow integers.
+ */
+ rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
+ escape = 0;
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ case 's':
+ strbuf_addstr(&ret, service + psoff);
+ break;
+ case 'S':
+ strbuf_addstr(&ret, service);
+ break;
+ }
+ escape = 0;
+ } else
+ switch (str[rpos]) {
+ case '%':
+ escape = 1;
+ break;
+ default:
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ }
+ rpos++;
+ }
+ switch (special) {
+ case 'G':
+ git_req = strbuf_detach(&ret, NULL);
+ return NULL;
+ case 'V':
+ git_req_vhost = strbuf_detach(&ret, NULL);
+ return NULL;
+ default:
+ return strbuf_detach(&ret, NULL);
+ }
+}
+
+/* Should be enough... */
+#define MAXARGUMENTS 256
+
+static const char **parse_argv(const char *arg, const char *service)
+{
+ int arguments = 0;
+ int i;
+ const char **ret;
+ char *temparray[MAXARGUMENTS + 1];
+
+ while (*arg) {
+ char *expanded;
+ if (arguments == MAXARGUMENTS)
+ die("remote-ext command has too many arguments");
+ expanded = strip_escapes(arg, service, &arg);
+ if (expanded)
+ temparray[arguments++] = expanded;
+ }
+
+ ret = xmalloc((arguments + 1) * sizeof(char *));
+ for (i = 0; i < arguments; i++)
+ ret[i] = temparray[i];
+ ret[arguments] = NULL;
+ return ret;
+}
+
+static void send_git_request(int stdin_fd, const char *serv, const char *repo,
+ const char *vhost)
+{
+ size_t bufferspace;
+ size_t wpos = 0;
+ char *buffer;
+
+ /*
+ * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
+ * 6 bytes extra (xxxx \0) if there is no vhost.
+ */
+ if (vhost)
+ bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+ else
+ bufferspace = strlen(serv) + strlen(repo) + 6;
+
+ if (bufferspace > 0xFFFF)
+ die("Request too large to send");
+ buffer = xmalloc(bufferspace);
+
+ /* Make the packet. */
+ wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
+ serv, repo, 0);
+
+ /* Add vhost if any. */
+ if (vhost)
+ sprintf(buffer + wpos, "host=%s%c", vhost, 0);
+
+ /* Send the request */
+ if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
+ die_errno("Failed to send request");
+
+ free(buffer);
+}
+
+static int run_child(const char *arg, const char *service)
+{
+ int r;
+ struct child_process child;
+
+ memset(&child, 0, sizeof(child));
+ child.in = -1;
+ child.out = -1;
+ child.err = 0;
+ child.argv = parse_argv(arg, service);
+
+ if (start_command(&child) < 0)
+ die("Can't run specified command");
+
+ if (git_req)
+ send_git_request(child.in, service, git_req, git_req_vhost);
+
+ r = bidirectional_transfer_loop(child.out, child.in);
+ if (!r)
+ r = finish_command(&child);
+ else
+ finish_command(&child);
+ return r;
+}
+
+#define MAXCOMMAND 4096
+
+static int command_loop(const char *child)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t i;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Comammand input error");
+ exit(0);
+ }
+ /* Strip end of line characters. */
+ i = strlen(buffer);
+ while (i > 0 && isspace(buffer[i - 1]))
+ buffer[--i] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ return run_child(child, buffer + 8);
+ } else {
+ fprintf(stderr, "Bad command");
+ return 1;
+ }
+ }
+}
+
+int cmd_remote_ext(int argc, const char **argv, const char *prefix)
+{
+ if (argc != 3)
+ die("Expected two arguments");
+
+ return command_loop(argv[2]);
+}
diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c
new file mode 100644
index 0000000..08d7121
--- /dev/null
+++ b/builtin/remote-fd.c
@@ -0,0 +1,79 @@
+#include "builtin.h"
+#include "transport.h"
+
+/*
+ * URL syntax:
+ * 'fd::<inoutfd>[/<anything>]' Read/write socket pair
+ * <inoutfd>.
+ * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write
+ * pipe <outfd>.
+ * [foo] indicates 'foo' is optional. <anything> is any string.
+ *
+ * The data output to <outfd>/<inoutfd> should be passed unmolested to
+ * git-receive-pack/git-upload-pack/git-upload-archive and output of
+ * git-receive-pack/git-upload-pack/git-upload-archive should be passed
+ * unmolested to <infd>/<inoutfd>.
+ *
+ */
+
+#define MAXCOMMAND 4096
+
+static void command_loop(int input_fd, int output_fd)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t i;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Input error");
+ return;
+ }
+ /* Strip end of line characters. */
+ i = strlen(buffer);
+ while (i > 0 && isspace(buffer[i - 1]))
+ buffer[--i] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ if (bidirectional_transfer_loop(input_fd,
+ output_fd))
+ die("Copying data between file descriptors failed");
+ return;
+ } else {
+ die("Bad command: %s", buffer);
+ }
+ }
+}
+
+int cmd_remote_fd(int argc, const char **argv, const char *prefix)
+{
+ int input_fd = -1;
+ int output_fd = -1;
+ char *end;
+
+ if (argc != 3)
+ die("Expected two arguments");
+
+ input_fd = (int)strtoul(argv[2], &end, 10);
+
+ if ((end == argv[2]) || (*end != ',' && *end != '/' && *end))
+ die("Bad URL syntax");
+
+ if (*end == '/' || !*end) {
+ output_fd = input_fd;
+ } else {
+ char *end2;
+ output_fd = (int)strtoul(end + 1, &end2, 10);
+
+ if ((end2 == end + 1) || (*end2 != '/' && *end2))
+ die("Bad URL syntax");
+ }
+
+ command_loop(input_fd, output_fd);
+ return 0;
+}
diff --git a/builtin/remote.c b/builtin/remote.c
index 6699bc5..05b1f5b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "parse-options.h"
#include "transport.h"
#include "remote.h"
@@ -9,13 +9,13 @@
static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose]",
- "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
+ "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
"git remote rename <old> <new>",
"git remote rm <name>",
"git remote set-head <name> (-a | -d | <branch>)",
"git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
"git remote set-branches <name> [--add] <branch>...",
"git remote set-url <name> <newurl> [<oldurl>]",
"git remote set-url --add <name> <newurl>",
@@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
return strcmp(string + len1 - len2, postfix);
}
-static int opt_parse_track(const struct option *opt, const char *arg, int not)
-{
- struct string_list *list = opt->value;
- if (not)
- string_list_clear(list, 0);
- else
- string_list_append(list, arg);
- return 0;
-}
-
static int fetch_remote(const char *name)
{
const char *argv[] = { "fetch", name, NULL, NULL };
@@ -117,6 +107,11 @@ enum {
TAGS_SET = 2
};
+#define MIRROR_NONE 0
+#define MIRROR_FETCH 1
+#define MIRROR_PUSH 2
+#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH)
+
static int add_branch(const char *key, const char *branchname,
const char *remotename, int mirror, struct strbuf *tmp)
{
@@ -131,10 +126,33 @@ static int add_branch(const char *key, const char *branchname,
return git_config_set_multivar(key, tmp->buf, "^$", 0);
}
+static const char mirror_advice[] =
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead";
+
+static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
+{
+ unsigned *mirror = opt->value;
+ if (not)
+ *mirror = MIRROR_NONE;
+ else if (!arg) {
+ warning("%s", mirror_advice);
+ *mirror = MIRROR_BOTH;
+ }
+ else if (!strcmp(arg, "fetch"))
+ *mirror = MIRROR_FETCH;
+ else if (!strcmp(arg, "push"))
+ *mirror = MIRROR_PUSH;
+ else
+ return error("unknown mirror argument: %s", arg);
+ return 0;
+}
+
static int add(int argc, const char **argv)
{
- int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT;
- struct string_list track = { NULL, 0, 0 };
+ int fetch = 0, fetch_tags = TAGS_DEFAULT;
+ unsigned mirror = MIRROR_NONE;
+ struct string_list track = STRING_LIST_INIT_NODUP;
const char *master = NULL;
struct remote *remote;
struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
@@ -148,10 +166,12 @@ static int add(int argc, const char **argv)
TAGS_SET),
OPT_SET_INT(0, NULL, &fetch_tags,
"or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
- OPT_CALLBACK('t', "track", &track, "branch",
- "branch(es) to track", opt_parse_track),
+ OPT_STRING_LIST('t', "track", &track, "branch",
+ "branch(es) to track"),
OPT_STRING('m', "master", &master, "branch", "master branch"),
- OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+ { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
+ "set up remote as a mirror to push to or fetch from",
+ PARSE_OPT_OPTARG, parse_mirror_opt },
OPT_END()
};
@@ -161,6 +181,11 @@ static int add(int argc, const char **argv)
if (argc < 2)
usage_with_options(builtin_remote_add_usage, options);
+ if (mirror && master)
+ die("specifying a master branch makes no sense with --mirror");
+ if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
+ die("specifying branches to track makes sense only with fetch mirrors");
+
name = argv[0];
url = argv[1];
@@ -177,18 +202,19 @@ static int add(int argc, const char **argv)
if (git_config_set(buf.buf, url))
return 1;
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.fetch", name);
-
- if (track.nr == 0)
- string_list_append(&track, "*");
- for (i = 0; i < track.nr; i++) {
- if (add_branch(buf.buf, track.items[i].string,
- name, mirror, &buf2))
- return 1;
+ if (!mirror || mirror & MIRROR_FETCH) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.fetch", name);
+ if (track.nr == 0)
+ string_list_append(&track, "*");
+ for (i = 0; i < track.nr; i++) {
+ if (add_branch(buf.buf, track.items[i].string,
+ name, mirror, &buf2))
+ return 1;
+ }
}
- if (mirror) {
+ if (mirror & MIRROR_PUSH) {
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.mirror", name);
if (git_config_set(buf.buf, "true"))
@@ -507,7 +533,7 @@ static int add_branch_for_removal(const char *refname,
return 0;
}
- /* don't delete non-remote refs */
+ /* don't delete non-remote-tracking refs */
if (prefixcmp(refname, "refs/remotes")) {
/* advise user how to delete local branches */
if (!prefixcmp(refname, "refs/heads/"))
@@ -596,7 +622,7 @@ static int mv(int argc, const char **argv)
};
struct remote *oldremote, *newremote;
struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
- struct string_list remote_branches = { NULL, 0, 0, 0 };
+ struct string_list remote_branches = STRING_LIST_INIT_NODUP;
struct rename_info rename;
int i;
@@ -734,8 +760,8 @@ static int rm(int argc, const char **argv)
struct remote *remote;
struct strbuf buf = STRBUF_INIT;
struct known_remotes known_remotes = { NULL, NULL };
- struct string_list branches = { NULL, 0, 0, 1 };
- struct string_list skipped = { NULL, 0, 0, 1 };
+ struct string_list branches = STRING_LIST_INIT_DUP;
+ struct string_list skipped = STRING_LIST_INIT_DUP;
struct branches_for_remote cb_data;
int i, result;
@@ -791,9 +817,9 @@ static int rm(int argc, const char **argv)
if (skipped.nr) {
fprintf(stderr, skipped.nr == 1 ?
- "Note: A non-remote branch was not removed; "
+ "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
"to delete it, use:\n" :
- "Note: Non-remote branches were not removed; "
+ "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
"to delete them, use:\n");
for (i = 0; i < skipped.nr; i++)
fprintf(stderr, " git branch -d %s\n",
@@ -1044,7 +1070,7 @@ static int show(int argc, const char **argv)
OPT_END()
};
struct ref_states states;
- struct string_list info_list = { NULL, 0, 0, 0 };
+ struct string_list info_list = STRING_LIST_INIT_NODUP;
struct show_info info;
argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
@@ -1200,7 +1226,7 @@ static int prune(int argc, const char **argv)
{
int dry_run = 0, result = 0;
struct option options[] = {
- OPT__DRY_RUN(&dry_run),
+ OPT__DRY_RUN(&dry_run, "dry run"),
OPT_END()
};
@@ -1483,7 +1509,7 @@ static int get_one_entry(struct remote *remote, void *priv)
static int show_all(void)
{
- struct string_list list = { NULL, 0, 0 };
+ struct string_list list = STRING_LIST_INIT_NODUP;
int result;
list.strdup_strings = 1;
@@ -1512,7 +1538,7 @@ static int show_all(void)
int cmd_remote(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
- OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
+ OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"),
OPT_END()
};
int result;
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 980d542..08213c7 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -1,70 +1,16 @@
#include "builtin.h"
#include "cache.h"
#include "dir.h"
+#include "parse-options.h"
#include "string-list.h"
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
-static const char git_rerere_usage[] =
-"git rerere [clear | status | diff | gc]";
-
-/* these values are days */
-static int cutoff_noresolve = 15;
-static int cutoff_resolve = 60;
-
-static time_t rerere_created_at(const char *name)
-{
- struct stat st;
- return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static void unlink_rr_item(const char *name)
-{
- unlink(rerere_path(name, "thisimage"));
- unlink(rerere_path(name, "preimage"));
- unlink(rerere_path(name, "postimage"));
- rmdir(git_path("rr-cache/%s", name));
-}
-
-static int git_rerere_gc_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "gc.rerereresolved"))
- cutoff_resolve = git_config_int(var, value);
- else if (!strcmp(var, "gc.rerereunresolved"))
- cutoff_noresolve = git_config_int(var, value);
- else
- return git_default_config(var, value, cb);
- return 0;
-}
-
-static void garbage_collect(struct string_list *rr)
-{
- struct string_list to_remove = { NULL, 0, 0, 1 };
- DIR *dir;
- struct dirent *e;
- int i, cutoff;
- time_t now = time(NULL), then;
-
- git_config(git_rerere_gc_config, NULL);
- dir = opendir(git_path("rr-cache"));
- if (!dir)
- die_errno("unable to open rr-cache directory");
- while ((e = readdir(dir))) {
- if (is_dot_or_dotdot(e->d_name))
- continue;
- then = rerere_created_at(e->d_name);
- if (!then)
- continue;
- cutoff = (has_rerere_resolution(e->d_name)
- ? cutoff_resolve : cutoff_noresolve);
- if (then < now - cutoff * 86400)
- string_list_append(&to_remove, e->d_name);
- }
- for (i = 0; i < to_remove.nr; i++)
- unlink_rr_item(to_remove.items[i].string);
- string_list_clear(&to_remove, 0);
-}
+static const char * const rerere_usage[] = {
+ "git rerere [clear | forget path... | status | remaining | diff | gc]",
+ NULL,
+};
static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
{
@@ -102,26 +48,30 @@ static int diff_two(const char *file1, const char *label1,
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
- struct string_list merge_rr = { NULL, 0, 0, 1 };
- int i, fd, flags = 0;
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
+ int i, fd, autoupdate = -1, flags = 0;
- if (2 < argc) {
- if (!strcmp(argv[1], "-h"))
- usage(git_rerere_usage);
- if (!strcmp(argv[1], "--rerere-autoupdate"))
- flags = RERERE_AUTOUPDATE;
- else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
- flags = RERERE_NOAUTOUPDATE;
- if (flags) {
- argc--;
- argv++;
- }
- }
- if (argc < 2)
+ struct option options[] = {
+ OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
+ "register clean resolutions in index", 1),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+
+ if (autoupdate == 1)
+ flags = RERERE_AUTOUPDATE;
+ if (autoupdate == 0)
+ flags = RERERE_NOAUTOUPDATE;
+
+ if (argc < 1)
return rerere(flags);
- if (!strcmp(argv[1], "forget")) {
- const char **pathspec = get_pathspec(prefix, argv + 2);
+ if (!strcmp(argv[0], "forget")) {
+ const char **pathspec;
+ if (argc < 2)
+ warning("'git rerere forget' without paths is deprecated");
+ pathspec = get_pathspec(prefix, argv + 1);
return rerere_forget(pathspec);
}
@@ -129,26 +79,31 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
if (fd < 0)
return 0;
- if (!strcmp(argv[1], "clear")) {
- for (i = 0; i < merge_rr.nr; i++) {
- const char *name = (const char *)merge_rr.items[i].util;
- if (!has_rerere_resolution(name))
- unlink_rr_item(name);
- }
- unlink_or_warn(git_path("rr-cache/MERGE_RR"));
- } else if (!strcmp(argv[1], "gc"))
- garbage_collect(&merge_rr);
- else if (!strcmp(argv[1], "status"))
+ if (!strcmp(argv[0], "clear")) {
+ rerere_clear(&merge_rr);
+ } else if (!strcmp(argv[0], "gc"))
+ rerere_gc(&merge_rr);
+ else if (!strcmp(argv[0], "status"))
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
- else if (!strcmp(argv[1], "diff"))
+ else if (!strcmp(argv[0], "remaining")) {
+ rerere_remaining(&merge_rr);
+ for (i = 0; i < merge_rr.nr; i++) {
+ if (merge_rr.items[i].util != RERERE_RESOLVED)
+ printf("%s\n", merge_rr.items[i].string);
+ else
+ /* prepare for later call to
+ * string_list_clear() */
+ merge_rr.items[i].util = NULL;
+ }
+ } else if (!strcmp(argv[0], "diff"))
for (i = 0; i < merge_rr.nr; i++) {
const char *path = merge_rr.items[i].string;
const char *name = (const char *)merge_rr.items[i].util;
diff_two(rerere_path(name, "preimage"), path, path, path);
}
else
- usage(git_rerere_usage);
+ usage_with_options(rerere_usage, options);
string_list_clear(&merge_rr, 1);
return 0;
diff --git a/builtin/reset.c b/builtin/reset.c
index 1283068..98bca04 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -7,7 +7,7 @@
*
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
*/
-#include "cache.h"
+#include "builtin.h"
#include "tag.h"
#include "object.h"
#include "commit.h"
@@ -30,7 +30,7 @@ static const char * const git_reset_usage[] = {
enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE };
static const char *reset_type_names[] = {
- "mixed", "soft", "hard", "merge", "keep", NULL
+ N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
};
static char *args_to_str(const char **argv)
@@ -92,20 +92,20 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
if (reset_type == KEEP) {
unsigned char head_sha1[20];
if (get_sha1("HEAD", head_sha1))
- return error("You do not have a valid HEAD.");
+ return error(_("You do not have a valid HEAD."));
if (!fill_tree_descriptor(desc, head_sha1))
- return error("Failed to find tree of HEAD.");
+ return error(_("Failed to find tree of HEAD."));
nr++;
opts.fn = twoway_merge;
}
if (!fill_tree_descriptor(desc + nr - 1, sha1))
- return error("Failed to find tree of %s.", sha1_to_hex(sha1));
+ return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
if (unpack_trees(nr, desc, &opts))
return -1;
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock))
- return error("Could not write new index file.");
+ return error(_("Could not write new index file."));
return 0;
}
@@ -115,7 +115,7 @@ static void print_new_head_line(struct commit *commit)
const char *hex, *body;
hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- printf("HEAD is now at %s", hex);
+ printf(_("HEAD is now at %s"), hex);
body = strstr(commit->buffer, "\n\n");
if (body) {
const char *eol;
@@ -139,10 +139,10 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
}
if (read_cache() < 0)
- return error("Could not read index");
+ return error(_("Could not read index"));
result = refresh_index(&the_index, (flags), NULL, NULL,
- "Unstaged changes after reset:") ? 1 : 0;
+ _("Unstaged changes after reset:")) ? 1 : 0;
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(index_lock))
return error ("Could not refresh index");
@@ -167,7 +167,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
ce = make_cache_entry(one->mode, one->sha1, one->path,
0, 0);
if (!ce)
- die("make_cache_entry failed for path '%s'",
+ die(_("make_cache_entry failed for path '%s'"),
one->path);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
ADD_CACHE_OK_TO_REPLACE);
@@ -222,14 +222,14 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size)
if (!rla)
rla = sep = "";
if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
- warning("Reflog action message too long: %.*s...", 50, buf);
+ warning(_("Reflog action message too long: %.*s..."), 50, buf);
}
static void die_if_unmerged_cache(int reset_type)
{
if (is_merge() || read_cache() < 0 || unmerged_cache())
- die("Cannot do a %s reset in the middle of a merge.",
- reset_type_names[reset_type]);
+ die(_("Cannot do a %s reset in the middle of a merge."),
+ _(reset_type_names[reset_type]));
}
@@ -243,7 +243,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
struct commit *commit;
char *reflog_action, msg[1024];
const struct option options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet, "be quiet, only report errors"),
OPT_SET_INT(0, "mixed", &reset_type,
"reset HEAD and index", MIXED),
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
@@ -300,16 +300,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
}
if (get_sha1(rev, sha1))
- die("Failed to resolve '%s' as a valid ref.", rev);
+ die(_("Failed to resolve '%s' as a valid ref."), rev);
commit = lookup_commit_reference(sha1);
if (!commit)
- die("Could not parse object '%s'.", rev);
+ die(_("Could not parse object '%s'."), rev);
hashcpy(sha1, commit->object.sha1);
if (patch_mode) {
if (reset_type != NONE)
- die("--patch is incompatible with --{hard,mixed,soft}");
+ die(_("--patch is incompatible with --{hard,mixed,soft}"));
return interactive_reset(rev, argv + i, prefix);
}
@@ -318,10 +318,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
* affecting the working tree nor HEAD. */
if (i < argc) {
if (reset_type == MIXED)
- warning("--mixed option is deprecated with paths.");
+ warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
- die("Cannot do %s reset with paths.",
- reset_type_names[reset_type]);
+ die(_("Cannot do %s reset with paths."),
+ _(reset_type_names[reset_type]));
return read_from_tree(prefix, argv + i, sha1,
quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
}
@@ -332,8 +332,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
setup_work_tree();
if (reset_type == MIXED && is_bare_repository())
- die("%s reset is not allowed in a bare repository",
- reset_type_names[reset_type]);
+ die(_("%s reset is not allowed in a bare repository"),
+ _(reset_type_names[reset_type]));
/* Soft reset does not touch the index file nor the working tree
* at all, but requires them in a good order. Other resets reset
@@ -348,7 +348,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (reset_type == KEEP)
err = err || reset_index_file(sha1, MIXED, quiet);
if (err)
- die("Could not reset index file to revision '%s'.", rev);
+ die(_("Could not reset index file to revision '%s'."), rev);
}
/* Any resets update HEAD to the head being switched to,
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index efe9360..56727e8 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -11,11 +11,15 @@
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
" limiting output:\n"
-" --max-count=nr\n"
-" --max-age=epoch\n"
-" --min-age=epoch\n"
+" --max-count=<n>\n"
+" --max-age=<epoch>\n"
+" --min-age=<epoch>\n"
" --sparse\n"
" --no-merges\n"
+" --min-parents=<n>\n"
+" --no-min-parents\n"
+" --max-parents=<n>\n"
+" --no-max-parents\n"
" --remove-empty\n"
" --all\n"
" --branches\n"
@@ -33,7 +37,7 @@ static const char rev_list_usage[] =
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
-" --abbrev=nr | --no-abbrev\n"
+" --abbrev=<n> | --no-abbrev\n"
" --abbrev-commit\n"
" --left-right\n"
" special purpose:\n"
@@ -51,7 +55,9 @@ static void show_commit(struct commit *commit, void *data)
graph_show_commit(revs->graph);
if (revs->count) {
- if (commit->object.flags & SYMMETRIC_LEFT)
+ if (commit->object.flags & PATCHSAME)
+ revs->count_same++;
+ else if (commit->object.flags & SYMMETRIC_LEFT)
revs->count_left++;
else
revs->count_right++;
@@ -64,18 +70,8 @@ static void show_commit(struct commit *commit, void *data)
if (info->header_prefix)
fputs(info->header_prefix, stdout);
- if (!revs->graph) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & UNINTERESTING)
- putchar('^');
- else if (revs->left_right) {
- if (commit->object.flags & SYMMETRIC_LEFT)
- putchar('<');
- else
- putchar('>');
- }
- }
+ if (!revs->graph)
+ fputs(get_revision_mark(revs, commit), stdout);
if (revs->abbrev_commit && revs->abbrev)
fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
stdout);
@@ -108,7 +104,8 @@ static void show_commit(struct commit *commit, void *data)
struct pretty_print_context ctx = {0};
ctx.abbrev = revs->abbrev;
ctx.date_mode = revs->date_mode;
- pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+ ctx.fmt = revs->commit_format;
+ pretty_print_commit(&ctx, commit, &buf);
if (revs->graph) {
if (buf.len) {
if (revs->commit_format != CMIT_FMT_ONELINE)
@@ -147,8 +144,10 @@ static void show_commit(struct commit *commit, void *data)
}
} else {
if (revs->commit_format != CMIT_FMT_USERFORMAT ||
- buf.len)
- printf("%s%c", buf.buf, info->hdr_termination);
+ buf.len) {
+ fwrite(buf.buf, 1, buf.len, stdout);
+ putchar(info->hdr_termination);
+ }
}
strbuf_release(&buf);
} else {
@@ -410,8 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
&info);
if (revs.count) {
- if (revs.left_right)
+ if (revs.left_right && revs.cherry_mark)
+ printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same);
+ else if (revs.left_right)
printf("%d\t%d\n", revs.count_left, revs.count_right);
+ else if (revs.cherry_mark)
+ printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same);
else
printf("%d\n", revs.count_left + revs.count_right);
}
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index b676e29..4c19f84 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -44,10 +44,15 @@ static int is_rev_argument(const char *arg)
"--branches=",
"--branches",
"--header",
+ "--ignore-missing",
"--max-age=",
"--max-count=",
"--min-age=",
"--no-merges",
+ "--min-parents=",
+ "--no-min-parents",
+ "--max-parents=",
+ "--no-max-parents",
"--objects",
"--objects-edge",
"--parents",
@@ -407,8 +412,8 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
ALLOC_GROW(opts, onb + 1, osz);
memset(opts + onb, 0, sizeof(opts[onb]));
argc = parse_options(argc, argv, prefix, opts, usage,
- keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0 |
- stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0 |
+ (keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |
+ (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |
PARSE_OPT_SHELL_EVAL);
strbuf_addf(&parsed, " --");
diff --git a/builtin/revert.c b/builtin/revert.c
index 8b9d829..1f27c63 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -3,7 +3,6 @@
#include "object.h"
#include "commit.h"
#include "tag.h"
-#include "wt-status.h"
#include "run-command.h"
#include "exec_cmd.h"
#include "utf8.h"
@@ -44,7 +43,11 @@ static const char **commit_argv;
static int allow_rerere_auto;
static const char *me;
+
+/* Merge strategy. */
static const char *strategy;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -55,6 +58,17 @@ static const char * const *revert_or_cherry_pick_usage(void)
return action == REVERT ? revert_usage : cherry_pick_usage;
}
+static int option_parse_x(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+ xopts[xopts_nr++] = xstrdup(arg);
+ return 0;
+}
+
static void parse_args(int argc, const char **argv)
{
const char * const * usage_str = revert_or_cherry_pick_usage();
@@ -62,11 +76,14 @@ static void parse_args(int argc, const char **argv)
struct option options[] = {
OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
- OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
+ { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_INTEGER('m', "mainline", &mainline, "parent number"),
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
+ OPT_CALLBACK('X', "strategy-option", &xopts, "option",
+ "option for merge strategy", option_parse_x),
OPT_END(),
OPT_END(),
OPT_END(),
@@ -79,7 +96,7 @@ static void parse_args(int argc, const char **argv)
OPT_END(),
};
if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
- die("program error");
+ die(_("program error"));
}
commit_argc = parse_options(argc, argv, NULL, options, usage_str,
@@ -102,9 +119,9 @@ struct commit_message {
static int get_message(const char *raw_message, struct commit_message *out)
{
const char *encoding;
- const char *p, *abbrev, *eol;
+ const char *abbrev, *subject;
+ int abbrev_len, subject_len;
char *q;
- int abbrev_len, oneline_len;
if (!raw_message)
return -1;
@@ -125,27 +142,17 @@ static int get_message(const char *raw_message, struct commit_message *out)
abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
abbrev_len = strlen(abbrev);
- /* Find beginning and end of commit subject. */
- p = out->message;
- while (*p && (*p != '\n' || p[1] != '\n'))
- p++;
- if (*p) {
- p += 2;
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- } else
- eol = p;
- oneline_len = eol - p;
+ subject_len = find_commit_subject(out->message, &subject);
out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
- strlen("... ") + oneline_len + 1);
+ strlen("... ") + subject_len + 1);
q = out->parent_label;
q = mempcpy(q, "parent of ", strlen("parent of "));
out->label = q;
q = mempcpy(q, abbrev, abbrev_len);
q = mempcpy(q, "... ", strlen("... "));
out->subject = q;
- q = mempcpy(q, p, oneline_len);
+ q = mempcpy(q, subject, subject_len);
*q = '\0';
return 0;
}
@@ -161,7 +168,7 @@ static char *get_encoding(const char *message)
const char *p = message, *eol;
if (!p)
- die ("Could not read commit message of %s",
+ die (_("Could not read commit message of %s"),
sha1_to_hex(commit->object.sha1));
while (*p && *p != '\n') {
for (eol = p + 1; *eol && *eol != '\n'; eol++)
@@ -191,77 +198,49 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
strbuf_addstr(msgbuf, p);
}
-static void set_author_ident_env(const char *message)
+static void write_cherry_pick_head(void)
{
- const char *p = message;
- if (!p)
- die ("Could not read commit message of %s",
- sha1_to_hex(commit->object.sha1));
- while (*p && *p != '\n') {
- const char *eol;
-
- for (eol = p; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "author ")) {
- char *line, *pend, *email, *timestamp;
-
- p += 7;
- line = xmemdupz(p, eol - p);
- email = strchr(line, '<');
- if (!email)
- die ("Could not extract author email from %s",
- sha1_to_hex(commit->object.sha1));
- if (email == line)
- pend = line;
- else
- for (pend = email; pend != line + 1 &&
- isspace(pend[-1]); pend--);
- ; /* do nothing */
- *pend = '\0';
- email++;
- timestamp = strchr(email, '>');
- if (!timestamp)
- die ("Could not extract author time from %s",
- sha1_to_hex(commit->object.sha1));
- *timestamp = '\0';
- for (timestamp++; *timestamp && isspace(*timestamp);
- timestamp++)
- ; /* do nothing */
- setenv("GIT_AUTHOR_NAME", line, 1);
- setenv("GIT_AUTHOR_EMAIL", email, 1);
- setenv("GIT_AUTHOR_DATE", timestamp, 1);
- free(line);
- return;
- }
- p = eol;
- if (*p == '\n')
- p++;
- }
- die ("No author information found in %s",
- sha1_to_hex(commit->object.sha1));
+ int fd;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
+
+ fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"),
+ git_path("CHERRY_PICK_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
+ die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
+ strbuf_release(&buf);
}
-static char *help_msg(void)
+static void advise(const char *advice, ...)
{
- struct strbuf helpbuf = STRBUF_INIT;
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
+ va_list params;
- if (msg)
- return msg;
+ va_start(params, advice);
+ vreportf("hint: ", advice, params);
+ va_end(params);
+}
- strbuf_addstr(&helpbuf, " After resolving the conflicts,\n"
- "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result");
+static void print_advice(void)
+{
+ char *msg = getenv("GIT_CHERRY_PICK_HELP");
- if (action == CHERRY_PICK) {
- strbuf_addf(&helpbuf, " with: \n"
- "\n"
- " git commit -c %s\n",
- sha1_to_hex(commit->object.sha1));
+ if (msg) {
+ fprintf(stderr, "%s\n", msg);
+ /*
+ * A conflict has occured but the porcelain
+ * (typically rebase --interactive) wants to take care
+ * of the commit itself so remove CHERRY_PICK_HEAD
+ */
+ unlink(git_path("CHERRY_PICK_HEAD"));
+ return;
}
- else
- strbuf_addch(&helpbuf, '.');
- return strbuf_detach(&helpbuf, NULL);
+
+ advise("after resolving the conflicts, mark the corrected paths");
+ advise("with 'git add <paths>' or 'git rm <paths>'");
+ advise("and commit the result with 'git commit'");
}
static void write_message(struct strbuf *msgbuf, const char *filename)
@@ -271,10 +250,10 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
int msg_fd = hold_lock_file_for_update(&msg_file, filename,
LOCK_DIE_ON_ERROR);
if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
- die_errno("Could not write to %s.", filename);
+ die_errno(_("Could not write to %s."), filename);
strbuf_release(msgbuf);
if (commit_lock_file(&msg_file) < 0)
- die("Error wrapping up %s", filename);
+ die(_("Error wrapping up %s"), filename);
}
static struct tree *empty_tree(void)
@@ -292,11 +271,19 @@ static NORETURN void die_dirty_index(const char *me)
if (read_cache_unmerged()) {
die_resolve_conflict(me);
} else {
- if (advice_commit_before_merge)
- die("Your local changes would be overwritten by %s.\n"
- "Please, commit your changes or stash them to proceed.", me);
- else
- die("Your local changes would be overwritten by %s.\n", me);
+ if (advice_commit_before_merge) {
+ if (action == REVERT)
+ die(_("Your local changes would be overwritten by revert.\n"
+ "Please, commit your changes or stash them to proceed."));
+ else
+ die(_("Your local changes would be overwritten by cherry-pick.\n"
+ "Please, commit your changes or stash them to proceed."));
+ } else {
+ if (action == REVERT)
+ die(_("Your local changes would be overwritten by revert.\n"));
+ else
+ die(_("Your local changes would be overwritten by cherry-pick.\n"));
+ }
}
}
@@ -311,19 +298,20 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
return write_ref_sha1(ref_lock, to, "cherry-pick");
}
-static void do_recursive_merge(struct commit *base, struct commit *next,
- const char *base_label, const char *next_label,
- unsigned char *head, struct strbuf *msgbuf,
- char *defmsg)
+static int do_recursive_merge(struct commit *base, struct commit *next,
+ const char *base_label, const char *next_label,
+ unsigned char *head, struct strbuf *msgbuf)
{
struct merge_options o;
struct tree *result, *next_tree, *base_tree, *head_tree;
int clean, index_fd;
+ const char **xopt;
static struct lock_file index_lock;
index_fd = hold_locked_index(&index_lock, 1);
read_cache();
+
init_merge_options(&o);
o.ancestor = base ? base_label : "(empty tree)";
o.branch1 = "HEAD";
@@ -333,6 +321,9 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
next_tree = next ? next->tree : empty_tree();
base_tree = base ? base->tree : empty_tree();
+ for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
+ parse_merge_opt(&o, *xopt);
+
clean = merge_trees(&o,
head_tree,
next_tree, base_tree, &result);
@@ -340,7 +331,8 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(&index_lock)))
- die("%s: Unable to write new index file", me);
+ /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+ die(_("%s: Unable to write new index file"), me);
rollback_lock_file(&index_lock);
if (!clean) {
@@ -357,14 +349,35 @@ static void do_recursive_merge(struct commit *base, struct commit *next,
i++;
}
}
- write_message(msgbuf, defmsg);
- fprintf(stderr, "Automatic %s failed.%s\n",
- me, help_msg());
- rerere(allow_rerere_auto);
- exit(1);
}
- write_message(msgbuf, defmsg);
- fprintf(stderr, "Finished one %s.\n", me);
+
+ return !clean;
+}
+
+/*
+ * If we are cherry-pick, and if the merge did not result in
+ * hand-editing, we will hit this commit and inherit the original
+ * author date and name.
+ * If we are revert, or if our cherry-pick results in a hand merge,
+ * we had better say that the current user is responsible for that.
+ */
+static int run_git_commit(const char *defmsg)
+{
+ /* 6 is max possible length of our args array including NULL */
+ const char *args[6];
+ int i = 0;
+
+ args[i++] = "commit";
+ args[i++] = "-n";
+ if (signoff)
+ args[i++] = "-s";
+ if (!edit) {
+ args[i++] = "-F";
+ args[i++] = defmsg;
+ }
+ args[i] = NULL;
+
+ return run_command_v_opt(args, RUN_GIT_CMD);
}
static int do_pick_commit(void)
@@ -375,6 +388,7 @@ static int do_pick_commit(void)
struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
char *defmsg = NULL;
struct strbuf msgbuf = STRBUF_INIT;
+ int res;
if (no_commit) {
/*
@@ -384,18 +398,16 @@ static int do_pick_commit(void)
* to work on.
*/
if (write_cache_as_tree(head, 0, NULL))
- die ("Your index file is unmerged.");
+ die (_("Your index file is unmerged."));
} else {
if (get_sha1("HEAD", head))
- die ("You do not have a valid HEAD");
+ die (_("You do not have a valid HEAD"));
if (index_differs_from("HEAD", 0))
die_dirty_index(me);
}
discard_cache();
if (!commit->parents) {
- if (action == REVERT)
- die ("Cannot revert a root commit");
parent = NULL;
}
else if (commit->parents->next) {
@@ -404,7 +416,7 @@ static int do_pick_commit(void)
struct commit_list *p;
if (!mainline)
- die("Commit %s is a merge but no -m option was given.",
+ die(_("Commit %s is a merge but no -m option was given."),
sha1_to_hex(commit->object.sha1));
for (cnt = 1, p = commit->parents;
@@ -412,24 +424,26 @@ static int do_pick_commit(void)
cnt++)
p = p->next;
if (cnt != mainline || !p)
- die("Commit %s does not have parent %d",
+ die(_("Commit %s does not have parent %d"),
sha1_to_hex(commit->object.sha1), mainline);
parent = p->item;
} else if (0 < mainline)
- die("Mainline was specified but commit %s is not a merge.",
+ die(_("Mainline was specified but commit %s is not a merge."),
sha1_to_hex(commit->object.sha1));
else
parent = commit->parents->item;
- if (allow_ff && !hashcmp(parent->object.sha1, head))
+ if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
return fast_forward_to(commit->object.sha1, head);
if (parent && parse_commit(parent) < 0)
- die("%s: cannot parse parent commit %s",
+ /* TRANSLATORS: The first %s will be "revert" or
+ "cherry-pick", the second %s a SHA1 */
+ die(_("%s: cannot parse parent commit %s"),
me, sha1_to_hex(parent->object.sha1));
if (get_message(commit->buffer, &msg) != 0)
- die("Cannot get commit message for %s",
+ die(_("Cannot get commit message for %s"),
sha1_to_hex(commit->object.sha1));
/*
@@ -451,7 +465,7 @@ static int do_pick_commit(void)
strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
- if (commit->parents->next) {
+ if (commit->parents && commit->parents->next) {
strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
}
@@ -461,72 +475,51 @@ static int do_pick_commit(void)
base_label = msg.parent_label;
next = commit;
next_label = msg.label;
- set_author_ident_env(msg.message);
add_message_to_msg(&msgbuf, msg.message);
if (no_replay) {
strbuf_addstr(&msgbuf, "(cherry picked from commit ");
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
strbuf_addstr(&msgbuf, ")\n");
}
+ if (!no_commit)
+ write_cherry_pick_head();
}
- if (!strategy || !strcmp(strategy, "recursive") || action == REVERT)
- do_recursive_merge(base, next, base_label, next_label,
- head, &msgbuf, defmsg);
- else {
- int res;
+ if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
+ res = do_recursive_merge(base, next, base_label, next_label,
+ head, &msgbuf);
+ write_message(&msgbuf, defmsg);
+ } else {
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
+
write_message(&msgbuf, defmsg);
+
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res = try_merge_command(strategy, common,
+ res = try_merge_command(strategy, xopts_nr, xopts, common,
sha1_to_hex(head), remotes);
free_commit_list(common);
free_commit_list(remotes);
- if (res) {
- fprintf(stderr, "Automatic %s with strategy %s failed.%s\n",
- me, strategy, help_msg());
- rerere(allow_rerere_auto);
- exit(1);
- }
}
- free_message(&msg);
-
- /*
- *
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-
- if (!no_commit) {
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int res;
- int i = 0;
-
- args[i++] = "commit";
- args[i++] = "-n";
- if (signoff)
- args[i++] = "-s";
- if (!edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
- }
- args[i] = NULL;
- res = run_command_v_opt(args, RUN_GIT_CMD);
- free(defmsg);
-
- return res;
+ if (res) {
+ error(action == REVERT
+ ? _("could not revert %s... %s")
+ : _("could not apply %s... %s"),
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
+ msg.subject);
+ print_advice();
+ rerere(allow_rerere_auto);
+ } else {
+ if (!no_commit)
+ res = run_git_commit(defmsg);
}
+ free_message(&msg);
free(defmsg);
- return 0;
+ return res;
}
static void prepare_revs(struct rev_info *revs)
@@ -543,10 +536,25 @@ static void prepare_revs(struct rev_info *revs)
usage(*revert_or_cherry_pick_usage());
if (prepare_revision_walk(revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
if (!revs->commits)
- die("empty commit set passed");
+ die(_("empty commit set passed"));
+}
+
+static void read_and_refresh_cache(const char *me)
+{
+ static struct lock_file index_lock;
+ int index_fd = hold_locked_index(&index_lock, 0);
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("git %s: failed to read the index"), me);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+ if (the_index.cache_changed) {
+ if (write_index(&the_index, index_fd) ||
+ commit_locked_index(&index_lock))
+ die(_("git %s: failed to refresh the index"), me);
+ }
+ rollback_lock_file(&index_lock);
}
static int revert_or_cherry_pick(int argc, const char **argv)
@@ -560,17 +568,16 @@ static int revert_or_cherry_pick(int argc, const char **argv)
if (allow_ff) {
if (signoff)
- die("cherry-pick --ff cannot be used with --signoff");
+ die(_("cherry-pick --ff cannot be used with --signoff"));
if (no_commit)
- die("cherry-pick --ff cannot be used with --no-commit");
+ die(_("cherry-pick --ff cannot be used with --no-commit"));
if (no_replay)
- die("cherry-pick --ff cannot be used with -x");
+ die(_("cherry-pick --ff cannot be used with -x"));
if (edit)
- die("cherry-pick --ff cannot be used with --edit");
+ die(_("cherry-pick --ff cannot be used with --edit"));
}
- if (read_cache() < 0)
- die("git %s: failed to read the index", me);
+ read_and_refresh_cache(me);
prepare_revs(&revs);
diff --git a/builtin/rm.c b/builtin/rm.c
index f3772c8..90c8a50 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -20,15 +20,6 @@ static struct {
const char **name;
} list;
-static void add_list(const char *name)
-{
- if (list.nr >= list.alloc) {
- list.alloc = alloc_nr(list.alloc);
- list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
- }
- list.name[list.nr++] = name;
-}
-
static int check_local_mod(unsigned char *head, int index_only)
{
/*
@@ -115,19 +106,19 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
if (local_changes && staged_changes) {
if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
- errs = error("'%s' has staged content different "
+ errs = error(_("'%s' has staged content different "
"from both the file and the HEAD\n"
- "(use -f to force removal)", name);
+ "(use -f to force removal)"), name);
}
else if (!index_only) {
if (staged_changes)
- errs = error("'%s' has changes staged in the index\n"
+ errs = error(_("'%s' has changes staged in the index\n"
"(use --cached to keep the file, "
- "or -f to force removal)", name);
+ "or -f to force removal)"), name);
if (local_changes)
- errs = error("'%s' has local modifications\n"
+ errs = error(_("'%s' has local modifications\n"
"(use --cached to keep the file, "
- "or -f to force removal)", name);
+ "or -f to force removal)"), name);
}
}
return errs;
@@ -139,10 +130,10 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
static int ignore_unmatch = 0;
static struct option builtin_rm_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__QUIET(&quiet),
+ OPT__DRY_RUN(&show_only, "dry run"),
+ OPT__QUIET(&quiet, "do not list removed files"),
OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
- OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"),
+ OPT__FORCE(&force, "override the up-to-date check"),
OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"),
OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
"exit with a zero status even if nothing matched"),
@@ -168,7 +159,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
newfd = hold_locked_index(&lock_file, 1);
if (read_cache() < 0)
- die("index file corrupt");
+ die(_("index file corrupt"));
pathspec = get_pathspec(prefix, argv);
refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
@@ -182,7 +173,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
struct cache_entry *ce = active_cache[i];
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
- add_list(ce->name);
+ ALLOC_GROW(list.name, list.nr + 1, list.alloc);
+ list.name[list.nr++] = ce->name;
}
if (pathspec) {
@@ -191,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
for (i = 0; (match = pathspec[i]) != NULL ; i++) {
if (!seen[i]) {
if (!ignore_unmatch) {
- die("pathspec '%s' did not match any files",
+ die(_("pathspec '%s' did not match any files"),
match);
}
}
@@ -199,7 +191,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
seen_any = 1;
}
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
- die("not removing '%s' recursively without -r",
+ die(_("not removing '%s' recursively without -r"),
*match ? match : ".");
}
@@ -235,7 +227,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
printf("rm '%s'\n", path);
if (remove_file_from_cache(path))
- die("git rm: unable to remove %s", path);
+ die(_("git rm: unable to remove %s"), path);
}
if (show_only)
@@ -265,7 +257,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ die(_("Unable to write new index file"));
}
return 0;
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 481602d..c1f6ddd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
@@ -48,6 +48,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
NULL,
NULL,
NULL,
+ NULL,
};
struct child_process po;
int i;
@@ -59,6 +60,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
argv[i++] = "--delta-base-offset";
if (args->quiet)
argv[i++] = "-q";
+ if (args->progress)
+ argv[i++] = "--progress";
memset(&po, 0, sizeof(po));
po.argv = argv;
po.in = -1;
@@ -101,7 +104,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
}
if (finish_command(&po))
- return error("pack-objects died with strange error");
+ return -1;
return 0;
}
@@ -225,8 +228,11 @@ static void print_helper_status(struct ref *ref)
static int sideband_demux(int in, int out, void *data)
{
- int *fd = data;
- int ret = recv_sideband("send-pack", fd[0], out);
+ int *fd = data, ret;
+#ifdef NO_PTHREADS
+ close(fd[1]);
+#endif
+ ret = recv_sideband("send-pack", fd[0], out);
close(out);
return ret;
}
@@ -336,6 +342,10 @@ int send_pack(struct send_pack_args *args,
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
ref->status = REF_STATUS_NONE;
+ if (args->stateless_rpc)
+ close(out);
+ if (git_connection_is_socket(conn))
+ shutdown(fd[0], SHUT_WR);
if (use_sideband)
finish_async(&demux);
return -1;
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 0a9681b..37f3193 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -29,9 +29,6 @@ static int compare_by_number(const void *a1, const void *a2)
return -1;
}
-const char *format_subject(struct strbuf *sb, const char *msg,
- const char *line_separator);
-
static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
@@ -141,9 +138,8 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
const char *author = NULL, *buffer;
struct strbuf buf = STRBUF_INIT;
struct strbuf ufbuf = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+ pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
buffer = buf.buf;
while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
@@ -158,15 +154,16 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
buffer = eol;
}
if (!author)
- die("Missing author: %s",
+ die(_("Missing author: %s"),
sha1_to_hex(commit->object.sha1));
if (log->user_format) {
struct pretty_print_context ctx = {0};
+ ctx.fmt = CMIT_FMT_USERFORMAT;
ctx.abbrev = log->abbrev;
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode = DATE_NORMAL;
- pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+ pretty_print_commit(&ctx, commit, &ufbuf);
buffer = ufbuf.buf;
} else if (*buffer) {
buffer++;
@@ -181,7 +178,7 @@ static void get_from_rev(struct rev_info *rev, struct shortlog *log)
struct commit *commit;
if (prepare_revision_walk(rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
while ((commit = get_revision(rev)) != NULL)
shortlog_add_commit(log, commit);
}
@@ -249,7 +246,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
static struct shortlog log;
static struct rev_info rev;
- int nongit;
+ int nongit = !startup_info->have_repository;
static const struct option options[] = {
OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
@@ -265,12 +262,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
struct parse_opt_ctx_t ctx;
- prefix = setup_git_directory_gently(&nongit);
git_config(git_default_config, NULL);
shortlog_init(&log);
init_revisions(&rev, prefix);
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
+ parse_options_start(&ctx, argc, argv, prefix, options,
+ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
for (;;) {
switch (parse_options_step(&ctx, options, shortlog_usage)) {
@@ -285,7 +281,7 @@ parse_done:
argc = parse_options_end(&ctx);
if (setup_revisions(argc, argv, &rev, NULL) != 1) {
- error("unrecognized argument: %s", argv[1]);
+ error(_("unrecognized argument: %s"), argv[1]);
usage_with_options(shortlog_usage, options);
}
@@ -297,7 +293,7 @@ parse_done:
add_head_to_pending(&rev);
if (rev.pending.nr == 0) {
if (isatty(0))
- fprintf(stderr, "(reading log message from standard input)\n");
+ fprintf(stderr, _("(reading log message from standard input)\n"));
read_from_stdin(&log);
}
else
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index e8719aa..facc63a 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -6,22 +6,12 @@
#include "parse-options.h"
static const char* show_branch_usage[] = {
- "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
+ "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]",
"git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
NULL
};
static int showbranch_use_color = -1;
-static char column_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_RED,
- GIT_COLOR_GREEN,
- GIT_COLOR_YELLOW,
- GIT_COLOR_BLUE,
- GIT_COLOR_MAGENTA,
- GIT_COLOR_CYAN,
-};
-
-#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
static int default_num;
static int default_alloc;
@@ -37,7 +27,7 @@ static const char **default_arg;
static const char *get_color_code(int idx)
{
if (showbranch_use_color)
- return column_colors[idx];
+ return column_colors_ansi[idx % column_colors_ansi_max];
return "";
}
@@ -243,7 +233,7 @@ static void join_revs(struct commit_list **list_p,
if (mark_seen(p, seen_p) && !still_interesting)
extra--;
p->object.flags |= flags;
- insert_by_date(p, list_p);
+ commit_list_insert_by_date(p, list_p);
}
}
@@ -293,8 +283,7 @@ static void show_one_commit(struct commit *commit, int no_name)
struct commit_name *name = commit->util;
if (commit->object.parsed) {
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
pretty_str = pretty.buf;
}
if (!prefixcmp(pretty_str, "[PATCH] "))
@@ -859,7 +848,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
*/
commit->object.flags |= flag;
if (commit->object.flags == flag)
- insert_by_date(commit, &list);
+ commit_list_insert_by_date(commit, &list);
rev[num_rev] = commit;
}
for (i = 0; i < num_rev; i++)
@@ -868,7 +857,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
- sort_by_date(&seen);
+ commit_list_sort_by_date(&seen);
if (merge_base)
return show_merge_base(seen, num_rev);
@@ -892,7 +881,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
for (j = 0; j < i; j++)
putchar(' ');
printf("%s%c%s [%s] ",
- get_color_code(i % COLUMN_COLORS_MAX),
+ get_color_code(i),
is_head ? '*' : '!',
get_color_reset_code(), ref_name[i]);
}
@@ -954,7 +943,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
else
mark = '+';
printf("%s%c%s",
- get_color_code(i % COLUMN_COLORS_MAX),
+ get_color_code(i),
mark, get_color_reset_code());
}
putchar(' ');
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 0b2a9ad..45f0340 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -120,7 +120,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag
*/
static int exclude_existing(const char *match)
{
- static struct string_list existing_refs = { NULL, 0, 0, 0 };
+ static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
char buf[1024];
int matchlen = match ? strlen(match) : 0;
@@ -193,7 +193,8 @@ static const struct option show_ref_options[] = {
"only show SHA1 hash using <n> digits",
PARSE_OPT_OPTARG, &hash_callback },
OPT__ABBREV(&abbrev),
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet,
+ "do not print results to stdout (useful with --verify)"),
{ OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
"pattern", "show refs from stdin that aren't in local repository",
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index ca855a5..dea849c 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -30,7 +30,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
int quiet = 0;
const char *msg = NULL;
struct option options[] = {
- OPT__QUIET(&quiet),
+ OPT__QUIET(&quiet,
+ "suppress error message for non-symbolic (detached) refs"),
OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
OPT_END(),
};
diff --git a/builtin/tag.c b/builtin/tag.c
index f7a7943..667515e 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -18,7 +18,7 @@
static const char * const git_tag_usage[] = {
"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
"git tag -d <tagname>...",
- "git tag -l [-n[<num>]] [<pattern>]",
+ "git tag -l [-n[<num>]] [<pattern>...]",
"git tag -v <tagname>...",
NULL
};
@@ -26,12 +26,21 @@ static const char * const git_tag_usage[] = {
static char signingkey[1000];
struct tag_filter {
- const char *pattern;
+ const char **patterns;
int lines;
struct commit_list *with_commit;
};
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+static int match_pattern(const char **patterns, const char *ref)
+{
+ /* no pattern means match everything */
+ if (!*patterns)
+ return 1;
+ for (; *patterns; patterns++)
+ if (!fnmatch(*patterns, ref, 0))
+ return 1;
+ return 0;
+}
static int in_commit_list(const struct commit_list *want, struct commit *c)
{
@@ -80,7 +89,7 @@ static int show_reference(const char *refname, const unsigned char *sha1,
{
struct tag_filter *filter = cb_data;
- if (!fnmatch(filter->pattern, refname, 0)) {
+ if (match_pattern(filter->patterns, refname)) {
int i;
unsigned long size;
enum object_type type;
@@ -114,9 +123,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
/* only take up to "lines" lines, and strip the signature */
+ size = parse_signature(buf, size);
for (i = 0, sp += 2;
- i < filter->lines && sp < buf + size &&
- prefixcmp(sp, PGP_SIGNATURE "\n");
+ i < filter->lines && sp < buf + size;
i++) {
if (i)
printf("\n ");
@@ -134,15 +143,12 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
-static int list_tags(const char *pattern, int lines,
+static int list_tags(const char **patterns, int lines,
struct commit_list *with_commit)
{
struct tag_filter filter;
- if (pattern == NULL)
- pattern = "*";
-
- filter.pattern = pattern;
+ filter.patterns = patterns;
filter.lines = lines;
filter.with_commit = with_commit;
@@ -164,12 +170,12 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
for (p = argv; *p; p++) {
if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
>= sizeof(ref)) {
- error("tag name too long: %.*s...", 50, *p);
+ error(_("tag name too long: %.*s..."), 50, *p);
had_error = 1;
continue;
}
if (!resolve_ref(ref, sha1, 1, NULL)) {
- error("tag '%s' not found.", *p);
+ error(_("tag '%s' not found."), *p);
had_error = 1;
continue;
}
@@ -184,7 +190,7 @@ static int delete_tag(const char *name, const char *ref,
{
if (delete_ref(ref, sha1, 0))
return 1;
- printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
return 0;
}
@@ -196,7 +202,7 @@ static int verify_tag(const char *name, const char *ref,
argv_verify_tag[2] = sha1_to_hex(sha1);
if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD))
- return error("could not verify the tag '%s'", name);
+ return error(_("could not verify the tag '%s'"), name);
return 0;
}
@@ -211,7 +217,7 @@ static int do_sign(struct strbuf *buffer)
if (!*signingkey) {
if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
sizeof(signingkey)) > sizeof(signingkey) - 1)
- return error("committer info too long.");
+ return error(_("committer info too long."));
bracket = strchr(signingkey, '>');
if (bracket)
bracket[1] = '\0';
@@ -231,20 +237,20 @@ static int do_sign(struct strbuf *buffer)
args[3] = NULL;
if (start_command(&gpg))
- return error("could not run gpg.");
+ return error(_("could not run gpg."));
if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
close(gpg.in);
close(gpg.out);
finish_command(&gpg);
- return error("gpg did not accept the tag data");
+ return error(_("gpg did not accept the tag data"));
}
close(gpg.in);
len = strbuf_read(buffer, gpg.out, 1024);
close(gpg.out);
if (finish_command(&gpg) || !len || len < 0)
- return error("gpg failed to sign the tag");
+ return error(_("gpg failed to sign the tag"));
/* Strip CR from the line endings, in case we are on Windows. */
for (i = j = 0; i < buffer->len; i++)
@@ -259,15 +265,15 @@ static int do_sign(struct strbuf *buffer)
}
static const char tag_template[] =
- "\n"
+ N_("\n"
"#\n"
"# Write a tag message\n"
- "#\n";
+ "#\n");
static void set_signingkey(const char *value)
{
if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
- die("signing key value too long (%.10s...)", value);
+ die(_("signing key value too long (%.10s...)"), value);
}
static int git_tag_config(const char *var, const char *value, void *cb)
@@ -286,8 +292,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
{
unsigned long size;
enum object_type type;
- char *buf, *sp, *eob;
- size_t len;
+ char *buf, *sp;
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
@@ -300,12 +305,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
return;
}
sp += 2; /* skip the 2 LFs */
- eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
- if (eob)
- len = eob - sp;
- else
- len = buf + size - sp;
- write_or_die(fd, sp, len);
+ write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
free(buf);
}
@@ -313,9 +313,9 @@ static void write_tag_body(int fd, const unsigned char *sha1)
static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
{
if (sign && do_sign(buf) < 0)
- return error("unable to sign the tag");
+ return error(_("unable to sign the tag"));
if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
- return error("unable to write tag file");
+ return error(_("unable to write tag file"));
return 0;
}
@@ -330,7 +330,7 @@ static void create_tag(const unsigned char *object, const char *tag,
type = sha1_object_info(object, NULL);
if (type <= OBJ_NONE)
- die("bad object type.");
+ die(_("bad object type."));
header_len = snprintf(header_buf, sizeof(header_buf),
"object %s\n"
@@ -343,7 +343,7 @@ static void create_tag(const unsigned char *object, const char *tag,
git_committer_info(IDENT_ERROR_ON_NO_NAME));
if (header_len > sizeof(header_buf) - 1)
- die("tag header too big.");
+ die(_("tag header too big."));
if (!message) {
int fd;
@@ -352,17 +352,17 @@ static void create_tag(const unsigned char *object, const char *tag,
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);
+ die_errno(_("could not create file '%s'"), path);
if (!is_null_sha1(prev))
write_tag_body(fd, prev);
else
- write_or_die(fd, tag_template, strlen(tag_template));
+ write_or_die(fd, _(tag_template), strlen(_(tag_template)));
close(fd);
if (launch_editor(path, buf, NULL)) {
fprintf(stderr,
- "Please supply the message using either -m or -F option.\n");
+ _("Please supply the message using either -m or -F option.\n"));
exit(1);
}
}
@@ -370,13 +370,13 @@ static void create_tag(const unsigned char *object, const char *tag,
stripspace(buf, 1);
if (!message && !buf->len)
- die("no tag message?");
+ die(_("no tag message?"));
strbuf_insert(buf, 0, header_buf, header_len);
if (build_tag_object(buf, sign, result) < 0) {
if (path)
- fprintf(stderr, "The tag message has been left in %s\n",
+ fprintf(stderr, _("The tag message has been left in %s\n"),
path);
exit(128);
}
@@ -404,11 +404,22 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
+{
+ if (name[0] == '-')
+ return CHECK_REF_FORMAT_ERROR;
+
+ strbuf_reset(sb);
+ strbuf_addf(sb, "refs/tags/%s", name);
+
+ return check_ref_format(sb->buf);
+}
+
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
+ struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
- char ref[PATH_MAX];
const char *object_ref, *tag;
struct ref_lock *lock;
@@ -428,13 +439,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_GROUP("Tag creation options"),
OPT_BOOLEAN('a', NULL, &annotate,
"annotated tag, needs a message"),
- OPT_CALLBACK('m', NULL, &msg, "msg",
- "message for the tag", parse_msg_arg),
- OPT_FILENAME('F', NULL, &msgfile, "message in a file"),
+ OPT_CALLBACK('m', NULL, &msg, "message",
+ "tag message", parse_msg_arg),
+ OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
OPT_STRING('u', NULL, &keyid, "key-id",
"use another key to sign the tag"),
- OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"),
+ OPT__FORCE(&force, "replace the tag if exists"),
OPT_GROUP("Tag listing options"),
{
@@ -466,12 +477,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (list + delete + verify > 1)
usage_with_options(git_tag_usage, options);
if (list)
- return list_tags(argv[0], lines == -1 ? 0 : lines,
+ return list_tags(argv, lines == -1 ? 0 : lines,
with_commit);
if (lines != -1)
- die("-n option is only allowed with -l.");
+ die(_("-n option is only allowed with -l."));
if (with_commit)
- die("--contains option is only allowed with -l.");
+ die(_("--contains option is only allowed with -l."));
if (delete)
return for_each_tag_name(argv, delete_tag);
if (verify)
@@ -479,17 +490,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (msg.given || msgfile) {
if (msg.given && msgfile)
- die("only one -F or -m option is allowed.");
+ die(_("only one -F or -m option is allowed."));
annotate = 1;
if (msg.given)
strbuf_addbuf(&buf, &(msg.buf));
else {
if (!strcmp(msgfile, "-")) {
if (strbuf_read(&buf, 0, 1024) < 0)
- die_errno("cannot read '%s'", msgfile);
+ die_errno(_("cannot read '%s'"), msgfile);
} else {
if (strbuf_read_file(&buf, msgfile, 1024) < 0)
- die_errno("could not open or read '%s'",
+ die_errno(_("could not open or read '%s'"),
msgfile);
}
}
@@ -499,33 +510,32 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
object_ref = argc == 2 ? argv[1] : "HEAD";
if (argc > 2)
- die("too many params");
+ die(_("too many params"));
if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ die(_("Failed to resolve '%s' as a valid ref."), object_ref);
- if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
- die("tag name too long: %.*s...", 50, tag);
- if (check_ref_format(ref))
- die("'%s' is not a valid tag name.", tag);
+ if (strbuf_check_tag_ref(&ref, tag))
+ die(_("'%s' is not a valid tag name."), tag);
- if (!resolve_ref(ref, prev, 1, NULL))
+ if (!resolve_ref(ref.buf, prev, 1, NULL))
hashclr(prev);
else if (!force)
- die("tag '%s' already exists", tag);
+ die(_("tag '%s' already exists"), tag);
if (annotate)
create_tag(object, tag, &buf, msg.given || msgfile,
sign, prev, object);
- lock = lock_any_ref_for_update(ref, prev, 0);
+ lock = lock_any_ref_for_update(ref.buf, prev, 0);
if (!lock)
- die("%s: cannot lock the ref", ref);
+ die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ die(_("%s: cannot update the ref"), ref.buf);
if (force && hashcmp(prev, object))
- printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+ printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
strbuf_release(&buf);
+ strbuf_release(&ref);
return 0;
}
diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c
index 608590a..1920029 100644
--- a/builtin/unpack-file.c
+++ b/builtin/unpack-file.c
@@ -1,6 +1,4 @@
-#include "cache.h"
-#include "blob.h"
-#include "exec_cmd.h"
+#include "builtin.h"
static char *create_temp_file(unsigned char *sha1)
{
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 685566e..14e04e6 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -83,14 +83,14 @@ static void use(int bytes)
offset += bytes;
/* make sure off_t is sufficiently large not to wrap */
- if (consumed_bytes > consumed_bytes + bytes)
+ if (signed_add_overflows(consumed_bytes, bytes))
die("pack too large for current definition of off_t");
consumed_bytes += bytes;
}
static void *get_data(unsigned long size)
{
- z_stream stream;
+ git_zstream stream;
void *buf = xmalloc(size);
memset(&stream, 0, sizeof(stream));
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 3ab214d..a6a23fa 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -10,6 +10,7 @@
#include "builtin.h"
#include "refs.h"
#include "resolve-undo.h"
+#include "parse-options.h"
/*
* Default to not allowing changes to the list of files. The
@@ -98,8 +99,11 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
fill_stat_cache_info(ce, st);
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
- if (index_path(ce->sha1, path, st, !info_only))
+ if (index_path(ce->sha1, path, st,
+ info_only ? 0 : HASH_WRITE_OBJECT)) {
+ free(ce);
return -1;
+ }
option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
if (add_cache_entry(ce, option))
@@ -397,8 +401,10 @@ static void read_index_info(int line_termination)
strbuf_release(&uq);
}
-static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+static const char * const update_index_usage[] = {
+ "git update-index [options] [--] [<file>...]",
+ NULL
+};
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
@@ -543,7 +549,10 @@ static int do_reupdate(int ac, const char **av,
*/
int pos;
int has_head = 1;
- const char **pathspec = get_pathspec(prefix, av + 1);
+ const char **paths = get_pathspec(prefix, av + 1);
+ struct pathspec pathspec;
+
+ init_pathspec(&pathspec, paths);
if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
@@ -556,7 +565,7 @@ static int do_reupdate(int ac, const char **av,
struct cache_entry *old = NULL;
int save_nr;
- if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+ if (ce_stage(ce) || !ce_path_match(ce, &pathspec))
continue;
if (has_head)
old = read_one_ent(NULL, head_sha1,
@@ -575,19 +584,218 @@ static int do_reupdate(int ac, const char **av,
if (save_nr != active_nr)
goto redo;
}
+ free_pathspec(&pathspec);
+ return 0;
+}
+
+struct refresh_params {
+ unsigned int flags;
+ int *has_errors;
+};
+
+static int refresh(struct refresh_params *o, unsigned int flag)
+{
+ setup_work_tree();
+ *o->has_errors |= refresh_cache(o->flags | flag);
+ return 0;
+}
+
+static int refresh_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ return refresh(opt->value, 0);
+}
+
+static int really_refresh_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ return refresh(opt->value, REFRESH_REALLY);
+}
+
+static int chmod_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ char *flip = opt->value;
+ if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2])
+ return error("option 'chmod' expects \"+x\" or \"-x\"");
+ *flip = arg[0];
+ return 0;
+}
+
+static int resolve_undo_clear_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ resolve_undo_clear();
+ return 0;
+}
+
+static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ unsigned char sha1[20];
+ unsigned int mode;
+
+ if (ctx->argc <= 3)
+ return error("option 'cacheinfo' expects three arguments");
+ if (strtoul_ui(*++ctx->argv, 8, &mode) ||
+ get_sha1_hex(*++ctx->argv, sha1) ||
+ add_cacheinfo(mode, sha1, *++ctx->argv, 0))
+ die("git update-index: --cacheinfo cannot add %s", *ctx->argv);
+ ctx->argc -= 3;
+ return 0;
+}
+
+static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ int *line_termination = opt->value;
+
+ if (ctx->argc != 1)
+ return error("option '%s' must be the last argument", opt->long_name);
+ allow_add = allow_replace = allow_remove = 1;
+ read_index_info(*line_termination);
+ return 0;
+}
+
+static int stdin_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset)
+{
+ int *read_from_stdin = opt->value;
+
+ if (ctx->argc != 1)
+ return error("option '%s' must be the last argument", opt->long_name);
+ *read_from_stdin = 1;
+ return 0;
+}
+
+static int unresolve_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int flags)
+{
+ int *has_errors = opt->value;
+ const char *prefix = startup_info->prefix;
+
+ /* consume remaining arguments. */
+ *has_errors = do_unresolve(ctx->argc, ctx->argv,
+ prefix, prefix ? strlen(prefix) : 0);
+ if (*has_errors)
+ active_cache_changed = 0;
+
+ ctx->argv += ctx->argc - 1;
+ ctx->argc = 1;
+ return 0;
+}
+
+static int reupdate_callback(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int flags)
+{
+ int *has_errors = opt->value;
+ const char *prefix = startup_info->prefix;
+
+ /* consume remaining arguments. */
+ setup_work_tree();
+ *has_errors = do_reupdate(ctx->argc, ctx->argv,
+ prefix, prefix ? strlen(prefix) : 0);
+ if (*has_errors)
+ active_cache_changed = 0;
+
+ ctx->argv += ctx->argc - 1;
+ ctx->argc = 1;
return 0;
}
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
- int i, newfd, entries, has_errors = 0, line_termination = '\n';
- int allow_options = 1;
+ int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
char set_executable_bit = 0;
- unsigned int refresh_flags = 0;
+ struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
struct lock_file *lock_file;
+ struct parse_opt_ctx_t ctx;
+ int parseopt_state = PARSE_OPT_UNKNOWN;
+ struct option options[] = {
+ OPT_BIT('q', NULL, &refresh_args.flags,
+ "continue refresh even when index needs update",
+ REFRESH_QUIET),
+ OPT_BIT(0, "ignore-submodules", &refresh_args.flags,
+ "refresh: ignore submodules",
+ REFRESH_IGNORE_SUBMODULES),
+ OPT_SET_INT(0, "add", &allow_add,
+ "do not ignore new files", 1),
+ OPT_SET_INT(0, "replace", &allow_replace,
+ "let files replace directories and vice-versa", 1),
+ OPT_SET_INT(0, "remove", &allow_remove,
+ "notice files missing from worktree", 1),
+ OPT_BIT(0, "unmerged", &refresh_args.flags,
+ "refresh even if index contains unmerged entries",
+ REFRESH_UNMERGED),
+ {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
+ "refresh stat information",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ refresh_callback},
+ {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
+ "like --refresh, but ignore assume-unchanged setting",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ really_refresh_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
+ "<mode> <object> <path>",
+ "add the specified entry to the index",
+ PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
+ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ (parse_opt_cb *) cacheinfo_callback},
+ {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x",
+ "override the executable bit of the listed files",
+ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ chmod_callback},
+ {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
+ "mark files as \"not changing\"",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+ {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL,
+ "clear assumed-unchanged bit",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+ {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL,
+ "mark files as \"index-only\"",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
+ {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
+ "clear skip-worktree bit",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+ OPT_SET_INT(0, "info-only", &info_only,
+ "add to index only; do not add content to object database", 1),
+ OPT_SET_INT(0, "force-remove", &force_remove,
+ "remove named paths even if present in worktree", 1),
+ OPT_SET_INT('z', NULL, &line_termination,
+ "with --stdin: input lines are terminated by null bytes", '\0'),
+ {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
+ "read list of paths to be updated from standard input",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) stdin_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
+ "add entries from standard input to the index",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) stdin_cacheinfo_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
+ "repopulate stages #2 and #3 for the listed paths",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) unresolve_callback},
+ {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
+ "only update entries that differ from HEAD",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ (parse_opt_cb *) reupdate_callback},
+ OPT_BIT(0, "ignore-missing", &refresh_args.flags,
+ "ignore files missing from worktree",
+ REFRESH_IGNORE_MISSING),
+ OPT_SET_INT(0, "verbose", &verbose,
+ "report actions to standard output", 1),
+ {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
+ "(for porcelains) forget saved unresolved conflicts",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ resolve_undo_clear_callback},
+ OPT_END()
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(update_index_usage[0]);
git_config(git_default_config, NULL);
@@ -602,151 +810,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
if (entries < 0)
die("cache corrupted");
- for (i = 1 ; i < argc; i++) {
- const char *path = argv[i];
- const char *p;
+ /*
+ * Custom copy of parse_options() because we want to handle
+ * filename arguments as they come.
+ */
+ parse_options_start(&ctx, argc, argv, prefix,
+ options, PARSE_OPT_STOP_AT_NON_OPTION);
+ while (ctx.argc) {
+ if (parseopt_state != PARSE_OPT_DONE)
+ parseopt_state = parse_options_step(&ctx, options,
+ update_index_usage);
+ if (!ctx.argc)
+ break;
+ switch (parseopt_state) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_DONE:
+ {
+ const char *path = ctx.argv[0];
+ const char *p;
- if (allow_options && *path == '-') {
- if (!strcmp(path, "--")) {
- allow_options = 0;
- continue;
- }
- if (!strcmp(path, "-q")) {
- refresh_flags |= REFRESH_QUIET;
- continue;
- }
- if (!strcmp(path, "--ignore-submodules")) {
- refresh_flags |= REFRESH_IGNORE_SUBMODULES;
- continue;
- }
- if (!strcmp(path, "--add")) {
- allow_add = 1;
- continue;
- }
- if (!strcmp(path, "--replace")) {
- allow_replace = 1;
- continue;
- }
- if (!strcmp(path, "--remove")) {
- allow_remove = 1;
- continue;
- }
- if (!strcmp(path, "--unmerged")) {
- refresh_flags |= REFRESH_UNMERGED;
- continue;
- }
- if (!strcmp(path, "--refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(refresh_flags);
- continue;
- }
- if (!strcmp(path, "--really-refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
- continue;
- }
- if (!strcmp(path, "--cacheinfo")) {
- unsigned char sha1[20];
- unsigned int mode;
-
- if (i+3 >= argc)
- die("git update-index: --cacheinfo <mode> <sha1> <path>");
-
- if (strtoul_ui(argv[i+1], 8, &mode) ||
- get_sha1_hex(argv[i+2], sha1) ||
- add_cacheinfo(mode, sha1, argv[i+3], 0))
- die("git update-index: --cacheinfo"
- " cannot add %s", argv[i+3]);
- i += 3;
- continue;
- }
- if (!strcmp(path, "--chmod=-x") ||
- !strcmp(path, "--chmod=+x")) {
- if (argc <= i+1)
- die("git update-index: %s <path>", path);
- set_executable_bit = path[8];
- continue;
- }
- if (!strcmp(path, "--assume-unchanged")) {
- mark_valid_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-assume-unchanged")) {
- mark_valid_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-skip-worktree")) {
- mark_skip_worktree_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--skip-worktree")) {
- mark_skip_worktree_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--info-only")) {
- info_only = 1;
- continue;
- }
- if (!strcmp(path, "--force-remove")) {
- force_remove = 1;
- continue;
- }
- if (!strcmp(path, "-z")) {
- line_termination = 0;
- continue;
- }
- if (!strcmp(path, "--stdin")) {
- if (i != argc - 1)
- die("--stdin must be at the end");
- read_from_stdin = 1;
- break;
- }
- if (!strcmp(path, "--index-info")) {
- if (i != argc - 1)
- die("--index-info must be at the end");
- allow_add = allow_replace = allow_remove = 1;
- read_index_info(line_termination);
- break;
- }
- if (!strcmp(path, "--unresolve")) {
- has_errors = do_unresolve(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
- setup_work_tree();
- has_errors = do_reupdate(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--ignore-missing")) {
- refresh_flags |= REFRESH_IGNORE_MISSING;
- continue;
- }
- if (!strcmp(path, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(path, "--clear-resolve-undo")) {
- resolve_undo_clear();
- continue;
- }
- if (!strcmp(path, "-h") || !strcmp(path, "--help"))
- usage(update_index_usage);
- die("unknown option %s", path);
+ setup_work_tree();
+ p = prefix_path(prefix, prefix_length, path);
+ update_one(p, NULL, 0);
+ if (set_executable_bit)
+ chmod_path(set_executable_bit, p);
+ if (p < path || p > path + strlen(path))
+ free((char *)p);
+ ctx.argc--;
+ ctx.argv++;
+ break;
+ }
+ case PARSE_OPT_UNKNOWN:
+ if (ctx.argv[0][1] == '-')
+ error("unknown option '%s'", ctx.argv[0] + 2);
+ else
+ error("unknown switch '%c'", *ctx.opt);
+ usage_with_options(update_index_usage, options);
}
- setup_work_tree();
- p = prefix_path(prefix, prefix_length, path);
- update_one(p, NULL, 0);
- if (set_executable_bit)
- chmod_path(set_executable_bit, p);
- if (p < path || p > path + strlen(path))
- free((char *)p);
}
+ argc = parse_options_end(&ctx);
+
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
@@ -770,10 +875,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
- finish:
if (active_cache_changed) {
if (newfd < 0) {
- if (refresh_flags & REFRESH_QUIET)
+ if (refresh_args.flags & REFRESH_QUIET)
exit(128);
unable_to_lock_index_die(get_index_file(), lock_error);
}
diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c
index 2b3fddc..b90dce6 100644
--- a/builtin/update-server-info.c
+++ b/builtin/update-server-info.c
@@ -11,8 +11,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix)
{
int force = 0;
struct option options[] = {
- OPT_BOOLEAN('f', "force", &force,
- "update the info files from scratch"),
+ OPT__FORCE(&force, "update the info files from scratch"),
OPT_END()
};
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 73f788e..2d0b383 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -64,7 +64,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
sent_argv[sent_argc] = NULL;
/* parse all options sent by the client */
- return write_archive(sent_argc, sent_argv, prefix, 0);
+ return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
}
__attribute__((format (printf, 1, 2)))
diff --git a/builtin/var.c b/builtin/var.c
index 70fdb4d..99d068a 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -3,8 +3,7 @@
*
* Copyright (C) Eric Biederman, 2005
*/
-#include "cache.h"
-#include "exec_cmd.h"
+#include "builtin.h"
static const char var_usage[] = "git var (-l | <variable>)";
@@ -74,14 +73,9 @@ static int show_config(const char *var, const char *value, void *cb)
int cmd_var(int argc, const char **argv, const char *prefix)
{
- const char *val;
- int nongit;
- if (argc != 2) {
+ const char *val = NULL;
+ if (argc != 2)
usage(var_usage);
- }
-
- setup_git_directory_gently(&nongit);
- val = NULL;
if (strcmp(argv[1], "-l") == 0) {
git_config(show_config, NULL);
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
index b6079ae..e841b4a 100644
--- a/builtin/verify-pack.c
+++ b/builtin/verify-pack.c
@@ -1,134 +1,53 @@
#include "builtin.h"
#include "cache.h"
-#include "pack.h"
-#include "pack-revindex.h"
+#include "run-command.h"
#include "parse-options.h"
-#define MAX_CHAIN 50
-
#define VERIFY_PACK_VERBOSE 01
#define VERIFY_PACK_STAT_ONLY 02
-static void show_pack_info(struct packed_git *p, unsigned int flags)
-{
- uint32_t nr_objects, i;
- int cnt;
- int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
-
- nr_objects = p->num_objects;
- memset(chain_histogram, 0, sizeof(chain_histogram));
- baseobjects = 0;
-
- for (i = 0; i < nr_objects; i++) {
- const unsigned char *sha1;
- unsigned char base_sha1[20];
- const char *type;
- unsigned long size;
- unsigned long store_size;
- off_t offset;
- unsigned int delta_chain_length;
-
- sha1 = nth_packed_object_sha1(p, i);
- if (!sha1)
- die("internal error pack-check nth-packed-object");
- offset = nth_packed_object_offset(p, i);
- type = packed_object_info_detail(p, offset, &size, &store_size,
- &delta_chain_length,
- base_sha1);
- if (!stat_only)
- printf("%s ", sha1_to_hex(sha1));
- if (!delta_chain_length) {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX"\n",
- type, size, store_size, (uintmax_t)offset);
- baseobjects++;
- }
- else {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
- type, size, store_size, (uintmax_t)offset,
- delta_chain_length, sha1_to_hex(base_sha1));
- if (delta_chain_length <= MAX_CHAIN)
- chain_histogram[delta_chain_length]++;
- else
- chain_histogram[0]++;
- }
- }
-
- if (baseobjects)
- printf("non delta: %lu object%s\n",
- baseobjects, baseobjects > 1 ? "s" : "");
-
- for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
- if (!chain_histogram[cnt])
- continue;
- printf("chain length = %d: %lu object%s\n", cnt,
- chain_histogram[cnt],
- chain_histogram[cnt] > 1 ? "s" : "");
- }
- if (chain_histogram[0])
- printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
- chain_histogram[0],
- chain_histogram[0] > 1 ? "s" : "");
-}
-
static int verify_one_pack(const char *path, unsigned int flags)
{
- char arg[PATH_MAX];
- int len;
+ struct child_process index_pack;
+ const char *argv[] = {"index-pack", NULL, NULL, NULL };
+ struct strbuf arg = STRBUF_INIT;
int verbose = flags & VERIFY_PACK_VERBOSE;
int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- struct packed_git *pack;
int err;
- len = strlcpy(arg, path, PATH_MAX);
- if (len >= PATH_MAX)
- return error("name too long: %s", path);
-
- /*
- * In addition to "foo.idx" we accept "foo.pack" and "foo";
- * normalize these forms to "foo.idx" for add_packed_git().
- */
- if (has_extension(arg, ".pack")) {
- strcpy(arg + len - 5, ".idx");
- len--;
- } else if (!has_extension(arg, ".idx")) {
- if (len + 4 >= PATH_MAX)
- return error("name too long: %s.idx", arg);
- strcpy(arg + len, ".idx");
- len += 4;
- }
+ if (stat_only)
+ argv[1] = "--verify-stat-only";
+ else if (verbose)
+ argv[1] = "--verify-stat";
+ else
+ argv[1] = "--verify";
/*
- * add_packed_git() uses our buffer (containing "foo.idx") to
- * build the pack filename ("foo.pack"). Make sure it fits.
+ * In addition to "foo.pack" we accept "foo.idx" and "foo";
+ * normalize these forms to "foo.pack" for "index-pack --verify".
*/
- if (len + 1 >= PATH_MAX) {
- arg[len - 4] = '\0';
- return error("name too long: %s.pack", arg);
- }
-
- pack = add_packed_git(arg, len, 1);
- if (!pack)
- return error("packfile %s not found.", arg);
+ strbuf_addstr(&arg, path);
+ if (has_extension(arg.buf, ".idx"))
+ strbuf_splice(&arg, arg.len - 3, 3, "pack", 4);
+ else if (!has_extension(arg.buf, ".pack"))
+ strbuf_add(&arg, ".pack", 5);
+ argv[2] = arg.buf;
- install_packed_git(pack);
+ memset(&index_pack, 0, sizeof(index_pack));
+ index_pack.argv = argv;
+ index_pack.git_cmd = 1;
- if (!stat_only)
- err = verify_pack(pack);
- else
- err = open_pack_index(pack);
+ err = run_command(&index_pack);
if (verbose || stat_only) {
if (err)
- printf("%s: bad\n", pack->pack_name);
+ printf("%s: bad\n", arg.buf);
else {
- show_pack_info(pack, flags);
if (!stat_only)
- printf("%s: ok\n", pack->pack_name);
+ printf("%s: ok\n", arg.buf);
}
}
+ strbuf_release(&arg);
return err;
}
@@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) {
if (verify_one_pack(argv[i], flags))
err = 1;
- discard_revindex();
}
return err;
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 9f482c2..3134766 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = {
NULL
};
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
{
struct child_process gpg;
const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
- char path[PATH_MAX], *eol;
+ char path[PATH_MAX];
size_t len;
int fd, ret;
@@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
close(fd);
/* find the length without signature */
- len = 0;
- while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
- eol = memchr(buf + len, '\n', size - len);
- len += eol ? eol - (buf + len) + 1 : size - len;
- }
+ len = parse_signature(buf, size);
if (verbose)
write_in_full(1, buf, len);
@@ -93,7 +87,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
{
int i = 1, verbose = 0, had_error = 0;
const struct option verify_tag_options[] = {
- OPT__VERBOSE(&verbose),
+ OPT__VERBOSE(&verbose, "print tag contents"),
OPT_END()
};
diff --git a/bundle.c b/bundle.c
index ff97adc..f48fd7d 100644
--- a/bundle.c
+++ b/bundle.c
@@ -200,7 +200,7 @@ int create_bundle(struct bundle_header *header, const char *path,
int bundle_fd = -1;
int bundle_to_stdout;
const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
- const char **argv_pack = xmalloc(5 * sizeof(const char *));
+ const char **argv_pack = xmalloc(6 * sizeof(const char *));
int i, ref_count = 0;
char buffer[1024];
struct rev_info revs;
@@ -346,7 +346,8 @@ int create_bundle(struct bundle_header *header, const char *path,
argv_pack[1] = "--all-progress-implied";
argv_pack[2] = "--stdout";
argv_pack[3] = "--thin";
- argv_pack[4] = NULL;
+ argv_pack[4] = "--delta-base-offset";
+ argv_pack[5] = NULL;
memset(&rls, 0, sizeof(rls));
rls.argv = argv_pack;
rls.in = -1;
@@ -372,8 +373,10 @@ int create_bundle(struct bundle_header *header, const char *path,
close(rls.in);
if (finish_command(&rls))
return error ("pack-objects died");
- if (!bundle_to_stdout)
- commit_lock_file(&lock);
+ if (!bundle_to_stdout) {
+ if (commit_lock_file(&lock))
+ die_errno("cannot create '%s'", path);
+ }
return 0;
}
diff --git a/cache-tree.c b/cache-tree.c
index d917437..f755590 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -22,8 +22,10 @@ void cache_tree_free(struct cache_tree **it_p)
if (!it)
return;
for (i = 0; i < it->subtree_nr; i++)
- if (it->down[i])
+ if (it->down[i]) {
cache_tree_free(&it->down[i]->cache_tree);
+ free(it->down[i]);
+ }
free(it->down);
free(it);
*it_p = NULL;
@@ -328,9 +330,11 @@ static int update_one(struct cache_tree *it,
mode = ce->ce_mode;
entlen = pathlen - baselen;
}
- if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
+ if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) {
+ strbuf_release(&buffer);
return error("invalid object %06o %s for '%.*s'",
mode, sha1_to_hex(sha1), entlen+baselen, path);
+ }
if (ce->ce_flags & CE_REMOVE)
continue; /* entry being removed */
diff --git a/cache.h b/cache.h
index c9fa3df..9e12d55 100644
--- a/cache.h
+++ b/cache.h
@@ -5,6 +5,8 @@
#include "strbuf.h"
#include "hash.h"
#include "advice.h"
+#include "gettext.h"
+#include "convert.h"
#include SHA1_HEADER
#ifndef git_SHA_CTX
@@ -15,13 +17,27 @@
#endif
#include <zlib.h>
-#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
-#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
-#endif
-
-void git_inflate_init(z_streamp strm);
-void git_inflate_end(z_streamp strm);
-int git_inflate(z_streamp strm, int flush);
+typedef struct git_zstream {
+ z_stream z;
+ unsigned long avail_in;
+ unsigned long avail_out;
+ unsigned long total_in;
+ unsigned long total_out;
+ unsigned char *next_in;
+ unsigned char *next_out;
+} git_zstream;
+
+void git_inflate_init(git_zstream *);
+void git_inflate_init_gzip_only(git_zstream *);
+void git_inflate_end(git_zstream *);
+int git_inflate(git_zstream *, int flush);
+
+void git_deflate_init(git_zstream *, int level);
+void git_deflate_init_gzip(git_zstream *, int level);
+void git_deflate_end(git_zstream *);
+int git_deflate_end_gently(git_zstream *);
+int git_deflate(git_zstream *, int flush);
+unsigned long git_deflate_bound(git_zstream *, unsigned long);
#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
#define DTYPE(de) ((de)->d_type)
@@ -170,27 +186,26 @@ struct cache_entry {
*
* In-memory only flags
*/
-#define CE_UPDATE (0x10000)
-#define CE_REMOVE (0x20000)
-#define CE_UPTODATE (0x40000)
-#define CE_ADDED (0x80000)
+#define CE_UPDATE (1 << 16)
+#define CE_REMOVE (1 << 17)
+#define CE_UPTODATE (1 << 18)
+#define CE_ADDED (1 << 19)
-#define CE_HASHED (0x100000)
-#define CE_UNHASHED (0x200000)
-#define CE_CONFLICTED (0x800000)
+#define CE_HASHED (1 << 20)
+#define CE_UNHASHED (1 << 21)
+#define CE_WT_REMOVE (1 << 22) /* remove in work directory */
+#define CE_CONFLICTED (1 << 23)
-/* Only remove in work directory, not index */
-#define CE_WT_REMOVE (0x400000)
-
-#define CE_UNPACKED (0x1000000)
+#define CE_UNPACKED (1 << 24)
+#define CE_NEW_SKIP_WORKTREE (1 << 25)
/*
* Extended on-disk flags
*/
-#define CE_INTENT_TO_ADD 0x20000000
-#define CE_SKIP_WORKTREE 0x40000000
+#define CE_INTENT_TO_ADD (1 << 29)
+#define CE_SKIP_WORKTREE (1 << 30)
/* CE_EXTENDED2 is for future extension */
-#define CE_EXTENDED2 0x80000000
+#define CE_EXTENDED2 (1 << 31)
#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
@@ -278,9 +293,16 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
else
return DT_UNKNOWN;
}
-#define canon_mode(mode) \
- (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
- S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
+static inline unsigned int canon_mode(unsigned int mode)
+{
+ if (S_ISREG(mode))
+ return S_IFREG | ce_permissions(mode);
+ if (S_ISLNK(mode))
+ return S_IFLNK;
+ if (S_ISDIR(mode))
+ return S_IFDIR;
+ return S_IFGITLINK;
+}
#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
#define cache_entry_size(len) flexible_size(cache_entry,len)
@@ -379,6 +401,7 @@ static inline enum object_type object_type(unsigned int mode)
#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
+#define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
@@ -397,7 +420,7 @@ static inline enum object_type object_type(unsigned int mode)
* environment creation or simple walk of the list.
* The number of non-NULL entries is available as a macro.
*/
-#define LOCAL_REPO_ENV_SIZE 8
+#define LOCAL_REPO_ENV_SIZE 9
extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
extern int is_bare_repository_cfg;
@@ -421,7 +444,7 @@ extern const char **get_pathspec(const char *prefix, const char **pathspec);
extern void setup_work_tree(void);
extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void);
-extern const char *prefix_path(const char *prefix, int len, const char *path);
+extern char *prefix_path(const char *prefix, int len, const char *path);
extern const char *prefix_filename(const char *prefix, int len, const char *path);
extern int check_filename(const char *prefix, const char *name);
extern void verify_filename(const char *prefix, const char *name);
@@ -429,6 +452,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
#define INIT_DB_QUIET 0x0001
+extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
extern int init_db(const char *template_dir, unsigned int flags);
#define alloc_nr(x) (((x)+16)*3/2)
@@ -438,7 +462,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
* at least 'nr' entries; the number of entries currently allocated
* is 'alloc', using the standard growing factor alloc_nr() macro.
*
- * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'.
*/
#define ALLOC_GROW(x, nr, alloc) \
do { \
@@ -449,7 +473,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
- } while(0)
+ } while (0)
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
@@ -493,9 +517,27 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
-extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
+struct pathspec {
+ const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
+ int nr;
+ unsigned int has_wildcard:1;
+ unsigned int recursive:1;
+ int max_depth;
+ struct pathspec_item {
+ const char *match;
+ int len;
+ unsigned int use_wildcard:1;
+ } *items;
+};
+
+extern int init_pathspec(struct pathspec *, const char **);
+extern void free_pathspec(struct pathspec *);
+extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
+
+#define HASH_WRITE_OBJECT 1
+#define HASH_FORMAT_CHECK 2
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
+extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_REALLY 0x0001 /* ignore_valid */
@@ -504,7 +546,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
-extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg);
+extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
struct lock_file {
struct lock_file *next;
@@ -520,6 +562,7 @@ extern NORETURN void unable_to_lock_index_die(const char *path, int err);
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
extern int commit_lock_file(struct lock_file *);
+extern void update_index_if_able(struct index_state *, struct lock_file *);
extern int hold_locked_index(struct lock_file *, int);
extern int commit_locked_index(struct lock_file *);
@@ -533,6 +576,7 @@ extern int trust_executable_bit;
extern int trust_ctime;
extern int quote_path_fully;
extern int has_symlinks;
+extern int minimum_abbrev, default_abbrev;
extern int ignore_case;
extern int assume_unchanged;
extern int prefer_symlink_refs;
@@ -547,40 +591,12 @@ extern int core_compression_seen;
extern size_t packed_git_window_size;
extern size_t packed_git_limit;
extern size_t delta_base_cache_limit;
+extern unsigned long big_file_threshold;
extern int read_replace_refs;
extern int fsync_object_files;
extern int core_preload_index;
extern int core_apply_sparse_checkout;
-enum safe_crlf {
- SAFE_CRLF_FALSE = 0,
- SAFE_CRLF_FAIL = 1,
- SAFE_CRLF_WARN = 2
-};
-
-extern enum safe_crlf safe_crlf;
-
-enum auto_crlf {
- AUTO_CRLF_FALSE = 0,
- AUTO_CRLF_TRUE = 1,
- AUTO_CRLF_INPUT = -1,
-};
-
-extern enum auto_crlf auto_crlf;
-
-enum eol {
- EOL_UNSET,
- EOL_CRLF,
- EOL_LF,
-#ifdef NATIVE_CRLF
- EOL_NATIVE = EOL_CRLF
-#else
- EOL_NATIVE = EOL_LF
-#endif
-};
-
-extern enum eol eol;
-
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
@@ -600,7 +616,7 @@ enum rebase_setup_type {
enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
- PUSH_DEFAULT_TRACKING,
+ PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT
};
@@ -641,19 +657,32 @@ extern char *git_pathdup(const char *fmt, ...)
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *git_path_submodule(const char *path, const char *fmt, ...)
+ __attribute__((format (printf, 2, 3)));
+
extern char *sha1_file_name(const unsigned char *sha1);
extern char *sha1_pack_name(const unsigned char *sha1);
extern char *sha1_pack_index_name(const unsigned char *sha1);
extern const char *find_unique_abbrev(const unsigned char *sha1, int);
extern const unsigned char null_sha1[20];
-static inline int is_null_sha1(const unsigned char *sha1)
+
+static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
{
- return !memcmp(sha1, null_sha1, 20);
+ int i;
+
+ for (i = 0; i < 20; i++, sha1++, sha2++) {
+ if (*sha1 != *sha2)
+ return *sha1 - *sha2;
+ }
+
+ return 0;
}
-static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+
+static inline int is_null_sha1(const unsigned char *sha1)
{
- return memcmp(sha1, sha2, 20);
+ return !hashcmp(sha1, null_sha1);
}
+
static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
{
memcpy(sha_dst, sha_src, 20);
@@ -665,9 +694,11 @@ static inline void hashclr(unsigned char *hash)
#define EMPTY_TREE_SHA1_HEX \
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
-#define EMPTY_TREE_SHA1_BIN \
+#define EMPTY_TREE_SHA1_BIN_LITERAL \
"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
"\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
+#define EMPTY_TREE_SHA1_BIN \
+ ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
int git_mkstemp(char *path, size_t n, const char *template);
@@ -697,33 +728,47 @@ int set_shared_perm(const char *path, int mode);
#define adjust_shared_perm(path) set_shared_perm((path), 0)
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
+int mkdir_in_gitdir(const char *path);
extern char *expand_user_path(const char *path);
char *enter_repo(char *path, int strict);
static inline int is_absolute_path(const char *path)
{
- return path[0] == '/' || has_dos_drive_prefix(path);
+ return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
}
int is_directory(const char *);
-const char *make_absolute_path(const char *path);
-const char *make_nonrelative_path(const char *path);
-const char *make_relative_path(const char *abs, const char *base);
+const char *real_path(const char *path);
+const char *absolute_path(const char *path);
+const char *relative_path(const char *abs, const char *base);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, const char *prefix_list);
char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
int offset_1st_component(const char *path);
-/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
-extern int sha1_object_info(const unsigned char *, unsigned long *);
-extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement);
+/* object replacement */
+#define READ_SHA1_FILE_REPLACE 1
+extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
- return read_sha1_file_repl(sha1, type, size, NULL);
+ return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE);
+}
+extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
+static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
+{
+ if (!read_replace_refs)
+ return sha1;
+ return do_lookup_replace_object(sha1);
}
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern int sha1_object_info(const unsigned char *, unsigned long *);
extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
extern int force_object_loose(const unsigned char *sha1, time_t mtime);
+extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
+extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
+extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
/* global flag to enable extra checks when accessing packed objects */
extern int do_check_packed_object_crc;
@@ -747,8 +792,8 @@ static inline unsigned int hexval(unsigned char c)
}
/* Convert to/from hex/sha1 representation */
-#define MINIMUM_ABBREV 4
-#define DEFAULT_ABBREV 7
+#define MINIMUM_ABBREV minimum_abbrev
+#define DEFAULT_ABBREV default_abbrev
struct object_context {
unsigned char tree[20];
@@ -757,15 +802,15 @@ struct object_context {
};
extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
+extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix);
static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
{
- return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
+ return get_sha1_with_mode_1(str, sha1, mode, 0, NULL);
}
-extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int gently, const char *prefix);
+extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, const char *prefix);
static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc)
{
- return get_sha1_with_context_1(str, sha1, orc, 1, NULL);
+ return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
}
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
@@ -811,6 +856,7 @@ const char *show_date_relative(unsigned long time, int tz,
char *timebuf,
size_t timebuf_size);
int parse_date(const char *date, char *buf, int bufsize);
+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
void datestamp(char *buf, int bufsize);
#define approxidate(s) approxidate_careful((s), NULL)
unsigned long approxidate_careful(const char *, int *);
@@ -848,7 +894,7 @@ struct cache_def {
extern int has_symlink_leading_path(const char *name, int len);
extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
-extern int has_symlink_or_noent_leading_path(const char *name, int len);
+extern int check_leading_path(const char *name, int len);
extern int has_dirs_only_path(const char *name, int len, int prefix_len);
extern void schedule_dir_for_removal(const char *name, int len);
extern void remove_scheduled_dirs(void);
@@ -885,7 +931,8 @@ extern struct packed_git {
time_t mtime;
int pack_fd;
unsigned pack_local:1,
- pack_keep:1;
+ pack_keep:1,
+ do_not_close:1;
unsigned char sha1[20];
/* something like ".git/objects/pack/xxxxx.pack" */
char pack_name[FLEX_ARRAY]; /* more */
@@ -930,6 +977,7 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
extern char *git_getpass(const char *prompt);
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
+extern int git_connection_is_socket(struct child_process *conn);
extern int path_match(const char *path, int nr, char **match);
struct extra_have_objects {
int nr, alloc;
@@ -950,7 +998,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
extern void pack_report(void);
extern int open_pack_index(struct packed_git *);
extern void close_pack_index(struct packed_git *);
-extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
+extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
extern void close_pack_windows(struct packed_git *);
extern void unuse_pack(struct pack_window **);
extern void free_pack_by_name(const char *);
@@ -962,17 +1010,57 @@ extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+
+struct object_info {
+ /* Request */
+ unsigned long *sizep;
+
+ /* Response */
+ enum {
+ OI_CACHED,
+ OI_LOOSE,
+ OI_PACKED,
+ OI_DBCACHED
+ } whence;
+ union {
+ /*
+ * struct {
+ * ... Nothing to expose in this case
+ * } cached;
+ * struct {
+ * ... Nothing to expose in this case
+ * } loose;
+ */
+ struct {
+ struct packed_git *pack;
+ off_t offset;
+ unsigned int is_delta;
+ } packed;
+ } u;
+};
+extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
/* Dumb servers support */
extern int update_server_info(int);
+/* git_config_parse_key() returns these negated: */
+#define CONFIG_INVALID_KEY 1
+#define CONFIG_NO_SECTION_OR_NAME 2
+/* git_config_set(), git_config_set_multivar() return the above or these: */
+#define CONFIG_NO_LOCK -1
+#define CONFIG_INVALID_FILE 3
+#define CONFIG_NO_WRITE 4
+#define CONFIG_NOTHING_SET 5
+#define CONFIG_INVALID_PATTERN 6
+
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *);
-extern int git_config_parse_parameter(const char *text);
+extern void git_config_push_parameter(const char *text);
extern int git_config_from_parameters(config_fn_t fn, void *data);
extern int git_config(config_fn_t fn, void *);
+extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
extern unsigned long git_config_ulong(const char *, const char *);
@@ -982,14 +1070,19 @@ extern int git_config_maybe_bool(const char *, const char *);
extern int git_config_string(const char **, const char *, const char *);
extern int git_config_pathname(const char **, const char *, const char *);
extern int git_config_set(const char *, const char *);
+extern int git_config_parse_key(const char *, char **, int *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
extern const char *git_etc_gitconfig(void);
extern int check_repository_format_version(const char *var, const char *value, void *cb);
extern int git_env_bool(const char *, int);
extern int git_config_system(void);
-extern int git_config_global(void);
extern int config_error_nonbool(const char *);
+extern const char *get_log_output_encoding(void);
+extern const char *get_commit_output_encoding(void);
+
+extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
+
extern const char *config_exclusive_filename;
#define MAX_GITNAME (1000)
@@ -1029,6 +1122,7 @@ extern int pager_in_use(void);
extern int pager_use_color;
extern const char *editor_program;
+extern const char *askpass_program;
extern const char *excludes_file;
/* base85 */
@@ -1046,14 +1140,14 @@ extern void alloc_report(void);
/* trace.c */
__attribute__((format (printf, 1, 2)))
extern void trace_printf(const char *format, ...);
+extern void trace_vprintf(const char *key, const char *format, va_list ap);
__attribute__((format (printf, 2, 3)))
extern void trace_argv_printf(const char **argv, const char *format, ...);
+extern void trace_repo_setup(const char *prefix);
+extern int trace_want(const char *key);
+extern void trace_strbuf(const char *key, const struct strbuf *buf);
-/* convert.c */
-/* returns 1 if *dst was used */
-extern int convert_to_git(const char *path, const char *src, size_t len,
- struct strbuf *dst, enum safe_crlf checksafe);
-extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
+void packet_trace_identity(const char *prog);
/* add */
/*
@@ -1072,15 +1166,17 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *
/*
* whitespace rules.
* used by both diff and apply
+ * last two digits are tab width
*/
-#define WS_BLANK_AT_EOL 01
-#define WS_SPACE_BEFORE_TAB 02
-#define WS_INDENT_WITH_NON_TAB 04
-#define WS_CR_AT_EOL 010
-#define WS_BLANK_AT_EOF 020
-#define WS_TAB_IN_INDENT 040
+#define WS_BLANK_AT_EOL 0100
+#define WS_SPACE_BEFORE_TAB 0200
+#define WS_INDENT_WITH_NON_TAB 0400
+#define WS_CR_AT_EOL 01000
+#define WS_BLANK_AT_EOF 02000
+#define WS_TAB_IN_INDENT 04000
#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
-#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
+#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
+#define WS_TAB_WIDTH_MASK 077
extern unsigned whitespace_rule_cfg;
extern unsigned whitespace_rule(const char *);
extern unsigned parse_whitespace_rule(const char *);
@@ -1089,6 +1185,7 @@ extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *str
extern char *whitespace_error_string(unsigned ws);
extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
+#define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK)
/* ls-files */
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
@@ -1096,6 +1193,15 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias);
int split_cmdline(char *cmdline, const char ***argv);
+/* Takes a negative value returned by split_cmdline */
+const char *split_cmdline_strerror(int cmdline_errno);
+
+/* git.c */
+struct startup_info {
+ int have_repository;
+ const char *prefix;
+};
+extern struct startup_info *startup_info;
/* builtin/merge.c */
int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
diff --git a/color.c b/color.c
index 1b00554..3db214c 100644
--- a/color.c
+++ b/color.c
@@ -3,6 +3,28 @@
int git_use_color_default = 0;
+/*
+ * The list of available column colors.
+ */
+const char *column_colors_ansi[] = {
+ GIT_COLOR_RED,
+ GIT_COLOR_GREEN,
+ GIT_COLOR_YELLOW,
+ GIT_COLOR_BLUE,
+ GIT_COLOR_MAGENTA,
+ GIT_COLOR_CYAN,
+ GIT_COLOR_BOLD_RED,
+ GIT_COLOR_BOLD_GREEN,
+ GIT_COLOR_BOLD_YELLOW,
+ GIT_COLOR_BOLD_BLUE,
+ GIT_COLOR_BOLD_MAGENTA,
+ GIT_COLOR_BOLD_CYAN,
+ GIT_COLOR_RESET,
+};
+
+/* Ignore the RESET at the end when giving the size */
+const int column_colors_ansi_max = ARRAY_SIZE(column_colors_ansi) - 1;
+
static int parse_color(const char *name, int len)
{
static const char * const color_names[] = {
@@ -175,6 +197,15 @@ int git_color_default_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)
+{
+ if (*color)
+ fprintf(fp, "%s", color);
+ fprintf(fp, "%s", sb->buf);
+ if (*color)
+ fprintf(fp, "%s", GIT_COLOR_RESET);
+}
+
static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
va_list args, const char *trail)
{
@@ -211,3 +242,8 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
va_end(args);
return r;
}
+
+int color_is_nil(const char *c)
+{
+ return !strcmp(c, "NIL");
+}
diff --git a/color.h b/color.h
index 03ca064..68a926a 100644
--- a/color.h
+++ b/color.h
@@ -1,6 +1,8 @@
#ifndef COLOR_H
#define COLOR_H
+struct strbuf;
+
/* 2 + (2 * num_attrs) + 8 + 1 + 8 + 'm' + NUL */
/* "\033[1;2;4;5;7;38;5;2xx;48;5;2xxm\0" */
/*
@@ -43,11 +45,17 @@
#define GIT_COLOR_BG_MAGENTA "\033[45m"
#define GIT_COLOR_BG_CYAN "\033[46m"
+/* A special value meaning "no color selected" */
+#define GIT_COLOR_NIL "NIL"
+
/*
* This variable stores the value of color.ui
*/
extern int git_use_color_default;
+/* A default list of colors to use for commit graphs and show-branch output */
+extern const char *column_colors_ansi[];
+extern const int column_colors_ansi_max;
/*
* Use this instead of git_default_config if you need the value of color.ui.
@@ -61,5 +69,8 @@ __attribute__((format (printf, 3, 4)))
int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
__attribute__((format (printf, 3, 4)))
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
+void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);
+
+int color_is_nil(const char *color);
#endif /* COLOR_H */
diff --git a/combine-diff.c b/combine-diff.c
index 655fa89..be67cfc 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -7,6 +7,7 @@
#include "xdiff-interface.h"
#include "log-tree.h"
#include "refs.h"
+#include "userdiff.h"
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
{
@@ -92,7 +93,9 @@ struct sline {
unsigned long *p_lno;
};
-static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned long *size)
+static char *grab_blob(const unsigned char *sha1, unsigned int mode,
+ unsigned long *size, struct userdiff_driver *textconv,
+ const char *path)
{
char *blob;
enum object_type type;
@@ -105,6 +108,11 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned lo
/* deleted blob */
*size = 0;
return xcalloc(1, 1);
+ } else if (textconv) {
+ struct diff_filespec *df = alloc_filespec(path);
+ fill_filespec(df, sha1, mode);
+ *size = fill_textconv(textconv, df, &blob);
+ free_filespec(df);
} else {
blob = read_sha1_file(sha1, &type, size);
if (type != OBJ_BLOB)
@@ -204,7 +212,9 @@ static void consume_line(void *state_, char *line, unsigned long len)
static void combine_diff(const unsigned char *parent, unsigned int mode,
mmfile_t *result_file,
struct sline *sline, unsigned int cnt, int n,
- int num_parent, int result_deleted)
+ int num_parent, int result_deleted,
+ struct userdiff_driver *textconv,
+ const char *path)
{
unsigned int p_lno, lno;
unsigned long nmask = (1UL << n);
@@ -217,7 +227,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode,
if (result_deleted)
return; /* result deleted */
- parent_file.ptr = grab_blob(parent, mode, &sz);
+ parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
parent_file.size = sz;
memset(&xpp, 0, sizeof(xpp));
xpp.flags = 0;
@@ -681,6 +691,82 @@ static void dump_quoted_path(const char *head,
puts(buf.buf);
}
+static void show_combined_header(struct combine_diff_path *elem,
+ int num_parent,
+ int dense,
+ struct rev_info *rev,
+ int mode_differs,
+ int show_file_header)
+{
+ struct diff_options *opt = &rev->diffopt;
+ int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+ const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
+ const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+ int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
+ const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
+ const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+ const char *abb;
+ int added = 0;
+ int deleted = 0;
+ int i;
+
+ if (rev->loginfo && !rev->no_commit_id)
+ show_log(rev);
+
+ dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
+ "", elem->path, c_meta, c_reset);
+ printf("%sindex ", c_meta);
+ for (i = 0; i < num_parent; i++) {
+ abb = find_unique_abbrev(elem->parent[i].sha1,
+ abbrev);
+ printf("%s%s", i ? "," : "", abb);
+ }
+ abb = find_unique_abbrev(elem->sha1, abbrev);
+ printf("..%s%s\n", abb, c_reset);
+
+ if (mode_differs) {
+ deleted = !elem->mode;
+
+ /* We say it was added if nobody had it */
+ added = !deleted;
+ for (i = 0; added && i < num_parent; i++)
+ if (elem->parent[i].status !=
+ DIFF_STATUS_ADDED)
+ added = 0;
+ if (added)
+ printf("%snew file mode %06o",
+ c_meta, elem->mode);
+ else {
+ if (deleted)
+ printf("%sdeleted file ", c_meta);
+ printf("mode ");
+ for (i = 0; i < num_parent; i++) {
+ printf("%s%06o", i ? "," : "",
+ elem->parent[i].mode);
+ }
+ if (elem->mode)
+ printf("..%06o", elem->mode);
+ }
+ printf("%s\n", c_reset);
+ }
+
+ if (!show_file_header)
+ return;
+
+ if (added)
+ dump_quoted_path("--- ", "", "/dev/null",
+ c_meta, c_reset);
+ else
+ dump_quoted_path("--- ", a_prefix, elem->path,
+ c_meta, c_reset);
+ if (deleted)
+ dump_quoted_path("+++ ", "", "/dev/null",
+ c_meta, c_reset);
+ else
+ dump_quoted_path("+++ ", b_prefix, elem->path,
+ c_meta, c_reset);
+}
+
static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
int dense, struct rev_info *rev)
{
@@ -692,17 +778,22 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
int mode_differs = 0;
int i, show_hunks;
int working_tree_file = is_null_sha1(elem->sha1);
- int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
- const char *a_prefix, *b_prefix;
mmfile_t result_file;
+ struct userdiff_driver *userdiff;
+ struct userdiff_driver *textconv = NULL;
+ int is_binary;
context = opt->context;
- a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
- b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
+ userdiff = userdiff_find_by_path(elem->path);
+ if (!userdiff)
+ userdiff = userdiff_find_by_name("default");
+ if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV))
+ textconv = userdiff_get_textconv(userdiff);
/* Read the result of merge first */
if (!working_tree_file)
- result = grab_blob(elem->sha1, elem->mode, &result_size);
+ result = grab_blob(elem->sha1, elem->mode, &result_size,
+ textconv, elem->path);
else {
/* Used by diff-tree to read from the working tree */
struct stat st;
@@ -725,9 +816,16 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
} else if (S_ISDIR(st.st_mode)) {
unsigned char sha1[20];
if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0)
- result = grab_blob(elem->sha1, elem->mode, &result_size);
+ result = grab_blob(elem->sha1, elem->mode,
+ &result_size, NULL, NULL);
else
- result = grab_blob(sha1, elem->mode, &result_size);
+ result = grab_blob(sha1, elem->mode,
+ &result_size, NULL, NULL);
+ } else if (textconv) {
+ struct diff_filespec *df = alloc_filespec(elem->path);
+ fill_filespec(df, null_sha1, st.st_mode);
+ result_size = fill_textconv(textconv, df, &result);
+ free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
size_t len = xsize_t(st.st_size);
ssize_t done;
@@ -777,6 +875,38 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
close(fd);
}
+ for (i = 0; i < num_parent; i++) {
+ if (elem->parent[i].mode != elem->mode) {
+ mode_differs = 1;
+ break;
+ }
+ }
+
+ if (textconv)
+ is_binary = 0;
+ else if (userdiff->binary != -1)
+ is_binary = userdiff->binary;
+ else {
+ is_binary = buffer_is_binary(result, result_size);
+ for (i = 0; !is_binary && i < num_parent; i++) {
+ char *buf;
+ unsigned long size;
+ buf = grab_blob(elem->parent[i].sha1,
+ elem->parent[i].mode,
+ &size, NULL, NULL);
+ if (buffer_is_binary(buf, size))
+ is_binary = 1;
+ free(buf);
+ }
+ }
+ if (is_binary) {
+ show_combined_header(elem, num_parent, dense, rev,
+ mode_differs, 0);
+ printf("Binary files differ\n");
+ free(result);
+ return;
+ }
+
for (cnt = 0, cp = result; cp < result + result_size; cp++) {
if (*cp == '\n')
cnt++;
@@ -824,71 +954,15 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
combine_diff(elem->parent[i].sha1,
elem->parent[i].mode,
&result_file, sline,
- cnt, i, num_parent, result_deleted);
- if (elem->parent[i].mode != elem->mode)
- mode_differs = 1;
+ cnt, i, num_parent, result_deleted,
+ textconv, elem->path);
}
show_hunks = make_hunks(sline, cnt, num_parent, dense);
if (show_hunks || mode_differs || working_tree_file) {
- const char *abb;
- int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
- const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
- const char *c_reset = diff_get_color(use_color, DIFF_RESET);
- int added = 0;
- int deleted = 0;
-
- if (rev->loginfo && !rev->no_commit_id)
- show_log(rev);
- dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
- "", elem->path, c_meta, c_reset);
- printf("%sindex ", c_meta);
- for (i = 0; i < num_parent; i++) {
- abb = find_unique_abbrev(elem->parent[i].sha1,
- abbrev);
- printf("%s%s", i ? "," : "", abb);
- }
- abb = find_unique_abbrev(elem->sha1, abbrev);
- printf("..%s%s\n", abb, c_reset);
-
- if (mode_differs) {
- deleted = !elem->mode;
-
- /* We say it was added if nobody had it */
- added = !deleted;
- for (i = 0; added && i < num_parent; i++)
- if (elem->parent[i].status !=
- DIFF_STATUS_ADDED)
- added = 0;
- if (added)
- printf("%snew file mode %06o",
- c_meta, elem->mode);
- else {
- if (deleted)
- printf("%sdeleted file ", c_meta);
- printf("mode ");
- for (i = 0; i < num_parent; i++) {
- printf("%s%06o", i ? "," : "",
- elem->parent[i].mode);
- }
- if (elem->mode)
- printf("..%06o", elem->mode);
- }
- printf("%s\n", c_reset);
- }
- if (added)
- dump_quoted_path("--- ", "", "/dev/null",
- c_meta, c_reset);
- else
- dump_quoted_path("--- ", a_prefix, elem->path,
- c_meta, c_reset);
- if (deleted)
- dump_quoted_path("+++ ", "", "/dev/null",
- c_meta, c_reset);
- else
- dump_quoted_path("+++ ", b_prefix, elem->path,
- c_meta, c_reset);
+ show_combined_header(elem, num_parent, dense, rev,
+ mode_differs, 1);
dump_sline(sline, cnt, num_parent,
DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
}
diff --git a/commit.c b/commit.c
index e9b0750..ac337c7 100644
--- a/commit.c
+++ b/commit.c
@@ -49,6 +49,19 @@ struct commit *lookup_commit(const unsigned char *sha1)
return check_commit(obj, sha1, 0);
}
+struct commit *lookup_commit_reference_by_name(const char *name)
+{
+ unsigned char sha1[20];
+ struct commit *commit;
+
+ if (get_sha1(name, sha1))
+ return NULL;
+ commit = lookup_commit_reference(sha1);
+ if (!commit || parse_commit(commit))
+ return NULL;
+ return commit;
+}
+
static unsigned long parse_commit_date(const char *buf, const char *tail)
{
const char *dateptr;
@@ -137,12 +150,8 @@ struct commit_graft *read_graft_line(char *buf, int len)
buf[--len] = '\0';
if (buf[0] == '#' || buf[0] == '\0')
return NULL;
- if ((len + 1) % 41) {
- bad_graft_data:
- error("bad graft data: %s", buf);
- free(graft);
- return NULL;
- }
+ if ((len + 1) % 41)
+ goto bad_graft_data;
i = (len + 1) / 41 - 1;
graft = xmalloc(sizeof(*graft) + 20 * i);
graft->nr_parent = i;
@@ -155,6 +164,11 @@ struct commit_graft *read_graft_line(char *buf, int len)
goto bad_graft_data;
}
return graft;
+
+bad_graft_data:
+ error("bad graft data: %s", buf);
+ free(graft);
+ return NULL;
}
static int read_graft_file(const char *graft_file)
@@ -231,10 +245,10 @@ int unregister_shallow(const unsigned char *sha1)
return 0;
}
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size)
{
- char *tail = buffer;
- char *bufptr = buffer;
+ const char *tail = buffer;
+ const char *bufptr = buffer;
unsigned char parent[20];
struct commit_list **pptr;
struct commit_graft *graft;
@@ -315,6 +329,25 @@ int parse_commit(struct commit *item)
return ret;
}
+int find_commit_subject(const char *commit_buffer, const char **subject)
+{
+ const char *eol;
+ const char *p = commit_buffer;
+
+ while (*p && (*p != '\n' || p[1] != '\n'))
+ p++;
+ if (*p) {
+ p += 2;
+ for (eol = p; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ } else
+ eol = p;
+
+ *subject = p;
+
+ return eol - p;
+}
+
struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
{
struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@ -341,7 +374,7 @@ void free_commit_list(struct commit_list *list)
}
}
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
{
struct commit_list **pp = list;
struct commit_list *p;
@@ -355,11 +388,11 @@ struct commit_list * insert_by_date(struct commit *item, struct commit_list **li
}
-void sort_by_date(struct commit_list **list)
+void commit_list_sort_by_date(struct commit_list **list)
{
struct commit_list *ret = NULL;
while (*list) {
- insert_by_date((*list)->item, &ret);
+ commit_list_insert_by_date((*list)->item, &ret);
*list = (*list)->next;
}
*list = ret;
@@ -379,7 +412,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
struct commit *commit = parents->item;
if (!parse_commit(commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
- insert_by_date(commit, list);
+ commit_list_insert_by_date(commit, list);
}
parents = parents->next;
}
@@ -468,7 +501,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
/* process the list in topological order */
if (!lifo)
- sort_by_date(&work);
+ commit_list_sort_by_date(&work);
pptr = list;
*list = NULL;
@@ -494,7 +527,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
*/
if (--parent->indegree == 1) {
if (!lifo)
- insert_by_date(parent, &work);
+ commit_list_insert_by_date(parent, &work);
else
commit_list_insert(parent, &work);
}
@@ -554,10 +587,10 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
}
one->object.flags |= PARENT1;
- insert_by_date(one, &list);
+ commit_list_insert_by_date(one, &list);
for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
- insert_by_date(twos[i], &list);
+ commit_list_insert_by_date(twos[i], &list);
}
while (interesting(list)) {
@@ -575,7 +608,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
if (flags == (PARENT1 | PARENT2)) {
if (!(commit->object.flags & RESULT)) {
commit->object.flags |= RESULT;
- insert_by_date(commit, &result);
+ commit_list_insert_by_date(commit, &result);
}
/* Mark parents of a found merge stale */
flags |= STALE;
@@ -589,7 +622,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
if (parse_commit(p))
return NULL;
p->object.flags |= flags;
- insert_by_date(p, &list);
+ commit_list_insert_by_date(p, &list);
}
}
@@ -599,7 +632,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
while (list) {
struct commit_list *next = list->next;
if (!(list->item->object.flags & STALE))
- insert_by_date(list->item, &result);
+ commit_list_insert_by_date(list->item, &result);
free(list);
list = next;
}
@@ -692,7 +725,7 @@ struct commit_list *get_merge_bases_many(struct commit *one,
result = NULL;
for (i = 0; i < cnt; i++) {
if (rslt[i])
- insert_by_date(rslt[i], &result);
+ commit_list_insert_by_date(rslt[i], &result);
}
free(rslt);
return result;
diff --git a/commit.h b/commit.h
index eb2b8ac..a2d571b 100644
--- a/commit.h
+++ b/commit.h
@@ -36,19 +36,23 @@ struct commit *lookup_commit(const unsigned char *sha1);
struct commit *lookup_commit_reference(const unsigned char *sha1);
struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
int quiet);
+struct commit *lookup_commit_reference_by_name(const char *name);
-int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
-
+int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
int parse_commit(struct commit *item);
-struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+/* Find beginning and length of commit subject. */
+int find_commit_subject(const char *commit_buffer, const char **subject);
+
+struct commit_list *commit_list_insert(struct commit *item,
+ struct commit_list **list);
unsigned commit_list_count(const struct commit_list *l);
-struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
+struct commit_list *commit_list_insert_by_date(struct commit *item,
+ struct commit_list **list);
+void commit_list_sort_by_date(struct commit_list **list);
void free_commit_list(struct commit_list *list);
-void sort_by_date(struct commit_list **list);
-
/* Commit formats */
enum cmit_fmt {
CMIT_FMT_RAW,
@@ -64,15 +68,17 @@ enum cmit_fmt {
CMIT_FMT_UNSPECIFIED
};
-struct pretty_print_context
-{
+struct pretty_print_context {
+ enum cmit_fmt fmt;
int abbrev;
const char *subject;
const char *after_subject;
+ int preserve_subject;
enum date_mode date_mode;
int need_8bit_cte;
int show_notes;
struct reflog_walk_info *reflog_info;
+ const char *output_encoding;
};
struct userformat_want {
@@ -81,27 +87,31 @@ struct userformat_want {
extern int has_non_ascii(const char *text);
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
+extern char *logmsg_reencode(const struct commit *commit,
+ const char *output_encoding);
extern char *reencode_commit_message(const struct commit *commit,
const char **encoding_p);
extern void get_commit_format(const char *arg, struct rev_info *);
+extern const char *format_subject(struct strbuf *sb, const char *msg,
+ const char *line_separator);
extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
extern void format_commit_message(const struct commit *commit,
const char *format, struct strbuf *sb,
const struct pretty_print_context *context);
-extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb,
- const struct pretty_print_context *context);
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
- const char *line, enum date_mode dmode,
- const char *encoding);
-void pp_title_line(enum cmit_fmt fmt,
+extern void pretty_print_commit(const struct pretty_print_context *pp,
+ const struct commit *commit,
+ struct strbuf *sb);
+extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+ struct strbuf *sb);
+void pp_user_info(const struct pretty_print_context *pp,
+ const char *what, struct strbuf *sb,
+ const char *line, const char *encoding);
+void pp_title_line(const struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
- const char *subject,
- const char *after_subject,
const char *encoding,
int need_8bit_cte);
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
int indent);
@@ -137,8 +147,6 @@ struct commit_graft *read_graft_line(char *buf, int len);
int register_commit_graft(struct commit_graft *, int);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
-const unsigned char *lookup_replace_object(const unsigned char *sha1);
-
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
@@ -153,7 +161,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit **, int);
-extern int interactive_add(int argc, const char **argv, const char *prefix);
+extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
extern int run_add_interactive(const char *revision, const char *patch_mode,
const char **pathspec);
diff --git a/compat/bswap.h b/compat/bswap.h
index 54756db..5061214 100644
--- a/compat/bswap.h
+++ b/compat/bswap.h
@@ -21,14 +21,16 @@ static inline uint32_t default_swab32(uint32_t val)
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
-#define bswap32(x) ({ \
- uint32_t __res; \
- if (__builtin_constant_p(x)) { \
- __res = default_swab32(x); \
- } else { \
- __asm__("bswap %0" : "=r" (__res) : "0" ((uint32_t)(x))); \
- } \
- __res; })
+#define bswap32 git_bswap32
+static inline uint32_t git_bswap32(uint32_t x)
+{
+ uint32_t result;
+ if (__builtin_constant_p(x))
+ result = default_swab32(x);
+ else
+ __asm__("bswap %0" : "=r" (result) : "0" (x));
+ return result;
+}
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
diff --git a/compat/cygwin.c b/compat/cygwin.c
index b4a51b9..dfe9b30 100644
--- a/compat/cygwin.c
+++ b/compat/cygwin.c
@@ -101,7 +101,7 @@ static int cygwin_stat(const char *path, struct stat *buf)
* and calling git_default_config() from here would break such variables.
*/
static int native_stat = 1;
-static int core_filemode;
+static int core_filemode = 1; /* matches trust_executable_bit default */
static int git_cygwin_config(const char *var, const char *value, void *cb)
{
@@ -114,8 +114,7 @@ static int git_cygwin_config(const char *var, const char *value, void *cb)
static int init_stat(void)
{
- if (have_git_dir()) {
- git_config(git_cygwin_config, NULL);
+ if (have_git_dir() && git_config(git_cygwin_config,NULL)) {
if (!core_filemode && native_stat) {
cygwin_stat_fn = cygwin_stat;
cygwin_lstat_fn = cygwin_lstat;
diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c
index 14feac7..9473aed 100644
--- a/compat/fnmatch/fnmatch.c
+++ b/compat/fnmatch/fnmatch.c
@@ -127,6 +127,10 @@ extern char *getenv ();
extern int errno;
# endif
+# ifndef NULL
+# define NULL 0
+# endif
+
/* This function doesn't exist on most systems. */
# if !defined HAVE___STRCHRNUL && !defined _LIBC
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
index f444982..ea249c6 100644
--- a/compat/inet_ntop.c
+++ b/compat/inet_ntop.c
@@ -17,9 +17,9 @@
#include <errno.h>
#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+
+#include "../git-compat-util.h"
+
#include <stdio.h>
#include <string.h>
@@ -50,10 +50,7 @@
* Paul Vixie, 1996.
*/
static const char *
-inet_ntop4(src, dst, size)
- const u_char *src;
- char *dst;
- size_t size;
+inet_ntop4(const u_char *src, char *dst, size_t size)
{
static const char fmt[] = "%u.%u.%u.%u";
char tmp[sizeof "255.255.255.255"];
@@ -78,10 +75,7 @@ inet_ntop4(src, dst, size)
* Paul Vixie, 1996.
*/
static const char *
-inet_ntop6(src, dst, size)
- const u_char *src;
- char *dst;
- size_t size;
+inet_ntop6(const u_char *src, char *dst, size_t size)
{
/*
* Note that int32_t and int16_t need only be "at least" large enough
@@ -178,11 +172,7 @@ inet_ntop6(src, dst, size)
* Paul Vixie, 1996.
*/
const char *
-inet_ntop(af, src, dst, size)
- int af;
- const void *src;
- char *dst;
- size_t size;
+inet_ntop(int af, const void *src, char *dst, size_t size)
{
switch (af) {
case AF_INET:
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
index 4078fc0..2ec995e 100644
--- a/compat/inet_pton.c
+++ b/compat/inet_pton.c
@@ -17,9 +17,9 @@
#include <errno.h>
#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
+
+#include "../git-compat-util.h"
+
#include <stdio.h>
#include <string.h>
@@ -41,7 +41,9 @@
*/
static int inet_pton4(const char *src, unsigned char *dst);
+#ifndef NO_IPV6
static int inet_pton6(const char *src, unsigned char *dst);
+#endif
/* int
* inet_pton4(src, dst)
diff --git a/compat/mingw.c b/compat/mingw.c
index 9a8e336..6ef0cc4 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2,6 +2,9 @@
#include "win32.h"
#include <conio.h>
#include "../strbuf.h"
+#include "../run-command.h"
+
+static const int delay[] = { 0, 1, 10, 20, 40 };
int err_win_to_posix(DWORD winerr)
{
@@ -116,6 +119,165 @@ int err_win_to_posix(DWORD winerr)
return error;
}
+static inline int is_file_in_use_error(DWORD errcode)
+{
+ switch (errcode) {
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_ACCESS_DENIED:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int read_yes_no_answer(void)
+{
+ char answer[1024];
+
+ if (fgets(answer, sizeof(answer), stdin)) {
+ size_t answer_len = strlen(answer);
+ int got_full_line = 0, c;
+
+ /* remove the newline */
+ if (answer_len >= 2 && answer[answer_len-2] == '\r') {
+ answer[answer_len-2] = '\0';
+ got_full_line = 1;
+ } else if (answer_len >= 1 && answer[answer_len-1] == '\n') {
+ answer[answer_len-1] = '\0';
+ got_full_line = 1;
+ }
+ /* flush the buffer in case we did not get the full line */
+ if (!got_full_line)
+ while ((c = getchar()) != EOF && c != '\n')
+ ;
+ } else
+ /* we could not read, return the
+ * default answer which is no */
+ return 0;
+
+ if (tolower(answer[0]) == 'y' && !answer[1])
+ return 1;
+ if (!strncasecmp(answer, "yes", sizeof(answer)))
+ return 1;
+ if (tolower(answer[0]) == 'n' && !answer[1])
+ return 0;
+ if (!strncasecmp(answer, "no", sizeof(answer)))
+ return 0;
+
+ /* did not find an answer we understand */
+ return -1;
+}
+
+static int ask_yes_no_if_possible(const char *format, ...)
+{
+ char question[4096];
+ const char *retry_hook[] = { NULL, NULL, NULL };
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(question, sizeof(question), format, args);
+ va_end(args);
+
+ if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
+ retry_hook[1] = question;
+ return !run_command_v_opt(retry_hook, 0);
+ }
+
+ if (!isatty(_fileno(stdin)) || !isatty(_fileno(stderr)))
+ return 0;
+
+ while (1) {
+ int answer;
+ fprintf(stderr, "%s (y/n) ", question);
+
+ if ((answer = read_yes_no_answer()) >= 0)
+ return answer;
+
+ fprintf(stderr, "Sorry, I did not understand your answer. "
+ "Please type 'y' or 'n'\n");
+ }
+}
+
+#undef unlink
+int mingw_unlink(const char *pathname)
+{
+ int ret, tries = 0;
+
+ /* read-only files cannot be removed */
+ chmod(pathname, 0666);
+ while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ if (!is_file_in_use_error(GetLastError()))
+ break;
+ /*
+ * We assume that some other process had the source or
+ * destination file open at the wrong moment and retry.
+ * In order to give the other process a higher chance to
+ * complete its operation, we give up our time slice now.
+ * If we have to retry again, we do sleep a bit.
+ */
+ Sleep(delay[tries]);
+ tries++;
+ }
+ while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+ ask_yes_no_if_possible("Unlink of file '%s' failed. "
+ "Should I try again?", pathname))
+ ret = unlink(pathname);
+ return ret;
+}
+
+static int is_dir_empty(const char *path)
+{
+ struct strbuf buf = STRBUF_INIT;
+ WIN32_FIND_DATAA findbuf;
+ HANDLE handle;
+
+ strbuf_addf(&buf, "%s\\*", path);
+ handle = FindFirstFileA(buf.buf, &findbuf);
+ if (handle == INVALID_HANDLE_VALUE) {
+ strbuf_release(&buf);
+ return GetLastError() == ERROR_NO_MORE_FILES;
+ }
+
+ while (!strcmp(findbuf.cFileName, ".") ||
+ !strcmp(findbuf.cFileName, ".."))
+ if (!FindNextFile(handle, &findbuf)) {
+ strbuf_release(&buf);
+ return GetLastError() == ERROR_NO_MORE_FILES;
+ }
+ FindClose(handle);
+ strbuf_release(&buf);
+ return 0;
+}
+
+#undef rmdir
+int mingw_rmdir(const char *pathname)
+{
+ int ret, tries = 0;
+
+ while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ if (!is_file_in_use_error(GetLastError()))
+ break;
+ if (!is_dir_empty(pathname)) {
+ errno = ENOTEMPTY;
+ break;
+ }
+ /*
+ * We assume that some other process had the source or
+ * destination file open at the wrong moment and retry.
+ * In order to give the other process a higher chance to
+ * complete its operation, we give up our time slice now.
+ * If we have to retry again, we do sleep a bit.
+ */
+ Sleep(delay[tries]);
+ tries++;
+ }
+ while (ret == -1 && is_file_in_use_error(GetLastError()) &&
+ ask_yes_no_if_possible("Deletion of directory '%s' failed. "
+ "Should I try again?", pathname))
+ ret = rmdir(pathname);
+ return ret;
+}
+
#undef open
int mingw_open (const char *filename, int oflags, ...)
{
@@ -127,7 +289,7 @@ int mingw_open (const char *filename, int oflags, ...)
mode = va_arg(args, int);
va_end(args);
- if (!strcmp(filename, "/dev/null"))
+ if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
fd = open(filename, oflags, mode);
@@ -160,7 +322,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t count)
#undef fopen
FILE *mingw_fopen (const char *filename, const char *otype)
{
- if (!strcmp(filename, "/dev/null"))
+ if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
return fopen(filename, otype);
}
@@ -192,12 +354,16 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
/* We keep the do_lstat code in a separate function to avoid recursion.
* When a path ends with a slash, the stat will fail with ENOENT. In
* this case, we strip the trailing slashes and stat again.
+ *
+ * If follow is true then act like stat() and report on the link
+ * target. Otherwise report on the link itself.
*/
-static int do_lstat(const char *file_name, struct stat *buf)
+static int do_lstat(int follow, const char *file_name, struct stat *buf)
{
+ int err;
WIN32_FILE_ATTRIBUTE_DATA fdata;
- if (!(errno = get_file_attr(file_name, &fdata))) {
+ if (!(err = get_file_attr(file_name, &fdata))) {
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
@@ -209,8 +375,28 @@ static int do_lstat(const char *file_name, struct stat *buf)
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ WIN32_FIND_DATAA findbuf;
+ HANDLE handle = FindFirstFileA(file_name, &findbuf);
+ if (handle != INVALID_HANDLE_VALUE) {
+ if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+ (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
+ if (follow) {
+ char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+ } else {
+ buf->st_mode = S_IFLNK;
+ }
+ buf->st_mode |= S_IREAD;
+ if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
+ buf->st_mode |= S_IWRITE;
+ }
+ FindClose(handle);
+ }
+ }
return 0;
}
+ errno = err;
return -1;
}
@@ -220,12 +406,12 @@ static int do_lstat(const char *file_name, struct stat *buf)
* complete. Note that Git stat()s are redirected to mingw_lstat()
* too, since Windows doesn't really handle symlinks that well.
*/
-int mingw_lstat(const char *file_name, struct stat *buf)
+static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
{
int namelen;
static char alt_name[PATH_MAX];
- if (!do_lstat(file_name, buf))
+ if (!do_lstat(follow, file_name, buf))
return 0;
/* if file_name ended in a '/', Windows returned ENOENT;
@@ -244,7 +430,16 @@ int mingw_lstat(const char *file_name, struct stat *buf)
memcpy(alt_name, file_name, namelen);
alt_name[namelen] = 0;
- return do_lstat(alt_name, buf);
+ return do_lstat(follow, alt_name, buf);
+}
+
+int mingw_lstat(const char *file_name, struct stat *buf)
+{
+ return do_stat_internal(0, file_name, buf);
+}
+int mingw_stat(const char *file_name, struct stat *buf)
+{
+ return do_stat_internal(1, file_name, buf);
}
#undef fstat
@@ -304,8 +499,13 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
goto revert_attrs;
}
- time_t_to_filetime(times->modtime, &mft);
- time_t_to_filetime(times->actime, &aft);
+ if (times) {
+ time_t_to_filetime(times->modtime, &mft);
+ time_t_to_filetime(times->actime, &aft);
+ } else {
+ GetSystemTimeAsFileTime(&mft);
+ aft = mft;
+ }
if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) {
errno = EINVAL;
rc = -1;
@@ -372,71 +572,6 @@ int pipe(int filedes[2])
return 0;
}
-int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
-{
- int i, pending;
-
- if (timeout >= 0) {
- if (nfds == 0) {
- Sleep(timeout);
- return 0;
- }
- return errno = EINVAL, error("poll timeout not supported");
- }
-
- /* When there is only one fd to wait for, then we pretend that
- * input is available and let the actual wait happen when the
- * caller invokes read().
- */
- if (nfds == 1) {
- if (!(ufds[0].events & POLLIN))
- return errno = EINVAL, error("POLLIN not set");
- ufds[0].revents = POLLIN;
- return 0;
- }
-
-repeat:
- pending = 0;
- for (i = 0; i < nfds; i++) {
- DWORD avail = 0;
- HANDLE h = (HANDLE) _get_osfhandle(ufds[i].fd);
- if (h == INVALID_HANDLE_VALUE)
- return -1; /* errno was set */
-
- if (!(ufds[i].events & POLLIN))
- return errno = EINVAL, error("POLLIN not set");
-
- /* this emulation works only for pipes */
- if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) {
- int err = GetLastError();
- if (err == ERROR_BROKEN_PIPE) {
- ufds[i].revents = POLLHUP;
- pending++;
- } else {
- errno = EINVAL;
- return error("PeekNamedPipe failed,"
- " GetLastError: %u", err);
- }
- } else if (avail) {
- ufds[i].revents = POLLIN;
- pending++;
- } else
- ufds[i].revents = 0;
- }
- if (!pending) {
- /* The only times that we spin here is when the process
- * that is connected through the pipes is waiting for
- * its own input data to become available. But since
- * the process (pack-objects) is itself CPU intensive,
- * it will happily pick up the time slice that we are
- * relinquishing here.
- */
- Sleep(0);
- goto repeat;
- }
- return 0;
-}
-
struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
/* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */
@@ -464,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len)
return ret;
}
-#undef getenv
-char *mingw_getenv(const char *name)
-{
- char *result = getenv(name);
- if (!result && !strcmp(name, "TMPDIR")) {
- /* on Windows it is TMP and TEMP */
- result = getenv("TMP");
- if (!result)
- result = getenv("TEMP");
- }
- return result;
-}
-
/*
* See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
* (Parsing C++ Command-Line Arguments)
@@ -576,7 +698,7 @@ static const char *parse_interpreter(const char *cmd)
*/
static char **get_path_split(void)
{
- char *p, **path, *envpath = getenv("PATH");
+ char *p, **path, *envpath = mingw_getenv("PATH");
int i, n = 0;
if (!envpath || !*envpath)
@@ -641,7 +763,7 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on
}
/*
- * Determines the absolute path of cmd using the the split path in path.
+ * Determines the absolute path of cmd using the split path in path.
* If cmd contains a slash or backslash, no lookup is performed.
*/
static char *path_lookup(const char *cmd, char **path, int exe_only)
@@ -666,6 +788,14 @@ static int env_compare(const void *a, const void *b)
return strcasecmp(*ea, *eb);
}
+struct pinfo_t {
+ struct pinfo_t *next;
+ pid_t pid;
+ HANDLE proc;
+} pinfo_t;
+struct pinfo_t *pinfo = NULL;
+CRITICAL_SECTION pinfo_cs;
+
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
const char *dir,
int prepend_cmd, int fhin, int fhout, int fherr)
@@ -758,7 +888,26 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
return -1;
}
CloseHandle(pi.hThread);
- return (pid_t)pi.hProcess;
+
+ /*
+ * The process ID is the human-readable identifier of the process
+ * that we want to present in log and error messages. The handle
+ * is not useful for this purpose. But we cannot close it, either,
+ * because it is not possible to turn a process ID into a process
+ * handle after the process terminated.
+ * Keep the handle in a list for waitpid.
+ */
+ EnterCriticalSection(&pinfo_cs);
+ {
+ struct pinfo_t *info = xmalloc(sizeof(struct pinfo_t));
+ info->pid = pi.dwProcessId;
+ info->proc = pi.hProcess;
+ info->next = pinfo;
+ pinfo = info;
+ }
+ LeaveCriticalSection(&pinfo_cs);
+
+ return (pid_t)pi.dwProcessId;
}
static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
@@ -868,6 +1017,30 @@ void mingw_execvp(const char *cmd, char *const *argv)
free_path_split(path);
}
+void mingw_execv(const char *cmd, char *const *argv)
+{
+ mingw_execve(cmd, argv, environ);
+}
+
+int mingw_kill(pid_t pid, int sig)
+{
+ if (pid > 0 && sig == SIGTERM) {
+ HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+
+ if (TerminateProcess(h, -1)) {
+ CloseHandle(h);
+ return 0;
+ }
+
+ errno = err_win_to_posix(GetLastError());
+ CloseHandle(h);
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
static char **copy_environ(void)
{
char **env;
@@ -942,9 +1115,39 @@ char **make_augmented_environ(const char *const *vars)
return env;
}
+#undef getenv
+
+/*
+ * The system's getenv looks up the name in a case-insensitive manner.
+ * This version tries a case-sensitive lookup and falls back to
+ * case-insensitive if nothing was found. This is necessary because,
+ * as a prominent example, CMD sets 'Path', but not 'PATH'.
+ * Warning: not thread-safe.
+ */
+static char *getenv_cs(const char *name)
+{
+ size_t len = strlen(name);
+ int i = lookup_env(environ, name, len);
+ if (i >= 0)
+ return environ[i] + len + 1; /* skip past name and '=' */
+ return getenv(name);
+}
+
+char *mingw_getenv(const char *name)
+{
+ char *result = getenv_cs(name);
+ if (!result && !strcmp(name, "TMPDIR")) {
+ /* on Windows it is TMP and TEMP */
+ result = getenv_cs("TMP");
+ if (!result)
+ result = getenv_cs("TEMP");
+ }
+ return result;
+}
+
/*
* Note, this isn't a complete replacement for getaddrinfo. It assumes
- * that service contains a numerical port, or that it it is null. It
+ * that service contains a numerical port, or that it is null. It
* does a simple search using gethostbyname, and returns one IPv4 host
* if one was found.
*/
@@ -952,19 +1155,22 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res)
{
- struct hostent *h = gethostbyname(node);
+ struct hostent *h = NULL;
struct addrinfo *ai;
struct sockaddr_in *sin;
- if (!h)
- return WSAGetLastError();
+ if (node) {
+ h = gethostbyname(node);
+ if (!h)
+ return WSAGetLastError();
+ }
ai = xmalloc(sizeof(struct addrinfo));
*res = ai;
ai->ai_flags = 0;
ai->ai_family = AF_INET;
- ai->ai_socktype = hints->ai_socktype;
- switch (hints->ai_socktype) {
+ ai->ai_socktype = hints ? hints->ai_socktype : 0;
+ switch (ai->ai_socktype) {
case SOCK_STREAM:
ai->ai_protocol = IPPROTO_TCP;
break;
@@ -976,14 +1182,25 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
break;
}
ai->ai_addrlen = sizeof(struct sockaddr_in);
- ai->ai_canonname = strdup(h->h_name);
+ if (hints && (hints->ai_flags & AI_CANONNAME))
+ ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+ else
+ ai->ai_canonname = NULL;
sin = xmalloc(ai->ai_addrlen);
memset(sin, 0, ai->ai_addrlen);
sin->sin_family = AF_INET;
+ /* Note: getaddrinfo is supposed to allow service to be a string,
+ * which should be looked up using getservbyname. This is
+ * currently not implemented */
if (service)
sin->sin_port = htons(atoi(service));
- sin->sin_addr = *(struct in_addr *)h->h_addr;
+ if (h)
+ sin->sin_addr = *(struct in_addr *)h->h_addr;
+ else if (hints && (hints->ai_flags & AI_PASSIVE))
+ sin->sin_addr.s_addr = INADDR_ANY;
+ else
+ sin->sin_addr.s_addr = INADDR_LOOPBACK;
ai->ai_addr = (struct sockaddr *)sin;
ai->ai_next = 0;
return 0;
@@ -1134,7 +1351,10 @@ int mingw_getnameinfo(const struct sockaddr *sa, socklen_t salen,
int mingw_socket(int domain, int type, int protocol)
{
int sockfd;
- SOCKET s = WSASocket(domain, type, protocol, NULL, 0, 0);
+ SOCKET s;
+
+ ensure_socket_initialization();
+ s = WSASocket(domain, type, protocol, NULL, 0, 0);
if (s == INVALID_SOCKET) {
/*
* WSAGetLastError() values are regular BSD error codes
@@ -1164,12 +1384,57 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz)
return connect(s, sa, sz);
}
+#undef bind
+int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return bind(s, sa, sz);
+}
+
+#undef setsockopt
+int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return setsockopt(s, lvl, optname, (const char*)optval, optlen);
+}
+
+#undef shutdown
+int mingw_shutdown(int sockfd, int how)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return shutdown(s, how);
+}
+
+#undef listen
+int mingw_listen(int sockfd, int backlog)
+{
+ SOCKET s = (SOCKET)_get_osfhandle(sockfd);
+ return listen(s, backlog);
+}
+
+#undef accept
+int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
+{
+ int sockfd2;
+
+ SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1);
+ SOCKET s2 = accept(s1, sa, sz);
+
+ /* convert into a file descriptor */
+ if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) {
+ int err = errno;
+ closesocket(s2);
+ return error("unable to make a socket file descriptor: %s",
+ strerror(err));
+ }
+ return sockfd2;
+}
+
#undef rename
int mingw_rename(const char *pold, const char *pnew)
{
DWORD attrs, gle;
int tries = 0;
- static const int delay[] = { 0, 1, 10, 20, 40 };
/*
* Try native rename() first to get errno right.
@@ -1211,6 +1476,11 @@ repeat:
tries++;
goto repeat;
}
+ if (gle == ERROR_ACCESS_DENIED &&
+ ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
+ "Should I try again?", pold, pnew))
+ goto repeat;
+
errno = EACCES;
return -1;
}
@@ -1381,6 +1651,7 @@ void mingw_open_html(const char *unixpath)
const char *, const char *, const char *, INT);
T ShellExecute;
HMODULE shell32;
+ int r;
shell32 = LoadLibrary("shell32.dll");
if (!shell32)
@@ -1390,9 +1661,12 @@ void mingw_open_html(const char *unixpath)
die("cannot run browser");
printf("Launching default browser to display HTML ...\n");
- ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
-
+ r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
FreeLibrary(shell32);
+ /* see the MSDN documentation referring to the result codes here */
+ if (r <= 32) {
+ die("failed to launch browser for %.*s", MAX_PATH, unixpath);
+ }
}
int link(const char *oldpath, const char *newpath)
@@ -1431,62 +1705,54 @@ char *getpass(const char *prompt)
return strbuf_detach(&buf, NULL);
}
-#ifndef NO_MINGW_REPLACE_READDIR
-/* MinGW readdir implementation to avoid extra lstats for Git */
-struct mingw_DIR
-{
- struct _finddata_t dd_dta; /* disk transfer area for this dir */
- struct mingw_dirent dd_dir; /* Our own implementation, including d_type */
- long dd_handle; /* _findnext handle */
- int dd_stat; /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */
- char dd_name[1]; /* given path for dir with search pattern (struct is extended) */
-};
-
-struct dirent *mingw_readdir(DIR *dir)
+pid_t waitpid(pid_t pid, int *status, unsigned options)
{
- WIN32_FIND_DATAA buf;
- HANDLE handle;
- struct mingw_DIR *mdir = (struct mingw_DIR*)dir;
-
- if (!dir->dd_handle) {
- errno = EBADF; /* No set_errno for mingw */
- return NULL;
+ HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
+ FALSE, pid);
+ if (!h) {
+ errno = ECHILD;
+ return -1;
}
- if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0)
- {
- DWORD lasterr;
- handle = FindFirstFileA(dir->dd_name, &buf);
- lasterr = GetLastError();
- dir->dd_handle = (long)handle;
- if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
- errno = err_win_to_posix(lasterr);
- return NULL;
+ if (pid > 0 && options & WNOHANG) {
+ if (WAIT_OBJECT_0 != WaitForSingleObject(h, 0)) {
+ CloseHandle(h);
+ return 0;
}
- } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) {
- return NULL;
- } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) {
- DWORD lasterr = GetLastError();
- FindClose((HANDLE)dir->dd_handle);
- dir->dd_handle = (long)INVALID_HANDLE_VALUE;
- /* POSIX says you shouldn't set errno when readdir can't
- find any more files; so, if another error we leave it set. */
- if (lasterr != ERROR_NO_MORE_FILES)
- errno = err_win_to_posix(lasterr);
- return NULL;
+ options &= ~WNOHANG;
}
- /* We get here if `buf' contains valid data. */
- strcpy(dir->dd_dir.d_name, buf.cFileName);
- ++dir->dd_stat;
+ if (options == 0) {
+ struct pinfo_t **ppinfo;
+ if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) {
+ CloseHandle(h);
+ return 0;
+ }
- /* Set file type, based on WIN32_FIND_DATA */
- mdir->dd_dir.d_type = 0;
- if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- mdir->dd_dir.d_type |= DT_DIR;
- else
- mdir->dd_dir.d_type |= DT_REG;
+ if (status)
+ GetExitCodeProcess(h, (LPDWORD)status);
+
+ EnterCriticalSection(&pinfo_cs);
- return (struct dirent*)&dir->dd_dir;
+ ppinfo = &pinfo;
+ while (*ppinfo) {
+ struct pinfo_t *info = *ppinfo;
+ if (info->pid == pid) {
+ CloseHandle(info->proc);
+ *ppinfo = info->next;
+ free(info);
+ break;
+ }
+ ppinfo = &info->next;
+ }
+
+ LeaveCriticalSection(&pinfo_cs);
+
+ CloseHandle(h);
+ return pid;
+ }
+ CloseHandle(h);
+
+ errno = EINVAL;
+ return -1;
}
-#endif // !NO_MINGW_REPLACE_READDIR
diff --git a/compat/mingw.h b/compat/mingw.h
index 3b2477b..ce9dd98 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -6,23 +6,34 @@
*/
typedef int pid_t;
+typedef int uid_t;
+typedef int socklen_t;
#define hstrerror strerror
#define S_IFLNK 0120000 /* Symbolic link */
#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
#define S_ISSOCK(x) 0
+
#define S_IRGRP 0
#define S_IWGRP 0
#define S_IXGRP 0
-#define S_ISGID 0
+#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
#define S_IROTH 0
+#define S_IWOTH 0
#define S_IXOTH 0
+#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
+#define S_ISUID 0
+#define S_ISGID 0
+#define S_ISVTX 0
#define WIFEXITED(x) 1
#define WIFSIGNALED(x) 0
#define WEXITSTATUS(x) ((x) & 0xff)
#define WTERMSIG(x) SIGTERM
+#define EWOULDBLOCK EAGAIN
+#define SHUT_WR SD_SEND
+
#define SIGHUP 1
#define SIGQUIT 3
#define SIGKILL 9
@@ -34,6 +45,9 @@ typedef int pid_t;
#define F_SETFD 2
#define FD_CLOEXEC 0x1
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define ECONNABORTED WSAECONNABORTED
+
struct passwd {
char *pw_name;
char *pw_gecos;
@@ -42,16 +56,6 @@ struct passwd {
extern char *getpass(const char *prompt);
-#ifndef POLLIN
-struct pollfd {
- int fd; /* file descriptor */
- short events; /* requested events */
- short revents; /* returned events */
-};
-#define POLLIN 1
-#define POLLHUP 2
-#endif
-
typedef void (__cdecl *sig_handler_t)(int);
struct sigaction {
sig_handler_t sa_handler;
@@ -66,6 +70,12 @@ struct itimerval {
#define ITIMER_REAL 0
/*
+ * sanitize preprocessor namespace polluted by Windows headers defining
+ * macros which collide with git local versions
+ */
+#undef HELP_COMMAND /* from winuser.h */
+
+/*
* trivial stubs
*/
@@ -75,17 +85,17 @@ static inline int symlink(const char *oldpath, const char *newpath)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
-static inline int fork(void)
+static inline pid_t fork(void)
{ errno = ENOSYS; return -1; }
static inline unsigned int alarm(unsigned int seconds)
{ return 0; }
static inline int fsync(int fd)
{ return _commit(fd); }
-static inline int getppid(void)
+static inline pid_t getppid(void)
{ return 1; }
static inline void sync(void)
{}
-static inline int getuid()
+static inline uid_t getuid(void)
{ return 1; }
static inline struct passwd *getpwnam(const char *name)
{ return NULL; }
@@ -109,21 +119,11 @@ static inline int mingw_mkdir(const char *path, int mode)
}
#define mkdir mingw_mkdir
-static inline int mingw_unlink(const char *pathname)
-{
- /* read-only files cannot be removed */
- chmod(pathname, 0666);
- return unlink(pathname);
-}
-#define unlink mingw_unlink
+#define WNOHANG 1
+pid_t waitpid(pid_t pid, int *status, unsigned options);
-static inline int waitpid(pid_t pid, int *status, unsigned options)
-{
- if (options == 0)
- return _cwait(status, pid, 0);
- errno = EINVAL;
- return -1;
-}
+#define kill mingw_kill
+int mingw_kill(pid_t pid, int sig);
#ifndef NO_OPENSSL
#include <openssl/ssl.h>
@@ -154,11 +154,10 @@ int pipe(int filedes[2]);
unsigned int sleep (unsigned int seconds);
int mkstemp(char *template);
int gettimeofday(struct timeval *tv, void *tz);
-int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
int getpagesize(void); /* defined in MinGW's libgcc.a */
-struct passwd *getpwuid(int uid);
+struct passwd *getpwuid(uid_t uid);
int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
@@ -167,6 +166,12 @@ int link(const char *oldpath, const char *newpath);
* replacements of existing functions
*/
+int mingw_unlink(const char *pathname);
+#define unlink mingw_unlink
+
+int mingw_rmdir(const char *path);
+#define rmdir mingw_rmdir
+
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open
@@ -206,6 +211,21 @@ int mingw_socket(int domain, int type, int protocol);
int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz);
#define connect mingw_connect
+int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz);
+#define bind mingw_bind
+
+int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen);
+#define setsockopt mingw_setsockopt
+
+int mingw_shutdown(int sockfd, int how);
+#define shutdown mingw_shutdown
+
+int mingw_listen(int sockfd, int backlog);
+#define listen mingw_listen
+
+int mingw_accept(int sockfd, struct sockaddr *sa, socklen_t *sz);
+#define accept mingw_accept
+
int mingw_rename(const char*, const char*);
#define rename mingw_rename
@@ -214,6 +234,22 @@ int mingw_getpagesize(void);
#define getpagesize mingw_getpagesize
#endif
+struct rlimit {
+ unsigned int rlim_cur;
+};
+#define RLIMIT_NOFILE 0
+
+static inline int getrlimit(int resource, struct rlimit *rlp)
+{
+ if (resource != RLIMIT_NOFILE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rlp->rlim_cur = 2048;
+ return 0;
+}
+
/* Use mingw_lstat() instead of lstat()/stat() and
* mingw_fstat() instead of fstat() on Windows.
*/
@@ -222,10 +258,11 @@ int mingw_getpagesize(void);
#ifndef ALREADY_DECLARED_STAT_FUNCS
#define stat _stati64
int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_stat(const char *file_name, struct stat *buf);
int mingw_fstat(int fd, struct stat *buf);
#define fstat mingw_fstat
#define lstat mingw_lstat
-#define _stati64(x,y) mingw_lstat(x,y)
+#define _stati64(x,y) mingw_stat(x,y)
#endif
int mingw_utime(const char *file_name, const struct utimbuf *times);
@@ -236,6 +273,8 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
+void mingw_execv(const char *cmd, char *const *argv);
+#define execv mingw_execv
static inline unsigned int git_ntohl(unsigned int x)
{ return (unsigned int)ntohl(x); }
@@ -261,6 +300,15 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format
#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
#define is_dir_sep(c) ((c) == '/' || (c) == '\\')
+static inline char *mingw_find_last_dir_sep(const char *path)
+{
+ char *ret = NULL;
+ for (; *path; ++path)
+ if (is_dir_sep(*path))
+ ret = (char *)path;
+ return ret;
+}
+#define find_last_dir_sep mingw_find_last_dir_sep
#define PATH_SEP ';'
#define PRIuMAX "I64u"
@@ -283,44 +331,17 @@ void free_environ(char **env);
static int mingw_main(); \
int main(int argc, const char **argv) \
{ \
+ extern CRITICAL_SECTION pinfo_cs; \
_fmode = _O_BINARY; \
_setmode(_fileno(stdin), _O_BINARY); \
_setmode(_fileno(stdout), _O_BINARY); \
_setmode(_fileno(stderr), _O_BINARY); \
argv[0] = xstrdup(_pgmptr); \
+ InitializeCriticalSection(&pinfo_cs); \
return mingw_main(argc, argv); \
} \
static int mingw_main(c,v)
-#ifndef NO_MINGW_REPLACE_READDIR
-/*
- * A replacement of readdir, to ensure that it reads the file type at
- * the same time. This avoid extra unneeded lstats in git on MinGW
- */
-#undef DT_UNKNOWN
-#undef DT_DIR
-#undef DT_REG
-#undef DT_LNK
-#define DT_UNKNOWN 0
-#define DT_DIR 1
-#define DT_REG 2
-#define DT_LNK 3
-
-struct mingw_dirent
-{
- long d_ino; /* Always zero. */
- union {
- unsigned short d_reclen; /* Always zero. */
- unsigned char d_type; /* Reimplementation adds this */
- };
- unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[FILENAME_MAX]; /* File name. */
-};
-#define dirent mingw_dirent
-#define readdir(x) mingw_readdir(x)
-struct dirent *mingw_readdir(DIR *dir);
-#endif // !NO_MINGW_REPLACE_READDIR
-
/*
* Used by Pthread API implementation for Windows
*/
diff --git a/compat/msvc.c b/compat/msvc.c
index ac04a4c..71843d7 100644
--- a/compat/msvc.c
+++ b/compat/msvc.c
@@ -3,33 +3,4 @@
#include <conio.h>
#include "../strbuf.h"
-DIR *opendir(const char *name)
-{
- int len;
- DIR *p;
- p = (DIR*)malloc(sizeof(DIR));
- memset(p, 0, sizeof(DIR));
- strncpy(p->dd_name, name, PATH_MAX);
- len = strlen(p->dd_name);
- p->dd_name[len] = '/';
- p->dd_name[len+1] = '*';
-
- if (p == NULL)
- return NULL;
-
- p->dd_handle = _findfirst(p->dd_name, &p->dd_dta);
-
- if (p->dd_handle == -1) {
- free(p);
- return NULL;
- }
- return p;
-}
-int closedir(DIR *dir)
-{
- _findclose(dir->dd_handle);
- free(dir);
- return 0;
-}
-
#include "mingw.c"
diff --git a/compat/msvc.h b/compat/msvc.h
index 023aba0..a33b01c 100644
--- a/compat/msvc.h
+++ b/compat/msvc.h
@@ -9,7 +9,6 @@
#define inline __inline
#define __inline__ __inline
#define __attribute__(x)
-#define va_copy(dst, src) ((dst) = (src))
#define strncasecmp _strnicmp
#define ftruncate _chsize
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index 74c42e3..ff7c2c4 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -100,7 +100,7 @@
If you don't like either of these options, you can define
CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything
- else. And if if you are sure that your program using malloc has
+ else. And if you are sure that your program using malloc has
no errors or vulnerabilities, you can define INSECURE to 1,
which might (or might not) provide a small performance improvement.
@@ -2069,7 +2069,7 @@ static void init_malloc_global_mutex() {
Each freshly allocated chunk must have both cinuse and pinuse set.
That is, each allocated chunk borders either a previously allocated
and still in-use chunk, or the base of its memory arena. This is
- ensured by making all allocations from the the `lowest' part of any
+ ensured by making all allocations from the `lowest' part of any
found chunk. Further, no free chunk physically borders another one,
so each free chunk is known to be preceded and followed by either
inuse chunks or the ends of memory.
@@ -2279,12 +2279,12 @@ nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
of the same size are arranged in a circularly-linked list, with only
the oldest chunk (the next to be used, in our FIFO ordering)
actually in the tree. (Tree members are distinguished by a non-null
- parent pointer.) If a chunk with the same size an an existing node
+ parent pointer.) If a chunk with the same size as an existing node
is inserted, it is linked off the existing node using pointers that
work in the same way as fd/bk pointers of small chunks.
Each tree contains a power of 2 sized range of chunk sizes (the
- smallest is 0x100 <= x < 0x180), which is is divided in half at each
+ smallest is 0x100 <= x < 0x180), which is divided in half at each
tree level, with the chunks in the smaller half of the range (0x100
<= x < 0x140 for the top nose) in the left subtree and the larger
half (0x140 <= x < 0x180) in the right subtree. This is, of course,
@@ -3943,7 +3943,7 @@ static void* sys_alloc(mstate m, size_t nb) {
least-preferred order):
1. A call to MORECORE that can normally contiguously extend memory.
(disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or
- or main space is mmapped or a previous contiguous call failed)
+ main space is mmapped or a previous contiguous call failed)
2. A call to MMAP new space (disabled if not HAVE_MMAP).
Note that under the default settings, if MORECORE is unable to
fulfill a request, and HAVE_MMAP is true, then mmap is
@@ -5748,5 +5748,3 @@ History:
structure of old version, but most details differ.)
*/
-
-
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
new file mode 100644
index 0000000..8c96ed9
--- /dev/null
+++ b/compat/regex/regcomp.c
@@ -0,0 +1,3884 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2007,2009,2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
+ size_t length, reg_syntax_t syntax);
+static void re_compile_fastmap_iter (regex_t *bufp,
+ const re_dfastate_t *init_state,
+ char *fastmap);
+static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len);
+#ifdef RE_ENABLE_I18N
+static void free_charset (re_charset_t *cset);
+#endif /* RE_ENABLE_I18N */
+static void free_workarea_compile (regex_t *preg);
+static reg_errcode_t create_initial_state (re_dfa_t *dfa);
+#ifdef RE_ENABLE_I18N
+static void optimize_utf8 (re_dfa_t *dfa);
+#endif
+static reg_errcode_t analyze (regex_t *preg);
+static reg_errcode_t preorder (bin_tree_t *root,
+ reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra);
+static reg_errcode_t postorder (bin_tree_t *root,
+ reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra);
+static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node);
+static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node);
+static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg,
+ bin_tree_t *node);
+static reg_errcode_t calc_first (void *extra, bin_tree_t *node);
+static reg_errcode_t calc_next (void *extra, bin_tree_t *node);
+static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node);
+static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint);
+static int search_duplicated_node (const re_dfa_t *dfa, int org_node,
+ unsigned int constraint);
+static reg_errcode_t calc_eclosure (re_dfa_t *dfa);
+static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa,
+ int node, int root);
+static reg_errcode_t calc_inveclosure (re_dfa_t *dfa);
+static int fetch_number (re_string_t *input, re_token_t *token,
+ reg_syntax_t syntax);
+static int peek_token (re_token_t *token, re_string_t *input,
+ reg_syntax_t syntax) internal_function;
+static bin_tree_t *parse (re_string_t *regexp, regex_t *preg,
+ reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp,
+ re_dfa_t *dfa, re_token_t *token,
+ reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa,
+ re_token_t *token, reg_syntax_t syntax,
+ reg_errcode_t *err);
+static reg_errcode_t parse_bracket_element (bracket_elem_t *elem,
+ re_string_t *regexp,
+ re_token_t *token, int token_len,
+ re_dfa_t *dfa,
+ reg_syntax_t syntax,
+ int accept_hyphen);
+static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem,
+ re_string_t *regexp,
+ re_token_t *token);
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+ re_charset_t *mbcset,
+ int *equiv_class_alloc,
+ const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+ bitset_t sbcset,
+ re_charset_t *mbcset,
+ int *char_class_alloc,
+ const char *class_name,
+ reg_syntax_t syntax);
+#else /* not RE_ENABLE_I18N */
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+ const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+ bitset_t sbcset,
+ const char *class_name,
+ reg_syntax_t syntax);
+#endif /* not RE_ENABLE_I18N */
+static bin_tree_t *build_charclass_op (re_dfa_t *dfa,
+ RE_TRANSLATE_TYPE trans,
+ const char *class_name,
+ const char *extra,
+ int non_match, reg_errcode_t *err);
+static bin_tree_t *create_tree (re_dfa_t *dfa,
+ bin_tree_t *left, bin_tree_t *right,
+ re_token_type_t type);
+static bin_tree_t *create_token_tree (re_dfa_t *dfa,
+ bin_tree_t *left, bin_tree_t *right,
+ const re_token_t *token);
+static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa);
+static void free_token (re_token_t *node);
+static reg_errcode_t free_tree (void *extra, bin_tree_t *node);
+static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node);
+
+/* This table gives an error message for each of the error codes listed
+ in regex.h. Obviously the order here has to be same as there.
+ POSIX doesn't require that we do anything for REG_NOERROR,
+ but why not be nice? */
+
+const char __re_error_msgid[] attribute_hidden =
+ {
+#define REG_NOERROR_IDX 0
+ gettext_noop ("Success") /* REG_NOERROR */
+ "\0"
+#define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success")
+ gettext_noop ("No match") /* REG_NOMATCH */
+ "\0"
+#define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match")
+ gettext_noop ("Invalid regular expression") /* REG_BADPAT */
+ "\0"
+#define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression")
+ gettext_noop ("Invalid collation character") /* REG_ECOLLATE */
+ "\0"
+#define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character")
+ gettext_noop ("Invalid character class name") /* REG_ECTYPE */
+ "\0"
+#define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name")
+ gettext_noop ("Trailing backslash") /* REG_EESCAPE */
+ "\0"
+#define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash")
+ gettext_noop ("Invalid back reference") /* REG_ESUBREG */
+ "\0"
+#define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference")
+ gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */
+ "\0"
+#define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^")
+ gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */
+ "\0"
+#define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(")
+ gettext_noop ("Unmatched \\{") /* REG_EBRACE */
+ "\0"
+#define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{")
+ gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */
+ "\0"
+#define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}")
+ gettext_noop ("Invalid range end") /* REG_ERANGE */
+ "\0"
+#define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end")
+ gettext_noop ("Memory exhausted") /* REG_ESPACE */
+ "\0"
+#define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted")
+ gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */
+ "\0"
+#define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression")
+ gettext_noop ("Premature end of regular expression") /* REG_EEND */
+ "\0"
+#define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression")
+ gettext_noop ("Regular expression too big") /* REG_ESIZE */
+ "\0"
+#define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big")
+ gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */
+ };
+
+const size_t __re_error_msgid_idx[] attribute_hidden =
+ {
+ REG_NOERROR_IDX,
+ REG_NOMATCH_IDX,
+ REG_BADPAT_IDX,
+ REG_ECOLLATE_IDX,
+ REG_ECTYPE_IDX,
+ REG_EESCAPE_IDX,
+ REG_ESUBREG_IDX,
+ REG_EBRACK_IDX,
+ REG_EPAREN_IDX,
+ REG_EBRACE_IDX,
+ REG_BADBR_IDX,
+ REG_ERANGE_IDX,
+ REG_ESPACE_IDX,
+ REG_BADRPT_IDX,
+ REG_EEND_IDX,
+ REG_ESIZE_IDX,
+ REG_ERPAREN_IDX
+ };
+
+/* Entry points for GNU code. */
+
+
+#ifdef ZOS_USS
+
+/* For ZOS USS we must define btowc */
+
+wchar_t
+btowc (int c)
+{
+ wchar_t wtmp[2];
+ char tmp[2];
+
+ tmp[0] = c;
+ tmp[1] = 0;
+
+ mbtowc (wtmp, tmp, 1);
+ return wtmp[0];
+}
+#endif
+
+/* re_compile_pattern is the GNU regular expression compiler: it
+ compiles PATTERN (of length LENGTH) and puts the result in BUFP.
+ Returns 0 if the pattern was valid, otherwise an error string.
+
+ Assumes the `allocated' (and perhaps `buffer') and `translate' fields
+ are set in BUFP on entry. */
+
+const char *
+re_compile_pattern (const char *pattern,
+ size_t length,
+ struct re_pattern_buffer *bufp)
+{
+ reg_errcode_t ret;
+
+ /* And GNU code determines whether or not to get register information
+ by passing null for the REGS argument to re_match, etc., not by
+ setting no_sub, unless RE_NO_SUB is set. */
+ bufp->no_sub = !!(re_syntax_options & RE_NO_SUB);
+
+ /* Match anchors at newline. */
+ bufp->newline_anchor = 1;
+
+ ret = re_compile_internal (bufp, pattern, length, re_syntax_options);
+
+ if (!ret)
+ return NULL;
+ return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+#ifdef _LIBC
+weak_alias (__re_compile_pattern, re_compile_pattern)
+#endif
+
+/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
+ also be assigned to arbitrarily: each pattern buffer stores its own
+ syntax, so it can be changed between regex compilations. */
+/* This has no initializer because initialized variables in Emacs
+ become read-only after dumping. */
+reg_syntax_t re_syntax_options;
+
+
+/* Specify the precise syntax of regexps for compilation. This provides
+ for compatibility for various utilities which historically have
+ different, incompatible syntaxes.
+
+ The argument SYNTAX is a bit mask comprised of the various bits
+ defined in regex.h. We return the old syntax. */
+
+reg_syntax_t
+re_set_syntax (reg_syntax_t syntax)
+{
+ reg_syntax_t ret = re_syntax_options;
+
+ re_syntax_options = syntax;
+ return ret;
+}
+#ifdef _LIBC
+weak_alias (__re_set_syntax, re_set_syntax)
+#endif
+
+int
+re_compile_fastmap (struct re_pattern_buffer *bufp)
+{
+ re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+ char *fastmap = bufp->fastmap;
+
+ memset (fastmap, '\0', sizeof (char) * SBC_MAX);
+ re_compile_fastmap_iter (bufp, dfa->init_state, fastmap);
+ if (dfa->init_state != dfa->init_state_word)
+ re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap);
+ if (dfa->init_state != dfa->init_state_nl)
+ re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap);
+ if (dfa->init_state != dfa->init_state_begbuf)
+ re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap);
+ bufp->fastmap_accurate = 1;
+ return 0;
+}
+#ifdef _LIBC
+weak_alias (__re_compile_fastmap, re_compile_fastmap)
+#endif
+
+static inline void
+__attribute ((always_inline))
+re_set_fastmap (char *fastmap, int icase, int ch)
+{
+ fastmap[ch] = 1;
+ if (icase)
+ fastmap[tolower (ch)] = 1;
+}
+
+/* Helper function for re_compile_fastmap.
+ Compile fastmap for the initial_state INIT_STATE. */
+
+static void
+re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state,
+ char *fastmap)
+{
+ volatile re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+ int node_cnt;
+ int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE));
+ for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt)
+ {
+ int node = init_state->nodes.elems[node_cnt];
+ re_token_type_t type = dfa->nodes[node].type;
+
+ if (type == CHARACTER)
+ {
+ re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c);
+#ifdef RE_ENABLE_I18N
+ if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+ {
+ unsigned char *buf = re_malloc (unsigned char, dfa->mb_cur_max), *p;
+ wchar_t wc;
+ mbstate_t state;
+
+ p = buf;
+ *p++ = dfa->nodes[node].opr.c;
+ while (++node < dfa->nodes_len
+ && dfa->nodes[node].type == CHARACTER
+ && dfa->nodes[node].mb_partial)
+ *p++ = dfa->nodes[node].opr.c;
+ memset (&state, '\0', sizeof (state));
+ if (__mbrtowc (&wc, (const char *) buf, p - buf,
+ &state) == p - buf
+ && (__wcrtomb ((char *) buf, towlower (wc), &state)
+ != (size_t) -1))
+ re_set_fastmap (fastmap, 0, buf[0]);
+ re_free (buf);
+ }
+#endif
+ }
+ else if (type == SIMPLE_BRACKET)
+ {
+ int i, ch;
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ {
+ int j;
+ bitset_word_t w = dfa->nodes[node].opr.sbcset[i];
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ if (w & ((bitset_word_t) 1 << j))
+ re_set_fastmap (fastmap, icase, ch);
+ }
+ }
+#ifdef RE_ENABLE_I18N
+ else if (type == COMPLEX_BRACKET)
+ {
+ re_charset_t *cset = dfa->nodes[node].opr.mbcset;
+ int i;
+
+# ifdef _LIBC
+ /* See if we have to try all bytes which start multiple collation
+ elements.
+ e.g. In da_DK, we want to catch 'a' since "aa" is a valid
+ collation element, and don't catch 'b' since 'b' is
+ the only collation element which starts from 'b' (and
+ it is caught by SIMPLE_BRACKET). */
+ if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0
+ && (cset->ncoll_syms || cset->nranges))
+ {
+ const int32_t *table = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ for (i = 0; i < SBC_MAX; ++i)
+ if (table[i] < 0)
+ re_set_fastmap (fastmap, icase, i);
+ }
+# endif /* _LIBC */
+
+ /* See if we have to start the match at all multibyte characters,
+ i.e. where we would not find an invalid sequence. This only
+ applies to multibyte character sets; for single byte character
+ sets, the SIMPLE_BRACKET again suffices. */
+ if (dfa->mb_cur_max > 1
+ && (cset->nchar_classes || cset->non_match || cset->nranges
+# ifdef _LIBC
+ || cset->nequiv_classes
+# endif /* _LIBC */
+ ))
+ {
+ unsigned char c = 0;
+ do
+ {
+ mbstate_t mbs;
+ memset (&mbs, 0, sizeof (mbs));
+ if (__mbrtowc (NULL, (char *) &c, 1, &mbs) == (size_t) -2)
+ re_set_fastmap (fastmap, false, (int) c);
+ }
+ while (++c != 0);
+ }
+
+ else
+ {
+ /* ... Else catch all bytes which can start the mbchars. */
+ for (i = 0; i < cset->nmbchars; ++i)
+ {
+ char buf[256];
+ mbstate_t state;
+ memset (&state, '\0', sizeof (state));
+ if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1)
+ re_set_fastmap (fastmap, icase, *(unsigned char *) buf);
+ if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+ {
+ if (__wcrtomb (buf, towlower (cset->mbchars[i]), &state)
+ != (size_t) -1)
+ re_set_fastmap (fastmap, false, *(unsigned char *) buf);
+ }
+ }
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ else if (type == OP_PERIOD
+#ifdef RE_ENABLE_I18N
+ || type == OP_UTF8_PERIOD
+#endif /* RE_ENABLE_I18N */
+ || type == END_OF_RE)
+ {
+ memset (fastmap, '\1', sizeof (char) * SBC_MAX);
+ if (type == END_OF_RE)
+ bufp->can_be_null = 1;
+ return;
+ }
+ }
+}
+
+/* Entry point for POSIX code. */
+/* regcomp takes a regular expression as a string and compiles it.
+
+ PREG is a regex_t *. We do not expect any fields to be initialized,
+ since POSIX says we shouldn't. Thus, we set
+
+ `buffer' to the compiled pattern;
+ `used' to the length of the compiled pattern;
+ `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
+ REG_EXTENDED bit in CFLAGS is set; otherwise, to
+ RE_SYNTAX_POSIX_BASIC;
+ `newline_anchor' to REG_NEWLINE being set in CFLAGS;
+ `fastmap' to an allocated space for the fastmap;
+ `fastmap_accurate' to zero;
+ `re_nsub' to the number of subexpressions in PATTERN.
+
+ PATTERN is the address of the pattern string.
+
+ CFLAGS is a series of bits which affect compilation.
+
+ If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
+ use POSIX basic syntax.
+
+ If REG_NEWLINE is set, then . and [^...] don't match newline.
+ Also, regexec will try a match beginning after every newline.
+
+ If REG_ICASE is set, then we considers upper- and lowercase
+ versions of letters to be equivalent when matching.
+
+ If REG_NOSUB is set, then when PREG is passed to regexec, that
+ routine will report only success or failure, and nothing about the
+ registers.
+
+ It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for
+ the return codes and their meanings.) */
+
+int
+regcomp (regex_t *__restrict preg,
+ const char *__restrict pattern,
+ int cflags)
+{
+ reg_errcode_t ret;
+ reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED
+ : RE_SYNTAX_POSIX_BASIC);
+
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ preg->used = 0;
+
+ /* Try to allocate space for the fastmap. */
+ preg->fastmap = re_malloc (char, SBC_MAX);
+ if (BE (preg->fastmap == NULL, 0))
+ return REG_ESPACE;
+
+ syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0;
+
+ /* If REG_NEWLINE is set, newlines are treated differently. */
+ if (cflags & REG_NEWLINE)
+ { /* REG_NEWLINE implies neither . nor [^...] match newline. */
+ syntax &= ~RE_DOT_NEWLINE;
+ syntax |= RE_HAT_LISTS_NOT_NEWLINE;
+ /* It also changes the matching behavior. */
+ preg->newline_anchor = 1;
+ }
+ else
+ preg->newline_anchor = 0;
+ preg->no_sub = !!(cflags & REG_NOSUB);
+ preg->translate = NULL;
+
+ ret = re_compile_internal (preg, pattern, strlen (pattern), syntax);
+
+ /* POSIX doesn't distinguish between an unmatched open-group and an
+ unmatched close-group: both are REG_EPAREN. */
+ if (ret == REG_ERPAREN)
+ ret = REG_EPAREN;
+
+ /* We have already checked preg->fastmap != NULL. */
+ if (BE (ret == REG_NOERROR, 1))
+ /* Compute the fastmap now, since regexec cannot modify the pattern
+ buffer. This function never fails in this implementation. */
+ (void) re_compile_fastmap (preg);
+ else
+ {
+ /* Some error occurred while compiling the expression. */
+ re_free (preg->fastmap);
+ preg->fastmap = NULL;
+ }
+
+ return (int) ret;
+}
+#ifdef _LIBC
+weak_alias (__regcomp, regcomp)
+#endif
+
+/* Returns a message corresponding to an error code, ERRCODE, returned
+ from either regcomp or regexec. We don't use PREG here. */
+
+size_t
+regerror(int errcode, const regex_t *__restrict preg,
+ char *__restrict errbuf, size_t errbuf_size)
+{
+ const char *msg;
+ size_t msg_size;
+
+ if (BE (errcode < 0
+ || errcode >= (int) (sizeof (__re_error_msgid_idx)
+ / sizeof (__re_error_msgid_idx[0])), 0))
+ /* Only error codes returned by the rest of the code should be passed
+ to this routine. If we are given anything else, or if other regex
+ code generates an invalid error code, then the program has a bug.
+ Dump core so we can fix it. */
+ abort ();
+
+ msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]);
+
+ msg_size = strlen (msg) + 1; /* Includes the null. */
+
+ if (BE (errbuf_size != 0, 1))
+ {
+ if (BE (msg_size > errbuf_size, 0))
+ {
+ memcpy (errbuf, msg, errbuf_size - 1);
+ errbuf[errbuf_size - 1] = 0;
+ }
+ else
+ memcpy (errbuf, msg, msg_size);
+ }
+
+ return msg_size;
+}
+#ifdef _LIBC
+weak_alias (__regerror, regerror)
+#endif
+
+
+#ifdef RE_ENABLE_I18N
+/* This static array is used for the map to single-byte characters when
+ UTF-8 is used. Otherwise we would allocate memory just to initialize
+ it the same all the time. UTF-8 is the preferred encoding so this is
+ a worthwhile optimization. */
+#if __GNUC__ >= 3
+static const bitset_t utf8_sb_map = {
+ /* Set the first 128 bits. */
+ [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX
+};
+#else /* ! (__GNUC__ >= 3) */
+static bitset_t utf8_sb_map;
+#endif /* __GNUC__ >= 3 */
+#endif /* RE_ENABLE_I18N */
+
+
+static void
+free_dfa_content (re_dfa_t *dfa)
+{
+ int i, j;
+
+ if (dfa->nodes)
+ for (i = 0; i < dfa->nodes_len; ++i)
+ free_token (dfa->nodes + i);
+ re_free (dfa->nexts);
+ for (i = 0; i < dfa->nodes_len; ++i)
+ {
+ if (dfa->eclosures != NULL)
+ re_node_set_free (dfa->eclosures + i);
+ if (dfa->inveclosures != NULL)
+ re_node_set_free (dfa->inveclosures + i);
+ if (dfa->edests != NULL)
+ re_node_set_free (dfa->edests + i);
+ }
+ re_free (dfa->edests);
+ re_free (dfa->eclosures);
+ re_free (dfa->inveclosures);
+ re_free (dfa->nodes);
+
+ if (dfa->state_table)
+ for (i = 0; i <= dfa->state_hash_mask; ++i)
+ {
+ struct re_state_table_entry *entry = dfa->state_table + i;
+ for (j = 0; j < entry->num; ++j)
+ {
+ re_dfastate_t *state = entry->array[j];
+ free_state (state);
+ }
+ re_free (entry->array);
+ }
+ re_free (dfa->state_table);
+#ifdef RE_ENABLE_I18N
+ if (dfa->sb_char != utf8_sb_map)
+ re_free (dfa->sb_char);
+#endif
+ re_free (dfa->subexp_map);
+#ifdef DEBUG
+ re_free (dfa->re_str);
+#endif
+
+ re_free (dfa);
+}
+
+
+/* Free dynamically allocated space used by PREG. */
+
+void
+regfree (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ if (BE (dfa != NULL, 1))
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+
+ re_free (preg->fastmap);
+ preg->fastmap = NULL;
+
+ re_free (preg->translate);
+ preg->translate = NULL;
+}
+#ifdef _LIBC
+weak_alias (__regfree, regfree)
+#endif
+
+/* Entry points compatible with 4.2 BSD regex library. We don't define
+ them unless specifically requested. */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+
+/* BSD has one and only one pattern buffer. */
+static struct re_pattern_buffer re_comp_buf;
+
+char *
+# ifdef _LIBC
+/* Make these definitions weak in libc, so POSIX programs can redefine
+ these names if they don't use our functions, and still use
+ regcomp/regexec above without link errors. */
+weak_function
+# endif
+re_comp (s)
+ const char *s;
+{
+ reg_errcode_t ret;
+ char *fastmap;
+
+ if (!s)
+ {
+ if (!re_comp_buf.buffer)
+ return gettext ("No previous regular expression");
+ return 0;
+ }
+
+ if (re_comp_buf.buffer)
+ {
+ fastmap = re_comp_buf.fastmap;
+ re_comp_buf.fastmap = NULL;
+ __regfree (&re_comp_buf);
+ memset (&re_comp_buf, '\0', sizeof (re_comp_buf));
+ re_comp_buf.fastmap = fastmap;
+ }
+
+ if (re_comp_buf.fastmap == NULL)
+ {
+ re_comp_buf.fastmap = (char *) malloc (SBC_MAX);
+ if (re_comp_buf.fastmap == NULL)
+ return (char *) gettext (__re_error_msgid
+ + __re_error_msgid_idx[(int) REG_ESPACE]);
+ }
+
+ /* Since `re_exec' always passes NULL for the `regs' argument, we
+ don't need to initialize the pattern buffer fields which affect it. */
+
+ /* Match anchors at newlines. */
+ re_comp_buf.newline_anchor = 1;
+
+ ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options);
+
+ if (!ret)
+ return NULL;
+
+ /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */
+ return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+
+#ifdef _LIBC
+libc_freeres_fn (free_mem)
+{
+ __regfree (&re_comp_buf);
+}
+#endif
+
+#endif /* _REGEX_RE_COMP */
+
+/* Internal entry point.
+ Compile the regular expression PATTERN, whose length is LENGTH.
+ SYNTAX indicate regular expression's syntax. */
+
+static reg_errcode_t
+re_compile_internal (regex_t *preg, const char * pattern, size_t length,
+ reg_syntax_t syntax)
+{
+ reg_errcode_t err = REG_NOERROR;
+ re_dfa_t *dfa;
+ re_string_t regexp;
+
+ /* Initialize the pattern buffer. */
+ preg->fastmap_accurate = 0;
+ preg->syntax = syntax;
+ preg->not_bol = preg->not_eol = 0;
+ preg->used = 0;
+ preg->re_nsub = 0;
+ preg->can_be_null = 0;
+ preg->regs_allocated = REGS_UNALLOCATED;
+
+ /* Initialize the dfa. */
+ dfa = (re_dfa_t *) preg->buffer;
+ if (BE (preg->allocated < sizeof (re_dfa_t), 0))
+ {
+ /* If zero allocated, but buffer is non-null, try to realloc
+ enough space. This loses if buffer's address is bogus, but
+ that is the user's responsibility. If ->buffer is NULL this
+ is a simple allocation. */
+ dfa = re_realloc (preg->buffer, re_dfa_t, 1);
+ if (dfa == NULL)
+ return REG_ESPACE;
+ preg->allocated = sizeof (re_dfa_t);
+ preg->buffer = (unsigned char *) dfa;
+ }
+ preg->used = sizeof (re_dfa_t);
+
+ err = init_dfa (dfa, length);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ return err;
+ }
+#ifdef DEBUG
+ /* Note: length+1 will not overflow since it is checked in init_dfa. */
+ dfa->re_str = re_malloc (char, length + 1);
+ strncpy (dfa->re_str, pattern, length + 1);
+#endif
+
+ __libc_lock_init (dfa->lock);
+
+ err = re_string_construct (&regexp, pattern, length, preg->translate,
+ syntax & RE_ICASE, dfa);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_compile_internal_free_return:
+ free_workarea_compile (preg);
+ re_string_destruct (&regexp);
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ return err;
+ }
+
+ /* Parse the regular expression, and build a structure tree. */
+ preg->re_nsub = 0;
+ dfa->str_tree = parse (&regexp, preg, syntax, &err);
+ if (BE (dfa->str_tree == NULL, 0))
+ goto re_compile_internal_free_return;
+
+ /* Analyze the tree and create the nfa. */
+ err = analyze (preg);
+ if (BE (err != REG_NOERROR, 0))
+ goto re_compile_internal_free_return;
+
+#ifdef RE_ENABLE_I18N
+ /* If possible, do searching in single byte encoding to speed things up. */
+ if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL)
+ optimize_utf8 (dfa);
+#endif
+
+ /* Then create the initial state of the dfa. */
+ err = create_initial_state (dfa);
+
+ /* Release work areas. */
+ free_workarea_compile (preg);
+ re_string_destruct (&regexp);
+
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ }
+
+ return err;
+}
+
+/* Initialize DFA. We use the length of the regular expression PAT_LEN
+ as the initial length of some arrays. */
+
+static reg_errcode_t
+init_dfa (re_dfa_t *dfa, size_t pat_len)
+{
+ unsigned int table_size;
+#ifndef _LIBC
+ char *codeset_name;
+#endif
+
+ memset (dfa, '\0', sizeof (re_dfa_t));
+
+ /* Force allocation of str_tree_storage the first time. */
+ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+
+ /* Avoid overflows. */
+ if (pat_len == SIZE_MAX)
+ return REG_ESPACE;
+
+ dfa->nodes_alloc = pat_len + 1;
+ dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc);
+
+ /* table_size = 2 ^ ceil(log pat_len) */
+ for (table_size = 1; ; table_size <<= 1)
+ if (table_size > pat_len)
+ break;
+
+ dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size);
+ dfa->state_hash_mask = table_size - 1;
+
+ dfa->mb_cur_max = MB_CUR_MAX;
+#ifdef _LIBC
+ if (dfa->mb_cur_max == 6
+ && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0)
+ dfa->is_utf8 = 1;
+ dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII)
+ != 0);
+#else
+# ifdef HAVE_LANGINFO_CODESET
+ codeset_name = nl_langinfo (CODESET);
+# else
+ codeset_name = getenv ("LC_ALL");
+ if (codeset_name == NULL || codeset_name[0] == '\0')
+ codeset_name = getenv ("LC_CTYPE");
+ if (codeset_name == NULL || codeset_name[0] == '\0')
+ codeset_name = getenv ("LANG");
+ if (codeset_name == NULL)
+ codeset_name = "";
+ else if (strchr (codeset_name, '.') != NULL)
+ codeset_name = strchr (codeset_name, '.') + 1;
+# endif
+
+ /* strcasecmp isn't a standard interface. brute force check */
+#if 0
+ if (strcasecmp (codeset_name, "UTF-8") == 0
+ || strcasecmp (codeset_name, "UTF8") == 0)
+ dfa->is_utf8 = 1;
+#else
+ if ( (codeset_name[0] == 'U' || codeset_name[0] == 'u')
+ && (codeset_name[1] == 'T' || codeset_name[1] == 't')
+ && (codeset_name[2] == 'F' || codeset_name[2] == 'f')
+ && (codeset_name[3] == '-'
+ ? codeset_name[4] == '8' && codeset_name[5] == '\0'
+ : codeset_name[3] == '8' && codeset_name[4] == '\0'))
+ dfa->is_utf8 = 1;
+#endif
+
+ /* We check exhaustively in the loop below if this charset is a
+ superset of ASCII. */
+ dfa->map_notascii = 0;
+#endif
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ if (dfa->is_utf8)
+ {
+#if !defined(__GNUC__) || __GNUC__ < 3
+ static short utf8_sb_map_inited = 0;
+
+ if (! utf8_sb_map_inited)
+ {
+ int i;
+
+ utf8_sb_map_inited = 0;
+ for (i = 0; i <= 0x80 / BITSET_WORD_BITS - 1; i++)
+ utf8_sb_map[i] = BITSET_WORD_MAX;
+ }
+#endif
+ dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map;
+ }
+ else
+ {
+ int i, j, ch;
+
+ dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+ if (BE (dfa->sb_char == NULL, 0))
+ return REG_ESPACE;
+
+ /* Set the bits corresponding to single byte chars. */
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ {
+ wint_t wch = __btowc (ch);
+ if (wch != WEOF)
+ dfa->sb_char[i] |= (bitset_word_t) 1 << j;
+# ifndef _LIBC
+ if (isascii (ch) && wch != ch)
+ dfa->map_notascii = 1;
+# endif
+ }
+ }
+ }
+#endif
+
+ if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0))
+ return REG_ESPACE;
+ return REG_NOERROR;
+}
+
+/* Initialize WORD_CHAR table, which indicate which character is
+ "word". In this case "word" means that it is the word construction
+ character used by some operators like "\<", "\>", etc. */
+
+static void
+internal_function
+init_word_char (re_dfa_t *dfa)
+{
+ int i, j, ch;
+ dfa->word_ops_used = 1;
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ if (isalnum (ch) || ch == '_')
+ dfa->word_char[i] |= (bitset_word_t) 1 << j;
+}
+
+/* Free the work area which are only used while compiling. */
+
+static void
+free_workarea_compile (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_storage_t *storage, *next;
+ for (storage = dfa->str_tree_storage; storage; storage = next)
+ {
+ next = storage->next;
+ re_free (storage);
+ }
+ dfa->str_tree_storage = NULL;
+ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+ dfa->str_tree = NULL;
+ re_free (dfa->org_indices);
+ dfa->org_indices = NULL;
+}
+
+/* Create initial states for all contexts. */
+
+static reg_errcode_t
+create_initial_state (re_dfa_t *dfa)
+{
+ int first, i;
+ reg_errcode_t err;
+ re_node_set init_nodes;
+
+ /* Initial states have the epsilon closure of the node which is
+ the first node of the regular expression. */
+ first = dfa->str_tree->first->node_idx;
+ dfa->init_node = first;
+ err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* The back-references which are in initial states can epsilon transit,
+ since in this case all of the subexpressions can be null.
+ Then we add epsilon closures of the nodes which are the next nodes of
+ the back-references. */
+ if (dfa->nbackref > 0)
+ for (i = 0; i < init_nodes.nelem; ++i)
+ {
+ int node_idx = init_nodes.elems[i];
+ re_token_type_t type = dfa->nodes[node_idx].type;
+
+ int clexp_idx;
+ if (type != OP_BACK_REF)
+ continue;
+ for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx)
+ {
+ re_token_t *clexp_node;
+ clexp_node = dfa->nodes + init_nodes.elems[clexp_idx];
+ if (clexp_node->type == OP_CLOSE_SUBEXP
+ && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx)
+ break;
+ }
+ if (clexp_idx == init_nodes.nelem)
+ continue;
+
+ if (type == OP_BACK_REF)
+ {
+ int dest_idx = dfa->edests[node_idx].elems[0];
+ if (!re_node_set_contains (&init_nodes, dest_idx))
+ {
+ reg_errcode_t err = re_node_set_merge (&init_nodes,
+ dfa->eclosures
+ + dest_idx);
+ if (err != REG_NOERROR)
+ return err;
+ i = 0;
+ }
+ }
+ }
+
+ /* It must be the first time to invoke acquire_state. */
+ dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0);
+ /* We don't check ERR here, since the initial state must not be NULL. */
+ if (BE (dfa->init_state == NULL, 0))
+ return err;
+ if (dfa->init_state->has_constraint)
+ {
+ dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes,
+ CONTEXT_WORD);
+ dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes,
+ CONTEXT_NEWLINE);
+ dfa->init_state_begbuf = re_acquire_state_context (&err, dfa,
+ &init_nodes,
+ CONTEXT_NEWLINE
+ | CONTEXT_BEGBUF);
+ if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+ || dfa->init_state_begbuf == NULL, 0))
+ return err;
+ }
+ else
+ dfa->init_state_word = dfa->init_state_nl
+ = dfa->init_state_begbuf = dfa->init_state;
+
+ re_node_set_free (&init_nodes);
+ return REG_NOERROR;
+}
+
+#ifdef RE_ENABLE_I18N
+/* If it is possible to do searching in single byte encoding instead of UTF-8
+ to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change
+ DFA nodes where needed. */
+
+static void
+optimize_utf8 (re_dfa_t *dfa)
+{
+ int node, i, mb_chars = 0, has_period = 0;
+
+ for (node = 0; node < dfa->nodes_len; ++node)
+ switch (dfa->nodes[node].type)
+ {
+ case CHARACTER:
+ if (dfa->nodes[node].opr.c >= 0x80)
+ mb_chars = 1;
+ break;
+ case ANCHOR:
+ switch (dfa->nodes[node].opr.ctx_type)
+ {
+ case LINE_FIRST:
+ case LINE_LAST:
+ case BUF_FIRST:
+ case BUF_LAST:
+ break;
+ default:
+ /* Word anchors etc. cannot be handled. It's okay to test
+ opr.ctx_type since constraints (for all DFA nodes) are
+ created by ORing one or more opr.ctx_type values. */
+ return;
+ }
+ break;
+ case OP_PERIOD:
+ has_period = 1;
+ break;
+ case OP_BACK_REF:
+ case OP_ALT:
+ case END_OF_RE:
+ case OP_DUP_ASTERISK:
+ case OP_OPEN_SUBEXP:
+ case OP_CLOSE_SUBEXP:
+ break;
+ case COMPLEX_BRACKET:
+ return;
+ case SIMPLE_BRACKET:
+ /* Just double check. The non-ASCII range starts at 0x80. */
+ assert (0x80 % BITSET_WORD_BITS == 0);
+ for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i)
+ if (dfa->nodes[node].opr.sbcset[i])
+ return;
+ break;
+ default:
+ abort ();
+ }
+
+ if (mb_chars || has_period)
+ for (node = 0; node < dfa->nodes_len; ++node)
+ {
+ if (dfa->nodes[node].type == CHARACTER
+ && dfa->nodes[node].opr.c >= 0x80)
+ dfa->nodes[node].mb_partial = 0;
+ else if (dfa->nodes[node].type == OP_PERIOD)
+ dfa->nodes[node].type = OP_UTF8_PERIOD;
+ }
+
+ /* The search can be in single byte locale. */
+ dfa->mb_cur_max = 1;
+ dfa->is_utf8 = 0;
+ dfa->has_mb_node = dfa->nbackref > 0 || has_period;
+}
+#endif
+
+/* Analyze the structure tree, and calculate "first", "next", "edest",
+ "eclosure", and "inveclosure". */
+
+static reg_errcode_t
+analyze (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ reg_errcode_t ret;
+
+ /* Allocate arrays. */
+ dfa->nexts = re_malloc (int, dfa->nodes_alloc);
+ dfa->org_indices = re_malloc (int, dfa->nodes_alloc);
+ dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc);
+ dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc);
+ if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL
+ || dfa->eclosures == NULL, 0))
+ return REG_ESPACE;
+
+ dfa->subexp_map = re_malloc (int, preg->re_nsub);
+ if (dfa->subexp_map != NULL)
+ {
+ int i;
+ for (i = 0; i < preg->re_nsub; i++)
+ dfa->subexp_map[i] = i;
+ preorder (dfa->str_tree, optimize_subexps, dfa);
+ for (i = 0; i < preg->re_nsub; i++)
+ if (dfa->subexp_map[i] != i)
+ break;
+ if (i == preg->re_nsub)
+ {
+ free (dfa->subexp_map);
+ dfa->subexp_map = NULL;
+ }
+ }
+
+ ret = postorder (dfa->str_tree, lower_subexps, preg);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ ret = postorder (dfa->str_tree, calc_first, dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ preorder (dfa->str_tree, calc_next, dfa);
+ ret = preorder (dfa->str_tree, link_nfa_nodes, dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ ret = calc_eclosure (dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ /* We only need this during the prune_impossible_nodes pass in regexec.c;
+ skip it if p_i_n will not run, as calc_inveclosure can be quadratic. */
+ if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match)
+ || dfa->nbackref)
+ {
+ dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len);
+ if (BE (dfa->inveclosures == NULL, 0))
+ return REG_ESPACE;
+ ret = calc_inveclosure (dfa);
+ }
+
+ return ret;
+}
+
+/* Our parse trees are very unbalanced, so we cannot use a stack to
+ implement parse tree visits. Instead, we use parent pointers and
+ some hairy code in these two functions. */
+static reg_errcode_t
+postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra)
+{
+ bin_tree_t *node, *prev;
+
+ for (node = root; ; )
+ {
+ /* Descend down the tree, preferably to the left (or to the right
+ if that's the only child). */
+ while (node->left || node->right)
+ if (node->left)
+ node = node->left;
+ else
+ node = node->right;
+
+ do
+ {
+ reg_errcode_t err = fn (extra, node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ if (node->parent == NULL)
+ return REG_NOERROR;
+ prev = node;
+ node = node->parent;
+ }
+ /* Go up while we have a node that is reached from the right. */
+ while (node->right == prev || node->right == NULL);
+ node = node->right;
+ }
+}
+
+static reg_errcode_t
+preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra)
+{
+ bin_tree_t *node;
+
+ for (node = root; ; )
+ {
+ reg_errcode_t err = fn (extra, node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* Go to the left node, or up and to the right. */
+ if (node->left)
+ node = node->left;
+ else
+ {
+ bin_tree_t *prev = NULL;
+ while (node->right == prev || node->right == NULL)
+ {
+ prev = node;
+ node = node->parent;
+ if (!node)
+ return REG_NOERROR;
+ }
+ node = node->right;
+ }
+ }
+}
+
+/* Optimization pass: if a SUBEXP is entirely contained, strip it and tell
+ re_search_internal to map the inner one's opr.idx to this one's. Adjust
+ backreferences as well. Requires a preorder visit. */
+static reg_errcode_t
+optimize_subexps (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+
+ if (node->token.type == OP_BACK_REF && dfa->subexp_map)
+ {
+ int idx = node->token.opr.idx;
+ node->token.opr.idx = dfa->subexp_map[idx];
+ dfa->used_bkref_map |= 1 << node->token.opr.idx;
+ }
+
+ else if (node->token.type == SUBEXP
+ && node->left && node->left->token.type == SUBEXP)
+ {
+ int other_idx = node->left->token.opr.idx;
+
+ node->left = node->left->left;
+ if (node->left)
+ node->left->parent = node;
+
+ dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx];
+ if (other_idx < BITSET_WORD_BITS)
+ dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx);
+ }
+
+ return REG_NOERROR;
+}
+
+/* Lowering pass: Turn each SUBEXP node into the appropriate concatenation
+ of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP. */
+static reg_errcode_t
+lower_subexps (void *extra, bin_tree_t *node)
+{
+ regex_t *preg = (regex_t *) extra;
+ reg_errcode_t err = REG_NOERROR;
+
+ if (node->left && node->left->token.type == SUBEXP)
+ {
+ node->left = lower_subexp (&err, preg, node->left);
+ if (node->left)
+ node->left->parent = node;
+ }
+ if (node->right && node->right->token.type == SUBEXP)
+ {
+ node->right = lower_subexp (&err, preg, node->right);
+ if (node->right)
+ node->right->parent = node;
+ }
+
+ return err;
+}
+
+static bin_tree_t *
+lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *body = node->left;
+ bin_tree_t *op, *cls, *tree1, *tree;
+
+ if (preg->no_sub
+ /* We do not optimize empty subexpressions, because otherwise we may
+ have bad CONCAT nodes with NULL children. This is obviously not
+ very common, so we do not lose much. An example that triggers
+ this case is the sed "script" /\(\)/x. */
+ && node->left != NULL
+ && (node->token.opr.idx >= BITSET_WORD_BITS
+ || !(dfa->used_bkref_map
+ & ((bitset_word_t) 1 << node->token.opr.idx))))
+ return node->left;
+
+ /* Convert the SUBEXP node to the concatenation of an
+ OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */
+ op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP);
+ cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP);
+ tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls;
+ tree = create_tree (dfa, op, tree1, CONCAT);
+ if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx;
+ op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp;
+ return tree;
+}
+
+/* Pass 1 in building the NFA: compute FIRST and create unlinked automaton
+ nodes. Requires a postorder visit. */
+static reg_errcode_t
+calc_first (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+ if (node->token.type == CONCAT)
+ {
+ node->first = node->left->first;
+ node->node_idx = node->left->node_idx;
+ }
+ else
+ {
+ node->first = node;
+ node->node_idx = re_dfa_add_node (dfa, node->token);
+ if (BE (node->node_idx == -1, 0))
+ return REG_ESPACE;
+ if (node->token.type == ANCHOR)
+ dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type;
+ }
+ return REG_NOERROR;
+}
+
+/* Pass 2: compute NEXT on the tree. Preorder visit. */
+static reg_errcode_t
+calc_next (void *extra, bin_tree_t *node)
+{
+ switch (node->token.type)
+ {
+ case OP_DUP_ASTERISK:
+ node->left->next = node;
+ break;
+ case CONCAT:
+ node->left->next = node->right->first;
+ node->right->next = node->next;
+ break;
+ default:
+ if (node->left)
+ node->left->next = node->next;
+ if (node->right)
+ node->right->next = node->next;
+ break;
+ }
+ return REG_NOERROR;
+}
+
+/* Pass 3: link all DFA nodes to their NEXT node (any order will do). */
+static reg_errcode_t
+link_nfa_nodes (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+ int idx = node->node_idx;
+ reg_errcode_t err = REG_NOERROR;
+
+ switch (node->token.type)
+ {
+ case CONCAT:
+ break;
+
+ case END_OF_RE:
+ assert (node->next == NULL);
+ break;
+
+ case OP_DUP_ASTERISK:
+ case OP_ALT:
+ {
+ int left, right;
+ dfa->has_plural_match = 1;
+ if (node->left != NULL)
+ left = node->left->first->node_idx;
+ else
+ left = node->next->node_idx;
+ if (node->right != NULL)
+ right = node->right->first->node_idx;
+ else
+ right = node->next->node_idx;
+ assert (left > -1);
+ assert (right > -1);
+ err = re_node_set_init_2 (dfa->edests + idx, left, right);
+ }
+ break;
+
+ case ANCHOR:
+ case OP_OPEN_SUBEXP:
+ case OP_CLOSE_SUBEXP:
+ err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx);
+ break;
+
+ case OP_BACK_REF:
+ dfa->nexts[idx] = node->next->node_idx;
+ if (node->token.type == OP_BACK_REF)
+ err = re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]);
+ break;
+
+ default:
+ assert (!IS_EPSILON_NODE (node->token.type));
+ dfa->nexts[idx] = node->next->node_idx;
+ break;
+ }
+
+ return err;
+}
+
+/* Duplicate the epsilon closure of the node ROOT_NODE.
+ Note that duplicated nodes have constraint INIT_CONSTRAINT in addition
+ to their own constraint. */
+
+static reg_errcode_t
+internal_function
+duplicate_node_closure (re_dfa_t *dfa, int top_org_node, int top_clone_node,
+ int root_node, unsigned int init_constraint)
+{
+ int org_node, clone_node, ret;
+ unsigned int constraint = init_constraint;
+ for (org_node = top_org_node, clone_node = top_clone_node;;)
+ {
+ int org_dest, clone_dest;
+ if (dfa->nodes[org_node].type == OP_BACK_REF)
+ {
+ /* If the back reference epsilon-transit, its destination must
+ also have the constraint. Then duplicate the epsilon closure
+ of the destination of the back reference, and store it in
+ edests of the back reference. */
+ org_dest = dfa->nexts[org_node];
+ re_node_set_empty (dfa->edests + clone_node);
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ dfa->nexts[clone_node] = dfa->nexts[org_node];
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ else if (dfa->edests[org_node].nelem == 0)
+ {
+ /* In case of the node can't epsilon-transit, don't duplicate the
+ destination and store the original destination as the
+ destination of the node. */
+ dfa->nexts[clone_node] = dfa->nexts[org_node];
+ break;
+ }
+ else if (dfa->edests[org_node].nelem == 1)
+ {
+ /* In case of the node can epsilon-transit, and it has only one
+ destination. */
+ org_dest = dfa->edests[org_node].elems[0];
+ re_node_set_empty (dfa->edests + clone_node);
+ /* If the node is root_node itself, it means the epsilon clsoure
+ has a loop. Then tie it to the destination of the root_node. */
+ if (org_node == root_node && clone_node != org_node)
+ {
+ ret = re_node_set_insert (dfa->edests + clone_node, org_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ break;
+ }
+ /* In case of the node has another constraint, add it. */
+ constraint |= dfa->nodes[org_node].constraint;
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ else /* dfa->edests[org_node].nelem == 2 */
+ {
+ /* In case of the node can epsilon-transit, and it has two
+ destinations. In the bin_tree_t and DFA, that's '|' and '*'. */
+ org_dest = dfa->edests[org_node].elems[0];
+ re_node_set_empty (dfa->edests + clone_node);
+ /* Search for a duplicated node which satisfies the constraint. */
+ clone_dest = search_duplicated_node (dfa, org_dest, constraint);
+ if (clone_dest == -1)
+ {
+ /* There is no such duplicated node, create a new one. */
+ reg_errcode_t err;
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ err = duplicate_node_closure (dfa, org_dest, clone_dest,
+ root_node, constraint);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ {
+ /* There is a duplicated node which satisfies the constraint,
+ use it to avoid infinite loop. */
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+
+ org_dest = dfa->edests[org_node].elems[1];
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ org_node = org_dest;
+ clone_node = clone_dest;
+ }
+ return REG_NOERROR;
+}
+
+/* Search for a node which is duplicated from the node ORG_NODE, and
+ satisfies the constraint CONSTRAINT. */
+
+static int
+search_duplicated_node (const re_dfa_t *dfa, int org_node,
+ unsigned int constraint)
+{
+ int idx;
+ for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx)
+ {
+ if (org_node == dfa->org_indices[idx]
+ && constraint == dfa->nodes[idx].constraint)
+ return idx; /* Found. */
+ }
+ return -1; /* Not found. */
+}
+
+/* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT.
+ Return the index of the new node, or -1 if insufficient storage is
+ available. */
+
+static int
+duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint)
+{
+ int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]);
+ if (BE (dup_idx != -1, 1))
+ {
+ dfa->nodes[dup_idx].constraint = constraint;
+ dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint;
+ dfa->nodes[dup_idx].duplicated = 1;
+
+ /* Store the index of the original node. */
+ dfa->org_indices[dup_idx] = org_idx;
+ }
+ return dup_idx;
+}
+
+static reg_errcode_t
+calc_inveclosure (re_dfa_t *dfa)
+{
+ int src, idx, ret;
+ for (idx = 0; idx < dfa->nodes_len; ++idx)
+ re_node_set_init_empty (dfa->inveclosures + idx);
+
+ for (src = 0; src < dfa->nodes_len; ++src)
+ {
+ int *elems = dfa->eclosures[src].elems;
+ for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx)
+ {
+ ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src);
+ if (BE (ret == -1, 0))
+ return REG_ESPACE;
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Calculate "eclosure" for all the node in DFA. */
+
+static reg_errcode_t
+calc_eclosure (re_dfa_t *dfa)
+{
+ int node_idx, incomplete;
+#ifdef DEBUG
+ assert (dfa->nodes_len > 0);
+#endif
+ incomplete = 0;
+ /* For each nodes, calculate epsilon closure. */
+ for (node_idx = 0; ; ++node_idx)
+ {
+ reg_errcode_t err;
+ re_node_set eclosure_elem;
+ if (node_idx == dfa->nodes_len)
+ {
+ if (!incomplete)
+ break;
+ incomplete = 0;
+ node_idx = 0;
+ }
+
+#ifdef DEBUG
+ assert (dfa->eclosures[node_idx].nelem != -1);
+#endif
+
+ /* If we have already calculated, skip it. */
+ if (dfa->eclosures[node_idx].nelem != 0)
+ continue;
+ /* Calculate epsilon closure of `node_idx'. */
+ err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (dfa->eclosures[node_idx].nelem == 0)
+ {
+ incomplete = 1;
+ re_node_set_free (&eclosure_elem);
+ }
+ }
+ return REG_NOERROR;
+}
+
+/* Calculate epsilon closure of NODE. */
+
+static reg_errcode_t
+calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root)
+{
+ reg_errcode_t err;
+ int i;
+ re_node_set eclosure;
+ int ret;
+ int incomplete = 0;
+ err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* This indicates that we are calculating this node now.
+ We reference this value to avoid infinite loop. */
+ dfa->eclosures[node].nelem = -1;
+
+ /* If the current node has constraints, duplicate all nodes
+ since they must inherit the constraints. */
+ if (dfa->nodes[node].constraint
+ && dfa->edests[node].nelem
+ && !dfa->nodes[dfa->edests[node].elems[0]].duplicated)
+ {
+ err = duplicate_node_closure (dfa, node, node, node,
+ dfa->nodes[node].constraint);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ /* Expand each epsilon destination nodes. */
+ if (IS_EPSILON_NODE(dfa->nodes[node].type))
+ for (i = 0; i < dfa->edests[node].nelem; ++i)
+ {
+ re_node_set eclosure_elem;
+ int edest = dfa->edests[node].elems[i];
+ /* If calculating the epsilon closure of `edest' is in progress,
+ return intermediate result. */
+ if (dfa->eclosures[edest].nelem == -1)
+ {
+ incomplete = 1;
+ continue;
+ }
+ /* If we haven't calculated the epsilon closure of `edest' yet,
+ calculate now. Otherwise use calculated epsilon closure. */
+ if (dfa->eclosures[edest].nelem == 0)
+ {
+ err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ eclosure_elem = dfa->eclosures[edest];
+ /* Merge the epsilon closure of `edest'. */
+ err = re_node_set_merge (&eclosure, &eclosure_elem);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ /* If the epsilon closure of `edest' is incomplete,
+ the epsilon closure of this node is also incomplete. */
+ if (dfa->eclosures[edest].nelem == 0)
+ {
+ incomplete = 1;
+ re_node_set_free (&eclosure_elem);
+ }
+ }
+
+ /* An epsilon closure includes itself. */
+ ret = re_node_set_insert (&eclosure, node);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ if (incomplete && !root)
+ dfa->eclosures[node].nelem = 0;
+ else
+ dfa->eclosures[node] = eclosure;
+ *new_set = eclosure;
+ return REG_NOERROR;
+}
+
+/* Functions for token which are used in the parser. */
+
+/* Fetch a token from INPUT.
+ We must not use this function inside bracket expressions. */
+
+static void
+internal_function
+fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax)
+{
+ re_string_skip_bytes (input, peek_token (result, input, syntax));
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+ We must not use this function inside bracket expressions. */
+
+static int
+internal_function
+peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+ unsigned char c;
+
+ if (re_string_eoi (input))
+ {
+ token->type = END_OF_RE;
+ return 0;
+ }
+
+ c = re_string_peek_byte (input, 0);
+ token->opr.c = c;
+
+ token->word_char = 0;
+#ifdef RE_ENABLE_I18N
+ token->mb_partial = 0;
+ if (input->mb_cur_max > 1 &&
+ !re_string_first_byte (input, re_string_cur_idx (input)))
+ {
+ token->type = CHARACTER;
+ token->mb_partial = 1;
+ return 1;
+ }
+#endif
+ if (c == '\\')
+ {
+ unsigned char c2;
+ if (re_string_cur_idx (input) + 1 >= re_string_length (input))
+ {
+ token->type = BACK_SLASH;
+ return 1;
+ }
+
+ c2 = re_string_peek_byte_case (input, 1);
+ token->opr.c = c2;
+ token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc = re_string_wchar_at (input,
+ re_string_cur_idx (input) + 1);
+ token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+ }
+ else
+#endif
+ token->word_char = IS_WORD_CHAR (c2) != 0;
+
+ switch (c2)
+ {
+ case '|':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR))
+ token->type = OP_ALT;
+ break;
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ if (!(syntax & RE_NO_BK_REFS))
+ {
+ token->type = OP_BACK_REF;
+ token->opr.idx = c2 - '1';
+ }
+ break;
+ case '<':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_FIRST;
+ }
+ break;
+ case '>':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_LAST;
+ }
+ break;
+ case 'b':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_DELIM;
+ }
+ break;
+ case 'B':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = NOT_WORD_DELIM;
+ }
+ break;
+ case 'w':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_WORD;
+ break;
+ case 'W':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_NOTWORD;
+ break;
+ case 's':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_SPACE;
+ break;
+ case 'S':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_NOTSPACE;
+ break;
+ case '`':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = BUF_FIRST;
+ }
+ break;
+ case '\'':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = BUF_LAST;
+ }
+ break;
+ case '(':
+ if (!(syntax & RE_NO_BK_PARENS))
+ token->type = OP_OPEN_SUBEXP;
+ break;
+ case ')':
+ if (!(syntax & RE_NO_BK_PARENS))
+ token->type = OP_CLOSE_SUBEXP;
+ break;
+ case '+':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_PLUS;
+ break;
+ case '?':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_QUESTION;
+ break;
+ case '{':
+ if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+ token->type = OP_OPEN_DUP_NUM;
+ break;
+ case '}':
+ if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+ token->type = OP_CLOSE_DUP_NUM;
+ break;
+ default:
+ break;
+ }
+ return 2;
+ }
+
+ token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input));
+ token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+ }
+ else
+#endif
+ token->word_char = IS_WORD_CHAR (token->opr.c);
+
+ switch (c)
+ {
+ case '\n':
+ if (syntax & RE_NEWLINE_ALT)
+ token->type = OP_ALT;
+ break;
+ case '|':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR))
+ token->type = OP_ALT;
+ break;
+ case '*':
+ token->type = OP_DUP_ASTERISK;
+ break;
+ case '+':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_PLUS;
+ break;
+ case '?':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_QUESTION;
+ break;
+ case '{':
+ if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+ token->type = OP_OPEN_DUP_NUM;
+ break;
+ case '}':
+ if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+ token->type = OP_CLOSE_DUP_NUM;
+ break;
+ case '(':
+ if (syntax & RE_NO_BK_PARENS)
+ token->type = OP_OPEN_SUBEXP;
+ break;
+ case ')':
+ if (syntax & RE_NO_BK_PARENS)
+ token->type = OP_CLOSE_SUBEXP;
+ break;
+ case '[':
+ token->type = OP_OPEN_BRACKET;
+ break;
+ case '.':
+ token->type = OP_PERIOD;
+ break;
+ case '^':
+ if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) &&
+ re_string_cur_idx (input) != 0)
+ {
+ char prev = re_string_peek_byte (input, -1);
+ if (!(syntax & RE_NEWLINE_ALT) || prev != '\n')
+ break;
+ }
+ token->type = ANCHOR;
+ token->opr.ctx_type = LINE_FIRST;
+ break;
+ case '$':
+ if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) &&
+ re_string_cur_idx (input) + 1 != re_string_length (input))
+ {
+ re_token_t next;
+ re_string_skip_bytes (input, 1);
+ peek_token (&next, input, syntax);
+ re_string_skip_bytes (input, -1);
+ if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP)
+ break;
+ }
+ token->type = ANCHOR;
+ token->opr.ctx_type = LINE_LAST;
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+ We must not use this function out of bracket expressions. */
+
+static int
+internal_function
+peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+ unsigned char c;
+ if (re_string_eoi (input))
+ {
+ token->type = END_OF_RE;
+ return 0;
+ }
+ c = re_string_peek_byte (input, 0);
+ token->opr.c = c;
+
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1 &&
+ !re_string_first_byte (input, re_string_cur_idx (input)))
+ {
+ token->type = CHARACTER;
+ return 1;
+ }
+#endif /* RE_ENABLE_I18N */
+
+ if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS)
+ && re_string_cur_idx (input) + 1 < re_string_length (input))
+ {
+ /* In this case, '\' escape a character. */
+ unsigned char c2;
+ re_string_skip_bytes (input, 1);
+ c2 = re_string_peek_byte (input, 0);
+ token->opr.c = c2;
+ token->type = CHARACTER;
+ return 1;
+ }
+ if (c == '[') /* '[' is a special char in a bracket exps. */
+ {
+ unsigned char c2;
+ int token_len;
+ if (re_string_cur_idx (input) + 1 < re_string_length (input))
+ c2 = re_string_peek_byte (input, 1);
+ else
+ c2 = 0;
+ token->opr.c = c2;
+ token_len = 2;
+ switch (c2)
+ {
+ case '.':
+ token->type = OP_OPEN_COLL_ELEM;
+ break;
+ case '=':
+ token->type = OP_OPEN_EQUIV_CLASS;
+ break;
+ case ':':
+ if (syntax & RE_CHAR_CLASSES)
+ {
+ token->type = OP_OPEN_CHAR_CLASS;
+ break;
+ }
+ /* else fall through. */
+ default:
+ token->type = CHARACTER;
+ token->opr.c = c;
+ token_len = 1;
+ break;
+ }
+ return token_len;
+ }
+ switch (c)
+ {
+ case '-':
+ token->type = OP_CHARSET_RANGE;
+ break;
+ case ']':
+ token->type = OP_CLOSE_BRACKET;
+ break;
+ case '^':
+ token->type = OP_NON_MATCH_LIST;
+ break;
+ default:
+ token->type = CHARACTER;
+ }
+ return 1;
+}
+
+/* Functions for parser. */
+
+/* Entry point of the parser.
+ Parse the regular expression REGEXP and return the structure tree.
+ If an error is occured, ERR is set by error code, and return NULL.
+ This function build the following tree, from regular expression <reg_exp>:
+ CAT
+ / \
+ / \
+ <reg_exp> EOR
+
+ CAT means concatenation.
+ EOR means end of regular expression. */
+
+static bin_tree_t *
+parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax,
+ reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree, *eor, *root;
+ re_token_t current_token;
+ dfa->syntax = syntax;
+ fetch_token (&current_token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+ tree = parse_reg_exp (regexp, preg, &current_token, syntax, 0, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ eor = create_tree (dfa, NULL, NULL, END_OF_RE);
+ if (tree != NULL)
+ root = create_tree (dfa, tree, eor, CONCAT);
+ else
+ root = eor;
+ if (BE (eor == NULL || root == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ return root;
+}
+
+/* This function build the following tree, from regular expression
+ <branch1>|<branch2>:
+ ALT
+ / \
+ / \
+ <branch1> <branch2>
+
+ ALT means alternative, which represents the operator `|'. */
+
+static bin_tree_t *
+parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree, *branch = NULL;
+ tree = parse_branch (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+
+ while (token->type == OP_ALT)
+ {
+ fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+ if (token->type != OP_ALT && token->type != END_OF_RE
+ && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+ {
+ branch = parse_branch (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && branch == NULL, 0))
+ return NULL;
+ }
+ else
+ branch = NULL;
+ tree = create_tree (dfa, tree, branch, OP_ALT);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ return tree;
+}
+
+/* This function build the following tree, from regular expression
+ <exp1><exp2>:
+ CAT
+ / \
+ / \
+ <exp1> <exp2>
+
+ CAT means concatenation. */
+
+static bin_tree_t *
+parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ bin_tree_t *tree, *exp;
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ tree = parse_expression (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+
+ while (token->type != OP_ALT && token->type != END_OF_RE
+ && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+ {
+ exp = parse_expression (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && exp == NULL, 0))
+ {
+ return NULL;
+ }
+ if (tree != NULL && exp != NULL)
+ {
+ tree = create_tree (dfa, tree, exp, CONCAT);
+ if (tree == NULL)
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ else if (tree == NULL)
+ tree = exp;
+ /* Otherwise exp == NULL, we don't need to create new tree. */
+ }
+ return tree;
+}
+
+/* This function build the following tree, from regular expression a*:
+ *
+ |
+ a
+*/
+
+static bin_tree_t *
+parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree;
+ switch (token->type)
+ {
+ case CHARACTER:
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ while (!re_string_eoi (regexp)
+ && !re_string_first_byte (regexp, re_string_cur_idx (regexp)))
+ {
+ bin_tree_t *mbc_remain;
+ fetch_token (token, regexp, syntax);
+ mbc_remain = create_token_tree (dfa, NULL, NULL, token);
+ tree = create_tree (dfa, tree, mbc_remain, CONCAT);
+ if (BE (mbc_remain == NULL || tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ }
+#endif
+ break;
+ case OP_OPEN_SUBEXP:
+ tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_OPEN_BRACKET:
+ tree = parse_bracket_exp (regexp, dfa, token, syntax, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_BACK_REF:
+ if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1))
+ {
+ *err = REG_ESUBREG;
+ return NULL;
+ }
+ dfa->used_bkref_map |= 1 << token->opr.idx;
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ ++dfa->nbackref;
+ dfa->has_mb_node = 1;
+ break;
+ case OP_OPEN_DUP_NUM:
+ if (syntax & RE_CONTEXT_INVALID_DUP)
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ /* FALLTHROUGH */
+ case OP_DUP_ASTERISK:
+ case OP_DUP_PLUS:
+ case OP_DUP_QUESTION:
+ if (syntax & RE_CONTEXT_INVALID_OPS)
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ else if (syntax & RE_CONTEXT_INDEP_OPS)
+ {
+ fetch_token (token, regexp, syntax);
+ return parse_expression (regexp, preg, token, syntax, nest, err);
+ }
+ /* else fall through */
+ case OP_CLOSE_SUBEXP:
+ if ((token->type == OP_CLOSE_SUBEXP) &&
+ !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD))
+ {
+ *err = REG_ERPAREN;
+ return NULL;
+ }
+ /* else fall through */
+ case OP_CLOSE_DUP_NUM:
+ /* We treat it as a normal character. */
+
+ /* Then we can these characters as normal characters. */
+ token->type = CHARACTER;
+ /* mb_partial and word_char bits should be initialized already
+ by peek_token. */
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ break;
+ case ANCHOR:
+ if ((token->opr.ctx_type
+ & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST))
+ && dfa->word_ops_used == 0)
+ init_word_char (dfa);
+ if (token->opr.ctx_type == WORD_DELIM
+ || token->opr.ctx_type == NOT_WORD_DELIM)
+ {
+ bin_tree_t *tree_first, *tree_last;
+ if (token->opr.ctx_type == WORD_DELIM)
+ {
+ token->opr.ctx_type = WORD_FIRST;
+ tree_first = create_token_tree (dfa, NULL, NULL, token);
+ token->opr.ctx_type = WORD_LAST;
+ }
+ else
+ {
+ token->opr.ctx_type = INSIDE_WORD;
+ tree_first = create_token_tree (dfa, NULL, NULL, token);
+ token->opr.ctx_type = INSIDE_NOTWORD;
+ }
+ tree_last = create_token_tree (dfa, NULL, NULL, token);
+ tree = create_tree (dfa, tree_first, tree_last, OP_ALT);
+ if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ else
+ {
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ /* We must return here, since ANCHORs can't be followed
+ by repetition operators.
+ eg. RE"^*" is invalid or "<ANCHOR(^)><CHAR(*)>",
+ it must not be "<ANCHOR(^)><REPEAT(*)>". */
+ fetch_token (token, regexp, syntax);
+ return tree;
+ case OP_PERIOD:
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ if (dfa->mb_cur_max > 1)
+ dfa->has_mb_node = 1;
+ break;
+ case OP_WORD:
+ case OP_NOTWORD:
+ tree = build_charclass_op (dfa, regexp->trans,
+ "alnum",
+ "_",
+ token->type == OP_NOTWORD, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_SPACE:
+ case OP_NOTSPACE:
+ tree = build_charclass_op (dfa, regexp->trans,
+ "space",
+ "",
+ token->type == OP_NOTSPACE, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_ALT:
+ case END_OF_RE:
+ return NULL;
+ case BACK_SLASH:
+ *err = REG_EESCAPE;
+ return NULL;
+ default:
+ /* Must not happen? */
+#ifdef DEBUG
+ assert (0);
+#endif
+ return NULL;
+ }
+ fetch_token (token, regexp, syntax);
+
+ while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS
+ || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM)
+ {
+ tree = parse_dup_op (tree, regexp, dfa, token, syntax, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ /* In BRE consecutive duplications are not allowed. */
+ if ((syntax & RE_CONTEXT_INVALID_DUP)
+ && (token->type == OP_DUP_ASTERISK
+ || token->type == OP_OPEN_DUP_NUM))
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ }
+
+ return tree;
+}
+
+/* This function build the following tree, from regular expression
+ (<reg_exp>):
+ SUBEXP
+ |
+ <reg_exp>
+*/
+
+static bin_tree_t *
+parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree;
+ size_t cur_nsub;
+ cur_nsub = preg->re_nsub++;
+
+ fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+
+ /* The subexpression may be a null string. */
+ if (token->type == OP_CLOSE_SUBEXP)
+ tree = NULL;
+ else
+ {
+ tree = parse_reg_exp (regexp, preg, token, syntax, nest, err);
+ if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0))
+ *err = REG_EPAREN;
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+
+ if (cur_nsub <= '9' - '1')
+ dfa->completed_bkref_map |= 1 << cur_nsub;
+
+ tree = create_tree (dfa, tree, NULL, SUBEXP);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ tree->token.opr.idx = cur_nsub;
+ return tree;
+}
+
+/* This function parse repetition operators like "*", "+", "{1,3}" etc. */
+
+static bin_tree_t *
+parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
+ re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err)
+{
+ bin_tree_t *tree = NULL, *old_tree = NULL;
+ int i, start, end, start_idx = re_string_cur_idx (regexp);
+#ifndef RE_TOKEN_INIT_BUG
+ re_token_t start_token = *token;
+#else
+ re_token_t start_token;
+
+ memcpy ((void *) &start_token, (void *) token, sizeof start_token);
+#endif
+
+ if (token->type == OP_OPEN_DUP_NUM)
+ {
+ end = 0;
+ start = fetch_number (regexp, token, syntax);
+ if (start == -1)
+ {
+ if (token->type == CHARACTER && token->opr.c == ',')
+ start = 0; /* We treat "{,m}" as "{0,m}". */
+ else
+ {
+ *err = REG_BADBR; /* <re>{} is invalid. */
+ return NULL;
+ }
+ }
+ if (BE (start != -2, 1))
+ {
+ /* We treat "{n}" as "{n,n}". */
+ end = ((token->type == OP_CLOSE_DUP_NUM) ? start
+ : ((token->type == CHARACTER && token->opr.c == ',')
+ ? fetch_number (regexp, token, syntax) : -2));
+ }
+ if (BE (start == -2 || end == -2, 0))
+ {
+ /* Invalid sequence. */
+ if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0))
+ {
+ if (token->type == END_OF_RE)
+ *err = REG_EBRACE;
+ else
+ *err = REG_BADBR;
+
+ return NULL;
+ }
+
+ /* If the syntax bit is set, rollback. */
+ re_string_set_index (regexp, start_idx);
+ *token = start_token;
+ token->type = CHARACTER;
+ /* mb_partial and word_char bits should be already initialized by
+ peek_token. */
+ return elem;
+ }
+
+ if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0))
+ {
+ /* First number greater than second. */
+ *err = REG_BADBR;
+ return NULL;
+ }
+ }
+ else
+ {
+ start = (token->type == OP_DUP_PLUS) ? 1 : 0;
+ end = (token->type == OP_DUP_QUESTION) ? 1 : -1;
+ }
+
+ fetch_token (token, regexp, syntax);
+
+ if (BE (elem == NULL, 0))
+ return NULL;
+ if (BE (start == 0 && end == 0, 0))
+ {
+ postorder (elem, free_tree, NULL);
+ return NULL;
+ }
+
+ /* Extract "<re>{n,m}" to "<re><re>...<re><re>{0,<m-n>}". */
+ if (BE (start > 0, 0))
+ {
+ tree = elem;
+ for (i = 2; i <= start; ++i)
+ {
+ elem = duplicate_tree (elem, dfa);
+ tree = create_tree (dfa, tree, elem, CONCAT);
+ if (BE (elem == NULL || tree == NULL, 0))
+ goto parse_dup_op_espace;
+ }
+
+ if (start == end)
+ return tree;
+
+ /* Duplicate ELEM before it is marked optional. */
+ elem = duplicate_tree (elem, dfa);
+ old_tree = tree;
+ }
+ else
+ old_tree = NULL;
+
+ if (elem->token.type == SUBEXP)
+ postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx);
+
+ tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT));
+ if (BE (tree == NULL, 0))
+ goto parse_dup_op_espace;
+
+ /* This loop is actually executed only when end != -1,
+ to rewrite <re>{0,n} as (<re>(<re>...<re>?)?)?... We have
+ already created the start+1-th copy. */
+ for (i = start + 2; i <= end; ++i)
+ {
+ elem = duplicate_tree (elem, dfa);
+ tree = create_tree (dfa, tree, elem, CONCAT);
+ if (BE (elem == NULL || tree == NULL, 0))
+ goto parse_dup_op_espace;
+
+ tree = create_tree (dfa, tree, NULL, OP_ALT);
+ if (BE (tree == NULL, 0))
+ goto parse_dup_op_espace;
+ }
+
+ if (old_tree)
+ tree = create_tree (dfa, old_tree, tree, CONCAT);
+
+ return tree;
+
+ parse_dup_op_espace:
+ *err = REG_ESPACE;
+ return NULL;
+}
+
+/* Size of the names for collating symbol/equivalence_class/character_class.
+ I'm not sure, but maybe enough. */
+#define BRACKET_NAME_BUF_SIZE 32
+
+#ifndef _LIBC
+ /* Local function for parse_bracket_exp only used in case of NOT _LIBC.
+ Build the range expression which starts from START_ELEM, and ends
+ at END_ELEM. The result are written to MBCSET and SBCSET.
+ RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+ mbcset->range_ends, is a pointer argument sinse we may
+ update it. */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc,
+ bracket_elem_t *start_elem, bracket_elem_t *end_elem)
+# else /* not RE_ENABLE_I18N */
+build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem,
+ bracket_elem_t *end_elem)
+# endif /* not RE_ENABLE_I18N */
+{
+ unsigned int start_ch, end_ch;
+ /* Equivalence Classes and Character Classes can't be a range start/end. */
+ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+ || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+ 0))
+ return REG_ERANGE;
+
+ /* We can handle no multi character collating elements without libc
+ support. */
+ if (BE ((start_elem->type == COLL_SYM
+ && strlen ((char *) start_elem->opr.name) > 1)
+ || (end_elem->type == COLL_SYM
+ && strlen ((char *) end_elem->opr.name) > 1), 0))
+ return REG_ECOLLATE;
+
+# ifdef RE_ENABLE_I18N
+ {
+ wchar_t wc;
+ wint_t start_wc;
+ wint_t end_wc;
+ wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+
+ start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch
+ : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+ : 0));
+ end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch
+ : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+ : 0));
+#ifdef GAWK
+ /*
+ * Fedora Core 2, maybe others, have broken `btowc' that returns -1
+ * for any value > 127. Sigh. Note that `start_ch' and `end_ch' are
+ * unsigned, so we don't have sign extension problems.
+ */
+ start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+ ? start_ch : start_elem->opr.wch);
+ end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+ ? end_ch : end_elem->opr.wch);
+#else
+ start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+ ? __btowc (start_ch) : start_elem->opr.wch);
+ end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+ ? __btowc (end_ch) : end_elem->opr.wch);
+#endif
+ if (start_wc == WEOF || end_wc == WEOF)
+ return REG_ECOLLATE;
+ cmp_buf[0] = start_wc;
+ cmp_buf[4] = end_wc;
+ if (wcscoll (cmp_buf, cmp_buf + 4) > 0)
+ return REG_ERANGE;
+
+ /* Got valid collation sequence values, add them as a new entry.
+ However, for !_LIBC we have no collation elements: if the
+ character set is single byte, the single byte character set
+ that we build below suffices. parse_bracket_exp passes
+ no MBCSET if dfa->mb_cur_max == 1. */
+ if (mbcset)
+ {
+ /* Check the space of the arrays. */
+ if (BE (*range_alloc == mbcset->nranges, 0))
+ {
+ /* There is not enough space, need realloc. */
+ wchar_t *new_array_start, *new_array_end;
+ int new_nranges;
+
+ /* +1 in case of mbcset->nranges is 0. */
+ new_nranges = 2 * mbcset->nranges + 1;
+ /* Use realloc since mbcset->range_starts and mbcset->range_ends
+ are NULL if *range_alloc == 0. */
+ new_array_start = re_realloc (mbcset->range_starts, wchar_t,
+ new_nranges);
+ new_array_end = re_realloc (mbcset->range_ends, wchar_t,
+ new_nranges);
+
+ if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+ return REG_ESPACE;
+
+ mbcset->range_starts = new_array_start;
+ mbcset->range_ends = new_array_end;
+ *range_alloc = new_nranges;
+ }
+
+ mbcset->range_starts[mbcset->nranges] = start_wc;
+ mbcset->range_ends[mbcset->nranges++] = end_wc;
+ }
+
+ /* Build the table for single byte characters. */
+ for (wc = 0; wc < SBC_MAX; ++wc)
+ {
+ cmp_buf[2] = wc;
+ if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+ && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+ bitset_set (sbcset, wc);
+ }
+ }
+# else /* not RE_ENABLE_I18N */
+ {
+ unsigned int ch;
+ start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch
+ : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+ : 0));
+ end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch
+ : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+ : 0));
+ if (start_ch > end_ch)
+ return REG_ERANGE;
+ /* Build the table for single byte characters. */
+ for (ch = 0; ch < SBC_MAX; ++ch)
+ if (start_ch <= ch && ch <= end_ch)
+ bitset_set (sbcset, ch);
+ }
+# endif /* not RE_ENABLE_I18N */
+ return REG_NOERROR;
+}
+#endif /* not _LIBC */
+
+#ifndef _LIBC
+/* Helper function for parse_bracket_exp only used in case of NOT _LIBC..
+ Build the collating element which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+ pointer argument since we may update it. */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset,
+ int *coll_sym_alloc, const unsigned char *name)
+# else /* not RE_ENABLE_I18N */
+build_collating_symbol (bitset_t sbcset, const unsigned char *name)
+# endif /* not RE_ENABLE_I18N */
+{
+ size_t name_len = strlen ((const char *) name);
+ if (BE (name_len != 1, 0))
+ return REG_ECOLLATE;
+ else
+ {
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+}
+#endif /* not _LIBC */
+
+/* This function parse bracket expression like "[abc]", "[a-c]",
+ "[[.a-a.]]" etc. */
+
+static bin_tree_t *
+parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
+ reg_syntax_t syntax, reg_errcode_t *err)
+{
+#ifdef _LIBC
+ const unsigned char *collseqmb;
+ const char *collseqwc;
+ uint32_t nrules;
+ int32_t table_size;
+ const int32_t *symb_table;
+ const unsigned char *extra;
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Seek the collating symbol entry correspondings to NAME.
+ Return the index of the symbol in the SYMB_TABLE. */
+
+ auto inline int32_t
+ __attribute ((always_inline))
+ seek_collating_symbol_entry (name, name_len)
+ const unsigned char *name;
+ size_t name_len;
+ {
+ int32_t hash = elem_hash ((const char *) name, name_len);
+ int32_t elem = hash % table_size;
+ if (symb_table[2 * elem] != 0)
+ {
+ int32_t second = hash % (table_size - 2) + 1;
+
+ do
+ {
+ /* First compare the hashing value. */
+ if (symb_table[2 * elem] == hash
+ /* Compare the length of the name. */
+ && name_len == extra[symb_table[2 * elem + 1]]
+ /* Compare the name. */
+ && memcmp (name, &extra[symb_table[2 * elem + 1] + 1],
+ name_len) == 0)
+ {
+ /* Yep, this is the entry. */
+ break;
+ }
+
+ /* Next entry. */
+ elem += second;
+ }
+ while (symb_table[2 * elem] != 0);
+ }
+ return elem;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environment.
+ Look up the collation sequence value of BR_ELEM.
+ Return the value if succeeded, UINT_MAX otherwise. */
+
+ auto inline unsigned int
+ __attribute ((always_inline))
+ lookup_collation_sequence_value (br_elem)
+ bracket_elem_t *br_elem;
+ {
+ if (br_elem->type == SB_CHAR)
+ {
+ /*
+ if (MB_CUR_MAX == 1)
+ */
+ if (nrules == 0)
+ return collseqmb[br_elem->opr.ch];
+ else
+ {
+ wint_t wc = __btowc (br_elem->opr.ch);
+ return __collseq_table_lookup (collseqwc, wc);
+ }
+ }
+ else if (br_elem->type == MB_CHAR)
+ {
+ if (nrules != 0)
+ return __collseq_table_lookup (collseqwc, br_elem->opr.wch);
+ }
+ else if (br_elem->type == COLL_SYM)
+ {
+ size_t sym_name_len = strlen ((char *) br_elem->opr.name);
+ if (nrules != 0)
+ {
+ int32_t elem, idx;
+ elem = seek_collating_symbol_entry (br_elem->opr.name,
+ sym_name_len);
+ if (symb_table[2 * elem] != 0)
+ {
+ /* We found the entry. */
+ idx = symb_table[2 * elem + 1];
+ /* Skip the name of collating element name. */
+ idx += 1 + extra[idx];
+ /* Skip the byte sequence of the collating element. */
+ idx += 1 + extra[idx];
+ /* Adjust for the alignment. */
+ idx = (idx + 3) & ~3;
+ /* Skip the multibyte collation sequence value. */
+ idx += sizeof (unsigned int);
+ /* Skip the wide char sequence of the collating element. */
+ idx += sizeof (unsigned int) *
+ (1 + *(unsigned int *) (extra + idx));
+ /* Return the collation sequence value. */
+ return *(unsigned int *) (extra + idx);
+ }
+ else if (symb_table[2 * elem] == 0 && sym_name_len == 1)
+ {
+ /* No valid character. Match it as a single byte
+ character. */
+ return collseqmb[br_elem->opr.name[0]];
+ }
+ }
+ else if (sym_name_len == 1)
+ return collseqmb[br_elem->opr.name[0]];
+ }
+ return UINT_MAX;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Build the range expression which starts from START_ELEM, and ends
+ at END_ELEM. The result are written to MBCSET and SBCSET.
+ RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+ mbcset->range_ends, is a pointer argument sinse we may
+ update it. */
+
+ auto inline reg_errcode_t
+ __attribute ((always_inline))
+ build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem)
+ re_charset_t *mbcset;
+ int *range_alloc;
+ bitset_t sbcset;
+ bracket_elem_t *start_elem, *end_elem;
+ {
+ unsigned int ch;
+ uint32_t start_collseq;
+ uint32_t end_collseq;
+
+ /* Equivalence Classes and Character Classes can't be a range
+ start/end. */
+ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+ || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+ 0))
+ return REG_ERANGE;
+
+ start_collseq = lookup_collation_sequence_value (start_elem);
+ end_collseq = lookup_collation_sequence_value (end_elem);
+ /* Check start/end collation sequence values. */
+ if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0))
+ return REG_ECOLLATE;
+ if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0))
+ return REG_ERANGE;
+
+ /* Got valid collation sequence values, add them as a new entry.
+ However, if we have no collation elements, and the character set
+ is single byte, the single byte character set that we
+ build below suffices. */
+ if (nrules > 0 || dfa->mb_cur_max > 1)
+ {
+ /* Check the space of the arrays. */
+ if (BE (*range_alloc == mbcset->nranges, 0))
+ {
+ /* There is not enough space, need realloc. */
+ uint32_t *new_array_start;
+ uint32_t *new_array_end;
+ int new_nranges;
+
+ /* +1 in case of mbcset->nranges is 0. */
+ new_nranges = 2 * mbcset->nranges + 1;
+ new_array_start = re_realloc (mbcset->range_starts, uint32_t,
+ new_nranges);
+ new_array_end = re_realloc (mbcset->range_ends, uint32_t,
+ new_nranges);
+
+ if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+ return REG_ESPACE;
+
+ mbcset->range_starts = new_array_start;
+ mbcset->range_ends = new_array_end;
+ *range_alloc = new_nranges;
+ }
+
+ mbcset->range_starts[mbcset->nranges] = start_collseq;
+ mbcset->range_ends[mbcset->nranges++] = end_collseq;
+ }
+
+ /* Build the table for single byte characters. */
+ for (ch = 0; ch < SBC_MAX; ch++)
+ {
+ uint32_t ch_collseq;
+ /*
+ if (MB_CUR_MAX == 1)
+ */
+ if (nrules == 0)
+ ch_collseq = collseqmb[ch];
+ else
+ ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch));
+ if (start_collseq <= ch_collseq && ch_collseq <= end_collseq)
+ bitset_set (sbcset, ch);
+ }
+ return REG_NOERROR;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Build the collating element which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+ pointer argument sinse we may update it. */
+
+ auto inline reg_errcode_t
+ __attribute ((always_inline))
+ build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name)
+ re_charset_t *mbcset;
+ int *coll_sym_alloc;
+ bitset_t sbcset;
+ const unsigned char *name;
+ {
+ int32_t elem, idx;
+ size_t name_len = strlen ((const char *) name);
+ if (nrules != 0)
+ {
+ elem = seek_collating_symbol_entry (name, name_len);
+ if (symb_table[2 * elem] != 0)
+ {
+ /* We found the entry. */
+ idx = symb_table[2 * elem + 1];
+ /* Skip the name of collating element name. */
+ idx += 1 + extra[idx];
+ }
+ else if (symb_table[2 * elem] == 0 && name_len == 1)
+ {
+ /* No valid character, treat it as a normal
+ character. */
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+ else
+ return REG_ECOLLATE;
+
+ /* Got valid collation sequence, add it as a new entry. */
+ /* Check the space of the arrays. */
+ if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->ncoll_syms is 0. */
+ int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1;
+ /* Use realloc since mbcset->coll_syms is NULL
+ if *alloc == 0. */
+ int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t,
+ new_coll_sym_alloc);
+ if (BE (new_coll_syms == NULL, 0))
+ return REG_ESPACE;
+ mbcset->coll_syms = new_coll_syms;
+ *coll_sym_alloc = new_coll_sym_alloc;
+ }
+ mbcset->coll_syms[mbcset->ncoll_syms++] = idx;
+ return REG_NOERROR;
+ }
+ else
+ {
+ if (BE (name_len != 1, 0))
+ return REG_ECOLLATE;
+ else
+ {
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+ }
+ }
+#endif
+
+ re_token_t br_token;
+ re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset;
+ int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0;
+ int equiv_class_alloc = 0, char_class_alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+ int non_match = 0;
+ bin_tree_t *work_tree;
+ int token_len;
+ int first_round = 1;
+#ifdef _LIBC
+ collseqmb = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+ nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules)
+ {
+ /*
+ if (MB_CUR_MAX > 1)
+ */
+ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+ table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB);
+ symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_SYMB_TABLEMB);
+ extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_SYMB_EXTRAMB);
+ }
+#endif
+ sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+ mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+#ifdef RE_ENABLE_I18N
+ if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else
+ if (BE (sbcset == NULL, 0))
+#endif /* RE_ENABLE_I18N */
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ token_len = peek_token_bracket (token, regexp, syntax);
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_BADPAT;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_NON_MATCH_LIST)
+ {
+#ifdef RE_ENABLE_I18N
+ mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+ non_match = 1;
+ if (syntax & RE_HAT_LISTS_NOT_NEWLINE)
+ bitset_set (sbcset, '\n');
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+ token_len = peek_token_bracket (token, regexp, syntax);
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_BADPAT;
+ goto parse_bracket_exp_free_return;
+ }
+ }
+
+ /* We treat the first ']' as a normal character. */
+ if (token->type == OP_CLOSE_BRACKET)
+ token->type = CHARACTER;
+
+ while (1)
+ {
+ bracket_elem_t start_elem, end_elem;
+ unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE];
+ unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE];
+ reg_errcode_t ret;
+ int token_len2 = 0, is_range_exp = 0;
+ re_token_t token2;
+
+ start_elem.opr.name = start_name_buf;
+ ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa,
+ syntax, first_round);
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ *err = ret;
+ goto parse_bracket_exp_free_return;
+ }
+ first_round = 0;
+
+ /* Get information about the next token. We need it in any case. */
+ token_len = peek_token_bracket (token, regexp, syntax);
+
+ /* Do not check for ranges if we know they are not allowed. */
+ if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS)
+ {
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_CHARSET_RANGE)
+ {
+ re_string_skip_bytes (regexp, token_len); /* Skip '-'. */
+ token_len2 = peek_token_bracket (&token2, regexp, syntax);
+ if (BE (token2.type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token2.type == OP_CLOSE_BRACKET)
+ {
+ /* We treat the last '-' as a normal character. */
+ re_string_skip_bytes (regexp, -token_len);
+ token->type = CHARACTER;
+ }
+ else
+ is_range_exp = 1;
+ }
+ }
+
+ if (is_range_exp == 1)
+ {
+ end_elem.opr.name = end_name_buf;
+ ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2,
+ dfa, syntax, 1);
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ *err = ret;
+ goto parse_bracket_exp_free_return;
+ }
+
+ token_len = peek_token_bracket (token, regexp, syntax);
+
+#ifdef _LIBC
+ *err = build_range_exp (sbcset, mbcset, &range_alloc,
+ &start_elem, &end_elem);
+#else
+# ifdef RE_ENABLE_I18N
+ *err = build_range_exp (sbcset,
+ dfa->mb_cur_max > 1 ? mbcset : NULL,
+ &range_alloc, &start_elem, &end_elem);
+# else
+ *err = build_range_exp (sbcset, &start_elem, &end_elem);
+# endif
+#endif /* RE_ENABLE_I18N */
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ }
+ else
+ {
+ switch (start_elem.type)
+ {
+ case SB_CHAR:
+ bitset_set (sbcset, start_elem.opr.ch);
+ break;
+#ifdef RE_ENABLE_I18N
+ case MB_CHAR:
+ /* Check whether the array has enough space. */
+ if (BE (mbchar_alloc == mbcset->nmbchars, 0))
+ {
+ wchar_t *new_mbchars;
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nmbchars is 0. */
+ mbchar_alloc = 2 * mbcset->nmbchars + 1;
+ /* Use realloc since array is NULL if *alloc == 0. */
+ new_mbchars = re_realloc (mbcset->mbchars, wchar_t,
+ mbchar_alloc);
+ if (BE (new_mbchars == NULL, 0))
+ goto parse_bracket_exp_espace;
+ mbcset->mbchars = new_mbchars;
+ }
+ mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch;
+ break;
+#endif /* RE_ENABLE_I18N */
+ case EQUIV_CLASS:
+ *err = build_equiv_class (sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &equiv_class_alloc,
+#endif /* RE_ENABLE_I18N */
+ start_elem.opr.name);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ case COLL_SYM:
+ *err = build_collating_symbol (sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &coll_sym_alloc,
+#endif /* RE_ENABLE_I18N */
+ start_elem.opr.name);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ case CHAR_CLASS:
+ *err = build_charclass (regexp->trans, sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &char_class_alloc,
+#endif /* RE_ENABLE_I18N */
+ (const char *) start_elem.opr.name, syntax);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ default:
+ assert (0);
+ break;
+ }
+ }
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_CLOSE_BRACKET)
+ break;
+ }
+
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+
+ /* If it is non-matching list. */
+ if (non_match)
+ bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure only single byte characters are set. */
+ if (dfa->mb_cur_max > 1)
+ bitset_mask (sbcset, dfa->sb_char);
+
+ if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes
+ || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes
+ || mbcset->non_match)))
+ {
+ bin_tree_t *mbc_tree;
+ int sbc_idx;
+ /* Build a tree for complex bracket. */
+ dfa->has_mb_node = 1;
+ br_token.type = COMPLEX_BRACKET;
+ br_token.opr.mbcset = mbcset;
+ mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (mbc_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx)
+ if (sbcset[sbc_idx])
+ break;
+ /* If there are no bits set in sbcset, there is no point
+ of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */
+ if (sbc_idx < BITSET_WORDS)
+ {
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+
+ /* Then join them by ALT node. */
+ work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ }
+ else
+ {
+ re_free (sbcset);
+ work_tree = mbc_tree;
+ }
+ }
+ else
+#endif /* not RE_ENABLE_I18N */
+ {
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ }
+ return work_tree;
+
+ parse_bracket_exp_espace:
+ *err = REG_ESPACE;
+ parse_bracket_exp_free_return:
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ return NULL;
+}
+
+/* Parse an element in the bracket expression. */
+
+static reg_errcode_t
+parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp,
+ re_token_t *token, int token_len, re_dfa_t *dfa,
+ reg_syntax_t syntax, int accept_hyphen)
+{
+#ifdef RE_ENABLE_I18N
+ int cur_char_size;
+ cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp));
+ if (cur_char_size > 1)
+ {
+ elem->type = MB_CHAR;
+ elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp));
+ re_string_skip_bytes (regexp, cur_char_size);
+ return REG_NOERROR;
+ }
+#endif /* RE_ENABLE_I18N */
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+ if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS
+ || token->type == OP_OPEN_EQUIV_CLASS)
+ return parse_bracket_symbol (elem, regexp, token);
+ if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen)
+ {
+ /* A '-' must only appear as anything but a range indicator before
+ the closing bracket. Everything else is an error. */
+ re_token_t token2;
+ (void) peek_token_bracket (&token2, regexp, syntax);
+ if (token2.type != OP_CLOSE_BRACKET)
+ /* The actual error value is not standardized since this whole
+ case is undefined. But ERANGE makes good sense. */
+ return REG_ERANGE;
+ }
+ elem->type = SB_CHAR;
+ elem->opr.ch = token->opr.c;
+ return REG_NOERROR;
+}
+
+/* Parse a bracket symbol in the bracket expression. Bracket symbols are
+ such as [:<character_class>:], [.<collating_element>.], and
+ [=<equivalent_class>=]. */
+
+static reg_errcode_t
+parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp,
+ re_token_t *token)
+{
+ unsigned char ch, delim = token->opr.c;
+ int i = 0;
+ if (re_string_eoi(regexp))
+ return REG_EBRACK;
+ for (;; ++i)
+ {
+ if (i >= BRACKET_NAME_BUF_SIZE)
+ return REG_EBRACK;
+ if (token->type == OP_OPEN_CHAR_CLASS)
+ ch = re_string_fetch_byte_case (regexp);
+ else
+ ch = re_string_fetch_byte (regexp);
+ if (re_string_eoi(regexp))
+ return REG_EBRACK;
+ if (ch == delim && re_string_peek_byte (regexp, 0) == ']')
+ break;
+ elem->opr.name[i] = ch;
+ }
+ re_string_skip_bytes (regexp, 1);
+ elem->opr.name[i] = '\0';
+ switch (token->type)
+ {
+ case OP_OPEN_COLL_ELEM:
+ elem->type = COLL_SYM;
+ break;
+ case OP_OPEN_EQUIV_CLASS:
+ elem->type = EQUIV_CLASS;
+ break;
+ case OP_OPEN_CHAR_CLASS:
+ elem->type = CHAR_CLASS;
+ break;
+ default:
+ break;
+ }
+ return REG_NOERROR;
+}
+
+ /* Helper function for parse_bracket_exp.
+ Build the equivalence class which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes,
+ is a pointer argument sinse we may update it. */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_equiv_class (bitset_t sbcset, re_charset_t *mbcset,
+ int *equiv_class_alloc, const unsigned char *name)
+#else /* not RE_ENABLE_I18N */
+build_equiv_class (bitset_t sbcset, const unsigned char *name)
+#endif /* not RE_ENABLE_I18N */
+{
+#ifdef _LIBC
+ uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules != 0)
+ {
+ const int32_t *table, *indirect;
+ const unsigned char *weights, *extra, *cp;
+ unsigned char char_buf[2];
+ int32_t idx1, idx2;
+ unsigned int ch;
+ size_t len;
+ /* This #include defines a local function! */
+# include <locale/weight.h>
+ /* Calculate the index for equivalence class. */
+ cp = name;
+ table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_WEIGHTMB);
+ extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_INDIRECTMB);
+ idx1 = findidx (&cp);
+ if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0))
+ /* This isn't a valid character. */
+ return REG_ECOLLATE;
+
+ /* Build single byte matcing table for this equivalence class. */
+ char_buf[1] = (unsigned char) '\0';
+ len = weights[idx1 & 0xffffff];
+ for (ch = 0; ch < SBC_MAX; ++ch)
+ {
+ char_buf[0] = ch;
+ cp = char_buf;
+ idx2 = findidx (&cp);
+/*
+ idx2 = table[ch];
+*/
+ if (idx2 == 0)
+ /* This isn't a valid character. */
+ continue;
+ /* Compare only if the length matches and the collation rule
+ index is the same. */
+ if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24))
+ {
+ int cnt = 0;
+
+ while (cnt <= len &&
+ weights[(idx1 & 0xffffff) + 1 + cnt]
+ == weights[(idx2 & 0xffffff) + 1 + cnt])
+ ++cnt;
+
+ if (cnt > len)
+ bitset_set (sbcset, ch);
+ }
+ }
+ /* Check whether the array has enough space. */
+ if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nequiv_classes is 0. */
+ int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1;
+ /* Use realloc since the array is NULL if *alloc == 0. */
+ int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes,
+ int32_t,
+ new_equiv_class_alloc);
+ if (BE (new_equiv_classes == NULL, 0))
+ return REG_ESPACE;
+ mbcset->equiv_classes = new_equiv_classes;
+ *equiv_class_alloc = new_equiv_class_alloc;
+ }
+ mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1;
+ }
+ else
+#endif /* _LIBC */
+ {
+ if (BE (strlen ((const char *) name) != 1, 0))
+ return REG_ECOLLATE;
+ bitset_set (sbcset, *name);
+ }
+ return REG_NOERROR;
+}
+
+ /* Helper function for parse_bracket_exp.
+ Build the character class which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes,
+ is a pointer argument sinse we may update it. */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+ re_charset_t *mbcset, int *char_class_alloc,
+ const char *class_name, reg_syntax_t syntax)
+#else /* not RE_ENABLE_I18N */
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+ const char *class_name, reg_syntax_t syntax)
+#endif /* not RE_ENABLE_I18N */
+{
+ int i;
+
+ /* In case of REG_ICASE "upper" and "lower" match the both of
+ upper and lower cases. */
+ if ((syntax & RE_ICASE)
+ && (strcmp (class_name, "upper") == 0 || strcmp (class_name, "lower") == 0))
+ class_name = "alpha";
+
+#ifdef RE_ENABLE_I18N
+ /* Check the space of the arrays. */
+ if (BE (*char_class_alloc == mbcset->nchar_classes, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nchar_classes is 0. */
+ int new_char_class_alloc = 2 * mbcset->nchar_classes + 1;
+ /* Use realloc since array is NULL if *alloc == 0. */
+ wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t,
+ new_char_class_alloc);
+ if (BE (new_char_classes == NULL, 0))
+ return REG_ESPACE;
+ mbcset->char_classes = new_char_classes;
+ *char_class_alloc = new_char_class_alloc;
+ }
+ mbcset->char_classes[mbcset->nchar_classes++] = __wctype (class_name);
+#endif /* RE_ENABLE_I18N */
+
+#define BUILD_CHARCLASS_LOOP(ctype_func) \
+ do { \
+ if (BE (trans != NULL, 0)) \
+ { \
+ for (i = 0; i < SBC_MAX; ++i) \
+ if (ctype_func (i)) \
+ bitset_set (sbcset, trans[i]); \
+ } \
+ else \
+ { \
+ for (i = 0; i < SBC_MAX; ++i) \
+ if (ctype_func (i)) \
+ bitset_set (sbcset, i); \
+ } \
+ } while (0)
+
+ if (strcmp (class_name, "alnum") == 0)
+ BUILD_CHARCLASS_LOOP (isalnum);
+ else if (strcmp (class_name, "cntrl") == 0)
+ BUILD_CHARCLASS_LOOP (iscntrl);
+ else if (strcmp (class_name, "lower") == 0)
+ BUILD_CHARCLASS_LOOP (islower);
+ else if (strcmp (class_name, "space") == 0)
+ BUILD_CHARCLASS_LOOP (isspace);
+ else if (strcmp (class_name, "alpha") == 0)
+ BUILD_CHARCLASS_LOOP (isalpha);
+ else if (strcmp (class_name, "digit") == 0)
+ BUILD_CHARCLASS_LOOP (isdigit);
+ else if (strcmp (class_name, "print") == 0)
+ BUILD_CHARCLASS_LOOP (isprint);
+ else if (strcmp (class_name, "upper") == 0)
+ BUILD_CHARCLASS_LOOP (isupper);
+ else if (strcmp (class_name, "blank") == 0)
+#ifndef GAWK
+ BUILD_CHARCLASS_LOOP (isblank);
+#else
+ /* see comments above */
+ BUILD_CHARCLASS_LOOP (is_blank);
+#endif
+ else if (strcmp (class_name, "graph") == 0)
+ BUILD_CHARCLASS_LOOP (isgraph);
+ else if (strcmp (class_name, "punct") == 0)
+ BUILD_CHARCLASS_LOOP (ispunct);
+ else if (strcmp (class_name, "xdigit") == 0)
+ BUILD_CHARCLASS_LOOP (isxdigit);
+ else
+ return REG_ECTYPE;
+
+ return REG_NOERROR;
+}
+
+static bin_tree_t *
+build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
+ const char *class_name,
+ const char *extra, int non_match,
+ reg_errcode_t *err)
+{
+ re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset;
+ int alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+ reg_errcode_t ret;
+ re_token_t br_token;
+ bin_tree_t *tree;
+
+ sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+ mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+
+#ifdef RE_ENABLE_I18N
+ if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else /* not RE_ENABLE_I18N */
+ if (BE (sbcset == NULL, 0))
+#endif /* not RE_ENABLE_I18N */
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ if (non_match)
+ {
+#ifdef RE_ENABLE_I18N
+ mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+ }
+
+ /* We don't care the syntax in this case. */
+ ret = build_charclass (trans, sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &alloc,
+#endif /* RE_ENABLE_I18N */
+ class_name, 0);
+
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ *err = ret;
+ return NULL;
+ }
+ /* \w match '_' also. */
+ for (; *extra; extra++)
+ bitset_set (sbcset, *extra);
+
+ /* If it is non-matching list. */
+ if (non_match)
+ bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure only single byte characters are set. */
+ if (dfa->mb_cur_max > 1)
+ bitset_mask (sbcset, dfa->sb_char);
+#endif
+
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (tree == NULL, 0))
+ goto build_word_op_espace;
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ bin_tree_t *mbc_tree;
+ /* Build a tree for complex bracket. */
+ br_token.type = COMPLEX_BRACKET;
+ br_token.opr.mbcset = mbcset;
+ dfa->has_mb_node = 1;
+ mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (mbc_tree == NULL, 0))
+ goto build_word_op_espace;
+ /* Then join them by ALT node. */
+ tree = create_tree (dfa, tree, mbc_tree, OP_ALT);
+ if (BE (mbc_tree != NULL, 1))
+ return tree;
+ }
+ else
+ {
+ free_charset (mbcset);
+ return tree;
+ }
+#else /* not RE_ENABLE_I18N */
+ return tree;
+#endif /* not RE_ENABLE_I18N */
+
+ build_word_op_espace:
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ *err = REG_ESPACE;
+ return NULL;
+}
+
+/* This is intended for the expressions like "a{1,3}".
+ Fetch a number from `input', and return the number.
+ Return -1, if the number field is empty like "{,1}".
+ Return -2, If an error is occured. */
+
+static int
+fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax)
+{
+ int num = -1;
+ unsigned char c;
+ while (1)
+ {
+ fetch_token (token, input, syntax);
+ c = token->opr.c;
+ if (BE (token->type == END_OF_RE, 0))
+ return -2;
+ if (token->type == OP_CLOSE_DUP_NUM || c == ',')
+ break;
+ num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2)
+ ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0'));
+ num = (num > RE_DUP_MAX) ? -2 : num;
+ }
+ return num;
+}
+
+#ifdef RE_ENABLE_I18N
+static void
+free_charset (re_charset_t *cset)
+{
+ re_free (cset->mbchars);
+# ifdef _LIBC
+ re_free (cset->coll_syms);
+ re_free (cset->equiv_classes);
+ re_free (cset->range_starts);
+ re_free (cset->range_ends);
+# endif
+ re_free (cset->char_classes);
+ re_free (cset);
+}
+#endif /* RE_ENABLE_I18N */
+
+/* Functions for binary tree operation. */
+
+/* Create a tree node. */
+
+static bin_tree_t *
+create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+ re_token_type_t type)
+{
+ re_token_t t;
+ t.type = type;
+ return create_token_tree (dfa, left, right, &t);
+}
+
+static bin_tree_t *
+create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+ const re_token_t *token)
+{
+ bin_tree_t *tree;
+ if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0))
+ {
+ bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1);
+
+ if (storage == NULL)
+ return NULL;
+ storage->next = dfa->str_tree_storage;
+ dfa->str_tree_storage = storage;
+ dfa->str_tree_storage_idx = 0;
+ }
+ tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++];
+
+ tree->parent = NULL;
+ tree->left = left;
+ tree->right = right;
+ tree->token = *token;
+ tree->token.duplicated = 0;
+ tree->token.opt_subexp = 0;
+ tree->first = NULL;
+ tree->next = NULL;
+ tree->node_idx = -1;
+
+ if (left != NULL)
+ left->parent = tree;
+ if (right != NULL)
+ right->parent = tree;
+ return tree;
+}
+
+/* Mark the tree SRC as an optional subexpression.
+ To be called from preorder or postorder. */
+
+static reg_errcode_t
+mark_opt_subexp (void *extra, bin_tree_t *node)
+{
+ int idx = (int) (long) extra;
+ if (node->token.type == SUBEXP && node->token.opr.idx == idx)
+ node->token.opt_subexp = 1;
+
+ return REG_NOERROR;
+}
+
+/* Free the allocated memory inside NODE. */
+
+static void
+free_token (re_token_t *node)
+{
+#ifdef RE_ENABLE_I18N
+ if (node->type == COMPLEX_BRACKET && node->duplicated == 0)
+ free_charset (node->opr.mbcset);
+ else
+#endif /* RE_ENABLE_I18N */
+ if (node->type == SIMPLE_BRACKET && node->duplicated == 0)
+ re_free (node->opr.sbcset);
+}
+
+/* Worker function for tree walking. Free the allocated memory inside NODE
+ and its children. */
+
+static reg_errcode_t
+free_tree (void *extra, bin_tree_t *node)
+{
+ free_token (&node->token);
+ return REG_NOERROR;
+}
+
+
+/* Duplicate the node SRC, and return new node. This is a preorder
+ visit similar to the one implemented by the generic visitor, but
+ we need more infrastructure to maintain two parallel trees --- so,
+ it's easier to duplicate. */
+
+static bin_tree_t *
+duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa)
+{
+ const bin_tree_t *node;
+ bin_tree_t *dup_root;
+ bin_tree_t **p_new = &dup_root, *dup_node = root->parent;
+
+ for (node = root; ; )
+ {
+ /* Create a new tree and link it back to the current parent. */
+ *p_new = create_token_tree (dfa, NULL, NULL, &node->token);
+ if (*p_new == NULL)
+ return NULL;
+ (*p_new)->parent = dup_node;
+ (*p_new)->token.duplicated = 1;
+ dup_node = *p_new;
+
+ /* Go to the left node, or up and to the right. */
+ if (node->left)
+ {
+ node = node->left;
+ p_new = &dup_node->left;
+ }
+ else
+ {
+ const bin_tree_t *prev = NULL;
+ while (node->right == prev || node->right == NULL)
+ {
+ prev = node;
+ node = node->parent;
+ dup_node = dup_node->parent;
+ if (!node)
+ return dup_root;
+ }
+ node = node->right;
+ p_new = &dup_node->right;
+ }
+ }
+}
diff --git a/compat/regex/regex.c b/compat/regex/regex.c
index 556d8ab..3dd8dfa 100644
--- a/compat/regex/regex.c
+++ b/compat/regex/regex.c
@@ -1,4924 +1,87 @@
-/* Extended regular expression matching and search library,
- version 0.12.
- (Implements POSIX draft P10003.2/D11.2, except for
- internationalization features.)
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
- Copyright (C) 1993 Free Software Foundation, Inc.
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
-
- This program is distributed in the hope that it will be useful,
+ The GNU C Library 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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
-
-/* AIX requires this to be the first thing in the file. */
-#if defined (_AIX) && !defined (REGEX_MALLOC)
- #pragma alloca
-#endif
-
-#define _GNU_SOURCE
-
-/* We need this for `regex.h', and perhaps for the Emacs include files. */
-#include <sys/types.h>
-
-/* We used to test for `BSTRING' here, but only GCC and Emacs define
- `BSTRING', as far as I know, and neither of them use this code. */
-#include <string.h>
-#ifndef bcmp
-#define bcmp(s1, s2, n) memcmp ((s1), (s2), (n))
-#endif
-#ifndef bcopy
-#define bcopy(s, d, n) memcpy ((d), (s), (n))
-#endif
-#ifndef bzero
-#define bzero(s, n) memset ((s), 0, (n))
-#endif
-
-#include <stdlib.h>
-
-
-/* Define the syntax stuff for \<, \>, etc. */
-
-/* This must be nonzero for the wordchar and notwordchar pattern
- commands in re_match_2. */
-#ifndef Sword
-#define Sword 1
-#endif
-
-#ifdef SYNTAX_TABLE
-
-extern char *re_syntax_table;
-
-#else /* not SYNTAX_TABLE */
-
-/* How many characters in the character set. */
-#define CHAR_SET_SIZE 256
-
-static char re_syntax_table[CHAR_SET_SIZE];
-
-static void
-init_syntax_once ()
-{
- register int c;
- static int done = 0;
-
- if (done)
- return;
-
- bzero (re_syntax_table, sizeof re_syntax_table);
-
- for (c = 'a'; c <= 'z'; c++)
- re_syntax_table[c] = Sword;
-
- for (c = 'A'; c <= 'Z'; c++)
- re_syntax_table[c] = Sword;
-
- for (c = '0'; c <= '9'; c++)
- re_syntax_table[c] = Sword;
-
- re_syntax_table['_'] = Sword;
-
- done = 1;
-}
-
-#endif /* not SYNTAX_TABLE */
-
-#define SYNTAX(c) re_syntax_table[c]
-
-
-/* Get the interface, including the syntax bits. */
-#include "regex.h"
-
-/* isalpha etc. are used for the character classes. */
-#include <ctype.h>
-
-#ifndef isascii
-#define isascii(c) 1
-#endif
-
-#ifdef isblank
-#define ISBLANK(c) (isascii (c) && isblank (c))
-#else
-#define ISBLANK(c) ((c) == ' ' || (c) == '\t')
-#endif
-#ifdef isgraph
-#define ISGRAPH(c) (isascii (c) && isgraph (c))
-#else
-#define ISGRAPH(c) (isascii (c) && isprint (c) && !isspace (c))
-#endif
-
-#define ISPRINT(c) (isascii (c) && isprint (c))
-#define ISDIGIT(c) (isascii (c) && isdigit (c))
-#define ISALNUM(c) (isascii (c) && isalnum (c))
-#define ISALPHA(c) (isascii (c) && isalpha (c))
-#define ISCNTRL(c) (isascii (c) && iscntrl (c))
-#define ISLOWER(c) (isascii (c) && islower (c))
-#define ISPUNCT(c) (isascii (c) && ispunct (c))
-#define ISSPACE(c) (isascii (c) && isspace (c))
-#define ISUPPER(c) (isascii (c) && isupper (c))
-#define ISXDIGIT(c) (isascii (c) && isxdigit (c))
-
-#ifndef NULL
-#define NULL 0
-#endif
-
-/* We remove any previous definition of `SIGN_EXTEND_CHAR',
- since ours (we hope) works properly with all combinations of
- machines, compilers, `char' and `unsigned char' argument types.
- (Per Bothner suggested the basic approach.) */
-#undef SIGN_EXTEND_CHAR
-#if __STDC__
-#define SIGN_EXTEND_CHAR(c) ((signed char) (c))
-#else /* not __STDC__ */
-/* As in Harbison and Steele. */
-#define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128)
-#endif
-
-/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we
- use `alloca' instead of `malloc'. This is because using malloc in
- re_search* or re_match* could cause memory leaks when C-g is used in
- Emacs; also, malloc is slower and causes storage fragmentation. On
- the other hand, malloc is more portable, and easier to debug.
-
- Because we sometimes use alloca, some routines have to be macros,
- not functions -- `alloca'-allocated space disappears at the end of the
- function it is called in. */
-
-#ifdef REGEX_MALLOC
-
-#define REGEX_ALLOCATE malloc
-#define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize)
-
-#else /* not REGEX_MALLOC */
-
-/* Emacs already defines alloca, sometimes. */
-#ifndef alloca
-
-/* Make alloca work the best possible way. */
-#ifdef __GNUC__
-#define alloca __builtin_alloca
-#else /* not __GNUC__ */
-#if HAVE_ALLOCA_H
-#include <alloca.h>
-#else /* not __GNUC__ or HAVE_ALLOCA_H */
-#ifndef _AIX /* Already did AIX, up at the top. */
-char *alloca ();
-#endif /* not _AIX */
-#endif /* not HAVE_ALLOCA_H */
-#endif /* not __GNUC__ */
-
-#endif /* not alloca */
-
-#define REGEX_ALLOCATE alloca
-
-/* Assumes a `char *destination' variable. */
-#define REGEX_REALLOCATE(source, osize, nsize) \
- (destination = (char *) alloca (nsize), \
- bcopy (source, destination, osize), \
- destination)
-
-#endif /* not REGEX_MALLOC */
-
-
-/* True if `size1' is non-NULL and PTR is pointing anywhere inside
- `string1' or just past its end. This works if PTR is NULL, which is
- a good thing. */
-#define FIRST_STRING_P(ptr) \
- (size1 && string1 <= (ptr) && (ptr) <= string1 + size1)
-
-/* (Re)Allocate N items of type T using malloc, or fail. */
-#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t)))
-#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t)))
-#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t)))
-
-#define BYTEWIDTH 8 /* In bits. */
-
-#define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
-
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-typedef char boolean;
-#define false 0
-#define true 1
-
-/* These are the command codes that appear in compiled regular
- expressions. Some opcodes are followed by argument bytes. A
- command code can specify any interpretation whatsoever for its
- arguments. Zero bytes may appear in the compiled regular expression.
-
- The value of `exactn' is needed in search.c (search_buffer) in Emacs.
- So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of
- `exactn' we use here must also be 1. */
-
-typedef enum
-{
- no_op = 0,
-
- /* Followed by one byte giving n, then by n literal bytes. */
- exactn = 1,
-
- /* Matches any (more or less) character. */
- anychar,
-
- /* Matches any one char belonging to specified set. First
- following byte is number of bitmap bytes. Then come bytes
- for a bitmap saying which chars are in. Bits in each byte
- are ordered low-bit-first. A character is in the set if its
- bit is 1. A character too large to have a bit in the map is
- automatically not in the set. */
- charset,
-
- /* Same parameters as charset, but match any character that is
- not one of those specified. */
- charset_not,
-
- /* Start remembering the text that is matched, for storing in a
- register. Followed by one byte with the register number, in
- the range 0 to one less than the pattern buffer's re_nsub
- field. Then followed by one byte with the number of groups
- inner to this one. (This last has to be part of the
- start_memory only because we need it in the on_failure_jump
- of re_match_2.) */
- start_memory,
-
- /* Stop remembering the text that is matched and store it in a
- memory register. Followed by one byte with the register
- number, in the range 0 to one less than `re_nsub' in the
- pattern buffer, and one byte with the number of inner groups,
- just like `start_memory'. (We need the number of inner
- groups here because we don't have any easy way of finding the
- corresponding start_memory when we're at a stop_memory.) */
- stop_memory,
-
- /* Match a duplicate of something remembered. Followed by one
- byte containing the register number. */
- duplicate,
-
- /* Fail unless at beginning of line. */
- begline,
-
- /* Fail unless at end of line. */
- endline,
-
- /* Succeeds if at beginning of buffer (if emacs) or at beginning
- of string to be matched (if not). */
- begbuf,
-
- /* Analogously, for end of buffer/string. */
- endbuf,
-
- /* Followed by two byte relative address to which to jump. */
- jump,
-
- /* Same as jump, but marks the end of an alternative. */
- jump_past_alt,
-
- /* Followed by two-byte relative address of place to resume at
- in case of failure. */
- on_failure_jump,
-
- /* Like on_failure_jump, but pushes a placeholder instead of the
- current string position when executed. */
- on_failure_keep_string_jump,
-
- /* Throw away latest failure point and then jump to following
- two-byte relative address. */
- pop_failure_jump,
-
- /* Change to pop_failure_jump if know won't have to backtrack to
- match; otherwise change to jump. This is used to jump
- back to the beginning of a repeat. If what follows this jump
- clearly won't match what the repeat does, such that we can be
- sure that there is no use backtracking out of repetitions
- already matched, then we change it to a pop_failure_jump.
- Followed by two-byte address. */
- maybe_pop_jump,
-
- /* Jump to following two-byte address, and push a dummy failure
- point. This failure point will be thrown away if an attempt
- is made to use it for a failure. A `+' construct makes this
- before the first repeat. Also used as an intermediary kind
- of jump when compiling an alternative. */
- dummy_failure_jump,
-
- /* Push a dummy failure point and continue. Used at the end of
- alternatives. */
- push_dummy_failure,
-
- /* Followed by two-byte relative address and two-byte number n.
- After matching N times, jump to the address upon failure. */
- succeed_n,
-
- /* Followed by two-byte relative address, and two-byte number n.
- Jump to the address N times, then fail. */
- jump_n,
-
- /* Set the following two-byte relative address to the
- subsequent two-byte number. The address *includes* the two
- bytes of number. */
- set_number_at,
-
- wordchar, /* Matches any word-constituent character. */
- notwordchar, /* Matches any char that is not a word-constituent. */
-
- wordbeg, /* Succeeds if at word beginning. */
- wordend, /* Succeeds if at word end. */
-
- wordbound, /* Succeeds if at a word boundary. */
- notwordbound /* Succeeds if not at a word boundary. */
-
-#ifdef emacs
- ,before_dot, /* Succeeds if before point. */
- at_dot, /* Succeeds if at point. */
- after_dot, /* Succeeds if after point. */
-
- /* Matches any character whose syntax is specified. Followed by
- a byte which contains a syntax code, e.g., Sword. */
- syntaxspec,
-
- /* Matches any character whose syntax is not that specified. */
- notsyntaxspec
-#endif /* emacs */
-} re_opcode_t;
-
-/* Common operations on the compiled pattern. */
-
-/* Store NUMBER in two contiguous bytes starting at DESTINATION. */
-
-#define STORE_NUMBER(destination, number) \
- do { \
- (destination)[0] = (number) & 0377; \
- (destination)[1] = (number) >> 8; \
- } while (0)
-
-/* Same as STORE_NUMBER, except increment DESTINATION to
- the byte after where the number is stored. Therefore, DESTINATION
- must be an lvalue. */
-
-#define STORE_NUMBER_AND_INCR(destination, number) \
- do { \
- STORE_NUMBER (destination, number); \
- (destination) += 2; \
- } while (0)
-
-/* Put into DESTINATION a number stored in two contiguous bytes starting
- at SOURCE. */
-
-#define EXTRACT_NUMBER(destination, source) \
- do { \
- (destination) = *(source) & 0377; \
- (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \
- } while (0)
-
-#ifdef DEBUG
-static void
-extract_number (dest, source)
- int *dest;
- unsigned char *source;
-{
- int temp = SIGN_EXTEND_CHAR (*(source + 1));
- *dest = *source & 0377;
- *dest += temp << 8;
-}
-
-#ifndef EXTRACT_MACROS /* To debug the macros. */
-#undef EXTRACT_NUMBER
-#define EXTRACT_NUMBER(dest, src) extract_number (&dest, src)
-#endif /* not EXTRACT_MACROS */
-
-#endif /* DEBUG */
-
-/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number.
- SOURCE must be an lvalue. */
-
-#define EXTRACT_NUMBER_AND_INCR(destination, source) \
- do { \
- EXTRACT_NUMBER (destination, source); \
- (source) += 2; \
- } while (0)
-
-#ifdef DEBUG
-static void
-extract_number_and_incr (destination, source)
- int *destination;
- unsigned char **source;
-{
- extract_number (destination, *source);
- *source += 2;
-}
-
-#ifndef EXTRACT_MACROS
-#undef EXTRACT_NUMBER_AND_INCR
-#define EXTRACT_NUMBER_AND_INCR(dest, src) \
- extract_number_and_incr (&dest, &src)
-#endif /* not EXTRACT_MACROS */
-
-#endif /* DEBUG */
-
-/* If DEBUG is defined, Regex prints many voluminous messages about what
- it is doing (if the variable `debug' is nonzero). If linked with the
- main program in `iregex.c', you can enter patterns and strings
- interactively. And if linked with the main program in `main.c' and
- the other test files, you can run the already-written tests. */
-
-#ifdef DEBUG
-
-/* We use standard I/O for debugging. */
-#include <stdio.h>
-
-/* It is useful to test things that ``must'' be true when debugging. */
-#include <assert.h>
-
-static int debug = 0;
-
-#define DEBUG_STATEMENT(e) e
-#define DEBUG_PRINT1(x) if (debug) printf (x)
-#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2)
-#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3)
-#define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4)
-#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \
- if (debug) print_partial_compiled_pattern (s, e)
-#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \
- if (debug) print_double_string (w, s1, sz1, s2, sz2)
-
-
-extern void printchar ();
-
-/* Print the fastmap in human-readable form. */
-
-void
-print_fastmap (fastmap)
- char *fastmap;
-{
- unsigned was_a_range = 0;
- unsigned i = 0;
-
- while (i < (1 << BYTEWIDTH))
- {
- if (fastmap[i++])
- {
- was_a_range = 0;
- printchar (i - 1);
- while (i < (1 << BYTEWIDTH) && fastmap[i])
- {
- was_a_range = 1;
- i++;
- }
- if (was_a_range)
- {
- printf ("-");
- printchar (i - 1);
- }
- }
- }
- putchar ('\n');
-}
-
-
-/* Print a compiled pattern string in human-readable form, starting at
- the START pointer into it and ending just before the pointer END. */
-
-void
-print_partial_compiled_pattern (start, end)
- unsigned char *start;
- unsigned char *end;
-{
- int mcnt, mcnt2;
- unsigned char *p = start;
- unsigned char *pend = end;
-
- if (start == NULL)
- {
- printf ("(null)\n");
- return;
- }
-
- /* Loop over pattern commands. */
- while (p < pend)
- {
- switch ((re_opcode_t) *p++)
- {
- case no_op:
- printf ("/no_op");
- break;
-
- case exactn:
- mcnt = *p++;
- printf ("/exactn/%d", mcnt);
- do
- {
- putchar ('/');
- printchar (*p++);
- }
- while (--mcnt);
- break;
-
- case start_memory:
- mcnt = *p++;
- printf ("/start_memory/%d/%d", mcnt, *p++);
- break;
-
- case stop_memory:
- mcnt = *p++;
- printf ("/stop_memory/%d/%d", mcnt, *p++);
- break;
-
- case duplicate:
- printf ("/duplicate/%d", *p++);
- break;
-
- case anychar:
- printf ("/anychar");
- break;
-
- case charset:
- case charset_not:
- {
- register int c;
-
- printf ("/charset%s",
- (re_opcode_t) *(p - 1) == charset_not ? "_not" : "");
-
- assert (p + *p < pend);
-
- for (c = 0; c < *p; c++)
- {
- unsigned bit;
- unsigned char map_byte = p[1 + c];
-
- putchar ('/');
-
- for (bit = 0; bit < BYTEWIDTH; bit++)
- if (map_byte & (1 << bit))
- printchar (c * BYTEWIDTH + bit);
- }
- p += 1 + *p;
- break;
- }
-
- case begline:
- printf ("/begline");
- break;
-
- case endline:
- printf ("/endline");
- break;
-
- case on_failure_jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/on_failure_jump/0/%d", mcnt);
- break;
-
- case on_failure_keep_string_jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/on_failure_keep_string_jump/0/%d", mcnt);
- break;
-
- case dummy_failure_jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/dummy_failure_jump/0/%d", mcnt);
- break;
-
- case push_dummy_failure:
- printf ("/push_dummy_failure");
- break;
-
- case maybe_pop_jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/maybe_pop_jump/0/%d", mcnt);
- break;
-
- case pop_failure_jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/pop_failure_jump/0/%d", mcnt);
- break;
-
- case jump_past_alt:
- extract_number_and_incr (&mcnt, &p);
- printf ("/jump_past_alt/0/%d", mcnt);
- break;
-
- case jump:
- extract_number_and_incr (&mcnt, &p);
- printf ("/jump/0/%d", mcnt);
- break;
-
- case succeed_n:
- extract_number_and_incr (&mcnt, &p);
- extract_number_and_incr (&mcnt2, &p);
- printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2);
- break;
-
- case jump_n:
- extract_number_and_incr (&mcnt, &p);
- extract_number_and_incr (&mcnt2, &p);
- printf ("/jump_n/0/%d/0/%d", mcnt, mcnt2);
- break;
-
- case set_number_at:
- extract_number_and_incr (&mcnt, &p);
- extract_number_and_incr (&mcnt2, &p);
- printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2);
- break;
-
- case wordbound:
- printf ("/wordbound");
- break;
-
- case notwordbound:
- printf ("/notwordbound");
- break;
-
- case wordbeg:
- printf ("/wordbeg");
- break;
-
- case wordend:
- printf ("/wordend");
-
-#ifdef emacs
- case before_dot:
- printf ("/before_dot");
- break;
-
- case at_dot:
- printf ("/at_dot");
- break;
-
- case after_dot:
- printf ("/after_dot");
- break;
-
- case syntaxspec:
- printf ("/syntaxspec");
- mcnt = *p++;
- printf ("/%d", mcnt);
- break;
-
- case notsyntaxspec:
- printf ("/notsyntaxspec");
- mcnt = *p++;
- printf ("/%d", mcnt);
- break;
-#endif /* emacs */
-
- case wordchar:
- printf ("/wordchar");
- break;
-
- case notwordchar:
- printf ("/notwordchar");
- break;
-
- case begbuf:
- printf ("/begbuf");
- break;
-
- case endbuf:
- printf ("/endbuf");
- break;
-
- default:
- printf ("?%d", *(p-1));
- }
- }
- printf ("/\n");
-}
-
-
-void
-print_compiled_pattern (bufp)
- struct re_pattern_buffer *bufp;
-{
- unsigned char *buffer = bufp->buffer;
-
- print_partial_compiled_pattern (buffer, buffer + bufp->used);
- printf ("%d bytes used/%d bytes allocated.\n", bufp->used, bufp->allocated);
-
- if (bufp->fastmap_accurate && bufp->fastmap)
- {
- printf ("fastmap: ");
- print_fastmap (bufp->fastmap);
- }
-
- printf ("re_nsub: %d\t", bufp->re_nsub);
- printf ("regs_alloc: %d\t", bufp->regs_allocated);
- printf ("can_be_null: %d\t", bufp->can_be_null);
- printf ("newline_anchor: %d\n", bufp->newline_anchor);
- printf ("no_sub: %d\t", bufp->no_sub);
- printf ("not_bol: %d\t", bufp->not_bol);
- printf ("not_eol: %d\t", bufp->not_eol);
- printf ("syntax: %d\n", bufp->syntax);
- /* Perhaps we should print the translate table? */
-}
-
-
-void
-print_double_string (where, string1, size1, string2, size2)
- const char *where;
- const char *string1;
- const char *string2;
- int size1;
- int size2;
-{
- unsigned this_char;
-
- if (where == NULL)
- printf ("(null)");
- else
- {
- if (FIRST_STRING_P (where))
- {
- for (this_char = where - string1; this_char < size1; this_char++)
- printchar (string1[this_char]);
-
- where = string2;
- }
-
- for (this_char = where - string2; this_char < size2; this_char++)
- printchar (string2[this_char]);
- }
-}
-
-#else /* not DEBUG */
-
-#undef assert
-#define assert(e)
-
-#define DEBUG_STATEMENT(e)
-#define DEBUG_PRINT1(x)
-#define DEBUG_PRINT2(x1, x2)
-#define DEBUG_PRINT3(x1, x2, x3)
-#define DEBUG_PRINT4(x1, x2, x3, x4)
-#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e)
-#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)
-
-#endif /* not DEBUG */
-
-/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
- also be assigned to arbitrarily: each pattern buffer stores its own
- syntax, so it can be changed between regex compilations. */
-reg_syntax_t re_syntax_options = RE_SYNTAX_EMACS;
-
-
-/* Specify the precise syntax of regexps for compilation. This provides
- for compatibility for various utilities which historically have
- different, incompatible syntaxes.
-
- The argument SYNTAX is a bit mask comprised of the various bits
- defined in regex.h. We return the old syntax. */
-
-reg_syntax_t
-re_set_syntax (syntax)
- reg_syntax_t syntax;
-{
- reg_syntax_t ret = re_syntax_options;
-
- re_syntax_options = syntax;
- return ret;
-}
-
-/* This table gives an error message for each of the error codes listed
- in regex.h. Obviously the order here has to be same as there. */
-
-static const char *re_error_msg[] =
- { NULL, /* REG_NOERROR */
- "No match", /* REG_NOMATCH */
- "Invalid regular expression", /* REG_BADPAT */
- "Invalid collation character", /* REG_ECOLLATE */
- "Invalid character class name", /* REG_ECTYPE */
- "Trailing backslash", /* REG_EESCAPE */
- "Invalid back reference", /* REG_ESUBREG */
- "Unmatched [ or [^", /* REG_EBRACK */
- "Unmatched ( or \\(", /* REG_EPAREN */
- "Unmatched \\{", /* REG_EBRACE */
- "Invalid content of \\{\\}", /* REG_BADBR */
- "Invalid range end", /* REG_ERANGE */
- "Memory exhausted", /* REG_ESPACE */
- "Invalid preceding regular expression", /* REG_BADRPT */
- "Premature end of regular expression", /* REG_EEND */
- "Regular expression too big", /* REG_ESIZE */
- "Unmatched ) or \\)", /* REG_ERPAREN */
- };
-
-/* Subroutine declarations and macros for regex_compile. */
-
-static void store_op1 (), store_op2 ();
-static void insert_op1 (), insert_op2 ();
-static boolean at_begline_loc_p (), at_endline_loc_p ();
-static boolean group_in_compile_stack ();
-static reg_errcode_t compile_range ();
-
-/* Fetch the next character in the uncompiled pattern---translating it
- if necessary. Also cast from a signed character in the constant
- string passed to us by the user to an unsigned char that we can use
- as an array index (in, e.g., `translate'). */
-#define PATFETCH(c) \
- do {if (p == pend) return REG_EEND; \
- c = (unsigned char) *p++; \
- if (translate) c = translate[c]; \
- } while (0)
-
-/* Fetch the next character in the uncompiled pattern, with no
- translation. */
-#define PATFETCH_RAW(c) \
- do {if (p == pend) return REG_EEND; \
- c = (unsigned char) *p++; \
- } while (0)
-
-/* Go backwards one character in the pattern. */
-#define PATUNFETCH p--
-
-
-/* If `translate' is non-null, return translate[D], else just D. We
- cast the subscript to translate because some data is declared as
- `char *', to avoid warnings when a string constant is passed. But
- when we use a character as a subscript we must make it unsigned. */
-#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d))
-
-
-/* Macros for outputting the compiled pattern into `buffer'. */
-
-/* If the buffer isn't allocated when it comes in, use this. */
-#define INIT_BUF_SIZE 32
-
-/* Make sure we have at least N more bytes of space in buffer. */
-#define GET_BUFFER_SPACE(n) \
- while (b - bufp->buffer + (n) > bufp->allocated) \
- EXTEND_BUFFER ()
-
-/* Make sure we have one more byte of buffer space and then add C to it. */
-#define BUF_PUSH(c) \
- do { \
- GET_BUFFER_SPACE (1); \
- *b++ = (unsigned char) (c); \
- } while (0)
-
-
-/* Ensure we have two more bytes of buffer space and then append C1 and C2. */
-#define BUF_PUSH_2(c1, c2) \
- do { \
- GET_BUFFER_SPACE (2); \
- *b++ = (unsigned char) (c1); \
- *b++ = (unsigned char) (c2); \
- } while (0)
-
-
-/* As with BUF_PUSH_2, except for three bytes. */
-#define BUF_PUSH_3(c1, c2, c3) \
- do { \
- GET_BUFFER_SPACE (3); \
- *b++ = (unsigned char) (c1); \
- *b++ = (unsigned char) (c2); \
- *b++ = (unsigned char) (c3); \
- } while (0)
-
-
-/* Store a jump with opcode OP at LOC to location TO. We store a
- relative address offset by the three bytes the jump itself occupies. */
-#define STORE_JUMP(op, loc, to) \
- store_op1 (op, loc, (to) - (loc) - 3)
-
-/* Likewise, for a two-argument jump. */
-#define STORE_JUMP2(op, loc, to, arg) \
- store_op2 (op, loc, (to) - (loc) - 3, arg)
-
-/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */
-#define INSERT_JUMP(op, loc, to) \
- insert_op1 (op, loc, (to) - (loc) - 3, b)
-
-/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */
-#define INSERT_JUMP2(op, loc, to, arg) \
- insert_op2 (op, loc, (to) - (loc) - 3, arg, b)
-
-
-/* This is not an arbitrary limit: the arguments which represent offsets
- into the pattern are two bytes long. So if 2^16 bytes turns out to
- be too small, many things would have to change. */
-#define MAX_BUF_SIZE (1L << 16)
-
-
-/* Extend the buffer by twice its current size via realloc and
- reset the pointers that pointed into the old block to point to the
- correct places in the new one. If extending the buffer results in it
- being larger than MAX_BUF_SIZE, then flag memory exhausted. */
-#define EXTEND_BUFFER() \
- do { \
- unsigned char *old_buffer = bufp->buffer; \
- if (bufp->allocated == MAX_BUF_SIZE) \
- return REG_ESIZE; \
- bufp->allocated <<= 1; \
- if (bufp->allocated > MAX_BUF_SIZE) \
- bufp->allocated = MAX_BUF_SIZE; \
- bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\
- if (bufp->buffer == NULL) \
- return REG_ESPACE; \
- /* If the buffer moved, move all the pointers into it. */ \
- if (old_buffer != bufp->buffer) \
- { \
- b = (b - old_buffer) + bufp->buffer; \
- begalt = (begalt - old_buffer) + bufp->buffer; \
- if (fixup_alt_jump) \
- fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\
- if (laststart) \
- laststart = (laststart - old_buffer) + bufp->buffer; \
- if (pending_exact) \
- pending_exact = (pending_exact - old_buffer) + bufp->buffer; \
- } \
- } while (0)
-
-
-/* Since we have one byte reserved for the register number argument to
- {start,stop}_memory, the maximum number of groups we can report
- things about is what fits in that byte. */
-#define MAX_REGNUM 255
-
-/* But patterns can have more than `MAX_REGNUM' registers. We just
- ignore the excess. */
-typedef unsigned regnum_t;
-
-
-/* Macros for the compile stack. */
-
-/* Since offsets can go either forwards or backwards, this type needs to
- be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */
-typedef int pattern_offset_t;
-
-typedef struct
-{
- pattern_offset_t begalt_offset;
- pattern_offset_t fixup_alt_jump;
- pattern_offset_t inner_group_offset;
- pattern_offset_t laststart_offset;
- regnum_t regnum;
-} compile_stack_elt_t;
-
-
-typedef struct
-{
- compile_stack_elt_t *stack;
- unsigned size;
- unsigned avail; /* Offset of next open position. */
-} compile_stack_type;
-
-
-#define INIT_COMPILE_STACK_SIZE 32
-
-#define COMPILE_STACK_EMPTY (compile_stack.avail == 0)
-#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size)
-
-/* The next available element. */
-#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail])
-
-
-/* Set the bit for character C in a list. */
-#define SET_LIST_BIT(c) \
- (b[((unsigned char) (c)) / BYTEWIDTH] \
- |= 1 << (((unsigned char) c) % BYTEWIDTH))
-
-
-/* Get the next unsigned number in the uncompiled pattern. */
-#define GET_UNSIGNED_NUMBER(num) \
- { if (p != pend) \
- { \
- PATFETCH (c); \
- while (ISDIGIT (c)) \
- { \
- if (num < 0) \
- num = 0; \
- num = num * 10 + c - '0'; \
- if (p == pend) \
- break; \
- PATFETCH (c); \
- } \
- } \
- }
-
-#define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */
-
-#define IS_CHAR_CLASS(string) \
- (STREQ (string, "alpha") || STREQ (string, "upper") \
- || STREQ (string, "lower") || STREQ (string, "digit") \
- || STREQ (string, "alnum") || STREQ (string, "xdigit") \
- || STREQ (string, "space") || STREQ (string, "print") \
- || STREQ (string, "punct") || STREQ (string, "graph") \
- || STREQ (string, "cntrl") || STREQ (string, "blank"))
-
-/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX.
- Returns one of error codes defined in `regex.h', or zero for success.
-
- Assumes the `allocated' (and perhaps `buffer') and `translate'
- fields are set in BUFP on entry.
-
- If it succeeds, results are put in BUFP (if it returns an error, the
- contents of BUFP are undefined):
- `buffer' is the compiled pattern;
- `syntax' is set to SYNTAX;
- `used' is set to the length of the compiled pattern;
- `fastmap_accurate' is zero;
- `re_nsub' is the number of subexpressions in PATTERN;
- `not_bol' and `not_eol' are zero;
-
- The `fastmap' and `newline_anchor' fields are neither
- examined nor set. */
-
-static reg_errcode_t
-regex_compile (pattern, size, syntax, bufp)
- const char *pattern;
- int size;
- reg_syntax_t syntax;
- struct re_pattern_buffer *bufp;
-{
- /* We fetch characters from PATTERN here. Even though PATTERN is
- `char *' (i.e., signed), we declare these variables as unsigned, so
- they can be reliably used as array indices. */
- register unsigned char c, c1;
-
- /* A random temporary spot in PATTERN. */
- const char *p1;
-
- /* Points to the end of the buffer, where we should append. */
- register unsigned char *b;
-
- /* Keeps track of unclosed groups. */
- compile_stack_type compile_stack;
-
- /* Points to the current (ending) position in the pattern. */
- const char *p = pattern;
- const char *pend = pattern + size;
-
- /* How to translate the characters in the pattern. */
- char *translate = bufp->translate;
-
- /* Address of the count-byte of the most recently inserted `exactn'
- command. This makes it possible to tell if a new exact-match
- character can be added to that command or if the character requires
- a new `exactn' command. */
- unsigned char *pending_exact = 0;
-
- /* Address of start of the most recently finished expression.
- This tells, e.g., postfix * where to find the start of its
- operand. Reset at the beginning of groups and alternatives. */
- unsigned char *laststart = 0;
-
- /* Address of beginning of regexp, or inside of last group. */
- unsigned char *begalt;
-
- /* Place in the uncompiled pattern (i.e., the {) to
- which to go back if the interval is invalid. */
- const char *beg_interval;
-
- /* Address of the place where a forward jump should go to the end of
- the containing expression. Each alternative of an `or' -- except the
- last -- ends with a forward jump of this sort. */
- unsigned char *fixup_alt_jump = 0;
-
- /* Counts open-groups as they are encountered. Remembered for the
- matching close-group on the compile stack, so the same register
- number is put in the stop_memory as the start_memory. */
- regnum_t regnum = 0;
-
-#ifdef DEBUG
- DEBUG_PRINT1 ("\nCompiling pattern: ");
- if (debug)
- {
- unsigned debug_count;
-
- for (debug_count = 0; debug_count < size; debug_count++)
- printchar (pattern[debug_count]);
- putchar ('\n');
- }
-#endif /* DEBUG */
-
- /* Initialize the compile stack. */
- compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t);
- if (compile_stack.stack == NULL)
- return REG_ESPACE;
-
- compile_stack.size = INIT_COMPILE_STACK_SIZE;
- compile_stack.avail = 0;
-
- /* Initialize the pattern buffer. */
- bufp->syntax = syntax;
- bufp->fastmap_accurate = 0;
- bufp->not_bol = bufp->not_eol = 0;
-
- /* Set `used' to zero, so that if we return an error, the pattern
- printer (for debugging) will think there's no pattern. We reset it
- at the end. */
- bufp->used = 0;
-
- /* Always count groups, whether or not bufp->no_sub is set. */
- bufp->re_nsub = 0;
-
-#if !defined (emacs) && !defined (SYNTAX_TABLE)
- /* Initialize the syntax table. */
- init_syntax_once ();
-#endif
-
- if (bufp->allocated == 0)
- {
- if (bufp->buffer)
- { /* If zero allocated, but buffer is non-null, try to realloc
- enough space. This loses if buffer's address is bogus, but
- that is the user's responsibility. */
- RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char);
- }
- else
- { /* Caller did not allocate a buffer. Do it for them. */
- bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char);
- }
- if (!bufp->buffer) return REG_ESPACE;
-
- bufp->allocated = INIT_BUF_SIZE;
- }
-
- begalt = b = bufp->buffer;
-
- /* Loop through the uncompiled pattern until we're at the end. */
- while (p != pend)
- {
- PATFETCH (c);
-
- switch (c)
- {
- case '^':
- {
- if ( /* If at start of pattern, it's an operator. */
- p == pattern + 1
- /* If context independent, it's an operator. */
- || syntax & RE_CONTEXT_INDEP_ANCHORS
- /* Otherwise, depends on what's come before. */
- || at_begline_loc_p (pattern, p, syntax))
- BUF_PUSH (begline);
- else
- goto normal_char;
- }
- break;
-
-
- case '$':
- {
- if ( /* If at end of pattern, it's an operator. */
- p == pend
- /* If context independent, it's an operator. */
- || syntax & RE_CONTEXT_INDEP_ANCHORS
- /* Otherwise, depends on what's next. */
- || at_endline_loc_p (p, pend, syntax))
- BUF_PUSH (endline);
- else
- goto normal_char;
- }
- break;
-
-
- case '+':
- case '?':
- if ((syntax & RE_BK_PLUS_QM)
- || (syntax & RE_LIMITED_OPS))
- goto normal_char;
- handle_plus:
- case '*':
- /* If there is no previous pattern... */
- if (!laststart)
- {
- if (syntax & RE_CONTEXT_INVALID_OPS)
- return REG_BADRPT;
- else if (!(syntax & RE_CONTEXT_INDEP_OPS))
- goto normal_char;
- }
-
- {
- /* Are we optimizing this jump? */
- boolean keep_string_p = false;
-
- /* 1 means zero (many) matches is allowed. */
- char zero_times_ok = 0, many_times_ok = 0;
-
- /* If there is a sequence of repetition chars, collapse it
- down to just one (the right one). We can't combine
- interval operators with these because of, e.g., `a{2}*',
- which should only match an even number of `a's. */
-
- for (;;)
- {
- zero_times_ok |= c != '+';
- many_times_ok |= c != '?';
-
- if (p == pend)
- break;
-
- PATFETCH (c);
-
- if (c == '*'
- || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?')))
- ;
-
- else if (syntax & RE_BK_PLUS_QM && c == '\\')
- {
- if (p == pend) return REG_EESCAPE;
-
- PATFETCH (c1);
- if (!(c1 == '+' || c1 == '?'))
- {
- PATUNFETCH;
- PATUNFETCH;
- break;
- }
-
- c = c1;
- }
- else
- {
- PATUNFETCH;
- break;
- }
-
- /* If we get here, we found another repeat character. */
- }
-
- /* Star, etc. applied to an empty pattern is equivalent
- to an empty pattern. */
- if (!laststart)
- break;
-
- /* Now we know whether or not zero matches is allowed
- and also whether or not two or more matches is allowed. */
- if (many_times_ok)
- { /* More than one repetition is allowed, so put in at the
- end a backward relative jump from `b' to before the next
- jump we're going to put in below (which jumps from
- laststart to after this jump).
-
- But if we are at the `*' in the exact sequence `.*\n',
- insert an unconditional jump backwards to the .,
- instead of the beginning of the loop. This way we only
- push a failure point once, instead of every time
- through the loop. */
- assert (p - 1 > pattern);
-
- /* Allocate the space for the jump. */
- GET_BUFFER_SPACE (3);
-
- /* We know we are not at the first character of the pattern,
- because laststart was nonzero. And we've already
- incremented `p', by the way, to be the character after
- the `*'. Do we have to do something analogous here
- for null bytes, because of RE_DOT_NOT_NULL? */
- if (TRANSLATE (*(p - 2)) == TRANSLATE ('.')
- && zero_times_ok
- && p < pend && TRANSLATE (*p) == TRANSLATE ('\n')
- && !(syntax & RE_DOT_NEWLINE))
- { /* We have .*\n. */
- STORE_JUMP (jump, b, laststart);
- keep_string_p = true;
- }
- else
- /* Anything else. */
- STORE_JUMP (maybe_pop_jump, b, laststart - 3);
-
- /* We've added more stuff to the buffer. */
- b += 3;
- }
-
- /* On failure, jump from laststart to b + 3, which will be the
- end of the buffer after this jump is inserted. */
- GET_BUFFER_SPACE (3);
- INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump
- : on_failure_jump,
- laststart, b + 3);
- pending_exact = 0;
- b += 3;
-
- if (!zero_times_ok)
- {
- /* At least one repetition is required, so insert a
- `dummy_failure_jump' before the initial
- `on_failure_jump' instruction of the loop. This
- effects a skip over that instruction the first time
- we hit that loop. */
- GET_BUFFER_SPACE (3);
- INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6);
- b += 3;
- }
- }
- break;
-
-
- case '.':
- laststart = b;
- BUF_PUSH (anychar);
- break;
-
-
- case '[':
- {
- boolean had_char_class = false;
-
- if (p == pend) return REG_EBRACK;
-
- /* Ensure that we have enough space to push a charset: the
- opcode, the length count, and the bitset; 34 bytes in all. */
- GET_BUFFER_SPACE (34);
-
- laststart = b;
-
- /* We test `*p == '^' twice, instead of using an if
- statement, so we only need one BUF_PUSH. */
- BUF_PUSH (*p == '^' ? charset_not : charset);
- if (*p == '^')
- p++;
-
- /* Remember the first position in the bracket expression. */
- p1 = p;
-
- /* Push the number of bytes in the bitmap. */
- BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH);
-
- /* Clear the whole map. */
- bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH);
-
- /* charset_not matches newline according to a syntax bit. */
- if ((re_opcode_t) b[-2] == charset_not
- && (syntax & RE_HAT_LISTS_NOT_NEWLINE))
- SET_LIST_BIT ('\n');
-
- /* Read in characters and ranges, setting map bits. */
- for (;;)
- {
- if (p == pend) return REG_EBRACK;
-
- PATFETCH (c);
-
- /* \ might escape characters inside [...] and [^...]. */
- if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\')
- {
- if (p == pend) return REG_EESCAPE;
-
- PATFETCH (c1);
- SET_LIST_BIT (c1);
- continue;
- }
-
- /* Could be the end of the bracket expression. If it's
- not (i.e., when the bracket expression is `[]' so
- far), the ']' character bit gets set way below. */
- if (c == ']' && p != p1 + 1)
- break;
-
- /* Look ahead to see if it's a range when the last thing
- was a character class. */
- if (had_char_class && c == '-' && *p != ']')
- return REG_ERANGE;
-
- /* Look ahead to see if it's a range when the last thing
- was a character: if this is a hyphen not at the
- beginning or the end of a list, then it's the range
- operator. */
- if (c == '-'
- && !(p - 2 >= pattern && p[-2] == '[')
- && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^')
- && *p != ']')
- {
- reg_errcode_t ret
- = compile_range (&p, pend, translate, syntax, b);
- if (ret != REG_NOERROR) return ret;
- }
-
- else if (p[0] == '-' && p[1] != ']')
- { /* This handles ranges made up of characters only. */
- reg_errcode_t ret;
-
- /* Move past the `-'. */
- PATFETCH (c1);
-
- ret = compile_range (&p, pend, translate, syntax, b);
- if (ret != REG_NOERROR) return ret;
- }
-
- /* See if we're at the beginning of a possible character
- class. */
-
- else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':')
- { /* Leave room for the null. */
- char str[CHAR_CLASS_MAX_LENGTH + 1];
-
- PATFETCH (c);
- c1 = 0;
-
- /* If pattern is `[[:'. */
- if (p == pend) return REG_EBRACK;
-
- for (;;)
- {
- PATFETCH (c);
- if (c == ':' || c == ']' || p == pend
- || c1 == CHAR_CLASS_MAX_LENGTH)
- break;
- str[c1++] = c;
- }
- str[c1] = '\0';
-
- /* If isn't a word bracketed by `[:' and:`]':
- undo the ending character, the letters, and leave
- the leading `:' and `[' (but set bits for them). */
- if (c == ':' && *p == ']')
- {
- int ch;
- boolean is_alnum = STREQ (str, "alnum");
- boolean is_alpha = STREQ (str, "alpha");
- boolean is_blank = STREQ (str, "blank");
- boolean is_cntrl = STREQ (str, "cntrl");
- boolean is_digit = STREQ (str, "digit");
- boolean is_graph = STREQ (str, "graph");
- boolean is_lower = STREQ (str, "lower");
- boolean is_print = STREQ (str, "print");
- boolean is_punct = STREQ (str, "punct");
- boolean is_space = STREQ (str, "space");
- boolean is_upper = STREQ (str, "upper");
- boolean is_xdigit = STREQ (str, "xdigit");
-
- if (!IS_CHAR_CLASS (str)) return REG_ECTYPE;
-
- /* Throw away the ] at the end of the character
- class. */
- PATFETCH (c);
-
- if (p == pend) return REG_EBRACK;
-
- for (ch = 0; ch < 1 << BYTEWIDTH; ch++)
- {
- if ( (is_alnum && ISALNUM (ch))
- || (is_alpha && ISALPHA (ch))
- || (is_blank && ISBLANK (ch))
- || (is_cntrl && ISCNTRL (ch))
- || (is_digit && ISDIGIT (ch))
- || (is_graph && ISGRAPH (ch))
- || (is_lower && ISLOWER (ch))
- || (is_print && ISPRINT (ch))
- || (is_punct && ISPUNCT (ch))
- || (is_space && ISSPACE (ch))
- || (is_upper && ISUPPER (ch))
- || (is_xdigit && ISXDIGIT (ch)))
- SET_LIST_BIT (ch);
- }
- had_char_class = true;
- }
- else
- {
- c1++;
- while (c1--)
- PATUNFETCH;
- SET_LIST_BIT ('[');
- SET_LIST_BIT (':');
- had_char_class = false;
- }
- }
- else
- {
- had_char_class = false;
- SET_LIST_BIT (c);
- }
- }
-
- /* Discard any (non)matching list bytes that are all 0 at the
- end of the map. Decrease the map-length byte too. */
- while ((int) b[-1] > 0 && b[b[-1] - 1] == 0)
- b[-1]--;
- b += b[-1];
- }
- break;
-
-
- case '(':
- if (syntax & RE_NO_BK_PARENS)
- goto handle_open;
- else
- goto normal_char;
-
-
- case ')':
- if (syntax & RE_NO_BK_PARENS)
- goto handle_close;
- else
- goto normal_char;
-
-
- case '\n':
- if (syntax & RE_NEWLINE_ALT)
- goto handle_alt;
- else
- goto normal_char;
-
-
- case '|':
- if (syntax & RE_NO_BK_VBAR)
- goto handle_alt;
- else
- goto normal_char;
-
-
- case '{':
- if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES)
- goto handle_interval;
- else
- goto normal_char;
-
-
- case '\\':
- if (p == pend) return REG_EESCAPE;
-
- /* Do not translate the character after the \, so that we can
- distinguish, e.g., \B from \b, even if we normally would
- translate, e.g., B to b. */
- PATFETCH_RAW (c);
-
- switch (c)
- {
- case '(':
- if (syntax & RE_NO_BK_PARENS)
- goto normal_backslash;
-
- handle_open:
- bufp->re_nsub++;
- regnum++;
-
- if (COMPILE_STACK_FULL)
- {
- RETALLOC (compile_stack.stack, compile_stack.size << 1,
- compile_stack_elt_t);
- if (compile_stack.stack == NULL) return REG_ESPACE;
-
- compile_stack.size <<= 1;
- }
-
- /* These are the values to restore when we hit end of this
- group. They are all relative offsets, so that if the
- whole pattern moves because of realloc, they will still
- be valid. */
- COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer;
- COMPILE_STACK_TOP.fixup_alt_jump
- = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0;
- COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer;
- COMPILE_STACK_TOP.regnum = regnum;
-
- /* We will eventually replace the 0 with the number of
- groups inner to this one. But do not push a
- start_memory for groups beyond the last one we can
- represent in the compiled pattern. */
- if (regnum <= MAX_REGNUM)
- {
- COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2;
- BUF_PUSH_3 (start_memory, regnum, 0);
- }
-
- compile_stack.avail++;
-
- fixup_alt_jump = 0;
- laststart = 0;
- begalt = b;
- /* If we've reached MAX_REGNUM groups, then this open
- won't actually generate any code, so we'll have to
- clear pending_exact explicitly. */
- pending_exact = 0;
- break;
-
-
- case ')':
- if (syntax & RE_NO_BK_PARENS) goto normal_backslash;
-
- if (COMPILE_STACK_EMPTY)
- {
- if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
- goto normal_backslash;
- else
- return REG_ERPAREN;
- }
-
- handle_close:
- if (fixup_alt_jump)
- { /* Push a dummy failure point at the end of the
- alternative for a possible future
- `pop_failure_jump' to pop. See comments at
- `push_dummy_failure' in `re_match_2'. */
- BUF_PUSH (push_dummy_failure);
-
- /* We allocated space for this jump when we assigned
- to `fixup_alt_jump', in the `handle_alt' case below. */
- STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1);
- }
-
- /* See similar code for backslashed left paren above. */
- if (COMPILE_STACK_EMPTY)
- {
- if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
- goto normal_char;
- else
- return REG_ERPAREN;
- }
-
- /* Since we just checked for an empty stack above, this
- ``can't happen''. */
- assert (compile_stack.avail != 0);
- {
- /* We don't just want to restore into `regnum', because
- later groups should continue to be numbered higher,
- as in `(ab)c(de)' -- the second group is #2. */
- regnum_t this_group_regnum;
-
- compile_stack.avail--;
- begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset;
- fixup_alt_jump
- = COMPILE_STACK_TOP.fixup_alt_jump
- ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1
- : 0;
- laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset;
- this_group_regnum = COMPILE_STACK_TOP.regnum;
- /* If we've reached MAX_REGNUM groups, then this open
- won't actually generate any code, so we'll have to
- clear pending_exact explicitly. */
- pending_exact = 0;
-
- /* We're at the end of the group, so now we know how many
- groups were inside this one. */
- if (this_group_regnum <= MAX_REGNUM)
- {
- unsigned char *inner_group_loc
- = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset;
-
- *inner_group_loc = regnum - this_group_regnum;
- BUF_PUSH_3 (stop_memory, this_group_regnum,
- regnum - this_group_regnum);
- }
- }
- break;
-
-
- case '|': /* `\|'. */
- if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR)
- goto normal_backslash;
- handle_alt:
- if (syntax & RE_LIMITED_OPS)
- goto normal_char;
-
- /* Insert before the previous alternative a jump which
- jumps to this alternative if the former fails. */
- GET_BUFFER_SPACE (3);
- INSERT_JUMP (on_failure_jump, begalt, b + 6);
- pending_exact = 0;
- b += 3;
-
- /* The alternative before this one has a jump after it
- which gets executed if it gets matched. Adjust that
- jump so it will jump to this alternative's analogous
- jump (put in below, which in turn will jump to the next
- (if any) alternative's such jump, etc.). The last such
- jump jumps to the correct final destination. A picture:
- _____ _____
- | | | |
- | v | v
- a | b | c
-
- If we are at `b', then fixup_alt_jump right now points to a
- three-byte space after `a'. We'll put in the jump, set
- fixup_alt_jump to right after `b', and leave behind three
- bytes which we'll fill in when we get to after `c'. */
-
- if (fixup_alt_jump)
- STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
-
- /* Mark and leave space for a jump after this alternative,
- to be filled in later either by next alternative or
- when know we're at the end of a series of alternatives. */
- fixup_alt_jump = b;
- GET_BUFFER_SPACE (3);
- b += 3;
-
- laststart = 0;
- begalt = b;
- break;
-
-
- case '{':
- /* If \{ is a literal. */
- if (!(syntax & RE_INTERVALS)
- /* If we're at `\{' and it's not the open-interval
- operator. */
- || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
- || (p - 2 == pattern && p == pend))
- goto normal_backslash;
-
- handle_interval:
- {
- /* If got here, then the syntax allows intervals. */
-
- /* At least (most) this many matches must be made. */
- int lower_bound = -1, upper_bound = -1;
-
- beg_interval = p - 1;
-
- if (p == pend)
- {
- if (syntax & RE_NO_BK_BRACES)
- goto unfetch_interval;
- else
- return REG_EBRACE;
- }
-
- GET_UNSIGNED_NUMBER (lower_bound);
-
- if (c == ',')
- {
- GET_UNSIGNED_NUMBER (upper_bound);
- if (upper_bound < 0) upper_bound = RE_DUP_MAX;
- }
- else
- /* Interval such as `{1}' => match exactly once. */
- upper_bound = lower_bound;
-
- if (lower_bound < 0 || upper_bound > RE_DUP_MAX
- || lower_bound > upper_bound)
- {
- if (syntax & RE_NO_BK_BRACES)
- goto unfetch_interval;
- else
- return REG_BADBR;
- }
-
- if (!(syntax & RE_NO_BK_BRACES))
- {
- if (c != '\\') return REG_EBRACE;
-
- PATFETCH (c);
- }
-
- if (c != '}')
- {
- if (syntax & RE_NO_BK_BRACES)
- goto unfetch_interval;
- else
- return REG_BADBR;
- }
-
- /* We just parsed a valid interval. */
-
- /* If it's invalid to have no preceding re. */
- if (!laststart)
- {
- if (syntax & RE_CONTEXT_INVALID_OPS)
- return REG_BADRPT;
- else if (syntax & RE_CONTEXT_INDEP_OPS)
- laststart = b;
- else
- goto unfetch_interval;
- }
-
- /* If the upper bound is zero, don't want to succeed at
- all; jump from `laststart' to `b + 3', which will be
- the end of the buffer after we insert the jump. */
- if (upper_bound == 0)
- {
- GET_BUFFER_SPACE (3);
- INSERT_JUMP (jump, laststart, b + 3);
- b += 3;
- }
-
- /* Otherwise, we have a nontrivial interval. When
- we're all done, the pattern will look like:
- set_number_at <jump count> <upper bound>
- set_number_at <succeed_n count> <lower bound>
- succeed_n <after jump addr> <succeed_n count>
- <body of loop>
- jump_n <succeed_n addr> <jump count>
- (The upper bound and `jump_n' are omitted if
- `upper_bound' is 1, though.) */
- else
- { /* If the upper bound is > 1, we need to insert
- more at the end of the loop. */
- unsigned nbytes = 10 + (upper_bound > 1) * 10;
-
- GET_BUFFER_SPACE (nbytes);
-
- /* Initialize lower bound of the `succeed_n', even
- though it will be set during matching by its
- attendant `set_number_at' (inserted next),
- because `re_compile_fastmap' needs to know.
- Jump to the `jump_n' we might insert below. */
- INSERT_JUMP2 (succeed_n, laststart,
- b + 5 + (upper_bound > 1) * 5,
- lower_bound);
- b += 5;
-
- /* Code to initialize the lower bound. Insert
- before the `succeed_n'. The `5' is the last two
- bytes of this `set_number_at', plus 3 bytes of
- the following `succeed_n'. */
- insert_op2 (set_number_at, laststart, 5, lower_bound, b);
- b += 5;
-
- if (upper_bound > 1)
- { /* More than one repetition is allowed, so
- append a backward jump to the `succeed_n'
- that starts this interval.
-
- When we've reached this during matching,
- we'll have matched the interval once, so
- jump back only `upper_bound - 1' times. */
- STORE_JUMP2 (jump_n, b, laststart + 5,
- upper_bound - 1);
- b += 5;
-
- /* The location we want to set is the second
- parameter of the `jump_n'; that is `b-2' as
- an absolute address. `laststart' will be
- the `set_number_at' we're about to insert;
- `laststart+3' the number to set, the source
- for the relative address. But we are
- inserting into the middle of the pattern --
- so everything is getting moved up by 5.
- Conclusion: (b - 2) - (laststart + 3) + 5,
- i.e., b - laststart.
-
- We insert this at the beginning of the loop
- so that if we fail during matching, we'll
- reinitialize the bounds. */
- insert_op2 (set_number_at, laststart, b - laststart,
- upper_bound - 1, b);
- b += 5;
- }
- }
- pending_exact = 0;
- beg_interval = NULL;
- }
- break;
-
- unfetch_interval:
- /* If an invalid interval, match the characters as literals. */
- assert (beg_interval);
- p = beg_interval;
- beg_interval = NULL;
-
- /* normal_char and normal_backslash need `c'. */
- PATFETCH (c);
-
- if (!(syntax & RE_NO_BK_BRACES))
- {
- if (p > pattern && p[-1] == '\\')
- goto normal_backslash;
- }
- goto normal_char;
-
-#ifdef emacs
- /* There is no way to specify the before_dot and after_dot
- operators. rms says this is ok. --karl */
- case '=':
- BUF_PUSH (at_dot);
- break;
-
- case 's':
- laststart = b;
- PATFETCH (c);
- BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]);
- break;
-
- case 'S':
- laststart = b;
- PATFETCH (c);
- BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]);
- break;
-#endif /* emacs */
-
-
- case 'w':
- laststart = b;
- BUF_PUSH (wordchar);
- break;
-
-
- case 'W':
- laststart = b;
- BUF_PUSH (notwordchar);
- break;
-
-
- case '<':
- BUF_PUSH (wordbeg);
- break;
-
- case '>':
- BUF_PUSH (wordend);
- break;
-
- case 'b':
- BUF_PUSH (wordbound);
- break;
-
- case 'B':
- BUF_PUSH (notwordbound);
- break;
-
- case '`':
- BUF_PUSH (begbuf);
- break;
-
- case '\'':
- BUF_PUSH (endbuf);
- break;
-
- case '1': case '2': case '3': case '4': case '5':
- case '6': case '7': case '8': case '9':
- if (syntax & RE_NO_BK_REFS)
- goto normal_char;
-
- c1 = c - '0';
-
- if (c1 > regnum)
- return REG_ESUBREG;
-
- /* Can't back reference to a subexpression if inside of it. */
- if (group_in_compile_stack (compile_stack, c1))
- goto normal_char;
-
- laststart = b;
- BUF_PUSH_2 (duplicate, c1);
- break;
-
-
- case '+':
- case '?':
- if (syntax & RE_BK_PLUS_QM)
- goto handle_plus;
- else
- goto normal_backslash;
-
- default:
- normal_backslash:
- /* You might think it would be useful for \ to mean
- not to translate; but if we don't translate it
- it will never match anything. */
- c = TRANSLATE (c);
- goto normal_char;
- }
- break;
-
-
- default:
- /* Expects the character in `c'. */
- normal_char:
- /* If no exactn currently being built. */
- if (!pending_exact
-
- /* If last exactn not at current position. */
- || pending_exact + *pending_exact + 1 != b
-
- /* We have only one byte following the exactn for the count. */
- || *pending_exact == (1 << BYTEWIDTH) - 1
-
- /* If followed by a repetition operator. */
- || *p == '*' || *p == '^'
- || ((syntax & RE_BK_PLUS_QM)
- ? *p == '\\' && (p[1] == '+' || p[1] == '?')
- : (*p == '+' || *p == '?'))
- || ((syntax & RE_INTERVALS)
- && ((syntax & RE_NO_BK_BRACES)
- ? *p == '{'
- : (p[0] == '\\' && p[1] == '{'))))
- {
- /* Start building a new exactn. */
-
- laststart = b;
-
- BUF_PUSH_2 (exactn, 0);
- pending_exact = b - 1;
- }
-
- BUF_PUSH (c);
- (*pending_exact)++;
- break;
- } /* switch (c) */
- } /* while p != pend */
-
-
- /* Through the pattern now. */
-
- if (fixup_alt_jump)
- STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
-
- if (!COMPILE_STACK_EMPTY)
- return REG_EPAREN;
-
- free (compile_stack.stack);
-
- /* We have succeeded; set the length of the buffer. */
- bufp->used = b - bufp->buffer;
-
-#ifdef DEBUG
- if (debug)
- {
- DEBUG_PRINT1 ("\nCompiled pattern: ");
- print_compiled_pattern (bufp);
- }
-#endif /* DEBUG */
-
- return REG_NOERROR;
-} /* regex_compile */
-
-/* Subroutines for `regex_compile'. */
-
-/* Store OP at LOC followed by two-byte integer parameter ARG. */
-
-static void
-store_op1 (op, loc, arg)
- re_opcode_t op;
- unsigned char *loc;
- int arg;
-{
- *loc = (unsigned char) op;
- STORE_NUMBER (loc + 1, arg);
-}
-
-
-/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */
-
-static void
-store_op2 (op, loc, arg1, arg2)
- re_opcode_t op;
- unsigned char *loc;
- int arg1, arg2;
-{
- *loc = (unsigned char) op;
- STORE_NUMBER (loc + 1, arg1);
- STORE_NUMBER (loc + 3, arg2);
-}
-
-
-/* Copy the bytes from LOC to END to open up three bytes of space at LOC
- for OP followed by two-byte integer parameter ARG. */
-
-static void
-insert_op1 (op, loc, arg, end)
- re_opcode_t op;
- unsigned char *loc;
- int arg;
- unsigned char *end;
-{
- register unsigned char *pfrom = end;
- register unsigned char *pto = end + 3;
-
- while (pfrom != loc)
- *--pto = *--pfrom;
-
- store_op1 (op, loc, arg);
-}
-
-
-/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */
-
-static void
-insert_op2 (op, loc, arg1, arg2, end)
- re_opcode_t op;
- unsigned char *loc;
- int arg1, arg2;
- unsigned char *end;
-{
- register unsigned char *pfrom = end;
- register unsigned char *pto = end + 5;
-
- while (pfrom != loc)
- *--pto = *--pfrom;
-
- store_op2 (op, loc, arg1, arg2);
-}
-
-
-/* P points to just after a ^ in PATTERN. Return true if that ^ comes
- after an alternative or a begin-subexpression. We assume there is at
- least one character before the ^. */
-
-static boolean
-at_begline_loc_p (pattern, p, syntax)
- const char *pattern, *p;
- reg_syntax_t syntax;
-{
- const char *prev = p - 2;
- boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\';
-
- return
- /* After a subexpression? */
- (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash))
- /* After an alternative? */
- || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash));
-}
-
-
-/* The dual of at_begline_loc_p. This one is for $. We assume there is
- at least one character after the $, i.e., `P < PEND'. */
-
-static boolean
-at_endline_loc_p (p, pend, syntax)
- const char *p, *pend;
- int syntax;
-{
- const char *next = p;
- boolean next_backslash = *next == '\\';
- const char *next_next = p + 1 < pend ? p + 1 : NULL;
-
- return
- /* Before a subexpression? */
- (syntax & RE_NO_BK_PARENS ? *next == ')'
- : next_backslash && next_next && *next_next == ')')
- /* Before an alternative? */
- || (syntax & RE_NO_BK_VBAR ? *next == '|'
- : next_backslash && next_next && *next_next == '|');
-}
-
-
-/* Returns true if REGNUM is in one of COMPILE_STACK's elements and
- false if it's not. */
-
-static boolean
-group_in_compile_stack (compile_stack, regnum)
- compile_stack_type compile_stack;
- regnum_t regnum;
-{
- int this_element;
-
- for (this_element = compile_stack.avail - 1;
- this_element >= 0;
- this_element--)
- if (compile_stack.stack[this_element].regnum == regnum)
- return true;
-
- return false;
-}
-
-
-/* Read the ending character of a range (in a bracket expression) from the
- uncompiled pattern *P_PTR (which ends at PEND). We assume the
- starting character is in `P[-2]'. (`P[-1]' is the character `-'.)
- Then we set the translation of all bits between the starting and
- ending characters (inclusive) in the compiled pattern B.
-
- Return an error code.
-
- We use these short variable names so we can use the same macros as
- `regex_compile' itself. */
-
-static reg_errcode_t
-compile_range (p_ptr, pend, translate, syntax, b)
- const char **p_ptr, *pend;
- char *translate;
- reg_syntax_t syntax;
- unsigned char *b;
-{
- unsigned this_char;
-
- const char *p = *p_ptr;
- int range_start, range_end;
-
- if (p == pend)
- return REG_ERANGE;
-
- /* Even though the pattern is a signed `char *', we need to fetch
- with unsigned char *'s; if the high bit of the pattern character
- is set, the range endpoints will be negative if we fetch using a
- signed char *.
-
- We also want to fetch the endpoints without translating them; the
- appropriate translation is done in the bit-setting loop below. */
- range_start = ((unsigned char *) p)[-2];
- range_end = ((unsigned char *) p)[0];
-
- /* Have to increment the pointer into the pattern string, so the
- caller isn't still at the ending character. */
- (*p_ptr)++;
-
- /* If the start is after the end, the range is empty. */
- if (range_start > range_end)
- return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR;
-
- /* Here we see why `this_char' has to be larger than an `unsigned
- char' -- the range is inclusive, so if `range_end' == 0xff
- (assuming 8-bit characters), we would otherwise go into an infinite
- loop, since all characters <= 0xff. */
- for (this_char = range_start; this_char <= range_end; this_char++)
- {
- SET_LIST_BIT (TRANSLATE (this_char));
- }
-
- return REG_NOERROR;
-}
-
-/* Failure stack declarations and macros; both re_compile_fastmap and
- re_match_2 use a failure stack. These have to be macros because of
- REGEX_ALLOCATE. */
-
-
-/* Number of failure points for which to initially allocate space
- when matching. If this number is exceeded, we allocate more
- space, so it is not a hard limit. */
-#ifndef INIT_FAILURE_ALLOC
-#define INIT_FAILURE_ALLOC 5
-#endif
-
-/* Roughly the maximum number of failure points on the stack. Would be
- exactly that if always used MAX_FAILURE_SPACE each time we failed.
- This is a variable only so users of regex can assign to it; we never
- change it ourselves. */
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Make sure noone compiles this code with a C++ compiler. */
+#ifdef __cplusplus
+# error "This is C code, use a C compiler"
+#endif
+
+#ifdef _LIBC
+/* We have to keep the namespace clean. */
+# define regfree(preg) __regfree (preg)
+# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef)
+# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags)
+# define regerror(errcode, preg, errbuf, errbuf_size) \
+ __regerror(errcode, preg, errbuf, errbuf_size)
+# define re_set_registers(bu, re, nu, st, en) \
+ __re_set_registers (bu, re, nu, st, en)
+# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \
+ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
+# define re_match(bufp, string, size, pos, regs) \
+ __re_match (bufp, string, size, pos, regs)
+# define re_search(bufp, string, size, startpos, range, regs) \
+ __re_search (bufp, string, size, startpos, range, regs)
+# define re_compile_pattern(pattern, length, bufp) \
+ __re_compile_pattern (pattern, length, bufp)
+# define re_set_syntax(syntax) __re_set_syntax (syntax)
+# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \
+ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop)
+# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp)
+
+# include "../locale/localeinfo.h"
+#endif
+
+#if defined (_MSC_VER)
+#include <stdio.h> /* for size_t */
+#endif
+
+/* On some systems, limits.h sets RE_DUP_MAX to a lower value than
+ GNU regex allows. Include it before <regex.h>, which correctly
+ #undefs RE_DUP_MAX and sets it to the right value. */
+#include <limits.h>
+
+#ifdef GAWK
+#undef alloca
+#define alloca alloca_is_bad_you_should_never_use_it
+#endif
+#include <regex.h>
+#include "regex_internal.h"
+
+#include "regex_internal.c"
+#ifdef GAWK
+#define bool int
+#define true (1)
+#define false (0)
+#endif
+#include "regcomp.c"
+#include "regexec.c"
+
+/* Binary backward compatibility. */
+#if _LIBC
+# include <shlib-compat.h>
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3)
+link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.")
int re_max_failures = 2000;
-
-typedef const unsigned char *fail_stack_elt_t;
-
-typedef struct
-{
- fail_stack_elt_t *stack;
- unsigned size;
- unsigned avail; /* Offset of next open position. */
-} fail_stack_type;
-
-#define FAIL_STACK_EMPTY() (fail_stack.avail == 0)
-#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0)
-#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size)
-#define FAIL_STACK_TOP() (fail_stack.stack[fail_stack.avail])
-
-
-/* Initialize `fail_stack'. Do `return -2' if the alloc fails. */
-
-#define INIT_FAIL_STACK() \
- do { \
- fail_stack.stack = (fail_stack_elt_t *) \
- REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \
- \
- if (fail_stack.stack == NULL) \
- return -2; \
- \
- fail_stack.size = INIT_FAILURE_ALLOC; \
- fail_stack.avail = 0; \
- } while (0)
-
-
-/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items.
-
- Return 1 if succeeds, and 0 if either ran out of memory
- allocating space for it or it was already too large.
-
- REGEX_REALLOCATE requires `destination' be declared. */
-
-#define DOUBLE_FAIL_STACK(fail_stack) \
- ((fail_stack).size > re_max_failures * MAX_FAILURE_ITEMS \
- ? 0 \
- : ((fail_stack).stack = (fail_stack_elt_t *) \
- REGEX_REALLOCATE ((fail_stack).stack, \
- (fail_stack).size * sizeof (fail_stack_elt_t), \
- ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \
- \
- (fail_stack).stack == NULL \
- ? 0 \
- : ((fail_stack).size <<= 1, \
- 1)))
-
-
-/* Push PATTERN_OP on FAIL_STACK.
-
- Return 1 if was able to do so and 0 if ran out of memory allocating
- space to do so. */
-#define PUSH_PATTERN_OP(pattern_op, fail_stack) \
- ((FAIL_STACK_FULL () \
- && !DOUBLE_FAIL_STACK (fail_stack)) \
- ? 0 \
- : ((fail_stack).stack[(fail_stack).avail++] = pattern_op, \
- 1))
-
-/* This pushes an item onto the failure stack. Must be a four-byte
- value. Assumes the variable `fail_stack'. Probably should only
- be called from within `PUSH_FAILURE_POINT'. */
-#define PUSH_FAILURE_ITEM(item) \
- fail_stack.stack[fail_stack.avail++] = (fail_stack_elt_t) item
-
-/* The complement operation. Assumes `fail_stack' is nonempty. */
-#define POP_FAILURE_ITEM() fail_stack.stack[--fail_stack.avail]
-
-/* Used to omit pushing failure point id's when we're not debugging. */
-#ifdef DEBUG
-#define DEBUG_PUSH PUSH_FAILURE_ITEM
-#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM ()
-#else
-#define DEBUG_PUSH(item)
-#define DEBUG_POP(item_addr)
-#endif
-
-
-/* Push the information about the state we will need
- if we ever fail back to it.
-
- Requires variables fail_stack, regstart, regend, reg_info, and
- num_regs be declared. DOUBLE_FAIL_STACK requires `destination' be
- declared.
-
- Does `return FAILURE_CODE' if runs out of memory. */
-
-#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \
- do { \
- char *destination; \
- /* Must be int, so when we don't save any registers, the arithmetic \
- of 0 + -1 isn't done as unsigned. */ \
- int this_reg; \
- \
- DEBUG_STATEMENT (failure_id++); \
- DEBUG_STATEMENT (nfailure_points_pushed++); \
- DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \
- DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\
- DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\
- \
- DEBUG_PRINT2 (" slots needed: %d\n", NUM_FAILURE_ITEMS); \
- DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \
- \
- /* Ensure we have enough space allocated for what we will push. */ \
- while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \
- { \
- if (!DOUBLE_FAIL_STACK (fail_stack)) \
- return failure_code; \
- \
- DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \
- (fail_stack).size); \
- DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\
- } \
- \
- /* Push the info, starting with the registers. */ \
- DEBUG_PRINT1 ("\n"); \
- \
- for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \
- this_reg++) \
- { \
- DEBUG_PRINT2 (" Pushing reg: %d\n", this_reg); \
- DEBUG_STATEMENT (num_regs_pushed++); \
- \
- DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
- PUSH_FAILURE_ITEM (regstart[this_reg]); \
- \
- DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
- PUSH_FAILURE_ITEM (regend[this_reg]); \
- \
- DEBUG_PRINT2 (" info: 0x%x\n ", reg_info[this_reg]); \
- DEBUG_PRINT2 (" match_null=%d", \
- REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \
- DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \
- DEBUG_PRINT2 (" matched_something=%d", \
- MATCHED_SOMETHING (reg_info[this_reg])); \
- DEBUG_PRINT2 (" ever_matched=%d", \
- EVER_MATCHED_SOMETHING (reg_info[this_reg])); \
- DEBUG_PRINT1 ("\n"); \
- PUSH_FAILURE_ITEM (reg_info[this_reg].word); \
- } \
- \
- DEBUG_PRINT2 (" Pushing low active reg: %d\n", lowest_active_reg);\
- PUSH_FAILURE_ITEM (lowest_active_reg); \
- \
- DEBUG_PRINT2 (" Pushing high active reg: %d\n", highest_active_reg);\
- PUSH_FAILURE_ITEM (highest_active_reg); \
- \
- DEBUG_PRINT2 (" Pushing pattern 0x%x: ", pattern_place); \
- DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \
- PUSH_FAILURE_ITEM (pattern_place); \
- \
- DEBUG_PRINT2 (" Pushing string 0x%x: `", string_place); \
- DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \
- size2); \
- DEBUG_PRINT1 ("'\n"); \
- PUSH_FAILURE_ITEM (string_place); \
- \
- DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \
- DEBUG_PUSH (failure_id); \
- } while (0)
-
-/* This is the number of items that are pushed and popped on the stack
- for each register. */
-#define NUM_REG_ITEMS 3
-
-/* Individual items aside from the registers. */
-#ifdef DEBUG
-#define NUM_NONREG_ITEMS 5 /* Includes failure point id. */
-#else
-#define NUM_NONREG_ITEMS 4
-#endif
-
-/* We push at most this many items on the stack. */
-#define MAX_FAILURE_ITEMS ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS)
-
-/* We actually push this many items. */
-#define NUM_FAILURE_ITEMS \
- ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS \
- + NUM_NONREG_ITEMS)
-
-/* How many items can still be added to the stack without overflowing it. */
-#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail)
-
-
-/* Pops what PUSH_FAIL_STACK pushes.
-
- We restore into the parameters, all of which should be lvalues:
- STR -- the saved data position.
- PAT -- the saved pattern position.
- LOW_REG, HIGH_REG -- the highest and lowest active registers.
- REGSTART, REGEND -- arrays of string positions.
- REG_INFO -- array of information about each subexpression.
-
- Also assumes the variables `fail_stack' and (if debugging), `bufp',
- `pend', `string1', `size1', `string2', and `size2'. */
-
-#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\
-{ \
- DEBUG_STATEMENT (fail_stack_elt_t failure_id;) \
- int this_reg; \
- const unsigned char *string_temp; \
- \
- assert (!FAIL_STACK_EMPTY ()); \
- \
- /* Remove failure points and point to how many regs pushed. */ \
- DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \
- DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \
- DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \
- \
- assert (fail_stack.avail >= NUM_NONREG_ITEMS); \
- \
- DEBUG_POP (&failure_id); \
- DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \
- \
- /* If the saved string location is NULL, it came from an \
- on_failure_keep_string_jump opcode, and we want to throw away the \
- saved NULL, thus retaining our current position in the string. */ \
- string_temp = POP_FAILURE_ITEM (); \
- if (string_temp != NULL) \
- str = (const char *) string_temp; \
- \
- DEBUG_PRINT2 (" Popping string 0x%x: `", str); \
- DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \
- DEBUG_PRINT1 ("'\n"); \
- \
- pat = (unsigned char *) POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" Popping pattern 0x%x: ", pat); \
- DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \
- \
- /* Restore register info. */ \
- high_reg = (unsigned) POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" Popping high active reg: %d\n", high_reg); \
- \
- low_reg = (unsigned) POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" Popping low active reg: %d\n", low_reg); \
- \
- for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \
- { \
- DEBUG_PRINT2 (" Popping reg: %d\n", this_reg); \
- \
- reg_info[this_reg].word = POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" info: 0x%x\n", reg_info[this_reg]); \
- \
- regend[this_reg] = (const char *) POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
- \
- regstart[this_reg] = (const char *) POP_FAILURE_ITEM (); \
- DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
- } \
- \
- DEBUG_STATEMENT (nfailure_points_popped++); \
-} /* POP_FAILURE_POINT */
-
-/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in
- BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible
- characters can start a string that matches the pattern. This fastmap
- is used by re_search to skip quickly over impossible starting points.
-
- The caller must supply the address of a (1 << BYTEWIDTH)-byte data
- area as BUFP->fastmap.
-
- We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in
- the pattern buffer.
-
- Returns 0 if we succeed, -2 if an internal error. */
-
-int
-re_compile_fastmap (bufp)
- struct re_pattern_buffer *bufp;
-{
- int j, k;
- fail_stack_type fail_stack;
-#ifndef REGEX_MALLOC
- char *destination;
-#endif
- /* We don't push any register information onto the failure stack. */
- unsigned num_regs = 0;
-
- register char *fastmap = bufp->fastmap;
- unsigned char *pattern = bufp->buffer;
- unsigned long size = bufp->used;
- const unsigned char *p = pattern;
- register unsigned char *pend = pattern + size;
-
- /* Assume that each path through the pattern can be null until
- proven otherwise. We set this false at the bottom of switch
- statement, to which we get only if a particular path doesn't
- match the empty string. */
- boolean path_can_be_null = true;
-
- /* We aren't doing a `succeed_n' to begin with. */
- boolean succeed_n_p = false;
-
- assert (fastmap != NULL && p != NULL);
-
- INIT_FAIL_STACK ();
- bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */
- bufp->fastmap_accurate = 1; /* It will be when we're done. */
- bufp->can_be_null = 0;
-
- while (p != pend || !FAIL_STACK_EMPTY ())
- {
- if (p == pend)
- {
- bufp->can_be_null |= path_can_be_null;
-
- /* Reset for next path. */
- path_can_be_null = true;
-
- p = fail_stack.stack[--fail_stack.avail];
- }
-
- /* We should never be about to go beyond the end of the pattern. */
- assert (p < pend);
-
-#ifdef SWITCH_ENUM_BUG
- switch ((int) ((re_opcode_t) *p++))
-#else
- switch ((re_opcode_t) *p++)
-#endif
- {
-
- /* I guess the idea here is to simply not bother with a fastmap
- if a backreference is used, since it's too hard to figure out
- the fastmap for the corresponding group. Setting
- `can_be_null' stops `re_search_2' from using the fastmap, so
- that is all we do. */
- case duplicate:
- bufp->can_be_null = 1;
- return 0;
-
-
- /* Following are the cases which match a character. These end
- with `break'. */
-
- case exactn:
- fastmap[p[1]] = 1;
- break;
-
-
- case charset:
- for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
- if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))
- fastmap[j] = 1;
- break;
-
-
- case charset_not:
- /* Chars beyond end of map must be allowed. */
- for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++)
- fastmap[j] = 1;
-
- for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
- if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))))
- fastmap[j] = 1;
- break;
-
-
- case wordchar:
- for (j = 0; j < (1 << BYTEWIDTH); j++)
- if (SYNTAX (j) == Sword)
- fastmap[j] = 1;
- break;
-
-
- case notwordchar:
- for (j = 0; j < (1 << BYTEWIDTH); j++)
- if (SYNTAX (j) != Sword)
- fastmap[j] = 1;
- break;
-
-
- case anychar:
- /* `.' matches anything ... */
- for (j = 0; j < (1 << BYTEWIDTH); j++)
- fastmap[j] = 1;
-
- /* ... except perhaps newline. */
- if (!(bufp->syntax & RE_DOT_NEWLINE))
- fastmap['\n'] = 0;
-
- /* Return if we have already set `can_be_null'; if we have,
- then the fastmap is irrelevant. Something's wrong here. */
- else if (bufp->can_be_null)
- return 0;
-
- /* Otherwise, have to check alternative paths. */
- break;
-
-
-#ifdef emacs
- case syntaxspec:
- k = *p++;
- for (j = 0; j < (1 << BYTEWIDTH); j++)
- if (SYNTAX (j) == (enum syntaxcode) k)
- fastmap[j] = 1;
- break;
-
-
- case notsyntaxspec:
- k = *p++;
- for (j = 0; j < (1 << BYTEWIDTH); j++)
- if (SYNTAX (j) != (enum syntaxcode) k)
- fastmap[j] = 1;
- break;
-
-
- /* All cases after this match the empty string. These end with
- `continue'. */
-
-
- case before_dot:
- case at_dot:
- case after_dot:
- continue;
-#endif /* not emacs */
-
-
- case no_op:
- case begline:
- case endline:
- case begbuf:
- case endbuf:
- case wordbound:
- case notwordbound:
- case wordbeg:
- case wordend:
- case push_dummy_failure:
- continue;
-
-
- case jump_n:
- case pop_failure_jump:
- case maybe_pop_jump:
- case jump:
- case jump_past_alt:
- case dummy_failure_jump:
- EXTRACT_NUMBER_AND_INCR (j, p);
- p += j;
- if (j > 0)
- continue;
-
- /* Jump backward implies we just went through the body of a
- loop and matched nothing. Opcode jumped to should be
- `on_failure_jump' or `succeed_n'. Just treat it like an
- ordinary jump. For a * loop, it has pushed its failure
- point already; if so, discard that as redundant. */
- if ((re_opcode_t) *p != on_failure_jump
- && (re_opcode_t) *p != succeed_n)
- continue;
-
- p++;
- EXTRACT_NUMBER_AND_INCR (j, p);
- p += j;
-
- /* If what's on the stack is where we are now, pop it. */
- if (!FAIL_STACK_EMPTY ()
- && fail_stack.stack[fail_stack.avail - 1] == p)
- fail_stack.avail--;
-
- continue;
-
-
- case on_failure_jump:
- case on_failure_keep_string_jump:
- handle_on_failure_jump:
- EXTRACT_NUMBER_AND_INCR (j, p);
-
- /* For some patterns, e.g., `(a?)?', `p+j' here points to the
- end of the pattern. We don't want to push such a point,
- since when we restore it above, entering the switch will
- increment `p' past the end of the pattern. We don't need
- to push such a point since we obviously won't find any more
- fastmap entries beyond `pend'. Such a pattern can match
- the null string, though. */
- if (p + j < pend)
- {
- if (!PUSH_PATTERN_OP (p + j, fail_stack))
- return -2;
- }
- else
- bufp->can_be_null = 1;
-
- if (succeed_n_p)
- {
- EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */
- succeed_n_p = false;
- }
-
- continue;
-
-
- case succeed_n:
- /* Get to the number of times to succeed. */
- p += 2;
-
- /* Increment p past the n for when k != 0. */
- EXTRACT_NUMBER_AND_INCR (k, p);
- if (k == 0)
- {
- p -= 4;
- succeed_n_p = true; /* Spaghetti code alert. */
- goto handle_on_failure_jump;
- }
- continue;
-
-
- case set_number_at:
- p += 4;
- continue;
-
-
- case start_memory:
- case stop_memory:
- p += 2;
- continue;
-
-
- default:
- abort (); /* We have listed all the cases. */
- } /* switch *p++ */
-
- /* Getting here means we have found the possible starting
- characters for one path of the pattern -- and that the empty
- string does not match. We need not follow this path further.
- Instead, look at the next alternative (remembered on the
- stack), or quit if no more. The test at the top of the loop
- does these things. */
- path_can_be_null = false;
- p = pend;
- } /* while p */
-
- /* Set `can_be_null' for the last path (also the first path, if the
- pattern is empty). */
- bufp->can_be_null |= path_can_be_null;
- return 0;
-} /* re_compile_fastmap */
-
-/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
- ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
- this memory for recording register information. STARTS and ENDS
- must be allocated using the malloc library routine, and must each
- be at least NUM_REGS * sizeof (regoff_t) bytes long.
-
- If NUM_REGS == 0, then subsequent matches should allocate their own
- register data.
-
- Unless this function is called, the first search or match using
- PATTERN_BUFFER will allocate its own register data, without
- freeing the old data. */
-
-void
-re_set_registers (bufp, regs, num_regs, starts, ends)
- struct re_pattern_buffer *bufp;
- struct re_registers *regs;
- unsigned num_regs;
- regoff_t *starts, *ends;
-{
- if (num_regs)
- {
- bufp->regs_allocated = REGS_REALLOCATE;
- regs->num_regs = num_regs;
- regs->start = starts;
- regs->end = ends;
- }
- else
- {
- bufp->regs_allocated = REGS_UNALLOCATED;
- regs->num_regs = 0;
- regs->start = regs->end = (regoff_t *) 0;
- }
-}
-
-/* Searching routines. */
-
-/* Like re_search_2, below, but only one string is specified, and
- doesn't let you say where to stop matching. */
-
-int
-re_search (bufp, string, size, startpos, range, regs)
- struct re_pattern_buffer *bufp;
- const char *string;
- int size, startpos, range;
- struct re_registers *regs;
-{
- return re_search_2 (bufp, NULL, 0, string, size, startpos, range,
- regs, size);
-}
-
-
-/* Using the compiled pattern in BUFP->buffer, first tries to match the
- virtual concatenation of STRING1 and STRING2, starting first at index
- STARTPOS, then at STARTPOS + 1, and so on.
-
- STRING1 and STRING2 have length SIZE1 and SIZE2, respectively.
-
- RANGE is how far to scan while trying to match. RANGE = 0 means try
- only at STARTPOS; in general, the last start tried is STARTPOS +
- RANGE.
-
- In REGS, return the indices of the virtual concatenation of STRING1
- and STRING2 that matched the entire BUFP->buffer and its contained
- subexpressions.
-
- Do not consider matching one past the index STOP in the virtual
- concatenation of STRING1 and STRING2.
-
- We return either the position in the strings at which the match was
- found, -1 if no match, or -2 if error (such as failure
- stack overflow). */
-
-int
-re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop)
- struct re_pattern_buffer *bufp;
- const char *string1, *string2;
- int size1, size2;
- int startpos;
- int range;
- struct re_registers *regs;
- int stop;
-{
- int val;
- register char *fastmap = bufp->fastmap;
- register char *translate = bufp->translate;
- int total_size = size1 + size2;
- int endpos = startpos + range;
-
- /* Check for out-of-range STARTPOS. */
- if (startpos < 0 || startpos > total_size)
- return -1;
-
- /* Fix up RANGE if it might eventually take us outside
- the virtual concatenation of STRING1 and STRING2. */
- if (endpos < -1)
- range = -1 - startpos;
- else if (endpos > total_size)
- range = total_size - startpos;
-
- /* If the search isn't to be a backwards one, don't waste time in a
- search for a pattern that must be anchored. */
- if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0)
- {
- if (startpos > 0)
- return -1;
- else
- range = 1;
- }
-
- /* Update the fastmap now if not correct already. */
- if (fastmap && !bufp->fastmap_accurate)
- if (re_compile_fastmap (bufp) == -2)
- return -2;
-
- /* Loop through the string, looking for a place to start matching. */
- for (;;)
- {
- /* If a fastmap is supplied, skip quickly over characters that
- cannot be the start of a match. If the pattern can match the
- null string, however, we don't need to skip characters; we want
- the first null string. */
- if (fastmap && startpos < total_size && !bufp->can_be_null)
- {
- if (range > 0) /* Searching forwards. */
- {
- register const char *d;
- register int lim = 0;
- int irange = range;
-
- if (startpos < size1 && startpos + range >= size1)
- lim = range - (size1 - startpos);
-
- d = (startpos >= size1 ? string2 - size1 : string1) + startpos;
-
- /* Written out as an if-else to avoid testing `translate'
- inside the loop. */
- if (translate)
- while (range > lim
- && !fastmap[(unsigned char)
- translate[(unsigned char) *d++]])
- range--;
- else
- while (range > lim && !fastmap[(unsigned char) *d++])
- range--;
-
- startpos += irange - range;
- }
- else /* Searching backwards. */
- {
- register char c = (size1 == 0 || startpos >= size1
- ? string2[startpos - size1]
- : string1[startpos]);
-
- if (!fastmap[(unsigned char) TRANSLATE (c)])
- goto advance;
- }
- }
-
- /* If can't match the null string, and that's all we have left, fail. */
- if (range >= 0 && startpos == total_size && fastmap
- && !bufp->can_be_null)
- return -1;
-
- val = re_match_2 (bufp, string1, size1, string2, size2,
- startpos, regs, stop);
- if (val >= 0)
- return startpos;
-
- if (val == -2)
- return -2;
-
- advance:
- if (!range)
- break;
- else if (range > 0)
- {
- range--;
- startpos++;
- }
- else
- {
- range++;
- startpos--;
- }
- }
- return -1;
-} /* re_search_2 */
-
-/* Declarations and macros for re_match_2. */
-
-static int bcmp_translate ();
-static boolean alt_match_null_string_p (),
- common_op_match_null_string_p (),
- group_match_null_string_p ();
-
-/* Structure for per-register (a.k.a. per-group) information.
- This must not be longer than one word, because we push this value
- onto the failure stack. Other register information, such as the
- starting and ending positions (which are addresses), and the list of
- inner groups (which is a bits list) are maintained in separate
- variables.
-
- We are making a (strictly speaking) nonportable assumption here: that
- the compiler will pack our bit fields into something that fits into
- the type of `word', i.e., is something that fits into one item on the
- failure stack. */
-typedef union
-{
- fail_stack_elt_t word;
- struct
- {
- /* This field is one if this group can match the empty string,
- zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */
-#define MATCH_NULL_UNSET_VALUE 3
- unsigned match_null_string_p : 2;
- unsigned is_active : 1;
- unsigned matched_something : 1;
- unsigned ever_matched_something : 1;
- } bits;
-} register_info_type;
-
-#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p)
-#define IS_ACTIVE(R) ((R).bits.is_active)
-#define MATCHED_SOMETHING(R) ((R).bits.matched_something)
-#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something)
-
-
-/* Call this when have matched a real character; it sets `matched' flags
- for the subexpressions which we are currently inside. Also records
- that those subexprs have matched. */
-#define SET_REGS_MATCHED() \
- do \
- { \
- unsigned r; \
- for (r = lowest_active_reg; r <= highest_active_reg; r++) \
- { \
- MATCHED_SOMETHING (reg_info[r]) \
- = EVER_MATCHED_SOMETHING (reg_info[r]) \
- = 1; \
- } \
- } \
- while (0)
-
-
-/* This converts PTR, a pointer into one of the search strings `string1'
- and `string2' into an offset from the beginning of that string. */
-#define POINTER_TO_OFFSET(ptr) \
- (FIRST_STRING_P (ptr) ? (ptr) - string1 : (ptr) - string2 + size1)
-
-/* Registers are set to a sentinel when they haven't yet matched. */
-#define REG_UNSET_VALUE ((char *) -1)
-#define REG_UNSET(e) ((e) == REG_UNSET_VALUE)
-
-
-/* Macros for dealing with the split strings in re_match_2. */
-
-#define MATCHING_IN_FIRST_STRING (dend == end_match_1)
-
-/* Call before fetching a character with *d. This switches over to
- string2 if necessary. */
-#define PREFETCH() \
- while (d == dend) \
- { \
- /* End of string2 => fail. */ \
- if (dend == end_match_2) \
- goto fail; \
- /* End of string1 => advance to string2. */ \
- d = string2; \
- dend = end_match_2; \
- }
-
-
-/* Test if at very beginning or at very end of the virtual concatenation
- of `string1' and `string2'. If only one string, it's `string2'. */
-#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2)
-#define AT_STRINGS_END(d) ((d) == end2)
-
-
-/* Test if D points to a character which is word-constituent. We have
- two special cases to check for: if past the end of string1, look at
- the first character in string2; and if before the beginning of
- string2, look at the last character in string1. */
-#define WORDCHAR_P(d) \
- (SYNTAX ((d) == end1 ? *string2 \
- : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \
- == Sword)
-
-/* Test if the character before D and the one at D differ with respect
- to being word-constituent. */
-#define AT_WORD_BOUNDARY(d) \
- (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \
- || WORDCHAR_P (d - 1) != WORDCHAR_P (d))
-
-
-/* Free everything we malloc. */
-#ifdef REGEX_MALLOC
-#define FREE_VAR(var) if (var) free (var); var = NULL
-#define FREE_VARIABLES() \
- do { \
- FREE_VAR (fail_stack.stack); \
- FREE_VAR (regstart); \
- FREE_VAR (regend); \
- FREE_VAR (old_regstart); \
- FREE_VAR (old_regend); \
- FREE_VAR (best_regstart); \
- FREE_VAR (best_regend); \
- FREE_VAR (reg_info); \
- FREE_VAR (reg_dummy); \
- FREE_VAR (reg_info_dummy); \
- } while (0)
-#else /* not REGEX_MALLOC */
-/* Some MIPS systems (at least) want this to free alloca'd storage. */
-#define FREE_VARIABLES() alloca (0)
-#endif /* not REGEX_MALLOC */
-
-
-/* These values must meet several constraints. They must not be valid
- register values; since we have a limit of 255 registers (because
- we use only one byte in the pattern for the register number), we can
- use numbers larger than 255. They must differ by 1, because of
- NUM_FAILURE_ITEMS above. And the value for the lowest register must
- be larger than the value for the highest register, so we do not try
- to actually save any registers when none are active. */
-#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH)
-#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1)
-
-/* Matching routines. */
-
-#ifndef emacs /* Emacs never uses this. */
-/* re_match is like re_match_2 except it takes only a single string. */
-
-int
-re_match (bufp, string, size, pos, regs)
- struct re_pattern_buffer *bufp;
- const char *string;
- int size, pos;
- struct re_registers *regs;
- {
- return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size);
-}
-#endif /* not emacs */
-
-
-/* re_match_2 matches the compiled pattern in BUFP against the
- the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1
- and SIZE2, respectively). We start matching at POS, and stop
- matching at STOP.
-
- If REGS is non-null and the `no_sub' field of BUFP is nonzero, we
- store offsets for the substring each group matched in REGS. See the
- documentation for exactly how many groups we fill.
-
- We return -1 if no match, -2 if an internal error (such as the
- failure stack overflowing). Otherwise, we return the length of the
- matched substring. */
-
-int
-re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
- struct re_pattern_buffer *bufp;
- const char *string1, *string2;
- int size1, size2;
- int pos;
- struct re_registers *regs;
- int stop;
-{
- /* General temporaries. */
- int mcnt;
- unsigned char *p1;
-
- /* Just past the end of the corresponding string. */
- const char *end1, *end2;
-
- /* Pointers into string1 and string2, just past the last characters in
- each to consider matching. */
- const char *end_match_1, *end_match_2;
-
- /* Where we are in the data, and the end of the current string. */
- const char *d, *dend;
-
- /* Where we are in the pattern, and the end of the pattern. */
- unsigned char *p = bufp->buffer;
- register unsigned char *pend = p + bufp->used;
-
- /* We use this to map every character in the string. */
- char *translate = bufp->translate;
-
- /* Failure point stack. Each place that can handle a failure further
- down the line pushes a failure point on this stack. It consists of
- restart, regend, and reg_info for all registers corresponding to
- the subexpressions we're currently inside, plus the number of such
- registers, and, finally, two char *'s. The first char * is where
- to resume scanning the pattern; the second one is where to resume
- scanning the strings. If the latter is zero, the failure point is
- a ``dummy''; if a failure happens and the failure point is a dummy,
- it gets discarded and the next next one is tried. */
- fail_stack_type fail_stack;
-#ifdef DEBUG
- static unsigned failure_id = 0;
- unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0;
-#endif
-
- /* We fill all the registers internally, independent of what we
- return, for use in backreferences. The number here includes
- an element for register zero. */
- unsigned num_regs = bufp->re_nsub + 1;
-
- /* The currently active registers. */
- unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG;
- unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG;
-
- /* Information on the contents of registers. These are pointers into
- the input strings; they record just what was matched (on this
- attempt) by a subexpression part of the pattern, that is, the
- regnum-th regstart pointer points to where in the pattern we began
- matching and the regnum-th regend points to right after where we
- stopped matching the regnum-th subexpression. (The zeroth register
- keeps track of what the whole pattern matches.) */
- const char **regstart = NULL, **regend = NULL;
-
- /* If a group that's operated upon by a repetition operator fails to
- match anything, then the register for its start will need to be
- restored because it will have been set to wherever in the string we
- are when we last see its open-group operator. Similarly for a
- register's end. */
- const char **old_regstart = NULL, **old_regend = NULL;
-
- /* The is_active field of reg_info helps us keep track of which (possibly
- nested) subexpressions we are currently in. The matched_something
- field of reg_info[reg_num] helps us tell whether or not we have
- matched any of the pattern so far this time through the reg_num-th
- subexpression. These two fields get reset each time through any
- loop their register is in. */
- register_info_type *reg_info = NULL;
-
- /* The following record the register info as found in the above
- variables when we find a match better than any we've seen before.
- This happens as we backtrack through the failure points, which in
- turn happens only if we have not yet matched the entire string. */
- unsigned best_regs_set = false;
- const char **best_regstart = NULL, **best_regend = NULL;
-
- /* Logically, this is `best_regend[0]'. But we don't want to have to
- allocate space for that if we're not allocating space for anything
- else (see below). Also, we never need info about register 0 for
- any of the other register vectors, and it seems rather a kludge to
- treat `best_regend' differently than the rest. So we keep track of
- the end of the best match so far in a separate variable. We
- initialize this to NULL so that when we backtrack the first time
- and need to test it, it's not garbage. */
- const char *match_end = NULL;
-
- /* Used when we pop values we don't care about. */
- const char **reg_dummy = NULL;
- register_info_type *reg_info_dummy = NULL;
-
-#ifdef DEBUG
- /* Counts the total number of registers pushed. */
- unsigned num_regs_pushed = 0;
-#endif
-
- DEBUG_PRINT1 ("\n\nEntering re_match_2.\n");
-
- INIT_FAIL_STACK ();
-
- /* Do not bother to initialize all the register variables if there are
- no groups in the pattern, as it takes a fair amount of time. If
- there are groups, we include space for register 0 (the whole
- pattern), even though we never use it, since it simplifies the
- array indexing. We should fix this. */
- if (bufp->re_nsub)
- {
- regstart = REGEX_TALLOC (num_regs, const char *);
- regend = REGEX_TALLOC (num_regs, const char *);
- old_regstart = REGEX_TALLOC (num_regs, const char *);
- old_regend = REGEX_TALLOC (num_regs, const char *);
- best_regstart = REGEX_TALLOC (num_regs, const char *);
- best_regend = REGEX_TALLOC (num_regs, const char *);
- reg_info = REGEX_TALLOC (num_regs, register_info_type);
- reg_dummy = REGEX_TALLOC (num_regs, const char *);
- reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type);
-
- if (!(regstart && regend && old_regstart && old_regend && reg_info
- && best_regstart && best_regend && reg_dummy && reg_info_dummy))
- {
- FREE_VARIABLES ();
- return -2;
- }
- }
-#ifdef REGEX_MALLOC
- else
- {
- /* We must initialize all our variables to NULL, so that
- `FREE_VARIABLES' doesn't try to free them. */
- regstart = regend = old_regstart = old_regend = best_regstart
- = best_regend = reg_dummy = NULL;
- reg_info = reg_info_dummy = (register_info_type *) NULL;
- }
-#endif /* REGEX_MALLOC */
-
- /* The starting position is bogus. */
- if (pos < 0 || pos > size1 + size2)
- {
- FREE_VARIABLES ();
- return -1;
- }
-
- /* Initialize subexpression text positions to -1 to mark ones that no
- start_memory/stop_memory has been seen for. Also initialize the
- register information struct. */
- for (mcnt = 1; mcnt < num_regs; mcnt++)
- {
- regstart[mcnt] = regend[mcnt]
- = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE;
-
- REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE;
- IS_ACTIVE (reg_info[mcnt]) = 0;
- MATCHED_SOMETHING (reg_info[mcnt]) = 0;
- EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0;
- }
-
- /* We move `string1' into `string2' if the latter's empty -- but not if
- `string1' is null. */
- if (size2 == 0 && string1 != NULL)
- {
- string2 = string1;
- size2 = size1;
- string1 = 0;
- size1 = 0;
- }
- end1 = string1 + size1;
- end2 = string2 + size2;
-
- /* Compute where to stop matching, within the two strings. */
- if (stop <= size1)
- {
- end_match_1 = string1 + stop;
- end_match_2 = string2;
- }
- else
- {
- end_match_1 = end1;
- end_match_2 = string2 + stop - size1;
- }
-
- /* `p' scans through the pattern as `d' scans through the data.
- `dend' is the end of the input string that `d' points within. `d'
- is advanced into the following input string whenever necessary, but
- this happens before fetching; therefore, at the beginning of the
- loop, `d' can be pointing at the end of a string, but it cannot
- equal `string2'. */
- if (size1 > 0 && pos <= size1)
- {
- d = string1 + pos;
- dend = end_match_1;
- }
- else
- {
- d = string2 + pos - size1;
- dend = end_match_2;
- }
-
- DEBUG_PRINT1 ("The compiled pattern is: ");
- DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend);
- DEBUG_PRINT1 ("The string to match is: `");
- DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2);
- DEBUG_PRINT1 ("'\n");
-
- /* This loops over pattern commands. It exits by returning from the
- function if the match is complete, or it drops through if the match
- fails at this starting point in the input data. */
- for (;;)
- {
- DEBUG_PRINT2 ("\n0x%x: ", p);
-
- if (p == pend)
- { /* End of pattern means we might have succeeded. */
- DEBUG_PRINT1 ("end of pattern ... ");
-
- /* If we haven't matched the entire string, and we want the
- longest match, try backtracking. */
- if (d != end_match_2)
- {
- DEBUG_PRINT1 ("backtracking.\n");
-
- if (!FAIL_STACK_EMPTY ())
- { /* More failure points to try. */
- boolean same_str_p = (FIRST_STRING_P (match_end)
- == MATCHING_IN_FIRST_STRING);
-
- /* If exceeds best match so far, save it. */
- if (!best_regs_set
- || (same_str_p && d > match_end)
- || (!same_str_p && !MATCHING_IN_FIRST_STRING))
- {
- best_regs_set = true;
- match_end = d;
-
- DEBUG_PRINT1 ("\nSAVING match as best so far.\n");
-
- for (mcnt = 1; mcnt < num_regs; mcnt++)
- {
- best_regstart[mcnt] = regstart[mcnt];
- best_regend[mcnt] = regend[mcnt];
- }
- }
- goto fail;
- }
-
- /* If no failure points, don't restore garbage. */
- else if (best_regs_set)
- {
- restore_best_regs:
- /* Restore best match. It may happen that `dend ==
- end_match_1' while the restored d is in string2.
- For example, the pattern `x.*y.*z' against the
- strings `x-' and `y-z-', if the two strings are
- not consecutive in memory. */
- DEBUG_PRINT1 ("Restoring best registers.\n");
-
- d = match_end;
- dend = ((d >= string1 && d <= end1)
- ? end_match_1 : end_match_2);
-
- for (mcnt = 1; mcnt < num_regs; mcnt++)
- {
- regstart[mcnt] = best_regstart[mcnt];
- regend[mcnt] = best_regend[mcnt];
- }
- }
- } /* d != end_match_2 */
-
- DEBUG_PRINT1 ("Accepting match.\n");
-
- /* If caller wants register contents data back, do it. */
- if (regs && !bufp->no_sub)
- {
- /* Have the register data arrays been allocated? */
- if (bufp->regs_allocated == REGS_UNALLOCATED)
- { /* No. So allocate them with malloc. We need one
- extra element beyond `num_regs' for the `-1' marker
- GNU code uses. */
- regs->num_regs = MAX (RE_NREGS, num_regs + 1);
- regs->start = TALLOC (regs->num_regs, regoff_t);
- regs->end = TALLOC (regs->num_regs, regoff_t);
- if (regs->start == NULL || regs->end == NULL)
- return -2;
- bufp->regs_allocated = REGS_REALLOCATE;
- }
- else if (bufp->regs_allocated == REGS_REALLOCATE)
- { /* Yes. If we need more elements than were already
- allocated, reallocate them. If we need fewer, just
- leave it alone. */
- if (regs->num_regs < num_regs + 1)
- {
- regs->num_regs = num_regs + 1;
- RETALLOC (regs->start, regs->num_regs, regoff_t);
- RETALLOC (regs->end, regs->num_regs, regoff_t);
- if (regs->start == NULL || regs->end == NULL)
- return -2;
- }
- }
- else
- assert (bufp->regs_allocated == REGS_FIXED);
-
- /* Convert the pointer data in `regstart' and `regend' to
- indices. Register zero has to be set differently,
- since we haven't kept track of any info for it. */
- if (regs->num_regs > 0)
- {
- regs->start[0] = pos;
- regs->end[0] = (MATCHING_IN_FIRST_STRING ? d - string1
- : d - string2 + size1);
- }
-
- /* Go through the first `min (num_regs, regs->num_regs)'
- registers, since that is all we initialized. */
- for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++)
- {
- if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt]))
- regs->start[mcnt] = regs->end[mcnt] = -1;
- else
- {
- regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]);
- regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]);
- }
- }
-
- /* If the regs structure we return has more elements than
- were in the pattern, set the extra elements to -1. If
- we (re)allocated the registers, this is the case,
- because we always allocate enough to have at least one
- -1 at the end. */
- for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++)
- regs->start[mcnt] = regs->end[mcnt] = -1;
- } /* regs && !bufp->no_sub */
-
- FREE_VARIABLES ();
- DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n",
- nfailure_points_pushed, nfailure_points_popped,
- nfailure_points_pushed - nfailure_points_popped);
- DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed);
-
- mcnt = d - pos - (MATCHING_IN_FIRST_STRING
- ? string1
- : string2 - size1);
-
- DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt);
-
- return mcnt;
- }
-
- /* Otherwise match next pattern command. */
-#ifdef SWITCH_ENUM_BUG
- switch ((int) ((re_opcode_t) *p++))
-#else
- switch ((re_opcode_t) *p++)
-#endif
- {
- /* Ignore these. Used to ignore the n of succeed_n's which
- currently have n == 0. */
- case no_op:
- DEBUG_PRINT1 ("EXECUTING no_op.\n");
- break;
-
-
- /* Match the next n pattern characters exactly. The following
- byte in the pattern defines n, and the n bytes after that
- are the characters to match. */
- case exactn:
- mcnt = *p++;
- DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt);
-
- /* This is written out as an if-else so we don't waste time
- testing `translate' inside the loop. */
- if (translate)
- {
- do
- {
- PREFETCH ();
- if (translate[(unsigned char) *d++] != (char) *p++)
- goto fail;
- }
- while (--mcnt);
- }
- else
- {
- do
- {
- PREFETCH ();
- if (*d++ != (char) *p++) goto fail;
- }
- while (--mcnt);
- }
- SET_REGS_MATCHED ();
- break;
-
-
- /* Match any character except possibly a newline or a null. */
- case anychar:
- DEBUG_PRINT1 ("EXECUTING anychar.\n");
-
- PREFETCH ();
-
- if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n')
- || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000'))
- goto fail;
-
- SET_REGS_MATCHED ();
- DEBUG_PRINT2 (" Matched `%d'.\n", *d);
- d++;
- break;
-
-
- case charset:
- case charset_not:
- {
- register unsigned char c;
- boolean not = (re_opcode_t) *(p - 1) == charset_not;
-
- DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : "");
-
- PREFETCH ();
- c = TRANSLATE (*d); /* The character to match. */
-
- /* Cast to `unsigned' instead of `unsigned char' in case the
- bit list is a full 32 bytes long. */
- if (c < (unsigned) (*p * BYTEWIDTH)
- && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
- not = !not;
-
- p += 1 + *p;
-
- if (!not) goto fail;
-
- SET_REGS_MATCHED ();
- d++;
- break;
- }
-
-
- /* The beginning of a group is represented by start_memory.
- The arguments are the register number in the next byte, and the
- number of groups inner to this one in the next. The text
- matched within the group is recorded (in the internal
- registers data structure) under the register number. */
- case start_memory:
- DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]);
-
- /* Find out if this group can match the empty string. */
- p1 = p; /* To send to group_match_null_string_p. */
-
- if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE)
- REG_MATCH_NULL_STRING_P (reg_info[*p])
- = group_match_null_string_p (&p1, pend, reg_info);
-
- /* Save the position in the string where we were the last time
- we were at this open-group operator in case the group is
- operated upon by a repetition operator, e.g., with `(a*)*b'
- against `ab'; then we want to ignore where we are now in
- the string in case this attempt to match fails. */
- old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
- ? REG_UNSET (regstart[*p]) ? d : regstart[*p]
- : regstart[*p];
- DEBUG_PRINT2 (" old_regstart: %d\n",
- POINTER_TO_OFFSET (old_regstart[*p]));
-
- regstart[*p] = d;
- DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p]));
-
- IS_ACTIVE (reg_info[*p]) = 1;
- MATCHED_SOMETHING (reg_info[*p]) = 0;
-
- /* This is the new highest active register. */
- highest_active_reg = *p;
-
- /* If nothing was active before, this is the new lowest active
- register. */
- if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
- lowest_active_reg = *p;
-
- /* Move past the register number and inner group count. */
- p += 2;
- break;
-
-
- /* The stop_memory opcode represents the end of a group. Its
- arguments are the same as start_memory's: the register
- number, and the number of inner groups. */
- case stop_memory:
- DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]);
-
- /* We need to save the string position the last time we were at
- this close-group operator in case the group is operated
- upon by a repetition operator, e.g., with `((a*)*(b*)*)*'
- against `aba'; then we want to ignore where we are now in
- the string in case this attempt to match fails. */
- old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
- ? REG_UNSET (regend[*p]) ? d : regend[*p]
- : regend[*p];
- DEBUG_PRINT2 (" old_regend: %d\n",
- POINTER_TO_OFFSET (old_regend[*p]));
-
- regend[*p] = d;
- DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p]));
-
- /* This register isn't active anymore. */
- IS_ACTIVE (reg_info[*p]) = 0;
-
- /* If this was the only register active, nothing is active
- anymore. */
- if (lowest_active_reg == highest_active_reg)
- {
- lowest_active_reg = NO_LOWEST_ACTIVE_REG;
- highest_active_reg = NO_HIGHEST_ACTIVE_REG;
- }
- else
- { /* We must scan for the new highest active register, since
- it isn't necessarily one less than now: consider
- (a(b)c(d(e)f)g). When group 3 ends, after the f), the
- new highest active register is 1. */
- unsigned char r = *p - 1;
- while (r > 0 && !IS_ACTIVE (reg_info[r]))
- r--;
-
- /* If we end up at register zero, that means that we saved
- the registers as the result of an `on_failure_jump', not
- a `start_memory', and we jumped to past the innermost
- `stop_memory'. For example, in ((.)*) we save
- registers 1 and 2 as a result of the *, but when we pop
- back to the second ), we are at the stop_memory 1.
- Thus, nothing is active. */
- if (r == 0)
- {
- lowest_active_reg = NO_LOWEST_ACTIVE_REG;
- highest_active_reg = NO_HIGHEST_ACTIVE_REG;
- }
- else
- highest_active_reg = r;
- }
-
- /* If just failed to match something this time around with a
- group that's operated on by a repetition operator, try to
- force exit from the ``loop'', and restore the register
- information for this group that we had before trying this
- last match. */
- if ((!MATCHED_SOMETHING (reg_info[*p])
- || (re_opcode_t) p[-3] == start_memory)
- && (p + 2) < pend)
- {
- boolean is_a_jump_n = false;
-
- p1 = p + 2;
- mcnt = 0;
- switch ((re_opcode_t) *p1++)
- {
- case jump_n:
- is_a_jump_n = true;
- case pop_failure_jump:
- case maybe_pop_jump:
- case jump:
- case dummy_failure_jump:
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- if (is_a_jump_n)
- p1 += 2;
- break;
-
- default:
- /* do nothing */ ;
- }
- p1 += mcnt;
-
- /* If the next operation is a jump backwards in the pattern
- to an on_failure_jump right before the start_memory
- corresponding to this stop_memory, exit from the loop
- by forcing a failure after pushing on the stack the
- on_failure_jump's jump in the pattern, and d. */
- if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump
- && (re_opcode_t) p1[3] == start_memory && p1[4] == *p)
- {
- /* If this group ever matched anything, then restore
- what its registers were before trying this last
- failed match, e.g., with `(a*)*b' against `ab' for
- regstart[1], and, e.g., with `((a*)*(b*)*)*'
- against `aba' for regend[3].
-
- Also restore the registers for inner groups for,
- e.g., `((a*)(b*))*' against `aba' (register 3 would
- otherwise get trashed). */
-
- if (EVER_MATCHED_SOMETHING (reg_info[*p]))
- {
- unsigned r;
-
- EVER_MATCHED_SOMETHING (reg_info[*p]) = 0;
-
- /* Restore this and inner groups' (if any) registers. */
- for (r = *p; r < *p + *(p + 1); r++)
- {
- regstart[r] = old_regstart[r];
-
- /* xx why this test? */
- if ((int) old_regend[r] >= (int) regstart[r])
- regend[r] = old_regend[r];
- }
- }
- p1++;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- PUSH_FAILURE_POINT (p1 + mcnt, d, -2);
-
- goto fail;
- }
- }
-
- /* Move past the register number and the inner group count. */
- p += 2;
- break;
-
-
- /* \<digit> has been turned into a `duplicate' command which is
- followed by the numeric value of <digit> as the register number. */
- case duplicate:
- {
- register const char *d2, *dend2;
- int regno = *p++; /* Get which register to match against. */
- DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno);
-
- /* Can't back reference a group which we've never matched. */
- if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno]))
- goto fail;
-
- /* Where in input to try to start matching. */
- d2 = regstart[regno];
-
- /* Where to stop matching; if both the place to start and
- the place to stop matching are in the same string, then
- set to the place to stop, otherwise, for now have to use
- the end of the first string. */
-
- dend2 = ((FIRST_STRING_P (regstart[regno])
- == FIRST_STRING_P (regend[regno]))
- ? regend[regno] : end_match_1);
- for (;;)
- {
- /* If necessary, advance to next segment in register
- contents. */
- while (d2 == dend2)
- {
- if (dend2 == end_match_2) break;
- if (dend2 == regend[regno]) break;
-
- /* End of string1 => advance to string2. */
- d2 = string2;
- dend2 = regend[regno];
- }
- /* At end of register contents => success */
- if (d2 == dend2) break;
-
- /* If necessary, advance to next segment in data. */
- PREFETCH ();
-
- /* How many characters left in this segment to match. */
- mcnt = dend - d;
-
- /* Want how many consecutive characters we can match in
- one shot, so, if necessary, adjust the count. */
- if (mcnt > dend2 - d2)
- mcnt = dend2 - d2;
-
- /* Compare that many; failure if mismatch, else move
- past them. */
- if (translate
- ? bcmp_translate (d, d2, mcnt, translate)
- : bcmp (d, d2, mcnt))
- goto fail;
- d += mcnt, d2 += mcnt;
- }
- }
- break;
-
-
- /* begline matches the empty string at the beginning of the string
- (unless `not_bol' is set in `bufp'), and, if
- `newline_anchor' is set, after newlines. */
- case begline:
- DEBUG_PRINT1 ("EXECUTING begline.\n");
-
- if (AT_STRINGS_BEG (d))
- {
- if (!bufp->not_bol) break;
- }
- else if (d[-1] == '\n' && bufp->newline_anchor)
- {
- break;
- }
- /* In all other cases, we fail. */
- goto fail;
-
-
- /* endline is the dual of begline. */
- case endline:
- DEBUG_PRINT1 ("EXECUTING endline.\n");
-
- if (AT_STRINGS_END (d))
- {
- if (!bufp->not_eol) break;
- }
-
- /* We have to ``prefetch'' the next character. */
- else if ((d == end1 ? *string2 : *d) == '\n'
- && bufp->newline_anchor)
- {
- break;
- }
- goto fail;
-
-
- /* Match at the very beginning of the data. */
- case begbuf:
- DEBUG_PRINT1 ("EXECUTING begbuf.\n");
- if (AT_STRINGS_BEG (d))
- break;
- goto fail;
-
-
- /* Match at the very end of the data. */
- case endbuf:
- DEBUG_PRINT1 ("EXECUTING endbuf.\n");
- if (AT_STRINGS_END (d))
- break;
- goto fail;
-
-
- /* on_failure_keep_string_jump is used to optimize `.*\n'. It
- pushes NULL as the value for the string on the stack. Then
- `pop_failure_point' will keep the current value for the
- string, instead of restoring it. To see why, consider
- matching `foo\nbar' against `.*\n'. The .* matches the foo;
- then the . fails against the \n. But the next thing we want
- to do is match the \n against the \n; if we restored the
- string value, we would be back at the foo.
-
- Because this is used only in specific cases, we don't need to
- check all the things that `on_failure_jump' does, to make
- sure the right things get saved on the stack. Hence we don't
- share its code. The only reason to push anything on the
- stack at all is that otherwise we would have to change
- `anychar's code to do something besides goto fail in this
- case; that seems worse than this. */
- case on_failure_keep_string_jump:
- DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump");
-
- EXTRACT_NUMBER_AND_INCR (mcnt, p);
- DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt);
-
- PUSH_FAILURE_POINT (p + mcnt, NULL, -2);
- break;
-
-
- /* Uses of on_failure_jump:
-
- Each alternative starts with an on_failure_jump that points
- to the beginning of the next alternative. Each alternative
- except the last ends with a jump that in effect jumps past
- the rest of the alternatives. (They really jump to the
- ending jump of the following alternative, because tensioning
- these jumps is a hassle.)
-
- Repeats start with an on_failure_jump that points past both
- the repetition text and either the following jump or
- pop_failure_jump back to this on_failure_jump. */
- case on_failure_jump:
- on_failure:
- DEBUG_PRINT1 ("EXECUTING on_failure_jump");
-
- EXTRACT_NUMBER_AND_INCR (mcnt, p);
- DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt);
-
- /* If this on_failure_jump comes right before a group (i.e.,
- the original * applied to a group), save the information
- for that group and all inner ones, so that if we fail back
- to this point, the group's information will be correct.
- For example, in \(a*\)*\1, we need the preceding group,
- and in \(\(a*\)b*\)\2, we need the inner group. */
-
- /* We can't use `p' to check ahead because we push
- a failure point to `p + mcnt' after we do this. */
- p1 = p;
-
- /* We need to skip no_op's before we look for the
- start_memory in case this on_failure_jump is happening as
- the result of a completed succeed_n, as in \(a\)\{1,3\}b\1
- against aba. */
- while (p1 < pend && (re_opcode_t) *p1 == no_op)
- p1++;
-
- if (p1 < pend && (re_opcode_t) *p1 == start_memory)
- {
- /* We have a new highest active register now. This will
- get reset at the start_memory we are about to get to,
- but we will have saved all the registers relevant to
- this repetition op, as described above. */
- highest_active_reg = *(p1 + 1) + *(p1 + 2);
- if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
- lowest_active_reg = *(p1 + 1);
- }
-
- DEBUG_PRINT1 (":\n");
- PUSH_FAILURE_POINT (p + mcnt, d, -2);
- break;
-
-
- /* A smart repeat ends with `maybe_pop_jump'.
- We change it to either `pop_failure_jump' or `jump'. */
- case maybe_pop_jump:
- EXTRACT_NUMBER_AND_INCR (mcnt, p);
- DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt);
- {
- register unsigned char *p2 = p;
-
- /* Compare the beginning of the repeat with what in the
- pattern follows its end. If we can establish that there
- is nothing that they would both match, i.e., that we
- would have to backtrack because of (as in, e.g., `a*a')
- then we can change to pop_failure_jump, because we'll
- never have to backtrack.
-
- This is not true in the case of alternatives: in
- `(a|ab)*' we do need to backtrack to the `ab' alternative
- (e.g., if the string was `ab'). But instead of trying to
- detect that here, the alternative has put on a dummy
- failure point which is what we will end up popping. */
-
- /* Skip over open/close-group commands. */
- while (p2 + 2 < pend
- && ((re_opcode_t) *p2 == stop_memory
- || (re_opcode_t) *p2 == start_memory))
- p2 += 3; /* Skip over args, too. */
-
- /* If we're at the end of the pattern, we can change. */
- if (p2 == pend)
- {
- /* Consider what happens when matching ":\(.*\)"
- against ":/". I don't really understand this code
- yet. */
- p[-3] = (unsigned char) pop_failure_jump;
- DEBUG_PRINT1
- (" End of pattern: change to `pop_failure_jump'.\n");
- }
-
- else if ((re_opcode_t) *p2 == exactn
- || (bufp->newline_anchor && (re_opcode_t) *p2 == endline))
- {
- register unsigned char c
- = *p2 == (unsigned char) endline ? '\n' : p2[2];
- p1 = p + mcnt;
-
- /* p1[0] ... p1[2] are the `on_failure_jump' corresponding
- to the `maybe_finalize_jump' of this case. Examine what
- follows. */
- if ((re_opcode_t) p1[3] == exactn && p1[5] != c)
- {
- p[-3] = (unsigned char) pop_failure_jump;
- DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n",
- c, p1[5]);
- }
-
- else if ((re_opcode_t) p1[3] == charset
- || (re_opcode_t) p1[3] == charset_not)
- {
- int not = (re_opcode_t) p1[3] == charset_not;
-
- if (c < (unsigned char) (p1[4] * BYTEWIDTH)
- && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
- not = !not;
-
- /* `not' is equal to 1 if c would match, which means
- that we can't change to pop_failure_jump. */
- if (!not)
- {
- p[-3] = (unsigned char) pop_failure_jump;
- DEBUG_PRINT1 (" No match => pop_failure_jump.\n");
- }
- }
- }
- }
- p -= 2; /* Point at relative address again. */
- if ((re_opcode_t) p[-1] != pop_failure_jump)
- {
- p[-1] = (unsigned char) jump;
- DEBUG_PRINT1 (" Match => jump.\n");
- goto unconditional_jump;
- }
- /* Note fall through. */
-
-
- /* The end of a simple repeat has a pop_failure_jump back to
- its matching on_failure_jump, where the latter will push a
- failure point. The pop_failure_jump takes off failure
- points put on by this pop_failure_jump's matching
- on_failure_jump; we got through the pattern to here from the
- matching on_failure_jump, so didn't fail. */
- case pop_failure_jump:
- {
- /* We need to pass separate storage for the lowest and
- highest registers, even though we don't care about the
- actual values. Otherwise, we will restore only one
- register from the stack, since lowest will == highest in
- `pop_failure_point'. */
- unsigned dummy_low_reg, dummy_high_reg;
- unsigned char *pdummy;
- const char *sdummy;
-
- DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n");
- POP_FAILURE_POINT (sdummy, pdummy,
- dummy_low_reg, dummy_high_reg,
- reg_dummy, reg_dummy, reg_info_dummy);
- }
- /* Note fall through. */
-
-
- /* Unconditionally jump (without popping any failure points). */
- case jump:
- unconditional_jump:
- EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */
- DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt);
- p += mcnt; /* Do the jump. */
- DEBUG_PRINT2 ("(to 0x%x).\n", p);
- break;
-
-
- /* We need this opcode so we can detect where alternatives end
- in `group_match_null_string_p' et al. */
- case jump_past_alt:
- DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n");
- goto unconditional_jump;
-
-
- /* Normally, the on_failure_jump pushes a failure point, which
- then gets popped at pop_failure_jump. We will end up at
- pop_failure_jump, also, and with a pattern of, say, `a+', we
- are skipping over the on_failure_jump, so we have to push
- something meaningless for pop_failure_jump to pop. */
- case dummy_failure_jump:
- DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n");
- /* It doesn't matter what we push for the string here. What
- the code at `fail' tests is the value for the pattern. */
- PUSH_FAILURE_POINT (0, 0, -2);
- goto unconditional_jump;
-
-
- /* At the end of an alternative, we need to push a dummy failure
- point in case we are followed by a `pop_failure_jump', because
- we don't want the failure point for the alternative to be
- popped. For example, matching `(a|ab)*' against `aab'
- requires that we match the `ab' alternative. */
- case push_dummy_failure:
- DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n");
- /* See comments just above at `dummy_failure_jump' about the
- two zeroes. */
- PUSH_FAILURE_POINT (0, 0, -2);
- break;
-
- /* Have to succeed matching what follows at least n times.
- After that, handle like `on_failure_jump'. */
- case succeed_n:
- EXTRACT_NUMBER (mcnt, p + 2);
- DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt);
-
- assert (mcnt >= 0);
- /* Originally, this is how many times we HAVE to succeed. */
- if (mcnt > 0)
- {
- mcnt--;
- p += 2;
- STORE_NUMBER_AND_INCR (p, mcnt);
- DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p, mcnt);
- }
- else if (mcnt == 0)
- {
- DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2);
- p[2] = (unsigned char) no_op;
- p[3] = (unsigned char) no_op;
- goto on_failure;
- }
- break;
-
- case jump_n:
- EXTRACT_NUMBER (mcnt, p + 2);
- DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt);
-
- /* Originally, this is how many times we CAN jump. */
- if (mcnt)
- {
- mcnt--;
- STORE_NUMBER (p + 2, mcnt);
- goto unconditional_jump;
- }
- /* If don't have to jump any more, skip over the rest of command. */
- else
- p += 4;
- break;
-
- case set_number_at:
- {
- DEBUG_PRINT1 ("EXECUTING set_number_at.\n");
-
- EXTRACT_NUMBER_AND_INCR (mcnt, p);
- p1 = p + mcnt;
- EXTRACT_NUMBER_AND_INCR (mcnt, p);
- DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt);
- STORE_NUMBER (p1, mcnt);
- break;
- }
-
- case wordbound:
- DEBUG_PRINT1 ("EXECUTING wordbound.\n");
- if (AT_WORD_BOUNDARY (d))
- break;
- goto fail;
-
- case notwordbound:
- DEBUG_PRINT1 ("EXECUTING notwordbound.\n");
- if (AT_WORD_BOUNDARY (d))
- goto fail;
- break;
-
- case wordbeg:
- DEBUG_PRINT1 ("EXECUTING wordbeg.\n");
- if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1)))
- break;
- goto fail;
-
- case wordend:
- DEBUG_PRINT1 ("EXECUTING wordend.\n");
- if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1)
- && (!WORDCHAR_P (d) || AT_STRINGS_END (d)))
- break;
- goto fail;
-
-#ifdef emacs
-#ifdef emacs19
- case before_dot:
- DEBUG_PRINT1 ("EXECUTING before_dot.\n");
- if (PTR_CHAR_POS ((unsigned char *) d) >= point)
- goto fail;
- break;
-
- case at_dot:
- DEBUG_PRINT1 ("EXECUTING at_dot.\n");
- if (PTR_CHAR_POS ((unsigned char *) d) != point)
- goto fail;
- break;
-
- case after_dot:
- DEBUG_PRINT1 ("EXECUTING after_dot.\n");
- if (PTR_CHAR_POS ((unsigned char *) d) <= point)
- goto fail;
- break;
-#else /* not emacs19 */
- case at_dot:
- DEBUG_PRINT1 ("EXECUTING at_dot.\n");
- if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point)
- goto fail;
- break;
-#endif /* not emacs19 */
-
- case syntaxspec:
- DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt);
- mcnt = *p++;
- goto matchsyntax;
-
- case wordchar:
- DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n");
- mcnt = (int) Sword;
- matchsyntax:
- PREFETCH ();
- if (SYNTAX (*d++) != (enum syntaxcode) mcnt)
- goto fail;
- SET_REGS_MATCHED ();
- break;
-
- case notsyntaxspec:
- DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt);
- mcnt = *p++;
- goto matchnotsyntax;
-
- case notwordchar:
- DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n");
- mcnt = (int) Sword;
- matchnotsyntax:
- PREFETCH ();
- if (SYNTAX (*d++) == (enum syntaxcode) mcnt)
- goto fail;
- SET_REGS_MATCHED ();
- break;
-
-#else /* not emacs */
- case wordchar:
- DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n");
- PREFETCH ();
- if (!WORDCHAR_P (d))
- goto fail;
- SET_REGS_MATCHED ();
- d++;
- break;
-
- case notwordchar:
- DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n");
- PREFETCH ();
- if (WORDCHAR_P (d))
- goto fail;
- SET_REGS_MATCHED ();
- d++;
- break;
-#endif /* not emacs */
-
- default:
- abort ();
- }
- continue; /* Successfully executed one pattern command; keep going. */
-
-
- /* We goto here if a matching operation fails. */
- fail:
- if (!FAIL_STACK_EMPTY ())
- { /* A restart point is known. Restore to that state. */
- DEBUG_PRINT1 ("\nFAIL:\n");
- POP_FAILURE_POINT (d, p,
- lowest_active_reg, highest_active_reg,
- regstart, regend, reg_info);
-
- /* If this failure point is a dummy, try the next one. */
- if (!p)
- goto fail;
-
- /* If we failed to the end of the pattern, don't examine *p. */
- assert (p <= pend);
- if (p < pend)
- {
- boolean is_a_jump_n = false;
-
- /* If failed to a backwards jump that's part of a repetition
- loop, need to pop this failure point and use the next one. */
- switch ((re_opcode_t) *p)
- {
- case jump_n:
- is_a_jump_n = true;
- case maybe_pop_jump:
- case pop_failure_jump:
- case jump:
- p1 = p + 1;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- p1 += mcnt;
-
- if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n)
- || (!is_a_jump_n
- && (re_opcode_t) *p1 == on_failure_jump))
- goto fail;
- break;
- default:
- /* do nothing */ ;
- }
- }
-
- if (d >= string1 && d <= end1)
- dend = end_match_1;
- }
- else
- break; /* Matching at this starting point really fails. */
- } /* for (;;) */
-
- if (best_regs_set)
- goto restore_best_regs;
-
- FREE_VARIABLES ();
-
- return -1; /* Failure to match. */
-} /* re_match_2 */
-
-/* Subroutine definitions for re_match_2. */
-
-
-/* We are passed P pointing to a register number after a start_memory.
-
- Return true if the pattern up to the corresponding stop_memory can
- match the empty string, and false otherwise.
-
- If we find the matching stop_memory, sets P to point to one past its number.
- Otherwise, sets P to an undefined byte less than or equal to END.
-
- We don't handle duplicates properly (yet). */
-
-static boolean
-group_match_null_string_p (p, end, reg_info)
- unsigned char **p, *end;
- register_info_type *reg_info;
-{
- int mcnt;
- /* Point to after the args to the start_memory. */
- unsigned char *p1 = *p + 2;
-
- while (p1 < end)
- {
- /* Skip over opcodes that can match nothing, and return true or
- false, as appropriate, when we get to one that can't, or to the
- matching stop_memory. */
-
- switch ((re_opcode_t) *p1)
- {
- /* Could be either a loop or a series of alternatives. */
- case on_failure_jump:
- p1++;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-
- /* If the next operation is not a jump backwards in the
- pattern. */
-
- if (mcnt >= 0)
- {
- /* Go through the on_failure_jumps of the alternatives,
- seeing if any of the alternatives cannot match nothing.
- The last alternative starts with only a jump,
- whereas the rest start with on_failure_jump and end
- with a jump, e.g., here is the pattern for `a|b|c':
-
- /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6
- /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3
- /exactn/1/c
-
- So, we have to first go through the first (n-1)
- alternatives and then deal with the last one separately. */
-
-
- /* Deal with the first (n-1) alternatives, which start
- with an on_failure_jump (see above) that jumps to right
- past a jump_past_alt. */
-
- while ((re_opcode_t) p1[mcnt-3] == jump_past_alt)
- {
- /* `mcnt' holds how many bytes long the alternative
- is, including the ending `jump_past_alt' and
- its number. */
-
- if (!alt_match_null_string_p (p1, p1 + mcnt - 3,
- reg_info))
- return false;
-
- /* Move to right after this alternative, including the
- jump_past_alt. */
- p1 += mcnt;
-
- /* Break if it's the beginning of an n-th alternative
- that doesn't begin with an on_failure_jump. */
- if ((re_opcode_t) *p1 != on_failure_jump)
- break;
-
- /* Still have to check that it's not an n-th
- alternative that starts with an on_failure_jump. */
- p1++;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- if ((re_opcode_t) p1[mcnt-3] != jump_past_alt)
- {
- /* Get to the beginning of the n-th alternative. */
- p1 -= 3;
- break;
- }
- }
-
- /* Deal with the last alternative: go back and get number
- of the `jump_past_alt' just before it. `mcnt' contains
- the length of the alternative. */
- EXTRACT_NUMBER (mcnt, p1 - 2);
-
- if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info))
- return false;
-
- p1 += mcnt; /* Get past the n-th alternative. */
- } /* if mcnt > 0 */
- break;
-
-
- case stop_memory:
- assert (p1[1] == **p);
- *p = p1 + 2;
- return true;
-
-
- default:
- if (!common_op_match_null_string_p (&p1, end, reg_info))
- return false;
- }
- } /* while p1 < end */
-
- return false;
-} /* group_match_null_string_p */
-
-
-/* Similar to group_match_null_string_p, but doesn't deal with alternatives:
- It expects P to be the first byte of a single alternative and END one
- byte past the last. The alternative can contain groups. */
-
-static boolean
-alt_match_null_string_p (p, end, reg_info)
- unsigned char *p, *end;
- register_info_type *reg_info;
-{
- int mcnt;
- unsigned char *p1 = p;
-
- while (p1 < end)
- {
- /* Skip over opcodes that can match nothing, and break when we get
- to one that can't. */
-
- switch ((re_opcode_t) *p1)
- {
- /* It's a loop. */
- case on_failure_jump:
- p1++;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- p1 += mcnt;
- break;
-
- default:
- if (!common_op_match_null_string_p (&p1, end, reg_info))
- return false;
- }
- } /* while p1 < end */
-
- return true;
-} /* alt_match_null_string_p */
-
-
-/* Deals with the ops common to group_match_null_string_p and
- alt_match_null_string_p.
-
- Sets P to one after the op and its arguments, if any. */
-
-static boolean
-common_op_match_null_string_p (p, end, reg_info)
- unsigned char **p, *end;
- register_info_type *reg_info;
-{
- int mcnt;
- boolean ret;
- int reg_no;
- unsigned char *p1 = *p;
-
- switch ((re_opcode_t) *p1++)
- {
- case no_op:
- case begline:
- case endline:
- case begbuf:
- case endbuf:
- case wordbeg:
- case wordend:
- case wordbound:
- case notwordbound:
-#ifdef emacs
- case before_dot:
- case at_dot:
- case after_dot:
+# endif
#endif
- break;
-
- case start_memory:
- reg_no = *p1;
- assert (reg_no > 0 && reg_no <= MAX_REGNUM);
- ret = group_match_null_string_p (&p1, end, reg_info);
-
- /* Have to set this here in case we're checking a group which
- contains a group and a back reference to it. */
-
- if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE)
- REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret;
-
- if (!ret)
- return false;
- break;
-
- /* If this is an optimized succeed_n for zero times, make the jump. */
- case jump:
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- if (mcnt >= 0)
- p1 += mcnt;
- else
- return false;
- break;
-
- case succeed_n:
- /* Get to the number of times to succeed. */
- p1 += 2;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
-
- if (mcnt == 0)
- {
- p1 -= 4;
- EXTRACT_NUMBER_AND_INCR (mcnt, p1);
- p1 += mcnt;
- }
- else
- return false;
- break;
-
- case duplicate:
- if (!REG_MATCH_NULL_STRING_P (reg_info[*p1]))
- return false;
- break;
-
- case set_number_at:
- p1 += 4;
-
- default:
- /* All other opcodes mean we cannot match the empty string. */
- return false;
- }
-
- *p = p1;
- return true;
-} /* common_op_match_null_string_p */
-
-
-/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN
- bytes; nonzero otherwise. */
-
-static int
-bcmp_translate(
- unsigned char *s1,
- unsigned char *s2,
- int len,
- char *translate
-)
-{
- register unsigned char *p1 = s1, *p2 = s2;
- while (len)
- {
- if (translate[*p1++] != translate[*p2++]) return 1;
- len--;
- }
- return 0;
-}
-
-/* Entry points for GNU code. */
-
-/* re_compile_pattern is the GNU regular expression compiler: it
- compiles PATTERN (of length SIZE) and puts the result in BUFP.
- Returns 0 if the pattern was valid, otherwise an error string.
-
- Assumes the `allocated' (and perhaps `buffer') and `translate' fields
- are set in BUFP on entry.
-
- We call regex_compile to do the actual compilation. */
-
-const char *
-re_compile_pattern (pattern, length, bufp)
- const char *pattern;
- int length;
- struct re_pattern_buffer *bufp;
-{
- reg_errcode_t ret;
-
- /* GNU code is written to assume at least RE_NREGS registers will be set
- (and at least one extra will be -1). */
- bufp->regs_allocated = REGS_UNALLOCATED;
-
- /* And GNU code determines whether or not to get register information
- by passing null for the REGS argument to re_match, etc., not by
- setting no_sub. */
- bufp->no_sub = 0;
-
- /* Match anchors at newline. */
- bufp->newline_anchor = 1;
-
- ret = regex_compile (pattern, length, re_syntax_options, bufp);
-
- return re_error_msg[(int) ret];
-}
-
-/* Entry points compatible with 4.2 BSD regex library. We don't define
- them if this is an Emacs or POSIX compilation. */
-
-#if !defined (emacs) && !defined (_POSIX_SOURCE)
-
-/* BSD has one and only one pattern buffer. */
-static struct re_pattern_buffer re_comp_buf;
-
-char *
-re_comp (s)
- const char *s;
-{
- reg_errcode_t ret;
-
- if (!s)
- {
- if (!re_comp_buf.buffer)
- return "No previous regular expression";
- return 0;
- }
-
- if (!re_comp_buf.buffer)
- {
- re_comp_buf.buffer = (unsigned char *) malloc (200);
- if (re_comp_buf.buffer == NULL)
- return "Memory exhausted";
- re_comp_buf.allocated = 200;
-
- re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH);
- if (re_comp_buf.fastmap == NULL)
- return "Memory exhausted";
- }
-
- /* Since `re_exec' always passes NULL for the `regs' argument, we
- don't need to initialize the pattern buffer fields which affect it. */
-
- /* Match anchors at newlines. */
- re_comp_buf.newline_anchor = 1;
-
- ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf);
-
- /* Yes, we're discarding `const' here. */
- return (char *) re_error_msg[(int) ret];
-}
-
-
-int
-re_exec (s)
- const char *s;
-{
- const int len = strlen (s);
- return
- 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0);
-}
-#endif /* not emacs and not _POSIX_SOURCE */
-
-/* POSIX.2 functions. Don't define these for Emacs. */
-
-#ifndef emacs
-
-/* regcomp takes a regular expression as a string and compiles it.
-
- PREG is a regex_t *. We do not expect any fields to be initialized,
- since POSIX says we shouldn't. Thus, we set
-
- `buffer' to the compiled pattern;
- `used' to the length of the compiled pattern;
- `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
- REG_EXTENDED bit in CFLAGS is set; otherwise, to
- RE_SYNTAX_POSIX_BASIC;
- `newline_anchor' to REG_NEWLINE being set in CFLAGS;
- `fastmap' and `fastmap_accurate' to zero;
- `re_nsub' to the number of subexpressions in PATTERN.
-
- PATTERN is the address of the pattern string.
-
- CFLAGS is a series of bits which affect compilation.
-
- If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
- use POSIX basic syntax.
-
- If REG_NEWLINE is set, then . and [^...] don't match newline.
- Also, regexec will try a match beginning after every newline.
-
- If REG_ICASE is set, then we considers upper- and lowercase
- versions of letters to be equivalent when matching.
-
- If REG_NOSUB is set, then when PREG is passed to regexec, that
- routine will report only success or failure, and nothing about the
- registers.
-
- It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for
- the return codes and their meanings.) */
-
-int
-regcomp (preg, pattern, cflags)
- regex_t *preg;
- const char *pattern;
- int cflags;
-{
- reg_errcode_t ret;
- unsigned syntax
- = (cflags & REG_EXTENDED) ?
- RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC;
-
- /* regex_compile will allocate the space for the compiled pattern. */
- preg->buffer = 0;
- preg->allocated = 0;
-
- /* Don't bother to use a fastmap when searching. This simplifies the
- REG_NEWLINE case: if we used a fastmap, we'd have to put all the
- characters after newlines into the fastmap. This way, we just try
- every character. */
- preg->fastmap = 0;
-
- if (cflags & REG_ICASE)
- {
- unsigned i;
-
- preg->translate = (char *) malloc (CHAR_SET_SIZE);
- if (preg->translate == NULL)
- return (int) REG_ESPACE;
-
- /* Map uppercase characters to corresponding lowercase ones. */
- for (i = 0; i < CHAR_SET_SIZE; i++)
- preg->translate[i] = ISUPPER (i) ? tolower (i) : i;
- }
- else
- preg->translate = NULL;
-
- /* If REG_NEWLINE is set, newlines are treated differently. */
- if (cflags & REG_NEWLINE)
- { /* REG_NEWLINE implies neither . nor [^...] match newline. */
- syntax &= ~RE_DOT_NEWLINE;
- syntax |= RE_HAT_LISTS_NOT_NEWLINE;
- /* It also changes the matching behavior. */
- preg->newline_anchor = 1;
- }
- else
- preg->newline_anchor = 0;
-
- preg->no_sub = !!(cflags & REG_NOSUB);
-
- /* POSIX says a null character in the pattern terminates it, so we
- can use strlen here in compiling the pattern. */
- ret = regex_compile (pattern, strlen (pattern), syntax, preg);
-
- /* POSIX doesn't distinguish between an unmatched open-group and an
- unmatched close-group: both are REG_EPAREN. */
- if (ret == REG_ERPAREN) ret = REG_EPAREN;
-
- return (int) ret;
-}
-
-
-/* regexec searches for a given pattern, specified by PREG, in the
- string STRING.
-
- If NMATCH is zero or REG_NOSUB was set in the cflags argument to
- `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at
- least NMATCH elements, and we set them to the offsets of the
- corresponding matched substrings.
-
- EFLAGS specifies `execution flags' which affect matching: if
- REG_NOTBOL is set, then ^ does not match at the beginning of the
- string; if REG_NOTEOL is set, then $ does not match at the end.
-
- We return 0 if we find a match and REG_NOMATCH if not. */
-
-int
-regexec (preg, string, nmatch, pmatch, eflags)
- const regex_t *preg;
- const char *string;
- size_t nmatch;
- regmatch_t pmatch[];
- int eflags;
-{
- int ret;
- struct re_registers regs;
- regex_t private_preg;
- int len = strlen (string);
- boolean want_reg_info = !preg->no_sub && nmatch > 0;
-
- private_preg = *preg;
-
- private_preg.not_bol = !!(eflags & REG_NOTBOL);
- private_preg.not_eol = !!(eflags & REG_NOTEOL);
-
- /* The user has told us exactly how many registers to return
- information about, via `nmatch'. We have to pass that on to the
- matching routines. */
- private_preg.regs_allocated = REGS_FIXED;
-
- if (want_reg_info)
- {
- regs.num_regs = nmatch;
- regs.start = TALLOC (nmatch, regoff_t);
- regs.end = TALLOC (nmatch, regoff_t);
- if (regs.start == NULL || regs.end == NULL)
- return (int) REG_NOMATCH;
- }
-
- /* Perform the searching operation. */
- ret = re_search (&private_preg, string, len,
- /* start: */ 0, /* range: */ len,
- want_reg_info ? &regs : (struct re_registers *) 0);
-
- /* Copy the register information to the POSIX structure. */
- if (want_reg_info)
- {
- if (ret >= 0)
- {
- unsigned r;
-
- for (r = 0; r < nmatch; r++)
- {
- pmatch[r].rm_so = regs.start[r];
- pmatch[r].rm_eo = regs.end[r];
- }
- }
-
- /* If we needed the temporary register info, free the space now. */
- free (regs.start);
- free (regs.end);
- }
-
- /* We want zero return to mean success, unlike `re_search'. */
- return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH;
-}
-
-
-/* Returns a message corresponding to an error code, ERRCODE, returned
- from either regcomp or regexec. We don't use PREG here. */
-
-size_t
-regerror(int errcode, const regex_t *preg,
- char *errbuf, size_t errbuf_size)
-{
- const char *msg;
- size_t msg_size;
-
- if (errcode < 0
- || errcode >= (sizeof (re_error_msg) / sizeof (re_error_msg[0])))
- /* Only error codes returned by the rest of the code should be passed
- to this routine. If we are given anything else, or if other regex
- code generates an invalid error code, then the program has a bug.
- Dump core so we can fix it. */
- abort ();
-
- msg = re_error_msg[errcode];
-
- /* POSIX doesn't require that we do anything in this case, but why
- not be nice. */
- if (! msg)
- msg = "Success";
-
- msg_size = strlen (msg) + 1; /* Includes the null. */
-
- if (errbuf_size != 0)
- {
- if (msg_size > errbuf_size)
- {
- strncpy (errbuf, msg, errbuf_size - 1);
- errbuf[errbuf_size - 1] = 0;
- }
- else
- strcpy (errbuf, msg);
- }
-
- return msg_size;
-}
-
-
-/* Free dynamically allocated space used by PREG. */
-
-void
-regfree (preg)
- regex_t *preg;
-{
- if (preg->buffer != NULL)
- free (preg->buffer);
- preg->buffer = NULL;
-
- preg->allocated = 0;
- preg->used = 0;
-
- if (preg->fastmap != NULL)
- free (preg->fastmap);
- preg->fastmap = NULL;
- preg->fastmap_accurate = 0;
-
- if (preg->translate != NULL)
- free (preg->translate);
- preg->translate = NULL;
-}
-
-#endif /* not emacs */
-
-/*
-Local variables:
-make-backup-files: t
-version-control: t
-trim-versions-without-asking: nil
-End:
-*/
diff --git a/compat/regex/regex.h b/compat/regex/regex.h
index 6eb64f1..61c9683 100644
--- a/compat/regex/regex.h
+++ b/compat/regex/regex.h
@@ -1,70 +1,90 @@
-/* Definitions for data structures and routines for the regular
- expression library, version 0.12.
+#include <stdio.h>
+#include <stddef.h>
- Copyright (C) 1985, 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
+/* Definitions for data structures and routines for the regular
+ expression library.
+ Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
+ The GNU C Library 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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
-#ifndef __REGEXP_LIBRARY_H__
-#define __REGEXP_LIBRARY_H__
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
-/* POSIX says that <sys/types.h> must be included (by the caller) before
- <regex.h>. */
+#ifndef _REGEX_H
+#define _REGEX_H 1
-#ifdef VMS
-/* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it
- should be there. */
+#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifndef _LIBC
+#define __USE_GNU 1
+#endif
+
+/* Allow the use in C++ code. */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following two types have to be signed and unsigned integer type
+ wide enough to hold a value of a pointer. For most ANSI compilers
+ ptrdiff_t and size_t should be likely OK. Still size of these two
+ types is 2 for Microsoft C. Ugh... */
+typedef long int s_reg_t;
+typedef unsigned long int active_reg_t;
/* The following bits are used to determine the regexp syntax we
recognize. The set/not-set meanings are chosen so that Emacs syntax
remains the value 0. The bits are given in alphabetical order, and
the definitions shifted by one from the previous bit; thus, when we
add or remove a bit, only one other definition need change. */
-typedef unsigned reg_syntax_t;
+typedef unsigned long int reg_syntax_t;
+#ifdef __USE_GNU
/* If this bit is not set, then \ inside a bracket expression is literal.
If set, then such a \ quotes the following character. */
-#define RE_BACKSLASH_ESCAPE_IN_LISTS (1)
+# define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1)
/* If this bit is not set, then + and ? are operators, and \+ and \? are
literals.
If set, then \+ and \? are operators and + and ? are literals. */
-#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
+# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
/* If this bit is set, then character classes are supported. They are:
[:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:],
[:space:], [:print:], [:punct:], [:graph:], and [:cntrl:].
If not set, then character classes are not supported. */
-#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
+# define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
/* If this bit is set, then ^ and $ are always anchors (outside bracket
expressions, of course).
If this bit is not set, then it depends:
- ^ is an anchor if it is at the beginning of a regular
- expression or after an open-group or an alternation operator;
- $ is an anchor if it is at the end of a regular expression, or
- before a close-group or an alternation operator.
+ ^ is an anchor if it is at the beginning of a regular
+ expression or after an open-group or an alternation operator;
+ $ is an anchor if it is at the end of a regular expression, or
+ before a close-group or an alternation operator.
This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because
POSIX draft 11.2 says that * etc. in leading positions is undefined.
We already implemented a previous draft which made those constructs
invalid, though, so we haven't changed the code back. */
-#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
+# define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
/* If this bit is set, then special characters are always special
regardless of where they are in the pattern.
@@ -72,63 +92,94 @@ typedef unsigned reg_syntax_t;
some contexts; otherwise they are ordinary. Specifically,
* + ? and intervals are only special when not after the beginning,
open-group, or alternation operator. */
-#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
+# define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
/* If this bit is set, then *, +, ?, and { cannot be first in an re or
immediately after an alternation or begin-group operator. */
-#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
+# define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
/* If this bit is set, then . matches newline.
If not set, then it doesn't. */
-#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
+# define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
/* If this bit is set, then . doesn't match NUL.
If not set, then it does. */
-#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
+# define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
/* If this bit is set, nonmatching lists [^...] do not match newline.
If not set, they do. */
-#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
+# define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
/* If this bit is set, either \{...\} or {...} defines an
interval, depending on RE_NO_BK_BRACES.
If not set, \{, \}, {, and } are literals. */
-#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
+# define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
/* If this bit is set, +, ? and | aren't recognized as operators.
If not set, they are. */
-#define RE_LIMITED_OPS (RE_INTERVALS << 1)
+# define RE_LIMITED_OPS (RE_INTERVALS << 1)
/* If this bit is set, newline is an alternation operator.
If not set, newline is literal. */
-#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
+# define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
/* If this bit is set, then `{...}' defines an interval, and \{ and \}
are literals.
If not set, then `\{...\}' defines an interval. */
-#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
+# define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
/* If this bit is set, (...) defines a group, and \( and \) are literals.
If not set, \(...\) defines a group, and ( and ) are literals. */
-#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
+# define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
/* If this bit is set, then \<digit> matches <digit>.
If not set, then \<digit> is a back-reference. */
-#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
+# define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
/* If this bit is set, then | is an alternation operator, and \| is literal.
If not set, then \| is an alternation operator, and | is literal. */
-#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
+# define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
/* If this bit is set, then an ending range point collating higher
than the starting range point, as in [z-a], is invalid.
If not set, then when ending range point collates higher than the
starting range point, the range is ignored. */
-#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
+# define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
/* If this bit is set, then an unmatched ) is ordinary.
If not set, then an unmatched ) is invalid. */
-#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+# define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+
+/* If this bit is set, succeed as soon as we match the whole pattern,
+ without further backtracking. */
+# define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1)
+
+/* If this bit is set, do not process the GNU regex operators.
+ If not set, then the GNU regex operators are recognized. */
+# define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1)
+
+/* If this bit is set, a syntactically invalid interval is treated as
+ a string of ordinary characters. For example, the ERE 'a{1' is
+ treated as 'a\{1'. */
+# define RE_INVALID_INTERVAL_ORD (RE_NO_GNU_OPS << 1)
+
+/* If this bit is set, then ignore case when matching.
+ If not set, then case is significant. */
+# define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1)
+
+/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only
+ for ^, because it is difficult to scan the regex backwards to find
+ whether ^ should be special. */
+# define RE_CARET_ANCHORS_HERE (RE_ICASE << 1)
+
+/* If this bit is set, then \{ cannot be first in an bre or
+ immediately after an alternation or begin-group operator. */
+# define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1)
+
+/* If this bit is set, then no_sub will be set to 1 during
+ re_compile_pattern. */
+#define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1)
+#endif
/* This global variable defines the particular regexp syntax to use (for
some interfaces). When a regexp is compiled, the syntax used is
@@ -136,6 +187,7 @@ typedef unsigned reg_syntax_t;
already-compiled regexps. */
extern reg_syntax_t re_syntax_options;
+#ifdef __USE_GNU
/* Define combinations of the above bits for the standard possibilities.
(The [[[ comments delimit what gets put into the Texinfo file, so
don't delete them!) */
@@ -143,13 +195,22 @@ extern reg_syntax_t re_syntax_options;
#define RE_SYNTAX_EMACS 0
#define RE_SYNTAX_AWK \
- (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
- | RE_NO_BK_PARENS | RE_NO_BK_REFS \
- | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
- | RE_UNMATCHED_RIGHT_PAREN_ORD)
-
-#define RE_SYNTAX_POSIX_AWK \
- (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS)
+ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
+ | RE_NO_BK_PARENS | RE_NO_BK_REFS \
+ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
+ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS)
+
+#define RE_SYNTAX_GNU_AWK \
+ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \
+ | RE_INVALID_INTERVAL_ORD) \
+ & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS \
+ | RE_CONTEXT_INVALID_OPS ))
+
+#define RE_SYNTAX_POSIX_AWK \
+ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \
+ | RE_INTERVALS | RE_NO_GNU_OPS \
+ | RE_INVALID_INTERVAL_ORD)
#define RE_SYNTAX_GREP \
(RE_BK_PLUS_QM | RE_CHAR_CLASSES \
@@ -163,7 +224,8 @@ extern reg_syntax_t re_syntax_options;
| RE_NO_BK_VBAR)
#define RE_SYNTAX_POSIX_EGREP \
- (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES)
+ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \
+ | RE_INVALID_INTERVAL_ORD)
/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */
#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC
@@ -176,7 +238,7 @@ extern reg_syntax_t re_syntax_options;
| RE_INTERVALS | RE_NO_EMPTY_RANGES)
#define RE_SYNTAX_POSIX_BASIC \
- (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM)
+ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP)
/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes
RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this
@@ -185,13 +247,13 @@ extern reg_syntax_t re_syntax_options;
(_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)
#define RE_SYNTAX_POSIX_EXTENDED \
- (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
- | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
- | RE_NO_BK_PARENS | RE_NO_BK_VBAR \
- | RE_UNMATCHED_RIGHT_PAREN_ORD)
+ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
+ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \
+ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD)
-/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
- replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
+/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is
+ removed and RE_NO_BK_REFS is added. */
#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \
(_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
| RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \
@@ -202,10 +264,12 @@ extern reg_syntax_t re_syntax_options;
/* Maximum number of duplicates an interval can allow. Some systems
(erroneously) define this in other header files, but we want our
value, so remove any previous define. */
-#ifdef RE_DUP_MAX
-#undef RE_DUP_MAX
+# ifdef RE_DUP_MAX
+# undef RE_DUP_MAX
+# endif
+/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */
+# define RE_DUP_MAX (0x7fff)
#endif
-#define RE_DUP_MAX ((1 << 15) - 1)
/* POSIX `cflags' bits (i.e., information for `regcomp'). */
@@ -240,18 +304,26 @@ extern reg_syntax_t re_syntax_options;
/* Like REG_NOTBOL, except for the end-of-line. */
#define REG_NOTEOL (1 << 1)
+/* Use PMATCH[0] to delimit the start and end of the search in the
+ buffer. */
+#define REG_STARTEND (1 << 2)
+
/* If any error codes are removed, changed, or added, update the
`re_error_msg' table in regex.c. */
typedef enum
{
+#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K
+ REG_ENOSYS = -1, /* This will never happen for this implementation. */
+#endif
+
REG_NOERROR = 0, /* Success. */
REG_NOMATCH, /* Didn't find a match (for regexec). */
/* POSIX regcomp return error codes. (In the order listed in the
standard.) */
REG_BADPAT, /* Invalid pattern. */
- REG_ECOLLATE, /* Not implemented. */
+ REG_ECOLLATE, /* Inalid collating element. */
REG_ECTYPE, /* Invalid character class name. */
REG_EESCAPE, /* Trailing backslash. */
REG_ESUBREG, /* Invalid back reference. */
@@ -275,85 +347,92 @@ typedef enum
compiled, the `re_nsub' field is available. All other fields are
private to the regex routines. */
+#ifndef RE_TRANSLATE_TYPE
+# define __RE_TRANSLATE_TYPE unsigned char *
+# ifdef __USE_GNU
+# define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE
+# endif
+#endif
+
+#ifdef __USE_GNU
+# define __REPB_PREFIX(name) name
+#else
+# define __REPB_PREFIX(name) __##name
+#endif
+
struct re_pattern_buffer
{
-/* [[[begin pattern_buffer]]] */
- /* Space that holds the compiled pattern. It is declared as
- `unsigned char *' because its elements are
- sometimes used as array indexes. */
- unsigned char *buffer;
+ /* Space that holds the compiled pattern. It is declared as
+ `unsigned char *' because its elements are sometimes used as
+ array indexes. */
+ unsigned char *__REPB_PREFIX(buffer);
- /* Number of bytes to which `buffer' points. */
- unsigned long allocated;
+ /* Number of bytes to which `buffer' points. */
+ unsigned long int __REPB_PREFIX(allocated);
- /* Number of bytes actually used in `buffer'. */
- unsigned long used;
+ /* Number of bytes actually used in `buffer'. */
+ unsigned long int __REPB_PREFIX(used);
- /* Syntax setting with which the pattern was compiled. */
- reg_syntax_t syntax;
+ /* Syntax setting with which the pattern was compiled. */
+ reg_syntax_t __REPB_PREFIX(syntax);
- /* Pointer to a fastmap, if any, otherwise zero. re_search uses
- the fastmap, if there is one, to skip over impossible
- starting points for matches. */
- char *fastmap;
+ /* Pointer to a fastmap, if any, otherwise zero. re_search uses the
+ fastmap, if there is one, to skip over impossible starting points
+ for matches. */
+ char *__REPB_PREFIX(fastmap);
- /* Either a translate table to apply to all characters before
- comparing them, or zero for no translation. The translation
- is applied to a pattern when it is compiled and to a string
- when it is matched. */
- char *translate;
+ /* Either a translate table to apply to all characters before
+ comparing them, or zero for no translation. The translation is
+ applied to a pattern when it is compiled and to a string when it
+ is matched. */
+ __RE_TRANSLATE_TYPE __REPB_PREFIX(translate);
- /* Number of subexpressions found by the compiler. */
+ /* Number of subexpressions found by the compiler. */
size_t re_nsub;
- /* Zero if this pattern cannot match the empty string, one else.
- Well, in truth it's used only in `re_search_2', to see
- whether or not we should use the fastmap, so we don't set
- this absolutely perfectly; see `re_compile_fastmap' (the
- `duplicate' case). */
- unsigned can_be_null : 1;
-
- /* If REGS_UNALLOCATED, allocate space in the `regs' structure
- for `max (RE_NREGS, re_nsub + 1)' groups.
- If REGS_REALLOCATE, reallocate space if necessary.
- If REGS_FIXED, use what's there. */
-#define REGS_UNALLOCATED 0
-#define REGS_REALLOCATE 1
-#define REGS_FIXED 2
- unsigned regs_allocated : 2;
-
- /* Set to zero when `regex_compile' compiles a pattern; set to one
- by `re_compile_fastmap' if it updates the fastmap. */
- unsigned fastmap_accurate : 1;
-
- /* If set, `re_match_2' does not return information about
- subexpressions. */
- unsigned no_sub : 1;
-
- /* If set, a beginning-of-line anchor doesn't match at the
- beginning of the string. */
- unsigned not_bol : 1;
-
- /* Similarly for an end-of-line anchor. */
- unsigned not_eol : 1;
-
- /* If true, an anchor at a newline matches. */
- unsigned newline_anchor : 1;
-
-/* [[[end pattern_buffer]]] */
-};
+ /* Zero if this pattern cannot match the empty string, one else.
+ Well, in truth it's used only in `re_search_2', to see whether or
+ not we should use the fastmap, so we don't set this absolutely
+ perfectly; see `re_compile_fastmap' (the `duplicate' case). */
+ unsigned __REPB_PREFIX(can_be_null) : 1;
+
+ /* If REGS_UNALLOCATED, allocate space in the `regs' structure
+ for `max (RE_NREGS, re_nsub + 1)' groups.
+ If REGS_REALLOCATE, reallocate space if necessary.
+ If REGS_FIXED, use what's there. */
+#ifdef __USE_GNU
+# define REGS_UNALLOCATED 0
+# define REGS_REALLOCATE 1
+# define REGS_FIXED 2
+#endif
+ unsigned __REPB_PREFIX(regs_allocated) : 2;
-typedef struct re_pattern_buffer regex_t;
+ /* Set to zero when `regex_compile' compiles a pattern; set to one
+ by `re_compile_fastmap' if it updates the fastmap. */
+ unsigned __REPB_PREFIX(fastmap_accurate) : 1;
+
+ /* If set, `re_match_2' does not return information about
+ subexpressions. */
+ unsigned __REPB_PREFIX(no_sub) : 1;
+
+ /* If set, a beginning-of-line anchor doesn't match at the beginning
+ of the string. */
+ unsigned __REPB_PREFIX(not_bol) : 1;
+
+ /* Similarly for an end-of-line anchor. */
+ unsigned __REPB_PREFIX(not_eol) : 1;
+ /* If true, an anchor at a newline matches. */
+ unsigned __REPB_PREFIX(newline_anchor) : 1;
+};
-/* search.c (search_buffer) in Emacs needs this one opcode value. It is
- defined both in `regex.c' and here. */
-#define RE_EXACTN_VALUE 1
+typedef struct re_pattern_buffer regex_t;
/* Type for byte offsets within the string. POSIX mandates this. */
typedef int regoff_t;
+#ifdef __USE_GNU
/* This is the structure we store register match data in. See
regex.texinfo for a full description of what registers match. */
struct re_registers
@@ -367,8 +446,9 @@ struct re_registers
/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer,
`re_match_2' returns information about at least this many registers
the first time a `regs' structure is passed. */
-#ifndef RE_NREGS
-#define RE_NREGS 30
+# ifndef RE_NREGS
+# define RE_NREGS 30
+# endif
#endif
@@ -383,38 +463,22 @@ typedef struct
/* Declarations for routines. */
-/* To avoid duplicating every routine declaration -- once with a
- prototype (if we are ANSI), and once without (if we aren't) -- we
- use the following macro to declare argument types. This
- unfortunately clutters up the declarations a bit, but I think it's
- worth it. */
-
-#if __STDC__
-
-#define _RE_ARGS(args) args
-
-#else /* not __STDC__ */
-
-#define _RE_ARGS(args) ()
-
-#endif /* not __STDC__ */
-
+#ifdef __USE_GNU
/* Sets the current default syntax to SYNTAX, and return the old syntax.
You can also simply assign to the `re_syntax_options' variable. */
-extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax));
+extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax);
/* Compile the regular expression PATTERN, with length LENGTH
and syntax given by the global `re_syntax_options', into the buffer
BUFFER. Return NULL if successful, and an error string if not. */
-extern const char *re_compile_pattern
- _RE_ARGS ((const char *pattern, int length,
- struct re_pattern_buffer *buffer));
+extern const char *re_compile_pattern (const char *__pattern, size_t __length,
+ struct re_pattern_buffer *__buffer);
/* Compile a fastmap for the compiled pattern in BUFFER; used to
accelerate searches. Return 0 if successful and -2 if was an
internal error. */
-extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
+extern int re_compile_fastmap (struct re_pattern_buffer *__buffer);
/* Search in the string STRING (with length LENGTH) for the pattern
@@ -422,31 +486,30 @@ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
characters. Return the starting position of the match, -1 for no
match, or -2 for an internal error. Also return register
information in REGS (if REGS and BUFFER->no_sub are nonzero). */
-extern int re_search
- _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
- int length, int start, int range, struct re_registers *regs));
+extern int re_search (struct re_pattern_buffer *__buffer, const char *__cstring,
+ int __length, int __start, int __range,
+ struct re_registers *__regs);
/* Like `re_search', but search in the concatenation of STRING1 and
STRING2. Also, stop searching at index START + STOP. */
-extern int re_search_2
- _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
- int length1, const char *string2, int length2,
- int start, int range, struct re_registers *regs, int stop));
+extern int re_search_2 (struct re_pattern_buffer *__buffer,
+ const char *__string1, int __length1,
+ const char *__string2, int __length2, int __start,
+ int __range, struct re_registers *__regs, int __stop);
/* Like `re_search', but return how many characters in STRING the regexp
in BUFFER matched, starting at position START. */
-extern int re_match
- _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
- int length, int start, struct re_registers *regs));
+extern int re_match (struct re_pattern_buffer *__buffer, const char *__cstring,
+ int __length, int __start, struct re_registers *__regs);
/* Relates to `re_match' as `re_search_2' relates to `re_search'. */
-extern int re_match_2
- _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
- int length1, const char *string2, int length2,
- int start, struct re_registers *regs, int stop));
+extern int re_match_2 (struct re_pattern_buffer *__buffer,
+ const char *__string1, int __length1,
+ const char *__string2, int __length2, int __start,
+ struct re_registers *__regs, int __stop);
/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
@@ -461,30 +524,59 @@ extern int re_match_2
Unless this function is called, the first search or match using
PATTERN_BUFFER will allocate its own register data, without
freeing the old data. */
-extern void re_set_registers
- _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs,
- unsigned num_regs, regoff_t *starts, regoff_t *ends));
-
+extern void re_set_registers (struct re_pattern_buffer *__buffer,
+ struct re_registers *__regs,
+ unsigned int __num_regs,
+ regoff_t *__starts, regoff_t *__ends);
+#endif /* Use GNU */
+
+#if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_BSD)
+# ifndef _CRAY
/* 4.2 bsd compatibility. */
-extern char *re_comp _RE_ARGS ((const char *));
-extern int re_exec _RE_ARGS ((const char *));
+extern char *re_comp (const char *);
+extern int re_exec (const char *);
+# endif
+#endif
+
+/* GCC 2.95 and later have "__restrict"; C99 compilers have
+ "restrict", and "configure" may have defined "restrict". */
+#ifndef __restrict
+# if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__))
+# if defined restrict || 199901L <= __STDC_VERSION__
+# define __restrict restrict
+# else
+# define __restrict
+# endif
+# endif
+#endif
+/* gcc 3.1 and up support the [restrict] syntax. */
+#ifndef __restrict_arr
+# if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \
+ && !defined __GNUG__
+# define __restrict_arr __restrict
+# else
+# define __restrict_arr
+# endif
+#endif
/* POSIX compatibility. */
-extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags));
-extern int regexec
- _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch,
- regmatch_t pmatch[], int eflags));
-extern size_t regerror
- _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf,
- size_t errbuf_size));
-extern void regfree _RE_ARGS ((regex_t *preg));
-
-#endif /* not __REGEXP_LIBRARY_H__ */
-
-/*
-Local variables:
-make-backup-files: t
-version-control: t
-trim-versions-without-asking: nil
-End:
-*/
+extern int regcomp (regex_t *__restrict __preg,
+ const char *__restrict __pattern,
+ int __cflags);
+
+extern int regexec (const regex_t *__restrict __preg,
+ const char *__restrict __cstring, size_t __nmatch,
+ regmatch_t __pmatch[__restrict_arr],
+ int __eflags);
+
+extern size_t regerror (int __errcode, const regex_t *__restrict __preg,
+ char *__restrict __errbuf, size_t __errbuf_size);
+
+extern void regfree (regex_t *__preg);
+
+
+#ifdef __cplusplus
+}
+#endif /* C++ */
+
+#endif /* regex.h */
diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c
new file mode 100644
index 0000000..193854c
--- /dev/null
+++ b/compat/regex/regex_internal.c
@@ -0,0 +1,1744 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2006, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static void re_string_construct_common (const char *str, int len,
+ re_string_t *pstr,
+ RE_TRANSLATE_TYPE trans, int icase,
+ const re_dfa_t *dfa) internal_function;
+static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa,
+ const re_node_set *nodes,
+ unsigned int hash) internal_function;
+static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa,
+ const re_node_set *nodes,
+ unsigned int context,
+ unsigned int hash) internal_function;
+
+#ifdef GAWK
+#undef MAX /* safety */
+static int
+MAX(size_t a, size_t b)
+{
+ return (a > b ? a : b);
+}
+#endif
+
+/* Functions for string operation. */
+
+/* This function allocate the buffers. It is necessary to call
+ re_string_reconstruct before using the object. */
+
+static reg_errcode_t
+internal_function
+re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len,
+ RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+ reg_errcode_t ret;
+ int init_buf_len;
+
+ /* Ensure at least one character fits into the buffers. */
+ if (init_len < dfa->mb_cur_max)
+ init_len = dfa->mb_cur_max;
+ init_buf_len = (len + 1 < init_len) ? len + 1: init_len;
+ re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+ ret = re_string_realloc_buffers (pstr, init_buf_len);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ pstr->word_char = dfa->word_char;
+ pstr->word_ops_used = dfa->word_ops_used;
+ pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+ pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len;
+ pstr->valid_raw_len = pstr->valid_len;
+ return REG_NOERROR;
+}
+
+/* This function allocate the buffers, and initialize them. */
+
+static reg_errcode_t
+internal_function
+re_string_construct (re_string_t *pstr, const char *str, int len,
+ RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+ reg_errcode_t ret;
+ memset (pstr, '\0', sizeof (re_string_t));
+ re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+ if (len > 0)
+ {
+ ret = re_string_realloc_buffers (pstr, len + 1);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+
+ if (icase)
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ while (1)
+ {
+ ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ if (pstr->valid_raw_len >= len)
+ break;
+ if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max)
+ break;
+ ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ build_upper_buffer (pstr);
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ build_wcs_buffer (pstr);
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ if (trans != NULL)
+ re_string_translate_buffer (pstr);
+ else
+ {
+ pstr->valid_len = pstr->bufs_len;
+ pstr->valid_raw_len = pstr->bufs_len;
+ }
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Helper functions for re_string_allocate, and re_string_construct. */
+
+static reg_errcode_t
+internal_function
+re_string_realloc_buffers (re_string_t *pstr, int new_buf_len)
+{
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ wint_t *new_wcs;
+
+ /* Avoid overflow in realloc. */
+ const size_t max_object_size = MAX (sizeof (wint_t), sizeof (int));
+ if (BE (SIZE_MAX / max_object_size < new_buf_len, 0))
+ return REG_ESPACE;
+
+ new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len);
+ if (BE (new_wcs == NULL, 0))
+ return REG_ESPACE;
+ pstr->wcs = new_wcs;
+ if (pstr->offsets != NULL)
+ {
+ int *new_offsets = re_realloc (pstr->offsets, int, new_buf_len);
+ if (BE (new_offsets == NULL, 0))
+ return REG_ESPACE;
+ pstr->offsets = new_offsets;
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ if (pstr->mbs_allocated)
+ {
+ unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char,
+ new_buf_len);
+ if (BE (new_mbs == NULL, 0))
+ return REG_ESPACE;
+ pstr->mbs = new_mbs;
+ }
+ pstr->bufs_len = new_buf_len;
+ return REG_NOERROR;
+}
+
+
+static void
+internal_function
+re_string_construct_common (const char *str, int len, re_string_t *pstr,
+ RE_TRANSLATE_TYPE trans, int icase,
+ const re_dfa_t *dfa)
+{
+ pstr->raw_mbs = (const unsigned char *) str;
+ pstr->len = len;
+ pstr->raw_len = len;
+ pstr->trans = trans;
+ pstr->icase = icase ? 1 : 0;
+ pstr->mbs_allocated = (trans != NULL || icase);
+ pstr->mb_cur_max = dfa->mb_cur_max;
+ pstr->is_utf8 = dfa->is_utf8;
+ pstr->map_notascii = dfa->map_notascii;
+ pstr->stop = pstr->len;
+ pstr->raw_stop = pstr->stop;
+}
+
+#ifdef RE_ENABLE_I18N
+
+/* Build wide character buffer PSTR->WCS.
+ If the byte sequence of the string are:
+ <mb1>(0), <mb1>(1), <mb2>(0), <mb2>(1), <sb3>
+ Then wide character buffer will be:
+ <wc1> , WEOF , <wc2> , WEOF , <wc3>
+ We use WEOF for padding, they indicate that the position isn't
+ a first byte of a multibyte character.
+
+ Note that this function assumes PSTR->VALID_LEN elements are already
+ built and starts from PSTR->VALID_LEN. */
+
+static void
+internal_function
+build_wcs_buffer (re_string_t *pstr)
+{
+#ifdef _LIBC
+ unsigned char buf[MB_LEN_MAX];
+ assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+ unsigned char buf[64];
+#endif
+ mbstate_t prev_st;
+ int byte_idx, end_idx, remain_len;
+ size_t mbclen;
+
+ /* Build the buffers from pstr->valid_len to either pstr->len or
+ pstr->bufs_len. */
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+ for (byte_idx = pstr->valid_len; byte_idx < end_idx;)
+ {
+ wchar_t wc;
+ const char *p;
+
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ /* Apply the translation if we need. */
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i, ch;
+
+ for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+ {
+ ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i];
+ buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch];
+ }
+ p = (const char *) buf;
+ }
+ else
+ p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx;
+ mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+ if (BE (mbclen == (size_t) -2, 0))
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0))
+ {
+ /* We treat these cases as a singlebyte character. */
+ mbclen = 1;
+ wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+ if (BE (pstr->trans != NULL, 0))
+ wc = pstr->trans[wc];
+ pstr->cur_state = prev_st;
+ }
+
+ /* Write wide character and padding. */
+ pstr->wcs[byte_idx++] = wc;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = byte_idx;
+}
+
+/* Build wide character buffer PSTR->WCS like build_wcs_buffer,
+ but for REG_ICASE. */
+
+static reg_errcode_t
+internal_function
+build_wcs_upper_buffer (re_string_t *pstr)
+{
+ mbstate_t prev_st;
+ int src_idx, byte_idx, end_idx, remain_len;
+ size_t mbclen;
+#ifdef _LIBC
+ char buf[MB_LEN_MAX];
+ assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+ char buf[64];
+#endif
+
+ byte_idx = pstr->valid_len;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ /* The following optimization assumes that ASCII characters can be
+ mapped to wide characters with a simple cast. */
+ if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed)
+ {
+ while (byte_idx < end_idx)
+ {
+ wchar_t wc;
+
+ if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx])
+ && mbsinit (&pstr->cur_state))
+ {
+ /* In case of a singlebyte character. */
+ pstr->mbs[byte_idx]
+ = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]);
+ /* The next step uses the assumption that wchar_t is encoded
+ ASCII-safe: all ASCII values can be converted like this. */
+ pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx];
+ ++byte_idx;
+ continue;
+ }
+
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ mbclen = __mbrtowc (&wc,
+ ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx
+ + byte_idx), remain_len, &pstr->cur_state);
+ if (BE (mbclen + 2 > 2, 1))
+ {
+ wchar_t wcu = wc;
+ if (iswlower (wc))
+ {
+ size_t mbcdlen;
+
+ wcu = towupper (wc);
+ mbcdlen = wcrtomb (buf, wcu, &prev_st);
+ if (BE (mbclen == mbcdlen, 1))
+ memcpy (pstr->mbs + byte_idx, buf, mbclen);
+ else
+ {
+ src_idx = byte_idx;
+ goto offsets_needed;
+ }
+ }
+ else
+ memcpy (pstr->mbs + byte_idx,
+ pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen);
+ pstr->wcs[byte_idx++] = wcu;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ else if (mbclen == (size_t) -1 || mbclen == 0)
+ {
+ /* It is an invalid character or '\0'. Just use the byte. */
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+ pstr->mbs[byte_idx] = ch;
+ /* And also cast it to wide char. */
+ pstr->wcs[byte_idx++] = (wchar_t) ch;
+ if (BE (mbclen == (size_t) -1, 0))
+ pstr->cur_state = prev_st;
+ }
+ else
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = byte_idx;
+ return REG_NOERROR;
+ }
+ else
+ for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;)
+ {
+ wchar_t wc;
+ const char *p;
+ offsets_needed:
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i, ch;
+
+ for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+ {
+ ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i];
+ buf[i] = pstr->trans[ch];
+ }
+ p = (const char *) buf;
+ }
+ else
+ p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx;
+ mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+ if (BE (mbclen + 2 > 2, 1))
+ {
+ wchar_t wcu = wc;
+ if (iswlower (wc))
+ {
+ size_t mbcdlen;
+
+ wcu = towupper (wc);
+ mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st);
+ if (BE (mbclen == mbcdlen, 1))
+ memcpy (pstr->mbs + byte_idx, buf, mbclen);
+ else if (mbcdlen != (size_t) -1)
+ {
+ size_t i;
+
+ if (byte_idx + mbcdlen > pstr->bufs_len)
+ {
+ pstr->cur_state = prev_st;
+ break;
+ }
+
+ if (pstr->offsets == NULL)
+ {
+ pstr->offsets = re_malloc (int, pstr->bufs_len);
+
+ if (pstr->offsets == NULL)
+ return REG_ESPACE;
+ }
+ if (!pstr->offsets_needed)
+ {
+ for (i = 0; i < (size_t) byte_idx; ++i)
+ pstr->offsets[i] = i;
+ pstr->offsets_needed = 1;
+ }
+
+ memcpy (pstr->mbs + byte_idx, buf, mbcdlen);
+ pstr->wcs[byte_idx] = wcu;
+ pstr->offsets[byte_idx] = src_idx;
+ for (i = 1; i < mbcdlen; ++i)
+ {
+ pstr->offsets[byte_idx + i]
+ = src_idx + (i < mbclen ? i : mbclen - 1);
+ pstr->wcs[byte_idx + i] = WEOF;
+ }
+ pstr->len += mbcdlen - mbclen;
+ if (pstr->raw_stop > src_idx)
+ pstr->stop += mbcdlen - mbclen;
+ end_idx = (pstr->bufs_len > pstr->len)
+ ? pstr->len : pstr->bufs_len;
+ byte_idx += mbcdlen;
+ src_idx += mbclen;
+ continue;
+ }
+ else
+ memcpy (pstr->mbs + byte_idx, p, mbclen);
+ }
+ else
+ memcpy (pstr->mbs + byte_idx, p, mbclen);
+
+ if (BE (pstr->offsets_needed != 0, 0))
+ {
+ size_t i;
+ for (i = 0; i < mbclen; ++i)
+ pstr->offsets[byte_idx + i] = src_idx + i;
+ }
+ src_idx += mbclen;
+
+ pstr->wcs[byte_idx++] = wcu;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ else if (mbclen == (size_t) -1 || mbclen == 0)
+ {
+ /* It is an invalid character or '\0'. Just use the byte. */
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx];
+
+ if (BE (pstr->trans != NULL, 0))
+ ch = pstr->trans [ch];
+ pstr->mbs[byte_idx] = ch;
+
+ if (BE (pstr->offsets_needed != 0, 0))
+ pstr->offsets[byte_idx] = src_idx;
+ ++src_idx;
+
+ /* And also cast it to wide char. */
+ pstr->wcs[byte_idx++] = (wchar_t) ch;
+ if (BE (mbclen == (size_t) -1, 0))
+ pstr->cur_state = prev_st;
+ }
+ else
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = src_idx;
+ return REG_NOERROR;
+}
+
+/* Skip characters until the index becomes greater than NEW_RAW_IDX.
+ Return the index. */
+
+static int
+internal_function
+re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc)
+{
+ mbstate_t prev_st;
+ int rawbuf_idx;
+ size_t mbclen;
+ wint_t wc = WEOF;
+
+ /* Skip the characters which are not necessary to check. */
+ for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len;
+ rawbuf_idx < new_raw_idx;)
+ {
+ wchar_t wc2;
+ int remain_len = pstr->len - rawbuf_idx;
+ prev_st = pstr->cur_state;
+ mbclen = __mbrtowc (&wc2, (const char *) pstr->raw_mbs + rawbuf_idx,
+ remain_len, &pstr->cur_state);
+ if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0))
+ {
+ /* We treat these cases as a single byte character. */
+ if (mbclen == 0 || remain_len == 0)
+ wc = L'\0';
+ else
+ wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx);
+ mbclen = 1;
+ pstr->cur_state = prev_st;
+ }
+ else
+ wc = (wint_t) wc2;
+ /* Then proceed the next character. */
+ rawbuf_idx += mbclen;
+ }
+ *last_wc = (wint_t) wc;
+ return rawbuf_idx;
+}
+#endif /* RE_ENABLE_I18N */
+
+/* Build the buffer PSTR->MBS, and apply the translation if we need.
+ This function is used in case of REG_ICASE. */
+
+static void
+internal_function
+build_upper_buffer (re_string_t *pstr)
+{
+ int char_idx, end_idx;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx)
+ {
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx];
+ if (BE (pstr->trans != NULL, 0))
+ ch = pstr->trans[ch];
+ if (islower (ch))
+ pstr->mbs[char_idx] = toupper (ch);
+ else
+ pstr->mbs[char_idx] = ch;
+ }
+ pstr->valid_len = char_idx;
+ pstr->valid_raw_len = char_idx;
+}
+
+/* Apply TRANS to the buffer in PSTR. */
+
+static void
+internal_function
+re_string_translate_buffer (re_string_t *pstr)
+{
+ int buf_idx, end_idx;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx)
+ {
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx];
+ pstr->mbs[buf_idx] = pstr->trans[ch];
+ }
+
+ pstr->valid_len = buf_idx;
+ pstr->valid_raw_len = buf_idx;
+}
+
+/* This function re-construct the buffers.
+ Concretely, convert to wide character in case of pstr->mb_cur_max > 1,
+ convert to upper case in case of REG_ICASE, apply translation. */
+
+static reg_errcode_t
+internal_function
+re_string_reconstruct (re_string_t *pstr, int idx, int eflags)
+{
+ int offset = idx - pstr->raw_mbs_idx;
+ if (BE (offset < 0, 0))
+ {
+ /* Reset buffer. */
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+#endif /* RE_ENABLE_I18N */
+ pstr->len = pstr->raw_len;
+ pstr->stop = pstr->raw_stop;
+ pstr->valid_len = 0;
+ pstr->raw_mbs_idx = 0;
+ pstr->valid_raw_len = 0;
+ pstr->offsets_needed = 0;
+ pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+ : CONTEXT_NEWLINE | CONTEXT_BEGBUF);
+ if (!pstr->mbs_allocated)
+ pstr->mbs = (unsigned char *) pstr->raw_mbs;
+ offset = idx;
+ }
+
+ if (BE (offset != 0, 1))
+ {
+ /* Should the already checked characters be kept? */
+ if (BE (offset < pstr->valid_raw_len, 1))
+ {
+ /* Yes, move them to the front of the buffer. */
+#ifdef RE_ENABLE_I18N
+ if (BE (pstr->offsets_needed, 0))
+ {
+ int low = 0, high = pstr->valid_len, mid;
+ do
+ {
+ mid = (high + low) / 2;
+ if (pstr->offsets[mid] > offset)
+ high = mid;
+ else if (pstr->offsets[mid] < offset)
+ low = mid + 1;
+ else
+ break;
+ }
+ while (low < high);
+ if (pstr->offsets[mid] < offset)
+ ++mid;
+ pstr->tip_context = re_string_context_at (pstr, mid - 1,
+ eflags);
+ /* This can be quite complicated, so handle specially
+ only the common and easy case where the character with
+ different length representation of lower and upper
+ case is present at or after offset. */
+ if (pstr->valid_len > offset
+ && mid == offset && pstr->offsets[mid] == offset)
+ {
+ memmove (pstr->wcs, pstr->wcs + offset,
+ (pstr->valid_len - offset) * sizeof (wint_t));
+ memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset);
+ pstr->valid_len -= offset;
+ pstr->valid_raw_len -= offset;
+ for (low = 0; low < pstr->valid_len; low++)
+ pstr->offsets[low] = pstr->offsets[low + offset] - offset;
+ }
+ else
+ {
+ /* Otherwise, just find out how long the partial multibyte
+ character at offset is and fill it with WEOF/255. */
+ pstr->len = pstr->raw_len - idx + offset;
+ pstr->stop = pstr->raw_stop - idx + offset;
+ pstr->offsets_needed = 0;
+ while (mid > 0 && pstr->offsets[mid - 1] == offset)
+ --mid;
+ while (mid < pstr->valid_len)
+ if (pstr->wcs[mid] != WEOF)
+ break;
+ else
+ ++mid;
+ if (mid == pstr->valid_len)
+ pstr->valid_len = 0;
+ else
+ {
+ pstr->valid_len = pstr->offsets[mid] - offset;
+ if (pstr->valid_len)
+ {
+ for (low = 0; low < pstr->valid_len; ++low)
+ pstr->wcs[low] = WEOF;
+ memset (pstr->mbs, 255, pstr->valid_len);
+ }
+ }
+ pstr->valid_raw_len = pstr->valid_len;
+ }
+ }
+ else
+#endif
+ {
+ pstr->tip_context = re_string_context_at (pstr, offset - 1,
+ eflags);
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ memmove (pstr->wcs, pstr->wcs + offset,
+ (pstr->valid_len - offset) * sizeof (wint_t));
+#endif /* RE_ENABLE_I18N */
+ if (BE (pstr->mbs_allocated, 0))
+ memmove (pstr->mbs, pstr->mbs + offset,
+ pstr->valid_len - offset);
+ pstr->valid_len -= offset;
+ pstr->valid_raw_len -= offset;
+#if DEBUG
+ assert (pstr->valid_len > 0);
+#endif
+ }
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ /* No, skip all characters until IDX. */
+ int prev_valid_len = pstr->valid_len;
+
+ if (BE (pstr->offsets_needed, 0))
+ {
+ pstr->len = pstr->raw_len - idx + offset;
+ pstr->stop = pstr->raw_stop - idx + offset;
+ pstr->offsets_needed = 0;
+ }
+#endif
+ pstr->valid_len = 0;
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ int wcs_idx;
+ wint_t wc = WEOF;
+
+ if (pstr->is_utf8)
+ {
+ const unsigned char *raw, *p, *end;
+
+ /* Special case UTF-8. Multi-byte chars start with any
+ byte other than 0x80 - 0xbf. */
+ raw = pstr->raw_mbs + pstr->raw_mbs_idx;
+ end = raw + (offset - pstr->mb_cur_max);
+ if (end < pstr->raw_mbs)
+ end = pstr->raw_mbs;
+ p = raw + offset - 1;
+#ifdef _LIBC
+ /* We know the wchar_t encoding is UCS4, so for the simple
+ case, ASCII characters, skip the conversion step. */
+ if (isascii (*p) && BE (pstr->trans == NULL, 1))
+ {
+ memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+ /* pstr->valid_len = 0; */
+ wc = (wchar_t) *p;
+ }
+ else
+#endif
+ for (; p >= end; --p)
+ if ((*p & 0xc0) != 0x80)
+ {
+ mbstate_t cur_state;
+ wchar_t wc2;
+ int mlen = raw + pstr->len - p;
+ unsigned char buf[6];
+ size_t mbclen;
+
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i = mlen < 6 ? mlen : 6;
+ while (--i >= 0)
+ buf[i] = pstr->trans[p[i]];
+ }
+ /* XXX Don't use mbrtowc, we know which conversion
+ to use (UTF-8 -> UCS4). */
+ memset (&cur_state, 0, sizeof (cur_state));
+ mbclen = __mbrtowc (&wc2, (const char *) p, mlen,
+ &cur_state);
+ if (raw + offset - p <= mbclen
+ && mbclen < (size_t) -2)
+ {
+ memset (&pstr->cur_state, '\0',
+ sizeof (mbstate_t));
+ pstr->valid_len = mbclen - (raw + offset - p);
+ wc = wc2;
+ }
+ break;
+ }
+ }
+
+ if (wc == WEOF)
+ pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx;
+ if (wc == WEOF)
+ pstr->tip_context
+ = re_string_context_at (pstr, prev_valid_len - 1, eflags);
+ else
+ pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0)
+ && IS_WIDE_WORD_CHAR (wc))
+ ? CONTEXT_WORD
+ : ((IS_WIDE_NEWLINE (wc)
+ && pstr->newline_anchor)
+ ? CONTEXT_NEWLINE : 0));
+ if (BE (pstr->valid_len, 0))
+ {
+ for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx)
+ pstr->wcs[wcs_idx] = WEOF;
+ if (pstr->mbs_allocated)
+ memset (pstr->mbs, 255, pstr->valid_len);
+ }
+ pstr->valid_raw_len = pstr->valid_len;
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1];
+ pstr->valid_raw_len = 0;
+ if (pstr->trans)
+ c = pstr->trans[c];
+ pstr->tip_context = (bitset_contain (pstr->word_char, c)
+ ? CONTEXT_WORD
+ : ((IS_NEWLINE (c) && pstr->newline_anchor)
+ ? CONTEXT_NEWLINE : 0));
+ }
+ }
+ if (!BE (pstr->mbs_allocated, 0))
+ pstr->mbs += offset;
+ }
+ pstr->raw_mbs_idx = idx;
+ pstr->len -= offset;
+ pstr->stop -= offset;
+
+ /* Then build the buffers. */
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ if (pstr->icase)
+ {
+ reg_errcode_t ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ else
+ build_wcs_buffer (pstr);
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ if (BE (pstr->mbs_allocated, 0))
+ {
+ if (pstr->icase)
+ build_upper_buffer (pstr);
+ else if (pstr->trans != NULL)
+ re_string_translate_buffer (pstr);
+ }
+ else
+ pstr->valid_len = pstr->len;
+
+ pstr->cur_idx = 0;
+ return REG_NOERROR;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_peek_byte_case (const re_string_t *pstr, int idx)
+{
+ int ch, off;
+
+ /* Handle the common (easiest) cases first. */
+ if (BE (!pstr->mbs_allocated, 1))
+ return re_string_peek_byte (pstr, idx);
+
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1
+ && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx))
+ return re_string_peek_byte (pstr, idx);
+#endif
+
+ off = pstr->cur_idx + idx;
+#ifdef RE_ENABLE_I18N
+ if (pstr->offsets_needed)
+ off = pstr->offsets[off];
+#endif
+
+ ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I
+ this function returns CAPITAL LETTER I instead of first byte of
+ DOTLESS SMALL LETTER I. The latter would confuse the parser,
+ since peek_byte_case doesn't advance cur_idx in any way. */
+ if (pstr->offsets_needed && !isascii (ch))
+ return re_string_peek_byte (pstr, idx);
+#endif
+
+ return ch;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_fetch_byte_case (re_string_t *pstr)
+{
+ if (BE (!pstr->mbs_allocated, 1))
+ return re_string_fetch_byte (pstr);
+
+#ifdef RE_ENABLE_I18N
+ if (pstr->offsets_needed)
+ {
+ int off, ch;
+
+ /* For tr_TR.UTF-8 [[:islower:]] there is
+ [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip
+ in that case the whole multi-byte character and return
+ the original letter. On the other side, with
+ [[: DOTLESS SMALL LETTER I return [[:I, as doing
+ anything else would complicate things too much. */
+
+ if (!re_string_first_byte (pstr, pstr->cur_idx))
+ return re_string_fetch_byte (pstr);
+
+ off = pstr->offsets[pstr->cur_idx];
+ ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+ if (! isascii (ch))
+ return re_string_fetch_byte (pstr);
+
+ re_string_skip_bytes (pstr,
+ re_string_char_size_at (pstr, pstr->cur_idx));
+ return ch;
+ }
+#endif
+
+ return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++];
+}
+
+static void
+internal_function
+re_string_destruct (re_string_t *pstr)
+{
+#ifdef RE_ENABLE_I18N
+ re_free (pstr->wcs);
+ re_free (pstr->offsets);
+#endif /* RE_ENABLE_I18N */
+ if (pstr->mbs_allocated)
+ re_free (pstr->mbs);
+}
+
+/* Return the context at IDX in INPUT. */
+
+static unsigned int
+internal_function
+re_string_context_at (const re_string_t *input, int idx, int eflags)
+{
+ int c;
+ if (BE (idx < 0, 0))
+ /* In this case, we use the value stored in input->tip_context,
+ since we can't know the character in input->mbs[-1] here. */
+ return input->tip_context;
+ if (BE (idx == input->len, 0))
+ return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF
+ : CONTEXT_NEWLINE | CONTEXT_ENDBUF);
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc;
+ int wc_idx = idx;
+ while(input->wcs[wc_idx] == WEOF)
+ {
+#ifdef DEBUG
+ /* It must not happen. */
+ assert (wc_idx >= 0);
+#endif
+ --wc_idx;
+ if (wc_idx < 0)
+ return input->tip_context;
+ }
+ wc = input->wcs[wc_idx];
+ if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc))
+ return CONTEXT_WORD;
+ return (IS_WIDE_NEWLINE (wc) && input->newline_anchor
+ ? CONTEXT_NEWLINE : 0);
+ }
+ else
+#endif
+ {
+ c = re_string_byte_at (input, idx);
+ if (bitset_contain (input->word_char, c))
+ return CONTEXT_WORD;
+ return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0;
+ }
+}
+
+/* Functions for set operation. */
+
+static reg_errcode_t
+internal_function
+re_node_set_alloc (re_node_set *set, int size)
+{
+ /*
+ * ADR: valgrind says size can be 0, which then doesn't
+ * free the block of size 0. Harumph. This seems
+ * to work ok, though.
+ */
+ if (size == 0)
+ {
+ memset(set, 0, sizeof(*set));
+ return REG_NOERROR;
+ }
+ set->alloc = size;
+ set->nelem = 0;
+ set->elems = re_malloc (int, size);
+ if (BE (set->elems == NULL, 0))
+ return REG_ESPACE;
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_1 (re_node_set *set, int elem)
+{
+ set->alloc = 1;
+ set->nelem = 1;
+ set->elems = re_malloc (int, 1);
+ if (BE (set->elems == NULL, 0))
+ {
+ set->alloc = set->nelem = 0;
+ return REG_ESPACE;
+ }
+ set->elems[0] = elem;
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_2 (re_node_set *set, int elem1, int elem2)
+{
+ set->alloc = 2;
+ set->elems = re_malloc (int, 2);
+ if (BE (set->elems == NULL, 0))
+ return REG_ESPACE;
+ if (elem1 == elem2)
+ {
+ set->nelem = 1;
+ set->elems[0] = elem1;
+ }
+ else
+ {
+ set->nelem = 2;
+ if (elem1 < elem2)
+ {
+ set->elems[0] = elem1;
+ set->elems[1] = elem2;
+ }
+ else
+ {
+ set->elems[0] = elem2;
+ set->elems[1] = elem1;
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_copy (re_node_set *dest, const re_node_set *src)
+{
+ dest->nelem = src->nelem;
+ if (src->nelem > 0)
+ {
+ dest->alloc = dest->nelem;
+ dest->elems = re_malloc (int, dest->alloc);
+ if (BE (dest->elems == NULL, 0))
+ {
+ dest->alloc = dest->nelem = 0;
+ return REG_ESPACE;
+ }
+ memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+ }
+ else
+ re_node_set_init_empty (dest);
+ return REG_NOERROR;
+}
+
+/* Calculate the intersection of the sets SRC1 and SRC2. And merge it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded.
+ Note: We assume dest->elems is NULL, when dest->alloc is 0. */
+
+static reg_errcode_t
+internal_function
+re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1,
+ const re_node_set *src2)
+{
+ int i1, i2, is, id, delta, sbase;
+ if (src1->nelem == 0 || src2->nelem == 0)
+ return REG_NOERROR;
+
+ /* We need dest->nelem + 2 * elems_in_intersection; this is a
+ conservative estimate. */
+ if (src1->nelem + src2->nelem + dest->nelem > dest->alloc)
+ {
+ int new_alloc = src1->nelem + src2->nelem + dest->alloc;
+ int *new_elems = re_realloc (dest->elems, int, new_alloc);
+ if (BE (new_elems == NULL, 0))
+ return REG_ESPACE;
+ dest->elems = new_elems;
+ dest->alloc = new_alloc;
+ }
+
+ /* Find the items in the intersection of SRC1 and SRC2, and copy
+ into the top of DEST those that are not already in DEST itself. */
+ sbase = dest->nelem + src1->nelem + src2->nelem;
+ i1 = src1->nelem - 1;
+ i2 = src2->nelem - 1;
+ id = dest->nelem - 1;
+ for (;;)
+ {
+ if (src1->elems[i1] == src2->elems[i2])
+ {
+ /* Try to find the item in DEST. Maybe we could binary search? */
+ while (id >= 0 && dest->elems[id] > src1->elems[i1])
+ --id;
+
+ if (id < 0 || dest->elems[id] != src1->elems[i1])
+ dest->elems[--sbase] = src1->elems[i1];
+
+ if (--i1 < 0 || --i2 < 0)
+ break;
+ }
+
+ /* Lower the highest of the two items. */
+ else if (src1->elems[i1] < src2->elems[i2])
+ {
+ if (--i2 < 0)
+ break;
+ }
+ else
+ {
+ if (--i1 < 0)
+ break;
+ }
+ }
+
+ id = dest->nelem - 1;
+ is = dest->nelem + src1->nelem + src2->nelem - 1;
+ delta = is - sbase + 1;
+
+ /* Now copy. When DELTA becomes zero, the remaining
+ DEST elements are already in place; this is more or
+ less the same loop that is in re_node_set_merge. */
+ dest->nelem += delta;
+ if (delta > 0 && id >= 0)
+ for (;;)
+ {
+ if (dest->elems[is] > dest->elems[id])
+ {
+ /* Copy from the top. */
+ dest->elems[id + delta--] = dest->elems[is--];
+ if (delta == 0)
+ break;
+ }
+ else
+ {
+ /* Slide from the bottom. */
+ dest->elems[id + delta] = dest->elems[id];
+ if (--id < 0)
+ break;
+ }
+ }
+
+ /* Copy remaining SRC elements. */
+ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int));
+
+ return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets SRC1 and SRC2. And store it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded. */
+
+static reg_errcode_t
+internal_function
+re_node_set_init_union (re_node_set *dest, const re_node_set *src1,
+ const re_node_set *src2)
+{
+ int i1, i2, id;
+ if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0)
+ {
+ dest->alloc = src1->nelem + src2->nelem;
+ dest->elems = re_malloc (int, dest->alloc);
+ if (BE (dest->elems == NULL, 0))
+ return REG_ESPACE;
+ }
+ else
+ {
+ if (src1 != NULL && src1->nelem > 0)
+ return re_node_set_init_copy (dest, src1);
+ else if (src2 != NULL && src2->nelem > 0)
+ return re_node_set_init_copy (dest, src2);
+ else
+ re_node_set_init_empty (dest);
+ return REG_NOERROR;
+ }
+ for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;)
+ {
+ if (src1->elems[i1] > src2->elems[i2])
+ {
+ dest->elems[id++] = src2->elems[i2++];
+ continue;
+ }
+ if (src1->elems[i1] == src2->elems[i2])
+ ++i2;
+ dest->elems[id++] = src1->elems[i1++];
+ }
+ if (i1 < src1->nelem)
+ {
+ memcpy (dest->elems + id, src1->elems + i1,
+ (src1->nelem - i1) * sizeof (int));
+ id += src1->nelem - i1;
+ }
+ else if (i2 < src2->nelem)
+ {
+ memcpy (dest->elems + id, src2->elems + i2,
+ (src2->nelem - i2) * sizeof (int));
+ id += src2->nelem - i2;
+ }
+ dest->nelem = id;
+ return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets DEST and SRC. And store it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded. */
+
+static reg_errcode_t
+internal_function
+re_node_set_merge (re_node_set *dest, const re_node_set *src)
+{
+ int is, id, sbase, delta;
+ if (src == NULL || src->nelem == 0)
+ return REG_NOERROR;
+ if (dest->alloc < 2 * src->nelem + dest->nelem)
+ {
+ int new_alloc = 2 * (src->nelem + dest->alloc);
+ int *new_buffer = re_realloc (dest->elems, int, new_alloc);
+ if (BE (new_buffer == NULL, 0))
+ return REG_ESPACE;
+ dest->elems = new_buffer;
+ dest->alloc = new_alloc;
+ }
+
+ if (BE (dest->nelem == 0, 0))
+ {
+ dest->nelem = src->nelem;
+ memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+ return REG_NOERROR;
+ }
+
+ /* Copy into the top of DEST the items of SRC that are not
+ found in DEST. Maybe we could binary search in DEST? */
+ for (sbase = dest->nelem + 2 * src->nelem,
+ is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; )
+ {
+ if (dest->elems[id] == src->elems[is])
+ is--, id--;
+ else if (dest->elems[id] < src->elems[is])
+ dest->elems[--sbase] = src->elems[is--];
+ else /* if (dest->elems[id] > src->elems[is]) */
+ --id;
+ }
+
+ if (is >= 0)
+ {
+ /* If DEST is exhausted, the remaining items of SRC must be unique. */
+ sbase -= is + 1;
+ memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int));
+ }
+
+ id = dest->nelem - 1;
+ is = dest->nelem + 2 * src->nelem - 1;
+ delta = is - sbase + 1;
+ if (delta == 0)
+ return REG_NOERROR;
+
+ /* Now copy. When DELTA becomes zero, the remaining
+ DEST elements are already in place. */
+ dest->nelem += delta;
+ for (;;)
+ {
+ if (dest->elems[is] > dest->elems[id])
+ {
+ /* Copy from the top. */
+ dest->elems[id + delta--] = dest->elems[is--];
+ if (delta == 0)
+ break;
+ }
+ else
+ {
+ /* Slide from the bottom. */
+ dest->elems[id + delta] = dest->elems[id];
+ if (--id < 0)
+ {
+ /* Copy remaining SRC elements. */
+ memcpy (dest->elems, dest->elems + sbase,
+ delta * sizeof (int));
+ break;
+ }
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+ SET should not already have ELEM.
+ return -1 if an error is occured, return 1 otherwise. */
+
+static int
+internal_function
+re_node_set_insert (re_node_set *set, int elem)
+{
+ int idx;
+ /* In case the set is empty. */
+ if (set->alloc == 0)
+ {
+ if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1))
+ return 1;
+ else
+ return -1;
+ }
+
+ if (BE (set->nelem, 0) == 0)
+ {
+ /* We already guaranteed above that set->alloc != 0. */
+ set->elems[0] = elem;
+ ++set->nelem;
+ return 1;
+ }
+
+ /* Realloc if we need. */
+ if (set->alloc == set->nelem)
+ {
+ int *new_elems;
+ set->alloc = set->alloc * 2;
+ new_elems = re_realloc (set->elems, int, set->alloc);
+ if (BE (new_elems == NULL, 0))
+ return -1;
+ set->elems = new_elems;
+ }
+
+ /* Move the elements which follows the new element. Test the
+ first element separately to skip a check in the inner loop. */
+ if (elem < set->elems[0])
+ {
+ idx = 0;
+ for (idx = set->nelem; idx > 0; idx--)
+ set->elems[idx] = set->elems[idx - 1];
+ }
+ else
+ {
+ for (idx = set->nelem; set->elems[idx - 1] > elem; idx--)
+ set->elems[idx] = set->elems[idx - 1];
+ }
+
+ /* Insert the new element. */
+ set->elems[idx] = elem;
+ ++set->nelem;
+ return 1;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+ SET should not already have any element greater than or equal to ELEM.
+ Return -1 if an error is occured, return 1 otherwise. */
+
+static int
+internal_function
+re_node_set_insert_last (re_node_set *set, int elem)
+{
+ /* Realloc if we need. */
+ if (set->alloc == set->nelem)
+ {
+ int *new_elems;
+ set->alloc = (set->alloc + 1) * 2;
+ new_elems = re_realloc (set->elems, int, set->alloc);
+ if (BE (new_elems == NULL, 0))
+ return -1;
+ set->elems = new_elems;
+ }
+
+ /* Insert the new element. */
+ set->elems[set->nelem++] = elem;
+ return 1;
+}
+
+/* Compare two node sets SET1 and SET2.
+ return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_compare (const re_node_set *set1, const re_node_set *set2)
+{
+ int i;
+ if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem)
+ return 0;
+ for (i = set1->nelem ; --i >= 0 ; )
+ if (set1->elems[i] != set2->elems[i])
+ return 0;
+ return 1;
+}
+
+/* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_contains (const re_node_set *set, int elem)
+{
+ unsigned int idx, right, mid;
+ if (set->nelem <= 0)
+ return 0;
+
+ /* Binary search the element. */
+ idx = 0;
+ right = set->nelem - 1;
+ while (idx < right)
+ {
+ mid = (idx + right) / 2;
+ if (set->elems[mid] < elem)
+ idx = mid + 1;
+ else
+ right = mid;
+ }
+ return set->elems[idx] == elem ? idx + 1 : 0;
+}
+
+static void
+internal_function
+re_node_set_remove_at (re_node_set *set, int idx)
+{
+ if (idx < 0 || idx >= set->nelem)
+ return;
+ --set->nelem;
+ for (; idx < set->nelem; idx++)
+ set->elems[idx] = set->elems[idx + 1];
+}
+
+
+/* Add the token TOKEN to dfa->nodes, and return the index of the token.
+ Or return -1, if an error will be occured. */
+
+static int
+internal_function
+re_dfa_add_node (re_dfa_t *dfa, re_token_t token)
+{
+ if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0))
+ {
+ size_t new_nodes_alloc = dfa->nodes_alloc * 2;
+ int *new_nexts, *new_indices;
+ re_node_set *new_edests, *new_eclosures;
+ re_token_t *new_nodes;
+
+ /* Avoid overflows in realloc. */
+ const size_t max_object_size = MAX (sizeof (re_token_t),
+ MAX (sizeof (re_node_set),
+ sizeof (int)));
+ if (BE (SIZE_MAX / max_object_size < new_nodes_alloc, 0))
+ return -1;
+
+ new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc);
+ if (BE (new_nodes == NULL, 0))
+ return -1;
+ dfa->nodes = new_nodes;
+ new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc);
+ new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc);
+ new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc);
+ new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc);
+ if (BE (new_nexts == NULL || new_indices == NULL
+ || new_edests == NULL || new_eclosures == NULL, 0))
+ return -1;
+ dfa->nexts = new_nexts;
+ dfa->org_indices = new_indices;
+ dfa->edests = new_edests;
+ dfa->eclosures = new_eclosures;
+ dfa->nodes_alloc = new_nodes_alloc;
+ }
+ dfa->nodes[dfa->nodes_len] = token;
+ dfa->nodes[dfa->nodes_len].constraint = 0;
+#ifdef RE_ENABLE_I18N
+ dfa->nodes[dfa->nodes_len].accept_mb =
+ (token.type == OP_PERIOD && dfa->mb_cur_max > 1) || token.type == COMPLEX_BRACKET;
+#endif
+ dfa->nexts[dfa->nodes_len] = -1;
+ re_node_set_init_empty (dfa->edests + dfa->nodes_len);
+ re_node_set_init_empty (dfa->eclosures + dfa->nodes_len);
+ return dfa->nodes_len++;
+}
+
+static inline unsigned int
+internal_function
+calc_state_hash (const re_node_set *nodes, unsigned int context)
+{
+ unsigned int hash = nodes->nelem + context;
+ int i;
+ for (i = 0 ; i < nodes->nelem ; i++)
+ hash += nodes->elems[i];
+ return hash;
+}
+
+/* Search for the state whose node_set is equivalent to NODES.
+ Return the pointer to the state, if we found it in the DFA.
+ Otherwise create the new one and return it. In case of an error
+ return NULL and set the error code in ERR.
+ Note: - We assume NULL as the invalid state, then it is possible that
+ return value is NULL and ERR is REG_NOERROR.
+ - We never return non-NULL value in case of any errors, it is for
+ optimization. */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa,
+ const re_node_set *nodes)
+{
+ unsigned int hash;
+ re_dfastate_t *new_state;
+ struct re_state_table_entry *spot;
+ int i;
+ if (BE (nodes->nelem == 0, 0))
+ {
+ *err = REG_NOERROR;
+ return NULL;
+ }
+ hash = calc_state_hash (nodes, 0);
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+ for (i = 0 ; i < spot->num ; i++)
+ {
+ re_dfastate_t *state = spot->array[i];
+ if (hash != state->hash)
+ continue;
+ if (re_node_set_compare (&state->nodes, nodes))
+ return state;
+ }
+
+ /* There are no appropriate state in the dfa, create the new one. */
+ new_state = create_ci_newstate (dfa, nodes, hash);
+ if (BE (new_state == NULL, 0))
+ *err = REG_ESPACE;
+
+ return new_state;
+}
+
+/* Search for the state whose node_set is equivalent to NODES and
+ whose context is equivalent to CONTEXT.
+ Return the pointer to the state, if we found it in the DFA.
+ Otherwise create the new one and return it. In case of an error
+ return NULL and set the error code in ERR.
+ Note: - We assume NULL as the invalid state, then it is possible that
+ return value is NULL and ERR is REG_NOERROR.
+ - We never return non-NULL value in case of any errors, it is for
+ optimization. */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa,
+ const re_node_set *nodes, unsigned int context)
+{
+ unsigned int hash;
+ re_dfastate_t *new_state;
+ struct re_state_table_entry *spot;
+ int i;
+ if (nodes->nelem == 0)
+ {
+ *err = REG_NOERROR;
+ return NULL;
+ }
+ hash = calc_state_hash (nodes, context);
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+ for (i = 0 ; i < spot->num ; i++)
+ {
+ re_dfastate_t *state = spot->array[i];
+ if (state->hash == hash
+ && state->context == context
+ && re_node_set_compare (state->entrance_nodes, nodes))
+ return state;
+ }
+ /* There are no appropriate state in `dfa', create the new one. */
+ new_state = create_cd_newstate (dfa, nodes, context, hash);
+ if (BE (new_state == NULL, 0))
+ *err = REG_ESPACE;
+
+ return new_state;
+}
+
+/* Finish initialization of the new state NEWSTATE, and using its hash value
+ HASH put in the appropriate bucket of DFA's state table. Return value
+ indicates the error code if failed. */
+
+static reg_errcode_t
+register_state (const re_dfa_t *dfa, re_dfastate_t *newstate,
+ unsigned int hash)
+{
+ struct re_state_table_entry *spot;
+ reg_errcode_t err;
+ int i;
+
+ newstate->hash = hash;
+ err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ for (i = 0; i < newstate->nodes.nelem; i++)
+ {
+ int elem = newstate->nodes.elems[i];
+ if (!IS_EPSILON_NODE (dfa->nodes[elem].type))
+ if (re_node_set_insert_last (&newstate->non_eps_nodes, elem) < 0)
+ return REG_ESPACE;
+ }
+
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+ if (BE (spot->alloc <= spot->num, 0))
+ {
+ int new_alloc = 2 * spot->num + 2;
+ re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *,
+ new_alloc);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ spot->array = new_array;
+ spot->alloc = new_alloc;
+ }
+ spot->array[spot->num++] = newstate;
+ return REG_NOERROR;
+}
+
+static void
+free_state (re_dfastate_t *state)
+{
+ re_node_set_free (&state->non_eps_nodes);
+ re_node_set_free (&state->inveclosure);
+ if (state->entrance_nodes != &state->nodes)
+ {
+ re_node_set_free (state->entrance_nodes);
+ re_free (state->entrance_nodes);
+ }
+ re_node_set_free (&state->nodes);
+ re_free (state->word_trtable);
+ re_free (state->trtable);
+ re_free (state);
+}
+
+/* Create the new state which is independ of contexts.
+ Return the new state if succeeded, otherwise return NULL. */
+
+static re_dfastate_t *
+internal_function
+create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+ unsigned int hash)
+{
+ int i;
+ reg_errcode_t err;
+ re_dfastate_t *newstate;
+
+ newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ if (BE (newstate == NULL, 0))
+ return NULL;
+ err = re_node_set_init_copy (&newstate->nodes, nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_free (newstate);
+ return NULL;
+ }
+
+ newstate->entrance_nodes = &newstate->nodes;
+ for (i = 0 ; i < nodes->nelem ; i++)
+ {
+ re_token_t *node = dfa->nodes + nodes->elems[i];
+ re_token_type_t type = node->type;
+ if (type == CHARACTER && !node->constraint)
+ continue;
+#ifdef RE_ENABLE_I18N
+ newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+ /* If the state has the halt node, the state is a halt state. */
+ if (type == END_OF_RE)
+ newstate->halt = 1;
+ else if (type == OP_BACK_REF)
+ newstate->has_backref = 1;
+ else if (type == ANCHOR || node->constraint)
+ newstate->has_constraint = 1;
+ }
+ err = register_state (dfa, newstate, hash);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_state (newstate);
+ newstate = NULL;
+ }
+ return newstate;
+}
+
+/* Create the new state which is depend on the context CONTEXT.
+ Return the new state if succeeded, otherwise return NULL. */
+
+static re_dfastate_t *
+internal_function
+create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+ unsigned int context, unsigned int hash)
+{
+ int i, nctx_nodes = 0;
+ reg_errcode_t err;
+ re_dfastate_t *newstate;
+
+ newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ if (BE (newstate == NULL, 0))
+ return NULL;
+ err = re_node_set_init_copy (&newstate->nodes, nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_free (newstate);
+ return NULL;
+ }
+
+ newstate->context = context;
+ newstate->entrance_nodes = &newstate->nodes;
+
+ for (i = 0 ; i < nodes->nelem ; i++)
+ {
+ re_token_t *node = dfa->nodes + nodes->elems[i];
+ re_token_type_t type = node->type;
+ unsigned int constraint = node->constraint;
+
+ if (type == CHARACTER && !constraint)
+ continue;
+#ifdef RE_ENABLE_I18N
+ newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+ /* If the state has the halt node, the state is a halt state. */
+ if (type == END_OF_RE)
+ newstate->halt = 1;
+ else if (type == OP_BACK_REF)
+ newstate->has_backref = 1;
+
+ if (constraint)
+ {
+ if (newstate->entrance_nodes == &newstate->nodes)
+ {
+ newstate->entrance_nodes = re_malloc (re_node_set, 1);
+ if (BE (newstate->entrance_nodes == NULL, 0))
+ {
+ free_state (newstate);
+ return NULL;
+ }
+ if (re_node_set_init_copy (newstate->entrance_nodes, nodes)
+ != REG_NOERROR)
+ return NULL;
+ nctx_nodes = 0;
+ newstate->has_constraint = 1;
+ }
+
+ if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context))
+ {
+ re_node_set_remove_at (&newstate->nodes, i - nctx_nodes);
+ ++nctx_nodes;
+ }
+ }
+ }
+ err = register_state (dfa, newstate, hash);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_state (newstate);
+ newstate = NULL;
+ }
+ return newstate;
+}
diff --git a/compat/regex/regex_internal.h b/compat/regex/regex_internal.h
new file mode 100644
index 0000000..4184d7f
--- /dev/null
+++ b/compat/regex/regex_internal.h
@@ -0,0 +1,810 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2005, 2007, 2008, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#ifndef _REGEX_INTERNAL_H
+#define _REGEX_INTERNAL_H 1
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
+# include <langinfo.h>
+#endif
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+#if defined HAVE_WCHAR_H || defined _LIBC
+# include <wchar.h>
+#endif /* HAVE_WCHAR_H || _LIBC */
+#if defined HAVE_WCTYPE_H || defined _LIBC
+# include <wctype.h>
+#endif /* HAVE_WCTYPE_H || _LIBC */
+#if defined HAVE_STDBOOL_H || defined _LIBC
+# include <stdbool.h>
+#endif /* HAVE_STDBOOL_H || _LIBC */
+#if !defined(ZOS_USS)
+#if defined HAVE_STDINT_H || defined _LIBC
+# include <stdint.h>
+#endif /* HAVE_STDINT_H || _LIBC */
+#endif /* !ZOS_USS */
+#if defined _LIBC
+# include <bits/libc-lock.h>
+#else
+# define __libc_lock_define(CLASS,NAME)
+# define __libc_lock_init(NAME) do { } while (0)
+# define __libc_lock_lock(NAME) do { } while (0)
+# define __libc_lock_unlock(NAME) do { } while (0)
+#endif
+
+#ifndef GAWK
+/* In case that the system doesn't have isblank(). */
+#if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank
+# define isblank(ch) ((ch) == ' ' || (ch) == '\t')
+#endif
+#else /* GAWK */
+/*
+ * This is a freaking mess. On glibc systems you have to define
+ * a magic constant to get isblank() out of <ctype.h>, since it's
+ * a C99 function. To heck with all that and borrow a page from
+ * dfa.c's book.
+ */
+
+static int
+is_blank (int c)
+{
+ return (c == ' ' || c == '\t');
+}
+#endif /* GAWK */
+
+#ifdef _LIBC
+# ifndef _RE_DEFINE_LOCALE_FUNCTIONS
+# define _RE_DEFINE_LOCALE_FUNCTIONS 1
+# include <locale/localeinfo.h>
+# include <locale/elem-hash.h>
+# include <locale/coll-lookup.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages. */
+#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
+# include <libintl.h>
+# ifdef _LIBC
+# undef gettext
+# define gettext(msgid) \
+ INTUSE(__dcgettext) (_libc_intl_domainname, msgid, LC_MESSAGES)
+# endif
+#else
+# define gettext(msgid) (msgid)
+#endif
+
+#ifndef gettext_noop
+/* This define is so xgettext can find the internationalizable
+ strings. */
+# define gettext_noop(String) String
+#endif
+
+/* For loser systems without the definition. */
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#ifndef NO_MBSUPPORT
+#include "mbsupport.h" /* gawk */
+#endif
+#ifndef MB_CUR_MAX
+#define MB_CUR_MAX 1
+#endif
+
+#if (defined MBS_SUPPORT) || _LIBC
+# define RE_ENABLE_I18N
+#endif
+
+#if __GNUC__ >= 3
+# define BE(expr, val) __builtin_expect (expr, val)
+#else
+# define BE(expr, val) (expr)
+# ifdef inline
+# undef inline
+# endif
+# define inline
+#endif
+
+/* Number of single byte character. */
+#define SBC_MAX 256
+
+#define COLL_ELEM_LEN_MAX 8
+
+/* The character which represents newline. */
+#define NEWLINE_CHAR '\n'
+#define WIDE_NEWLINE_CHAR L'\n'
+
+/* Rename to standard API for using out of glibc. */
+#ifndef _LIBC
+# ifdef __wctype
+# undef __wctype
+# endif
+# define __wctype wctype
+# ifdef __iswctype
+# undef __iswctype
+# endif
+# define __iswctype iswctype
+# define __btowc btowc
+# define __mbrtowc mbrtowc
+#undef __mempcpy /* GAWK */
+# define __mempcpy mempcpy
+# define __wcrtomb wcrtomb
+# define __regfree regfree
+# define attribute_hidden
+#endif /* not _LIBC */
+
+#ifdef __GNUC__
+# define __attribute(arg) __attribute__ (arg)
+#else
+# define __attribute(arg)
+#endif
+
+extern const char __re_error_msgid[] attribute_hidden;
+extern const size_t __re_error_msgid_idx[] attribute_hidden;
+
+/* An integer used to represent a set of bits. It must be unsigned,
+ and must be at least as wide as unsigned int. */
+typedef unsigned long int bitset_word_t;
+/* All bits set in a bitset_word_t. */
+#define BITSET_WORD_MAX ULONG_MAX
+/* Number of bits in a bitset_word_t. */
+#define BITSET_WORD_BITS (sizeof (bitset_word_t) * CHAR_BIT)
+/* Number of bitset_word_t in a bit_set. */
+#define BITSET_WORDS (SBC_MAX / BITSET_WORD_BITS)
+typedef bitset_word_t bitset_t[BITSET_WORDS];
+typedef bitset_word_t *re_bitset_ptr_t;
+typedef const bitset_word_t *re_const_bitset_ptr_t;
+
+#define bitset_set(set,i) \
+ (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS)
+#define bitset_clear(set,i) \
+ (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_contain(set,i) \
+ (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_empty(set) memset (set, '\0', sizeof (bitset_t))
+#define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t))
+#define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_t))
+
+#define PREV_WORD_CONSTRAINT 0x0001
+#define PREV_NOTWORD_CONSTRAINT 0x0002
+#define NEXT_WORD_CONSTRAINT 0x0004
+#define NEXT_NOTWORD_CONSTRAINT 0x0008
+#define PREV_NEWLINE_CONSTRAINT 0x0010
+#define NEXT_NEWLINE_CONSTRAINT 0x0020
+#define PREV_BEGBUF_CONSTRAINT 0x0040
+#define NEXT_ENDBUF_CONSTRAINT 0x0080
+#define WORD_DELIM_CONSTRAINT 0x0100
+#define NOT_WORD_DELIM_CONSTRAINT 0x0200
+
+typedef enum
+{
+ INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+ WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+ WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+ INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+ LINE_FIRST = PREV_NEWLINE_CONSTRAINT,
+ LINE_LAST = NEXT_NEWLINE_CONSTRAINT,
+ BUF_FIRST = PREV_BEGBUF_CONSTRAINT,
+ BUF_LAST = NEXT_ENDBUF_CONSTRAINT,
+ WORD_DELIM = WORD_DELIM_CONSTRAINT,
+ NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT
+} re_context_type;
+
+typedef struct
+{
+ int alloc;
+ int nelem;
+ int *elems;
+} re_node_set;
+
+typedef enum
+{
+ NON_TYPE = 0,
+
+ /* Node type, These are used by token, node, tree. */
+ CHARACTER = 1,
+ END_OF_RE = 2,
+ SIMPLE_BRACKET = 3,
+ OP_BACK_REF = 4,
+ OP_PERIOD = 5,
+#ifdef RE_ENABLE_I18N
+ COMPLEX_BRACKET = 6,
+ OP_UTF8_PERIOD = 7,
+#endif /* RE_ENABLE_I18N */
+
+ /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used
+ when the debugger shows values of this enum type. */
+#define EPSILON_BIT 8
+ OP_OPEN_SUBEXP = EPSILON_BIT | 0,
+ OP_CLOSE_SUBEXP = EPSILON_BIT | 1,
+ OP_ALT = EPSILON_BIT | 2,
+ OP_DUP_ASTERISK = EPSILON_BIT | 3,
+ ANCHOR = EPSILON_BIT | 4,
+
+ /* Tree type, these are used only by tree. */
+ CONCAT = 16,
+ SUBEXP = 17,
+
+ /* Token type, these are used only by token. */
+ OP_DUP_PLUS = 18,
+ OP_DUP_QUESTION,
+ OP_OPEN_BRACKET,
+ OP_CLOSE_BRACKET,
+ OP_CHARSET_RANGE,
+ OP_OPEN_DUP_NUM,
+ OP_CLOSE_DUP_NUM,
+ OP_NON_MATCH_LIST,
+ OP_OPEN_COLL_ELEM,
+ OP_CLOSE_COLL_ELEM,
+ OP_OPEN_EQUIV_CLASS,
+ OP_CLOSE_EQUIV_CLASS,
+ OP_OPEN_CHAR_CLASS,
+ OP_CLOSE_CHAR_CLASS,
+ OP_WORD,
+ OP_NOTWORD,
+ OP_SPACE,
+ OP_NOTSPACE,
+ BACK_SLASH
+
+} re_token_type_t;
+
+#ifdef RE_ENABLE_I18N
+typedef struct
+{
+ /* Multibyte characters. */
+ wchar_t *mbchars;
+
+ /* Collating symbols. */
+# ifdef _LIBC
+ int32_t *coll_syms;
+# endif
+
+ /* Equivalence classes. */
+# ifdef _LIBC
+ int32_t *equiv_classes;
+# endif
+
+ /* Range expressions. */
+# ifdef _LIBC
+ uint32_t *range_starts;
+ uint32_t *range_ends;
+# else /* not _LIBC */
+ wchar_t *range_starts;
+ wchar_t *range_ends;
+# endif /* not _LIBC */
+
+ /* Character classes. */
+ wctype_t *char_classes;
+
+ /* If this character set is the non-matching list. */
+ unsigned int non_match : 1;
+
+ /* # of multibyte characters. */
+ int nmbchars;
+
+ /* # of collating symbols. */
+ int ncoll_syms;
+
+ /* # of equivalence classes. */
+ int nequiv_classes;
+
+ /* # of range expressions. */
+ int nranges;
+
+ /* # of character classes. */
+ int nchar_classes;
+} re_charset_t;
+#endif /* RE_ENABLE_I18N */
+
+typedef struct
+{
+ union
+ {
+ unsigned char c; /* for CHARACTER */
+ re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset; /* for COMPLEX_BRACKET */
+#endif /* RE_ENABLE_I18N */
+ int idx; /* for BACK_REF */
+ re_context_type ctx_type; /* for ANCHOR */
+ } opr;
+#if __GNUC__ >= 2
+ re_token_type_t type : 8;
+#else
+ re_token_type_t type;
+#endif
+ unsigned int constraint : 10; /* context constraint */
+ unsigned int duplicated : 1;
+ unsigned int opt_subexp : 1;
+#ifdef RE_ENABLE_I18N
+ unsigned int accept_mb : 1;
+ /* These 2 bits can be moved into the union if needed (e.g. if running out
+ of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */
+ unsigned int mb_partial : 1;
+#endif
+ unsigned int word_char : 1;
+} re_token_t;
+
+#define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT)
+
+struct re_string_t
+{
+ /* Indicate the raw buffer which is the original string passed as an
+ argument of regexec(), re_search(), etc.. */
+ const unsigned char *raw_mbs;
+ /* Store the multibyte string. In case of "case insensitive mode" like
+ REG_ICASE, upper cases of the string are stored, otherwise MBS points
+ the same address that RAW_MBS points. */
+ unsigned char *mbs;
+#ifdef RE_ENABLE_I18N
+ /* Store the wide character string which is corresponding to MBS. */
+ wint_t *wcs;
+ int *offsets;
+ mbstate_t cur_state;
+#endif
+ /* Index in RAW_MBS. Each character mbs[i] corresponds to
+ raw_mbs[raw_mbs_idx + i]. */
+ int raw_mbs_idx;
+ /* The length of the valid characters in the buffers. */
+ int valid_len;
+ /* The corresponding number of bytes in raw_mbs array. */
+ int valid_raw_len;
+ /* The length of the buffers MBS and WCS. */
+ int bufs_len;
+ /* The index in MBS, which is updated by re_string_fetch_byte. */
+ int cur_idx;
+ /* length of RAW_MBS array. */
+ int raw_len;
+ /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */
+ int len;
+ /* End of the buffer may be shorter than its length in the cases such
+ as re_match_2, re_search_2. Then, we use STOP for end of the buffer
+ instead of LEN. */
+ int raw_stop;
+ /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */
+ int stop;
+
+ /* The context of mbs[0]. We store the context independently, since
+ the context of mbs[0] may be different from raw_mbs[0], which is
+ the beginning of the input string. */
+ unsigned int tip_context;
+ /* The translation passed as a part of an argument of re_compile_pattern. */
+ RE_TRANSLATE_TYPE trans;
+ /* Copy of re_dfa_t's word_char. */
+ re_const_bitset_ptr_t word_char;
+ /* 1 if REG_ICASE. */
+ unsigned char icase;
+ unsigned char is_utf8;
+ unsigned char map_notascii;
+ unsigned char mbs_allocated;
+ unsigned char offsets_needed;
+ unsigned char newline_anchor;
+ unsigned char word_ops_used;
+ int mb_cur_max;
+};
+typedef struct re_string_t re_string_t;
+
+
+struct re_dfa_t;
+typedef struct re_dfa_t re_dfa_t;
+
+#ifndef _LIBC
+# ifdef __i386__
+# define internal_function __attribute ((regparm (3), stdcall))
+# else
+# define internal_function
+# endif
+#endif
+
+#ifndef NOT_IN_libc
+static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr,
+ int new_buf_len)
+ internal_function;
+# ifdef RE_ENABLE_I18N
+static void build_wcs_buffer (re_string_t *pstr) internal_function;
+static reg_errcode_t build_wcs_upper_buffer (re_string_t *pstr)
+ internal_function;
+# endif /* RE_ENABLE_I18N */
+static void build_upper_buffer (re_string_t *pstr) internal_function;
+static void re_string_translate_buffer (re_string_t *pstr) internal_function;
+static unsigned int re_string_context_at (const re_string_t *input, int idx,
+ int eflags)
+ internal_function __attribute ((pure));
+#endif
+#define re_string_peek_byte(pstr, offset) \
+ ((pstr)->mbs[(pstr)->cur_idx + offset])
+#define re_string_fetch_byte(pstr) \
+ ((pstr)->mbs[(pstr)->cur_idx++])
+#define re_string_first_byte(pstr, idx) \
+ ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF)
+#define re_string_is_single_byte_char(pstr, idx) \
+ ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \
+ || (pstr)->wcs[(idx) + 1] != WEOF))
+#define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx)
+#define re_string_cur_idx(pstr) ((pstr)->cur_idx)
+#define re_string_get_buffer(pstr) ((pstr)->mbs)
+#define re_string_length(pstr) ((pstr)->len)
+#define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx])
+#define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx))
+#define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx))
+
+#ifndef _LIBC
+# if HAVE_ALLOCA
+# if (_MSC_VER)
+# include <malloc.h>
+# define __libc_use_alloca(n) 0
+# else
+# include <alloca.h>
+/* The OS usually guarantees only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ allocate anything larger than 4096 bytes. Also care for the possibility
+ of a few compiler-allocated temporary stack slots. */
+# define __libc_use_alloca(n) ((n) < 4032)
+# endif
+# else
+/* alloca is implemented with malloc, so just use malloc. */
+# define __libc_use_alloca(n) 0
+# endif
+#endif
+
+#define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t)))
+/* SunOS 4.1.x realloc doesn't accept null pointers: pre-Standard C. Sigh. */
+#define re_realloc(p,t,n) ((p != NULL) ? (t *) realloc (p,(n)*sizeof(t)) : (t *) calloc(n,sizeof(t)))
+#define re_free(p) free (p)
+
+struct bin_tree_t
+{
+ struct bin_tree_t *parent;
+ struct bin_tree_t *left;
+ struct bin_tree_t *right;
+ struct bin_tree_t *first;
+ struct bin_tree_t *next;
+
+ re_token_t token;
+
+ /* `node_idx' is the index in dfa->nodes, if `type' == 0.
+ Otherwise `type' indicate the type of this node. */
+ int node_idx;
+};
+typedef struct bin_tree_t bin_tree_t;
+
+#define BIN_TREE_STORAGE_SIZE \
+ ((1024 - sizeof (void *)) / sizeof (bin_tree_t))
+
+struct bin_tree_storage_t
+{
+ struct bin_tree_storage_t *next;
+ bin_tree_t data[BIN_TREE_STORAGE_SIZE];
+};
+typedef struct bin_tree_storage_t bin_tree_storage_t;
+
+#define CONTEXT_WORD 1
+#define CONTEXT_NEWLINE (CONTEXT_WORD << 1)
+#define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1)
+#define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1)
+
+#define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD)
+#define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE)
+#define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF)
+#define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF)
+#define IS_ORDINARY_CONTEXT(c) ((c) == 0)
+
+#define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_')
+#define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR)
+#define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_')
+#define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR)
+
+#define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \
+ ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+ || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+ || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\
+ || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context)))
+
+#define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \
+ ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+ || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+ || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \
+ || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context)))
+
+struct re_dfastate_t
+{
+ unsigned int hash;
+ re_node_set nodes;
+ re_node_set non_eps_nodes;
+ re_node_set inveclosure;
+ re_node_set *entrance_nodes;
+ struct re_dfastate_t **trtable, **word_trtable;
+ unsigned int context : 4;
+ unsigned int halt : 1;
+ /* If this state can accept `multi byte'.
+ Note that we refer to multibyte characters, and multi character
+ collating elements as `multi byte'. */
+ unsigned int accept_mb : 1;
+ /* If this state has backreference node(s). */
+ unsigned int has_backref : 1;
+ unsigned int has_constraint : 1;
+};
+typedef struct re_dfastate_t re_dfastate_t;
+
+struct re_state_table_entry
+{
+ int num;
+ int alloc;
+ re_dfastate_t **array;
+};
+
+/* Array type used in re_sub_match_last_t and re_sub_match_top_t. */
+
+typedef struct
+{
+ int next_idx;
+ int alloc;
+ re_dfastate_t **array;
+} state_array_t;
+
+/* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */
+
+typedef struct
+{
+ int node;
+ int str_idx; /* The position NODE match at. */
+ state_array_t path;
+} re_sub_match_last_t;
+
+/* Store information about the node NODE whose type is OP_OPEN_SUBEXP.
+ And information about the node, whose type is OP_CLOSE_SUBEXP,
+ corresponding to NODE is stored in LASTS. */
+
+typedef struct
+{
+ int str_idx;
+ int node;
+ state_array_t *path;
+ int alasts; /* Allocation size of LASTS. */
+ int nlasts; /* The number of LASTS. */
+ re_sub_match_last_t **lasts;
+} re_sub_match_top_t;
+
+struct re_backref_cache_entry
+{
+ int node;
+ int str_idx;
+ int subexp_from;
+ int subexp_to;
+ char more;
+ char unused;
+ unsigned short int eps_reachable_subexps_map;
+};
+
+typedef struct
+{
+ /* The string object corresponding to the input string. */
+ re_string_t input;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+ const re_dfa_t *const dfa;
+#else
+ const re_dfa_t *dfa;
+#endif
+ /* EFLAGS of the argument of regexec. */
+ int eflags;
+ /* Where the matching ends. */
+ int match_last;
+ int last_node;
+ /* The state log used by the matcher. */
+ re_dfastate_t **state_log;
+ int state_log_top;
+ /* Back reference cache. */
+ int nbkref_ents;
+ int abkref_ents;
+ struct re_backref_cache_entry *bkref_ents;
+ int max_mb_elem_len;
+ int nsub_tops;
+ int asub_tops;
+ re_sub_match_top_t **sub_tops;
+} re_match_context_t;
+
+typedef struct
+{
+ re_dfastate_t **sifted_states;
+ re_dfastate_t **limited_states;
+ int last_node;
+ int last_str_idx;
+ re_node_set limits;
+} re_sift_context_t;
+
+struct re_fail_stack_ent_t
+{
+ int idx;
+ int node;
+ regmatch_t *regs;
+ re_node_set eps_via_nodes;
+};
+
+struct re_fail_stack_t
+{
+ int num;
+ int alloc;
+ struct re_fail_stack_ent_t *stack;
+};
+
+struct re_dfa_t
+{
+ re_token_t *nodes;
+ size_t nodes_alloc;
+ size_t nodes_len;
+ int *nexts;
+ int *org_indices;
+ re_node_set *edests;
+ re_node_set *eclosures;
+ re_node_set *inveclosures;
+ struct re_state_table_entry *state_table;
+ re_dfastate_t *init_state;
+ re_dfastate_t *init_state_word;
+ re_dfastate_t *init_state_nl;
+ re_dfastate_t *init_state_begbuf;
+ bin_tree_t *str_tree;
+ bin_tree_storage_t *str_tree_storage;
+ re_bitset_ptr_t sb_char;
+ int str_tree_storage_idx;
+
+ /* number of subexpressions `re_nsub' is in regex_t. */
+ unsigned int state_hash_mask;
+ int init_node;
+ int nbackref; /* The number of backreference in this dfa. */
+
+ /* Bitmap expressing which backreference is used. */
+ bitset_word_t used_bkref_map;
+ bitset_word_t completed_bkref_map;
+
+ unsigned int has_plural_match : 1;
+ /* If this dfa has "multibyte node", which is a backreference or
+ a node which can accept multibyte character or multi character
+ collating element. */
+ unsigned int has_mb_node : 1;
+ unsigned int is_utf8 : 1;
+ unsigned int map_notascii : 1;
+ unsigned int word_ops_used : 1;
+ int mb_cur_max;
+ bitset_t word_char;
+ reg_syntax_t syntax;
+ int *subexp_map;
+#ifdef DEBUG
+ char* re_str;
+#endif
+#if defined _LIBC
+ __libc_lock_define (, lock)
+#endif
+};
+
+#define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set))
+#define re_node_set_remove(set,id) \
+ (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1))
+#define re_node_set_empty(p) ((p)->nelem = 0)
+#define re_node_set_free(set) re_free ((set)->elems)
+
+
+typedef enum
+{
+ SB_CHAR,
+ MB_CHAR,
+ EQUIV_CLASS,
+ COLL_SYM,
+ CHAR_CLASS
+} bracket_elem_type;
+
+typedef struct
+{
+ bracket_elem_type type;
+ union
+ {
+ unsigned char ch;
+ unsigned char *name;
+ wchar_t wch;
+ } opr;
+} bracket_elem_t;
+
+
+/* Inline functions for bitset operation. */
+static inline void
+bitset_not (bitset_t set)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ set[bitset_i] = ~set[bitset_i];
+}
+
+static inline void
+bitset_merge (bitset_t dest, const bitset_t src)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ dest[bitset_i] |= src[bitset_i];
+}
+
+static inline void
+bitset_mask (bitset_t dest, const bitset_t src)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ dest[bitset_i] &= src[bitset_i];
+}
+
+#ifdef RE_ENABLE_I18N
+/* Inline functions for re_string. */
+static inline int
+internal_function __attribute ((pure))
+re_string_char_size_at (const re_string_t *pstr, int idx)
+{
+ int byte_idx;
+ if (pstr->mb_cur_max == 1)
+ return 1;
+ for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx)
+ if (pstr->wcs[idx + byte_idx] != WEOF)
+ break;
+ return byte_idx;
+}
+
+static inline wint_t
+internal_function __attribute ((pure))
+re_string_wchar_at (const re_string_t *pstr, int idx)
+{
+ if (pstr->mb_cur_max == 1)
+ return (wint_t) pstr->mbs[idx];
+ return (wint_t) pstr->wcs[idx];
+}
+
+# ifndef NOT_IN_libc
+static int
+internal_function __attribute ((pure))
+re_string_elem_size_at (const re_string_t *pstr, int idx)
+{
+# ifdef _LIBC
+ const unsigned char *p, *extra;
+ const int32_t *table, *indirect;
+ int32_t tmp;
+# include <locale/weight.h>
+ uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+
+ if (nrules != 0)
+ {
+ table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_INDIRECTMB);
+ p = pstr->mbs + idx;
+ tmp = findidx (&p);
+ return p - pstr->mbs - idx;
+ }
+ else
+# endif /* _LIBC */
+ return 1;
+}
+# endif
+#endif /* RE_ENABLE_I18N */
+
+#endif /* _REGEX_INTERNAL_H */
diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c
new file mode 100644
index 0000000..0194965
--- /dev/null
+++ b/compat/regex/regexec.c
@@ -0,0 +1,4369 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2005, 2007, 2009, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags,
+ int n) internal_function;
+static void match_ctx_clean (re_match_context_t *mctx) internal_function;
+static void match_ctx_free (re_match_context_t *cache) internal_function;
+static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node,
+ int str_idx, int from, int to)
+ internal_function;
+static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+ internal_function;
+static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node,
+ int str_idx) internal_function;
+static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop,
+ int node, int str_idx)
+ internal_function;
+static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+ re_dfastate_t **limited_sts, int last_node,
+ int last_str_idx)
+ internal_function;
+static reg_errcode_t re_search_internal (const regex_t *preg,
+ const char *string, int length,
+ int start, int range, int stop,
+ size_t nmatch, regmatch_t pmatch[],
+ int eflags);
+static int re_search_2_stub (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2,
+ int start, int range, struct re_registers *regs,
+ int stop, int ret_len);
+static int re_search_stub (struct re_pattern_buffer *bufp,
+ const char *string, int length, int start,
+ int range, int stop, struct re_registers *regs,
+ int ret_len);
+static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch,
+ int nregs, int regs_allocated);
+static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx);
+static int check_matching (re_match_context_t *mctx, int fl_longest_match,
+ int *p_match_first) internal_function;
+static int check_halt_state_context (const re_match_context_t *mctx,
+ const re_dfastate_t *state, int idx)
+ internal_function;
+static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+ regmatch_t *prev_idx_match, int cur_node,
+ int cur_idx, int nmatch) internal_function;
+static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs,
+ int str_idx, int dest_node, int nregs,
+ regmatch_t *regs,
+ re_node_set *eps_via_nodes)
+ internal_function;
+static reg_errcode_t set_regs (const regex_t *preg,
+ const re_match_context_t *mctx,
+ size_t nmatch, regmatch_t *pmatch,
+ int fl_backtrack) internal_function;
+static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs)
+ internal_function;
+
+#ifdef RE_ENABLE_I18N
+static int sift_states_iter_mb (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int node_idx, int str_idx, int max_str_idx)
+ internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t sift_states_backward (const re_match_context_t *mctx,
+ re_sift_context_t *sctx)
+ internal_function;
+static reg_errcode_t build_sifted_states (const re_match_context_t *mctx,
+ re_sift_context_t *sctx, int str_idx,
+ re_node_set *cur_dest)
+ internal_function;
+static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int str_idx,
+ re_node_set *dest_nodes)
+ internal_function;
+static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa,
+ re_node_set *dest_nodes,
+ const re_node_set *candidates)
+ internal_function;
+static int check_dst_limits (const re_match_context_t *mctx,
+ re_node_set *limits,
+ int dst_node, int dst_idx, int src_node,
+ int src_idx) internal_function;
+static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx,
+ int boundaries, int subexp_idx,
+ int from_node, int bkref_idx)
+ internal_function;
+static int check_dst_limits_calc_pos (const re_match_context_t *mctx,
+ int limit, int subexp_idx,
+ int node, int str_idx,
+ int bkref_idx) internal_function;
+static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa,
+ re_node_set *dest_nodes,
+ const re_node_set *candidates,
+ re_node_set *limits,
+ struct re_backref_cache_entry *bkref_ents,
+ int str_idx) internal_function;
+static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int str_idx, const re_node_set *candidates)
+ internal_function;
+static reg_errcode_t merge_state_array (const re_dfa_t *dfa,
+ re_dfastate_t **dst,
+ re_dfastate_t **src, int num)
+ internal_function;
+static re_dfastate_t *find_recover_state (reg_errcode_t *err,
+ re_match_context_t *mctx) internal_function;
+static re_dfastate_t *transit_state (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *state) internal_function;
+static re_dfastate_t *merge_state_with_log (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *next_state)
+ internal_function;
+static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx,
+ re_node_set *cur_nodes,
+ int str_idx) internal_function;
+#if 0
+static re_dfastate_t *transit_state_sb (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *pstate)
+ internal_function;
+#endif
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t transit_state_mb (re_match_context_t *mctx,
+ re_dfastate_t *pstate)
+ internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t transit_state_bkref (re_match_context_t *mctx,
+ const re_node_set *nodes)
+ internal_function;
+static reg_errcode_t get_subexp (re_match_context_t *mctx,
+ int bkref_node, int bkref_str_idx)
+ internal_function;
+static reg_errcode_t get_subexp_sub (re_match_context_t *mctx,
+ const re_sub_match_top_t *sub_top,
+ re_sub_match_last_t *sub_last,
+ int bkref_node, int bkref_str)
+ internal_function;
+static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+ int subexp_idx, int type) internal_function;
+static reg_errcode_t check_arrival (re_match_context_t *mctx,
+ state_array_t *path, int top_node,
+ int top_str, int last_node, int last_str,
+ int type) internal_function;
+static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx,
+ int str_idx,
+ re_node_set *cur_nodes,
+ re_node_set *next_nodes)
+ internal_function;
+static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa,
+ re_node_set *cur_nodes,
+ int ex_subexp, int type)
+ internal_function;
+static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa,
+ re_node_set *dst_nodes,
+ int target, int ex_subexp,
+ int type) internal_function;
+static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx,
+ re_node_set *cur_nodes, int cur_str,
+ int subexp_num, int type)
+ internal_function;
+static int build_trtable (const re_dfa_t *dfa,
+ re_dfastate_t *state) internal_function;
+#ifdef RE_ENABLE_I18N
+static int check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+ const re_string_t *input, int idx)
+ internal_function;
+# ifdef _LIBC
+static unsigned int find_collation_sequence_value (const unsigned char *mbs,
+ size_t name_len)
+ internal_function;
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+static int group_nodes_into_DFAstates (const re_dfa_t *dfa,
+ const re_dfastate_t *state,
+ re_node_set *states_node,
+ bitset_t *states_ch) internal_function;
+static int check_node_accept (const re_match_context_t *mctx,
+ const re_token_t *node, int idx)
+ internal_function;
+static reg_errcode_t extend_buffers (re_match_context_t *mctx)
+ internal_function;
+
+/* Entry point for POSIX code. */
+
+/* regexec searches for a given pattern, specified by PREG, in the
+ string STRING.
+
+ If NMATCH is zero or REG_NOSUB was set in the cflags argument to
+ `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at
+ least NMATCH elements, and we set them to the offsets of the
+ corresponding matched substrings.
+
+ EFLAGS specifies `execution flags' which affect matching: if
+ REG_NOTBOL is set, then ^ does not match at the beginning of the
+ string; if REG_NOTEOL is set, then $ does not match at the end.
+
+ We return 0 if we find a match and REG_NOMATCH if not. */
+
+int
+regexec (
+ const regex_t *__restrict preg,
+ const char *__restrict string,
+ size_t nmatch,
+ regmatch_t pmatch[],
+ int eflags)
+{
+ reg_errcode_t err;
+ int start, length;
+
+ if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND))
+ return REG_BADPAT;
+
+ if (eflags & REG_STARTEND)
+ {
+ start = pmatch[0].rm_so;
+ length = pmatch[0].rm_eo;
+ }
+ else
+ {
+ start = 0;
+ length = strlen (string);
+ }
+
+ __libc_lock_lock (dfa->lock);
+ if (preg->no_sub)
+ err = re_search_internal (preg, string, length, start, length - start,
+ length, 0, NULL, eflags);
+ else
+ err = re_search_internal (preg, string, length, start, length - start,
+ length, nmatch, pmatch, eflags);
+ __libc_lock_unlock (dfa->lock);
+ return err != REG_NOERROR;
+}
+
+#ifdef _LIBC
+# include <shlib-compat.h>
+versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4);
+
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+__typeof__ (__regexec) __compat_regexec;
+
+int
+attribute_compat_text_section
+__compat_regexec (const regex_t *__restrict preg,
+ const char *__restrict string, size_t nmatch,
+ regmatch_t pmatch[], int eflags)
+{
+ return regexec (preg, string, nmatch, pmatch,
+ eflags & (REG_NOTBOL | REG_NOTEOL));
+}
+compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0);
+# endif
+#endif
+
+/* Entry points for GNU code. */
+
+/* re_match, re_search, re_match_2, re_search_2
+
+ The former two functions operate on STRING with length LENGTH,
+ while the later two operate on concatenation of STRING1 and STRING2
+ with lengths LENGTH1 and LENGTH2, respectively.
+
+ re_match() matches the compiled pattern in BUFP against the string,
+ starting at index START.
+
+ re_search() first tries matching at index START, then it tries to match
+ starting from index START + 1, and so on. The last start position tried
+ is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same
+ way as re_match().)
+
+ The parameter STOP of re_{match,search}_2 specifies that no match exceeding
+ the first STOP characters of the concatenation of the strings should be
+ concerned.
+
+ If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match
+ and all groups is stroed in REGS. (For the "_2" variants, the offsets are
+ computed relative to the concatenation, not relative to the individual
+ strings.)
+
+ On success, re_match* functions return the length of the match, re_search*
+ return the position of the start of the match. Return value -1 means no
+ match was found and -2 indicates an internal error. */
+
+int
+re_match (struct re_pattern_buffer *bufp,
+ const char *string,
+ int length,
+ int start,
+ struct re_registers *regs)
+{
+ return re_search_stub (bufp, string, length, start, 0, length, regs, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match, re_match)
+#endif
+
+int
+re_search (struct re_pattern_buffer *bufp,
+ const char *string,
+ int length, int start, int range,
+ struct re_registers *regs)
+{
+ return re_search_stub (bufp, string, length, start, range, length, regs, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search, re_search)
+#endif
+
+int
+re_match_2 (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ struct re_registers *regs, int stop)
+{
+ return re_search_2_stub (bufp, string1, length1, string2, length2,
+ start, 0, regs, stop, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match_2, re_match_2)
+#endif
+
+int
+re_search_2 (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ int range, struct re_registers *regs, int stop)
+{
+ return re_search_2_stub (bufp, string1, length1, string2, length2,
+ start, range, regs, stop, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search_2, re_search_2)
+#endif
+
+static int
+re_search_2_stub (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ int range, struct re_registers *regs,
+ int stop, int ret_len)
+{
+ const char *str;
+ int rval;
+ int len = length1 + length2;
+ int free_str = 0;
+
+ if (BE (length1 < 0 || length2 < 0 || stop < 0, 0))
+ return -2;
+
+ /* Concatenate the strings. */
+ if (length2 > 0)
+ if (length1 > 0)
+ {
+ char *s = re_malloc (char, len);
+
+ if (BE (s == NULL, 0))
+ return -2;
+ memcpy (s, string1, length1);
+ memcpy (s + length1, string2, length2);
+ str = s;
+ free_str = 1;
+ }
+ else
+ str = string2;
+ else
+ str = string1;
+
+ rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len);
+ if (free_str)
+ re_free ((char *) str);
+ return rval;
+}
+
+/* The parameters have the same meaning as those of re_search.
+ Additional parameters:
+ If RET_LEN is nonzero the length of the match is returned (re_match style);
+ otherwise the position of the match is returned. */
+
+static int
+re_search_stub (struct re_pattern_buffer *bufp,
+ const char *string, int length, int start,
+ int range, int stop,
+ struct re_registers *regs, int ret_len)
+{
+ reg_errcode_t result;
+ regmatch_t *pmatch;
+ int nregs, rval;
+ int eflags = 0;
+
+ /* Check for out-of-range. */
+ if (BE (start < 0 || start > length, 0))
+ return -1;
+ if (BE (start + range > length, 0))
+ range = length - start;
+ else if (BE (start + range < 0, 0))
+ range = -start;
+
+ __libc_lock_lock (dfa->lock);
+
+ eflags |= (bufp->not_bol) ? REG_NOTBOL : 0;
+ eflags |= (bufp->not_eol) ? REG_NOTEOL : 0;
+
+ /* Compile fastmap if we haven't yet. */
+ if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate)
+ re_compile_fastmap (bufp);
+
+ if (BE (bufp->no_sub, 0))
+ regs = NULL;
+
+ /* We need at least 1 register. */
+ if (regs == NULL)
+ nregs = 1;
+ else if (BE (bufp->regs_allocated == REGS_FIXED &&
+ regs->num_regs < bufp->re_nsub + 1, 0))
+ {
+ nregs = regs->num_regs;
+ if (BE (nregs < 1, 0))
+ {
+ /* Nothing can be copied to regs. */
+ regs = NULL;
+ nregs = 1;
+ }
+ }
+ else
+ nregs = bufp->re_nsub + 1;
+ pmatch = re_malloc (regmatch_t, nregs);
+ if (BE (pmatch == NULL, 0))
+ {
+ rval = -2;
+ goto out;
+ }
+
+ result = re_search_internal (bufp, string, length, start, range, stop,
+ nregs, pmatch, eflags);
+
+ rval = 0;
+
+ /* I hope we needn't fill ther regs with -1's when no match was found. */
+ if (result != REG_NOERROR)
+ rval = -1;
+ else if (regs != NULL)
+ {
+ /* If caller wants register contents data back, copy them. */
+ bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs,
+ bufp->regs_allocated);
+ if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0))
+ rval = -2;
+ }
+
+ if (BE (rval == 0, 1))
+ {
+ if (ret_len)
+ {
+ assert (pmatch[0].rm_so == start);
+ rval = pmatch[0].rm_eo - start;
+ }
+ else
+ rval = pmatch[0].rm_so;
+ }
+ re_free (pmatch);
+ out:
+ __libc_lock_unlock (dfa->lock);
+ return rval;
+}
+
+static unsigned
+re_copy_regs (struct re_registers *regs,
+ regmatch_t *pmatch,
+ int nregs, int regs_allocated)
+{
+ int rval = REGS_REALLOCATE;
+ int i;
+ int need_regs = nregs + 1;
+ /* We need one extra element beyond `num_regs' for the `-1' marker GNU code
+ uses. */
+
+ /* Have the register data arrays been allocated? */
+ if (regs_allocated == REGS_UNALLOCATED)
+ { /* No. So allocate them with malloc. */
+ regs->start = re_malloc (regoff_t, need_regs);
+ if (BE (regs->start == NULL, 0))
+ return REGS_UNALLOCATED;
+ regs->end = re_malloc (regoff_t, need_regs);
+ if (BE (regs->end == NULL, 0))
+ {
+ re_free (regs->start);
+ return REGS_UNALLOCATED;
+ }
+ regs->num_regs = need_regs;
+ }
+ else if (regs_allocated == REGS_REALLOCATE)
+ { /* Yes. If we need more elements than were already
+ allocated, reallocate them. If we need fewer, just
+ leave it alone. */
+ if (BE (need_regs > regs->num_regs, 0))
+ {
+ regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs);
+ regoff_t *new_end;
+ if (BE (new_start == NULL, 0))
+ return REGS_UNALLOCATED;
+ new_end = re_realloc (regs->end, regoff_t, need_regs);
+ if (BE (new_end == NULL, 0))
+ {
+ re_free (new_start);
+ return REGS_UNALLOCATED;
+ }
+ regs->start = new_start;
+ regs->end = new_end;
+ regs->num_regs = need_regs;
+ }
+ }
+ else
+ {
+ assert (regs_allocated == REGS_FIXED);
+ /* This function may not be called with REGS_FIXED and nregs too big. */
+ assert (regs->num_regs >= nregs);
+ rval = REGS_FIXED;
+ }
+
+ /* Copy the regs. */
+ for (i = 0; i < nregs; ++i)
+ {
+ regs->start[i] = pmatch[i].rm_so;
+ regs->end[i] = pmatch[i].rm_eo;
+ }
+ for ( ; i < regs->num_regs; ++i)
+ regs->start[i] = regs->end[i] = -1;
+
+ return rval;
+}
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+ ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
+ this memory for recording register information. STARTS and ENDS
+ must be allocated using the malloc library routine, and must each
+ be at least NUM_REGS * sizeof (regoff_t) bytes long.
+
+ If NUM_REGS == 0, then subsequent matches should allocate their own
+ register data.
+
+ Unless this function is called, the first search or match using
+ PATTERN_BUFFER will allocate its own register data, without
+ freeing the old data. */
+
+void
+re_set_registers (struct re_pattern_buffer *bufp,
+ struct re_registers *regs,
+ unsigned num_regs,
+ regoff_t *starts,
+ regoff_t *ends)
+{
+ if (num_regs)
+ {
+ bufp->regs_allocated = REGS_REALLOCATE;
+ regs->num_regs = num_regs;
+ regs->start = starts;
+ regs->end = ends;
+ }
+ else
+ {
+ bufp->regs_allocated = REGS_UNALLOCATED;
+ regs->num_regs = 0;
+ regs->start = regs->end = (regoff_t *) 0;
+ }
+}
+#ifdef _LIBC
+weak_alias (__re_set_registers, re_set_registers)
+#endif
+
+/* Entry points compatible with 4.2 BSD regex library. We don't define
+ them unless specifically requested. */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+int
+# ifdef _LIBC
+weak_function
+# endif
+re_exec (s)
+ const char *s;
+{
+ return 0 == regexec (&re_comp_buf, s, 0, NULL, 0);
+}
+#endif /* _REGEX_RE_COMP */
+
+/* Internal entry point. */
+
+/* Searches for a compiled pattern PREG in the string STRING, whose
+ length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same
+ mingings with regexec. START, and RANGE have the same meanings
+ with re_search.
+ Return REG_NOERROR if we find a match, and REG_NOMATCH if not,
+ otherwise return the error code.
+ Note: We assume front end functions already check ranges.
+ (START + RANGE >= 0 && START + RANGE <= LENGTH) */
+
+static reg_errcode_t
+re_search_internal (const regex_t *preg,
+ const char *string,
+ int length, int start, int range, int stop,
+ size_t nmatch, regmatch_t pmatch[],
+ int eflags)
+{
+ reg_errcode_t err;
+ const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+ int left_lim, right_lim, incr;
+ int fl_longest_match, match_first, match_kind, match_last = -1;
+ int extra_nmatch;
+ int sb, ch;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+ re_match_context_t mctx = { .dfa = dfa };
+#else
+ re_match_context_t mctx;
+#endif
+ char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate
+ && range && !preg->can_be_null) ? preg->fastmap : NULL;
+ RE_TRANSLATE_TYPE t = preg->translate;
+
+#if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L))
+ memset (&mctx, '\0', sizeof (re_match_context_t));
+ mctx.dfa = dfa;
+#endif
+
+ extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0;
+ nmatch -= extra_nmatch;
+
+ /* Check if the DFA haven't been compiled. */
+ if (BE (preg->used == 0 || dfa->init_state == NULL
+ || dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+ || dfa->init_state_begbuf == NULL, 0))
+ return REG_NOMATCH;
+
+#ifdef DEBUG
+ /* We assume front-end functions already check them. */
+ assert (start + range >= 0 && start + range <= length);
+#endif
+
+ /* If initial states with non-begbuf contexts have no elements,
+ the regex must be anchored. If preg->newline_anchor is set,
+ we'll never use init_state_nl, so do not check it. */
+ if (dfa->init_state->nodes.nelem == 0
+ && dfa->init_state_word->nodes.nelem == 0
+ && (dfa->init_state_nl->nodes.nelem == 0
+ || !preg->newline_anchor))
+ {
+ if (start != 0 && start + range != 0)
+ return REG_NOMATCH;
+ start = range = 0;
+ }
+
+ /* We must check the longest matching, if nmatch > 0. */
+ fl_longest_match = (nmatch != 0 || dfa->nbackref);
+
+ err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1,
+ preg->translate, preg->syntax & RE_ICASE, dfa);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ mctx.input.stop = stop;
+ mctx.input.raw_stop = stop;
+ mctx.input.newline_anchor = preg->newline_anchor;
+
+ err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* We will log all the DFA states through which the dfa pass,
+ if nmatch > 1, or this dfa has "multibyte node", which is a
+ back-reference or a node which can accept multibyte character or
+ multi character collating element. */
+ if (nmatch > 1 || dfa->has_mb_node)
+ {
+ /* Avoid overflow. */
+ if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= mctx.input.bufs_len, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+
+ mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1);
+ if (BE (mctx.state_log == NULL, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ }
+ else
+ mctx.state_log = NULL;
+
+ match_first = start;
+ mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+ : CONTEXT_NEWLINE | CONTEXT_BEGBUF;
+
+ /* Check incrementally whether of not the input string match. */
+ incr = (range < 0) ? -1 : 1;
+ left_lim = (range < 0) ? start + range : start;
+ right_lim = (range < 0) ? start : start + range;
+ sb = dfa->mb_cur_max == 1;
+ match_kind =
+ (fastmap
+ ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0)
+ | (range >= 0 ? 2 : 0)
+ | (t != NULL ? 1 : 0))
+ : 8);
+
+ for (;; match_first += incr)
+ {
+ err = REG_NOMATCH;
+ if (match_first < left_lim || right_lim < match_first)
+ goto free_return;
+
+ /* Advance as rapidly as possible through the string, until we
+ find a plausible place to start matching. This may be done
+ with varying efficiency, so there are various possibilities:
+ only the most common of them are specialized, in order to
+ save on code size. We use a switch statement for speed. */
+ switch (match_kind)
+ {
+ case 8:
+ /* No fastmap. */
+ break;
+
+ case 7:
+ /* Fastmap with single-byte translation, match forward. */
+ while (BE (match_first < right_lim, 1)
+ && !fastmap[t[(unsigned char) string[match_first]]])
+ ++match_first;
+ goto forward_match_found_start_or_reached_end;
+
+ case 6:
+ /* Fastmap without translation, match forward. */
+ while (BE (match_first < right_lim, 1)
+ && !fastmap[(unsigned char) string[match_first]])
+ ++match_first;
+
+ forward_match_found_start_or_reached_end:
+ if (BE (match_first == right_lim, 0))
+ {
+ ch = match_first >= length
+ ? 0 : (unsigned char) string[match_first];
+ if (!fastmap[t ? t[ch] : ch])
+ goto free_return;
+ }
+ break;
+
+ case 4:
+ case 5:
+ /* Fastmap without multi-byte translation, match backwards. */
+ while (match_first >= left_lim)
+ {
+ ch = match_first >= length
+ ? 0 : (unsigned char) string[match_first];
+ if (fastmap[t ? t[ch] : ch])
+ break;
+ --match_first;
+ }
+ if (match_first < left_lim)
+ goto free_return;
+ break;
+
+ default:
+ /* In this case, we can't determine easily the current byte,
+ since it might be a component byte of a multibyte
+ character. Then we use the constructed buffer instead. */
+ for (;;)
+ {
+ /* If MATCH_FIRST is out of the valid range, reconstruct the
+ buffers. */
+ unsigned int offset = match_first - mctx.input.raw_mbs_idx;
+ if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0))
+ {
+ err = re_string_reconstruct (&mctx.input, match_first,
+ eflags);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ offset = match_first - mctx.input.raw_mbs_idx;
+ }
+ /* If MATCH_FIRST is out of the buffer, leave it as '\0'.
+ Note that MATCH_FIRST must not be smaller than 0. */
+ ch = (match_first >= length
+ ? 0 : re_string_byte_at (&mctx.input, offset));
+ if (fastmap[ch])
+ break;
+ match_first += incr;
+ if (match_first < left_lim || match_first > right_lim)
+ {
+ err = REG_NOMATCH;
+ goto free_return;
+ }
+ }
+ break;
+ }
+
+ /* Reconstruct the buffers so that the matcher can assume that
+ the matching starts from the beginning of the buffer. */
+ err = re_string_reconstruct (&mctx.input, match_first, eflags);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+#ifdef RE_ENABLE_I18N
+ /* Don't consider this char as a possible match start if it part,
+ yet isn't the head, of a multibyte character. */
+ if (!sb && !re_string_first_byte (&mctx.input, 0))
+ continue;
+#endif
+
+ /* It seems to be appropriate one, then use the matcher. */
+ /* We assume that the matching starts from 0. */
+ mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0;
+ match_last = check_matching (&mctx, fl_longest_match,
+ range >= 0 ? &match_first : NULL);
+ if (match_last != -1)
+ {
+ if (BE (match_last == -2, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ else
+ {
+ mctx.match_last = match_last;
+ if ((!preg->no_sub && nmatch > 1) || dfa->nbackref)
+ {
+ re_dfastate_t *pstate = mctx.state_log[match_last];
+ mctx.last_node = check_halt_state_context (&mctx, pstate,
+ match_last);
+ }
+ if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match)
+ || dfa->nbackref)
+ {
+ err = prune_impossible_nodes (&mctx);
+ if (err == REG_NOERROR)
+ break;
+ if (BE (err != REG_NOMATCH, 0))
+ goto free_return;
+ match_last = -1;
+ }
+ else
+ break; /* We found a match. */
+ }
+ }
+
+ match_ctx_clean (&mctx);
+ }
+
+#ifdef DEBUG
+ assert (match_last != -1);
+ assert (err == REG_NOERROR);
+#endif
+
+ /* Set pmatch[] if we need. */
+ if (nmatch > 0)
+ {
+ int reg_idx;
+
+ /* Initialize registers. */
+ for (reg_idx = 1; reg_idx < nmatch; ++reg_idx)
+ pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1;
+
+ /* Set the points where matching start/end. */
+ pmatch[0].rm_so = 0;
+ pmatch[0].rm_eo = mctx.match_last;
+
+ if (!preg->no_sub && nmatch > 1)
+ {
+ err = set_regs (preg, &mctx, nmatch, pmatch,
+ dfa->has_plural_match && dfa->nbackref > 0);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+
+ /* At last, add the offset to the each registers, since we slided
+ the buffers so that we could assume that the matching starts
+ from 0. */
+ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+ if (pmatch[reg_idx].rm_so != -1)
+ {
+#ifdef RE_ENABLE_I18N
+ if (BE (mctx.input.offsets_needed != 0, 0))
+ {
+ pmatch[reg_idx].rm_so =
+ (pmatch[reg_idx].rm_so == mctx.input.valid_len
+ ? mctx.input.valid_raw_len
+ : mctx.input.offsets[pmatch[reg_idx].rm_so]);
+ pmatch[reg_idx].rm_eo =
+ (pmatch[reg_idx].rm_eo == mctx.input.valid_len
+ ? mctx.input.valid_raw_len
+ : mctx.input.offsets[pmatch[reg_idx].rm_eo]);
+ }
+#else
+ assert (mctx.input.offsets_needed == 0);
+#endif
+ pmatch[reg_idx].rm_so += match_first;
+ pmatch[reg_idx].rm_eo += match_first;
+ }
+ for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx)
+ {
+ pmatch[nmatch + reg_idx].rm_so = -1;
+ pmatch[nmatch + reg_idx].rm_eo = -1;
+ }
+
+ if (dfa->subexp_map)
+ for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++)
+ if (dfa->subexp_map[reg_idx] != reg_idx)
+ {
+ pmatch[reg_idx + 1].rm_so
+ = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so;
+ pmatch[reg_idx + 1].rm_eo
+ = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo;
+ }
+ }
+
+ free_return:
+ re_free (mctx.state_log);
+ if (dfa->nbackref)
+ match_ctx_free (&mctx);
+ re_string_destruct (&mctx.input);
+ return err;
+}
+
+static reg_errcode_t
+prune_impossible_nodes (re_match_context_t *mctx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int halt_node, match_last;
+ reg_errcode_t ret;
+ re_dfastate_t **sifted_states;
+ re_dfastate_t **lim_states = NULL;
+ re_sift_context_t sctx;
+#ifdef DEBUG
+ assert (mctx->state_log != NULL);
+#endif
+ match_last = mctx->match_last;
+ halt_node = mctx->last_node;
+
+ /* Avoid overflow. */
+ if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= match_last, 0))
+ return REG_ESPACE;
+
+ sifted_states = re_malloc (re_dfastate_t *, match_last + 1);
+ if (BE (sifted_states == NULL, 0))
+ {
+ ret = REG_ESPACE;
+ goto free_return;
+ }
+ if (dfa->nbackref)
+ {
+ lim_states = re_malloc (re_dfastate_t *, match_last + 1);
+ if (BE (lim_states == NULL, 0))
+ {
+ ret = REG_ESPACE;
+ goto free_return;
+ }
+ while (1)
+ {
+ memset (lim_states, '\0',
+ sizeof (re_dfastate_t *) * (match_last + 1));
+ sift_ctx_init (&sctx, sifted_states, lim_states, halt_node,
+ match_last);
+ ret = sift_states_backward (mctx, &sctx);
+ re_node_set_free (&sctx.limits);
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ if (sifted_states[0] != NULL || lim_states[0] != NULL)
+ break;
+ do
+ {
+ --match_last;
+ if (match_last < 0)
+ {
+ ret = REG_NOMATCH;
+ goto free_return;
+ }
+ } while (mctx->state_log[match_last] == NULL
+ || !mctx->state_log[match_last]->halt);
+ halt_node = check_halt_state_context (mctx,
+ mctx->state_log[match_last],
+ match_last);
+ }
+ ret = merge_state_array (dfa, sifted_states, lim_states,
+ match_last + 1);
+ re_free (lim_states);
+ lim_states = NULL;
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ }
+ else
+ {
+ sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last);
+ ret = sift_states_backward (mctx, &sctx);
+ re_node_set_free (&sctx.limits);
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ if (sifted_states[0] == NULL)
+ {
+ ret = REG_NOMATCH;
+ goto free_return;
+ }
+ }
+ re_free (mctx->state_log);
+ mctx->state_log = sifted_states;
+ sifted_states = NULL;
+ mctx->last_node = halt_node;
+ mctx->match_last = match_last;
+ ret = REG_NOERROR;
+ free_return:
+ re_free (sifted_states);
+ re_free (lim_states);
+ return ret;
+}
+
+/* Acquire an initial state and return it.
+ We must select appropriate initial state depending on the context,
+ since initial states may have constraints like "\<", "^", etc.. */
+
+static inline re_dfastate_t *
+__attribute ((always_inline)) internal_function
+acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx,
+ int idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ if (dfa->init_state->has_constraint)
+ {
+ unsigned int context;
+ context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags);
+ if (IS_WORD_CONTEXT (context))
+ return dfa->init_state_word;
+ else if (IS_ORDINARY_CONTEXT (context))
+ return dfa->init_state;
+ else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context))
+ return dfa->init_state_begbuf;
+ else if (IS_NEWLINE_CONTEXT (context))
+ return dfa->init_state_nl;
+ else if (IS_BEGBUF_CONTEXT (context))
+ {
+ /* It is relatively rare case, then calculate on demand. */
+ return re_acquire_state_context (err, dfa,
+ dfa->init_state->entrance_nodes,
+ context);
+ }
+ else
+ /* Must not happen? */
+ return dfa->init_state;
+ }
+ else
+ return dfa->init_state;
+}
+
+/* Check whether the regular expression match input string INPUT or not,
+ and return the index where the matching end, return -1 if not match,
+ or return -2 in case of an error.
+ FL_LONGEST_MATCH means we want the POSIX longest matching.
+ If P_MATCH_FIRST is not NULL, and the match fails, it is set to the
+ next place where we may want to try matching.
+ Note that the matcher assume that the maching starts from the current
+ index of the buffer. */
+
+static int
+internal_function
+check_matching (re_match_context_t *mctx, int fl_longest_match,
+ int *p_match_first)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int match = 0;
+ int match_last = -1;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+ re_dfastate_t *cur_state;
+ int at_init_state = p_match_first != NULL;
+ int next_start_idx = cur_str_idx;
+
+ err = REG_NOERROR;
+ cur_state = acquire_init_state_context (&err, mctx, cur_str_idx);
+ /* An initial state must not be NULL (invalid). */
+ if (BE (cur_state == NULL, 0))
+ {
+ assert (err == REG_ESPACE);
+ return -2;
+ }
+
+ if (mctx->state_log != NULL)
+ {
+ mctx->state_log[cur_str_idx] = cur_state;
+
+ /* Check OP_OPEN_SUBEXP in the initial state in case that we use them
+ later. E.g. Processing back references. */
+ if (BE (dfa->nbackref, 0))
+ {
+ at_init_state = 0;
+ err = check_subexp_matching_top (mctx, &cur_state->nodes, 0);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (cur_state->has_backref)
+ {
+ err = transit_state_bkref (mctx, &cur_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ }
+
+ /* If the RE accepts NULL string. */
+ if (BE (cur_state->halt, 0))
+ {
+ if (!cur_state->has_constraint
+ || check_halt_state_context (mctx, cur_state, cur_str_idx))
+ {
+ if (!fl_longest_match)
+ return cur_str_idx;
+ else
+ {
+ match_last = cur_str_idx;
+ match = 1;
+ }
+ }
+ }
+
+ while (!re_string_eoi (&mctx->input))
+ {
+ re_dfastate_t *old_state = cur_state;
+ int next_char_idx = re_string_cur_idx (&mctx->input) + 1;
+
+ if (BE (next_char_idx >= mctx->input.bufs_len, 0)
+ || (BE (next_char_idx >= mctx->input.valid_len, 0)
+ && mctx->input.valid_len < mctx->input.len))
+ {
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ assert (err == REG_ESPACE);
+ return -2;
+ }
+ }
+
+ cur_state = transit_state (&err, mctx, cur_state);
+ if (mctx->state_log != NULL)
+ cur_state = merge_state_with_log (&err, mctx, cur_state);
+
+ if (cur_state == NULL)
+ {
+ /* Reached the invalid state or an error. Try to recover a valid
+ state using the state log, if available and if we have not
+ already found a valid (even if not the longest) match. */
+ if (BE (err != REG_NOERROR, 0))
+ return -2;
+
+ if (mctx->state_log == NULL
+ || (match && !fl_longest_match)
+ || (cur_state = find_recover_state (&err, mctx)) == NULL)
+ break;
+ }
+
+ if (BE (at_init_state, 0))
+ {
+ if (old_state == cur_state)
+ next_start_idx = next_char_idx;
+ else
+ at_init_state = 0;
+ }
+
+ if (cur_state->halt)
+ {
+ /* Reached a halt state.
+ Check the halt state can satisfy the current context. */
+ if (!cur_state->has_constraint
+ || check_halt_state_context (mctx, cur_state,
+ re_string_cur_idx (&mctx->input)))
+ {
+ /* We found an appropriate halt state. */
+ match_last = re_string_cur_idx (&mctx->input);
+ match = 1;
+
+ /* We found a match, do not modify match_first below. */
+ p_match_first = NULL;
+ if (!fl_longest_match)
+ break;
+ }
+ }
+ }
+
+ if (p_match_first)
+ *p_match_first += next_start_idx;
+
+ return match_last;
+}
+
+/* Check NODE match the current context. */
+
+static int
+internal_function
+check_halt_node_context (const re_dfa_t *dfa, int node, unsigned int context)
+{
+ re_token_type_t type = dfa->nodes[node].type;
+ unsigned int constraint = dfa->nodes[node].constraint;
+ if (type != END_OF_RE)
+ return 0;
+ if (!constraint)
+ return 1;
+ if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context))
+ return 0;
+ return 1;
+}
+
+/* Check the halt state STATE match the current context.
+ Return 0 if not match, if the node, STATE has, is a halt node and
+ match the context, return the node. */
+
+static int
+internal_function
+check_halt_state_context (const re_match_context_t *mctx,
+ const re_dfastate_t *state, int idx)
+{
+ int i;
+ unsigned int context;
+#ifdef DEBUG
+ assert (state->halt);
+#endif
+ context = re_string_context_at (&mctx->input, idx, mctx->eflags);
+ for (i = 0; i < state->nodes.nelem; ++i)
+ if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context))
+ return state->nodes.elems[i];
+ return 0;
+}
+
+/* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA
+ corresponding to the DFA).
+ Return the destination node, and update EPS_VIA_NODES, return -1 in case
+ of errors. */
+
+static int
+internal_function
+proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs,
+ int *pidx, int node, re_node_set *eps_via_nodes,
+ struct re_fail_stack_t *fs)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int i, err;
+ if (IS_EPSILON_NODE (dfa->nodes[node].type))
+ {
+ re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes;
+ re_node_set *edests = &dfa->edests[node];
+ int dest_node;
+ err = re_node_set_insert (eps_via_nodes, node);
+ if (BE (err < 0, 0))
+ return -2;
+ /* Pick up a valid destination, or return -1 if none is found. */
+ for (dest_node = -1, i = 0; i < edests->nelem; ++i)
+ {
+ int candidate = edests->elems[i];
+ if (!re_node_set_contains (cur_nodes, candidate))
+ continue;
+ if (dest_node == -1)
+ dest_node = candidate;
+
+ else
+ {
+ /* In order to avoid infinite loop like "(a*)*", return the second
+ epsilon-transition if the first was already considered. */
+ if (re_node_set_contains (eps_via_nodes, dest_node))
+ return candidate;
+
+ /* Otherwise, push the second epsilon-transition on the fail stack. */
+ else if (fs != NULL
+ && push_fail_stack (fs, *pidx, candidate, nregs, regs,
+ eps_via_nodes))
+ return -2;
+
+ /* We know we are going to exit. */
+ break;
+ }
+ }
+ return dest_node;
+ }
+ else
+ {
+ int naccepted = 0;
+ re_token_type_t type = dfa->nodes[node].type;
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->nodes[node].accept_mb)
+ naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx);
+ else
+#endif /* RE_ENABLE_I18N */
+ if (type == OP_BACK_REF)
+ {
+ int subexp_idx = dfa->nodes[node].opr.idx + 1;
+ naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so;
+ if (fs != NULL)
+ {
+ if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1)
+ return -1;
+ else if (naccepted)
+ {
+ char *buf = (char *) re_string_get_buffer (&mctx->input);
+ if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx,
+ naccepted) != 0)
+ return -1;
+ }
+ }
+
+ if (naccepted == 0)
+ {
+ int dest_node;
+ err = re_node_set_insert (eps_via_nodes, node);
+ if (BE (err < 0, 0))
+ return -2;
+ dest_node = dfa->edests[node].elems[0];
+ if (re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+ dest_node))
+ return dest_node;
+ }
+ }
+
+ if (naccepted != 0
+ || check_node_accept (mctx, dfa->nodes + node, *pidx))
+ {
+ int dest_node = dfa->nexts[node];
+ *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted;
+ if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL
+ || !re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+ dest_node)))
+ return -1;
+ re_node_set_empty (eps_via_nodes);
+ return dest_node;
+ }
+ }
+ return -1;
+}
+
+static reg_errcode_t
+internal_function
+push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node,
+ int nregs, regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+ reg_errcode_t err;
+ int num = fs->num++;
+ if (fs->num == fs->alloc)
+ {
+ struct re_fail_stack_ent_t *new_array;
+ new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t)
+ * fs->alloc * 2));
+ if (new_array == NULL)
+ return REG_ESPACE;
+ fs->alloc *= 2;
+ fs->stack = new_array;
+ }
+ fs->stack[num].idx = str_idx;
+ fs->stack[num].node = dest_node;
+ fs->stack[num].regs = re_malloc (regmatch_t, nregs);
+ if (fs->stack[num].regs == NULL)
+ return REG_ESPACE;
+ memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs);
+ err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes);
+ return err;
+}
+
+static int
+internal_function
+pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs,
+ regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+ int num = --fs->num;
+ assert (num >= 0);
+ *pidx = fs->stack[num].idx;
+ memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs);
+ re_node_set_free (eps_via_nodes);
+ re_free (fs->stack[num].regs);
+ *eps_via_nodes = fs->stack[num].eps_via_nodes;
+ return fs->stack[num].node;
+}
+
+/* Set the positions where the subexpressions are starts/ends to registers
+ PMATCH.
+ Note: We assume that pmatch[0] is already set, and
+ pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */
+
+static reg_errcode_t
+internal_function
+set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch,
+ regmatch_t *pmatch, int fl_backtrack)
+{
+ const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+ int idx, cur_node;
+ re_node_set eps_via_nodes;
+ struct re_fail_stack_t *fs;
+ struct re_fail_stack_t fs_body = { 0, 2, NULL };
+ regmatch_t *prev_idx_match;
+ int prev_idx_match_malloced = 0;
+
+#ifdef DEBUG
+ assert (nmatch > 1);
+ assert (mctx->state_log != NULL);
+#endif
+ if (fl_backtrack)
+ {
+ fs = &fs_body;
+ fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc);
+ if (fs->stack == NULL)
+ return REG_ESPACE;
+ }
+ else
+ fs = NULL;
+
+ cur_node = dfa->init_node;
+ re_node_set_init_empty (&eps_via_nodes);
+
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca (nmatch * sizeof (regmatch_t)))
+ prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t));
+ else
+#endif
+ {
+ prev_idx_match = re_malloc (regmatch_t, nmatch);
+ if (prev_idx_match == NULL)
+ {
+ free_fail_stack_return (fs);
+ return REG_ESPACE;
+ }
+ prev_idx_match_malloced = 1;
+ }
+ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+
+ for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;)
+ {
+ update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch);
+
+ if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node)
+ {
+ int reg_idx;
+ if (fs)
+ {
+ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+ if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1)
+ break;
+ if (reg_idx == nmatch)
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return free_fail_stack_return (fs);
+ }
+ cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+ &eps_via_nodes);
+ }
+ else
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return REG_NOERROR;
+ }
+ }
+
+ /* Proceed to next node. */
+ cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node,
+ &eps_via_nodes, fs);
+
+ if (BE (cur_node < 0, 0))
+ {
+ if (BE (cur_node == -2, 0))
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ free_fail_stack_return (fs);
+ return REG_ESPACE;
+ }
+ if (fs)
+ cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+ &eps_via_nodes);
+ else
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return REG_NOMATCH;
+ }
+ }
+ }
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return free_fail_stack_return (fs);
+}
+
+static reg_errcode_t
+internal_function
+free_fail_stack_return (struct re_fail_stack_t *fs)
+{
+ if (fs)
+ {
+ int fs_idx;
+ for (fs_idx = 0; fs_idx < fs->num; ++fs_idx)
+ {
+ re_node_set_free (&fs->stack[fs_idx].eps_via_nodes);
+ re_free (fs->stack[fs_idx].regs);
+ }
+ re_free (fs->stack);
+ }
+ return REG_NOERROR;
+}
+
+static void
+internal_function
+update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+ regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch)
+{
+ int type = dfa->nodes[cur_node].type;
+ if (type == OP_OPEN_SUBEXP)
+ {
+ int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+
+ /* We are at the first node of this sub expression. */
+ if (reg_num < nmatch)
+ {
+ pmatch[reg_num].rm_so = cur_idx;
+ pmatch[reg_num].rm_eo = -1;
+ }
+ }
+ else if (type == OP_CLOSE_SUBEXP)
+ {
+ int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+ if (reg_num < nmatch)
+ {
+ /* We are at the last node of this sub expression. */
+ if (pmatch[reg_num].rm_so < cur_idx)
+ {
+ pmatch[reg_num].rm_eo = cur_idx;
+ /* This is a non-empty match or we are not inside an optional
+ subexpression. Accept this right away. */
+ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+ }
+ else
+ {
+ if (dfa->nodes[cur_node].opt_subexp
+ && prev_idx_match[reg_num].rm_so != -1)
+ /* We transited through an empty match for an optional
+ subexpression, like (a?)*, and this is not the subexp's
+ first match. Copy back the old content of the registers
+ so that matches of an inner subexpression are undone as
+ well, like in ((a?))*. */
+ memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch);
+ else
+ /* We completed a subexpression, but it may be part of
+ an optional one, so do not update PREV_IDX_MATCH. */
+ pmatch[reg_num].rm_eo = cur_idx;
+ }
+ }
+ }
+}
+
+/* This function checks the STATE_LOG from the SCTX->last_str_idx to 0
+ and sift the nodes in each states according to the following rules.
+ Updated state_log will be wrote to STATE_LOG.
+
+ Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if...
+ 1. When STR_IDX == MATCH_LAST(the last index in the state_log):
+ If `a' isn't the LAST_NODE and `a' can't epsilon transit to
+ the LAST_NODE, we throw away the node `a'.
+ 2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts
+ string `s' and transit to `b':
+ i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw
+ away the node `a'.
+ ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is
+ thrown away, we throw away the node `a'.
+ 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b':
+ i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the
+ node `a'.
+ ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away,
+ we throw away the node `a'. */
+
+#define STATE_NODE_CONTAINS(state,node) \
+ ((state) != NULL && re_node_set_contains (&(state)->nodes, node))
+
+static reg_errcode_t
+internal_function
+sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx)
+{
+ reg_errcode_t err;
+ int null_cnt = 0;
+ int str_idx = sctx->last_str_idx;
+ re_node_set cur_dest;
+
+#ifdef DEBUG
+ assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL);
+#endif
+
+ /* Build sifted state_log[str_idx]. It has the nodes which can epsilon
+ transit to the last_node and the last_node itself. */
+ err = re_node_set_init_1 (&cur_dest, sctx->last_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* Then check each states in the state_log. */
+ while (str_idx > 0)
+ {
+ /* Update counters. */
+ null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0;
+ if (null_cnt > mctx->max_mb_elem_len)
+ {
+ memset (sctx->sifted_states, '\0',
+ sizeof (re_dfastate_t *) * str_idx);
+ re_node_set_free (&cur_dest);
+ return REG_NOERROR;
+ }
+ re_node_set_empty (&cur_dest);
+ --str_idx;
+
+ if (mctx->state_log[str_idx])
+ {
+ err = build_sifted_states (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+
+ /* Add all the nodes which satisfy the following conditions:
+ - It can epsilon transit to a node in CUR_DEST.
+ - It is in CUR_SRC.
+ And update state_log. */
+ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ err = REG_NOERROR;
+ free_return:
+ re_node_set_free (&cur_dest);
+ return err;
+}
+
+static reg_errcode_t
+internal_function
+build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int str_idx, re_node_set *cur_dest)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes;
+ int i;
+
+ /* Then build the next sifted state.
+ We build the next sifted state on `cur_dest', and update
+ `sifted_states[str_idx]' with `cur_dest'.
+ Note:
+ `cur_dest' is the sifted state from `state_log[str_idx + 1]'.
+ `cur_src' points the node_set of the old `state_log[str_idx]'
+ (with the epsilon nodes pre-filtered out). */
+ for (i = 0; i < cur_src->nelem; i++)
+ {
+ int prev_node = cur_src->elems[i];
+ int naccepted = 0;
+ int ret;
+
+#ifdef DEBUG
+ re_token_type_t type = dfa->nodes[prev_node].type;
+ assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+ /* If the node may accept `multi byte'. */
+ if (dfa->nodes[prev_node].accept_mb)
+ naccepted = sift_states_iter_mb (mctx, sctx, prev_node,
+ str_idx, sctx->last_str_idx);
+#endif /* RE_ENABLE_I18N */
+
+ /* We don't check backreferences here.
+ See update_cur_sifted_state(). */
+ if (!naccepted
+ && check_node_accept (mctx, dfa->nodes + prev_node, str_idx)
+ && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1],
+ dfa->nexts[prev_node]))
+ naccepted = 1;
+
+ if (naccepted == 0)
+ continue;
+
+ if (sctx->limits.nelem)
+ {
+ int to_idx = str_idx + naccepted;
+ if (check_dst_limits (mctx, &sctx->limits,
+ dfa->nexts[prev_node], to_idx,
+ prev_node, str_idx))
+ continue;
+ }
+ ret = re_node_set_insert (cur_dest, prev_node);
+ if (BE (ret == -1, 0))
+ return REG_ESPACE;
+ }
+
+ return REG_NOERROR;
+}
+
+/* Helper functions. */
+
+static reg_errcode_t
+internal_function
+clean_state_log_if_needed (re_match_context_t *mctx, int next_state_log_idx)
+{
+ int top = mctx->state_log_top;
+
+ if (next_state_log_idx >= mctx->input.bufs_len
+ || (next_state_log_idx >= mctx->input.valid_len
+ && mctx->input.valid_len < mctx->input.len))
+ {
+ reg_errcode_t err;
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (top < next_state_log_idx)
+ {
+ memset (mctx->state_log + top + 1, '\0',
+ sizeof (re_dfastate_t *) * (next_state_log_idx - top));
+ mctx->state_log_top = next_state_log_idx;
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst,
+ re_dfastate_t **src, int num)
+{
+ int st_idx;
+ reg_errcode_t err;
+ for (st_idx = 0; st_idx < num; ++st_idx)
+ {
+ if (dst[st_idx] == NULL)
+ dst[st_idx] = src[st_idx];
+ else if (src[st_idx] != NULL)
+ {
+ re_node_set merged_set;
+ err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes,
+ &src[st_idx]->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ dst[st_idx] = re_acquire_state (&err, dfa, &merged_set);
+ re_node_set_free (&merged_set);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+update_cur_sifted_state (const re_match_context_t *mctx,
+ re_sift_context_t *sctx, int str_idx,
+ re_node_set *dest_nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err = REG_NOERROR;
+ const re_node_set *candidates;
+ candidates = ((mctx->state_log[str_idx] == NULL) ? NULL
+ : &mctx->state_log[str_idx]->nodes);
+
+ if (dest_nodes->nelem == 0)
+ sctx->sifted_states[str_idx] = NULL;
+ else
+ {
+ if (candidates)
+ {
+ /* At first, add the nodes which can epsilon transit to a node in
+ DEST_NODE. */
+ err = add_epsilon_src_nodes (dfa, dest_nodes, candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* Then, check the limitations in the current sift_context. */
+ if (sctx->limits.nelem)
+ {
+ err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits,
+ mctx->bkref_ents, str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+
+ sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (candidates && mctx->state_log[str_idx]->has_backref)
+ {
+ err = sift_states_bkref (mctx, sctx, str_idx, candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes,
+ const re_node_set *candidates)
+{
+ reg_errcode_t err = REG_NOERROR;
+ int i;
+
+ re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (!state->inveclosure.alloc)
+ {
+ err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ for (i = 0; i < dest_nodes->nelem; i++)
+ {
+ err = re_node_set_merge (&state->inveclosure,
+ dfa->inveclosures + dest_nodes->elems[i]);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ }
+ }
+ return re_node_set_add_intersect (dest_nodes, candidates,
+ &state->inveclosure);
+}
+
+static reg_errcode_t
+internal_function
+sub_epsilon_src_nodes (const re_dfa_t *dfa, int node, re_node_set *dest_nodes,
+ const re_node_set *candidates)
+{
+ int ecl_idx;
+ reg_errcode_t err;
+ re_node_set *inv_eclosure = dfa->inveclosures + node;
+ re_node_set except_nodes;
+ re_node_set_init_empty (&except_nodes);
+ for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+ {
+ int cur_node = inv_eclosure->elems[ecl_idx];
+ if (cur_node == node)
+ continue;
+ if (IS_EPSILON_NODE (dfa->nodes[cur_node].type))
+ {
+ int edst1 = dfa->edests[cur_node].elems[0];
+ int edst2 = ((dfa->edests[cur_node].nelem > 1)
+ ? dfa->edests[cur_node].elems[1] : -1);
+ if ((!re_node_set_contains (inv_eclosure, edst1)
+ && re_node_set_contains (dest_nodes, edst1))
+ || (edst2 > 0
+ && !re_node_set_contains (inv_eclosure, edst2)
+ && re_node_set_contains (dest_nodes, edst2)))
+ {
+ err = re_node_set_add_intersect (&except_nodes, candidates,
+ dfa->inveclosures + cur_node);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&except_nodes);
+ return err;
+ }
+ }
+ }
+ }
+ for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+ {
+ int cur_node = inv_eclosure->elems[ecl_idx];
+ if (!re_node_set_contains (&except_nodes, cur_node))
+ {
+ int idx = re_node_set_contains (dest_nodes, cur_node) - 1;
+ re_node_set_remove_at (dest_nodes, idx);
+ }
+ }
+ re_node_set_free (&except_nodes);
+ return REG_NOERROR;
+}
+
+static int
+internal_function
+check_dst_limits (const re_match_context_t *mctx, re_node_set *limits,
+ int dst_node, int dst_idx, int src_node, int src_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int lim_idx, src_pos, dst_pos;
+
+ int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx);
+ int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx);
+ for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+ {
+ int subexp_idx;
+ struct re_backref_cache_entry *ent;
+ ent = mctx->bkref_ents + limits->elems[lim_idx];
+ subexp_idx = dfa->nodes[ent->node].opr.idx;
+
+ dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+ subexp_idx, dst_node, dst_idx,
+ dst_bkref_idx);
+ src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+ subexp_idx, src_node, src_idx,
+ src_bkref_idx);
+
+ /* In case of:
+ <src> <dst> ( <subexp> )
+ ( <subexp> ) <src> <dst>
+ ( <subexp1> <src> <subexp2> <dst> <subexp3> ) */
+ if (src_pos == dst_pos)
+ continue; /* This is unrelated limitation. */
+ else
+ return 1;
+ }
+ return 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries,
+ int subexp_idx, int from_node, int bkref_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ const re_node_set *eclosures = dfa->eclosures + from_node;
+ int node_idx;
+
+ /* Else, we are on the boundary: examine the nodes on the epsilon
+ closure. */
+ for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx)
+ {
+ int node = eclosures->elems[node_idx];
+ switch (dfa->nodes[node].type)
+ {
+ case OP_BACK_REF:
+ if (bkref_idx != -1)
+ {
+ struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx;
+ do
+ {
+ int dst, cpos;
+
+ if (ent->node != node)
+ continue;
+
+ if (subexp_idx < BITSET_WORD_BITS
+ && !(ent->eps_reachable_subexps_map
+ & ((bitset_word_t) 1 << subexp_idx)))
+ continue;
+
+ /* Recurse trying to reach the OP_OPEN_SUBEXP and
+ OP_CLOSE_SUBEXP cases below. But, if the
+ destination node is the same node as the source
+ node, don't recurse because it would cause an
+ infinite loop: a regex that exhibits this behavior
+ is ()\1*\1* */
+ dst = dfa->edests[node].elems[0];
+ if (dst == from_node)
+ {
+ if (boundaries & 1)
+ return -1;
+ else /* if (boundaries & 2) */
+ return 0;
+ }
+
+ cpos =
+ check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+ dst, bkref_idx);
+ if (cpos == -1 /* && (boundaries & 1) */)
+ return -1;
+ if (cpos == 0 && (boundaries & 2))
+ return 0;
+
+ if (subexp_idx < BITSET_WORD_BITS)
+ ent->eps_reachable_subexps_map
+ &= ~((bitset_word_t) 1 << subexp_idx);
+ }
+ while (ent++->more);
+ }
+ break;
+
+ case OP_OPEN_SUBEXP:
+ if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx)
+ return -1;
+ break;
+
+ case OP_CLOSE_SUBEXP:
+ if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx)
+ return 0;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return (boundaries & 2) ? 1 : 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit,
+ int subexp_idx, int from_node, int str_idx,
+ int bkref_idx)
+{
+ struct re_backref_cache_entry *lim = mctx->bkref_ents + limit;
+ int boundaries;
+
+ /* If we are outside the range of the subexpression, return -1 or 1. */
+ if (str_idx < lim->subexp_from)
+ return -1;
+
+ if (lim->subexp_to < str_idx)
+ return 1;
+
+ /* If we are within the subexpression, return 0. */
+ boundaries = (str_idx == lim->subexp_from);
+ boundaries |= (str_idx == lim->subexp_to) << 1;
+ if (boundaries == 0)
+ return 0;
+
+ /* Else, examine epsilon closure. */
+ return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+ from_node, bkref_idx);
+}
+
+/* Check the limitations of sub expressions LIMITS, and remove the nodes
+ which are against limitations from DEST_NODES. */
+
+static reg_errcode_t
+internal_function
+check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes,
+ const re_node_set *candidates, re_node_set *limits,
+ struct re_backref_cache_entry *bkref_ents, int str_idx)
+{
+ reg_errcode_t err;
+ int node_idx, lim_idx;
+
+ for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+ {
+ int subexp_idx;
+ struct re_backref_cache_entry *ent;
+ ent = bkref_ents + limits->elems[lim_idx];
+
+ if (str_idx <= ent->subexp_from || ent->str_idx < str_idx)
+ continue; /* This is unrelated limitation. */
+
+ subexp_idx = dfa->nodes[ent->node].opr.idx;
+ if (ent->subexp_to == str_idx)
+ {
+ int ops_node = -1;
+ int cls_node = -1;
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ re_token_type_t type = dfa->nodes[node].type;
+ if (type == OP_OPEN_SUBEXP
+ && subexp_idx == dfa->nodes[node].opr.idx)
+ ops_node = node;
+ else if (type == OP_CLOSE_SUBEXP
+ && subexp_idx == dfa->nodes[node].opr.idx)
+ cls_node = node;
+ }
+
+ /* Check the limitation of the open subexpression. */
+ /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */
+ if (ops_node >= 0)
+ {
+ err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ /* Check the limitation of the close subexpression. */
+ if (cls_node >= 0)
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ if (!re_node_set_contains (dfa->inveclosures + node,
+ cls_node)
+ && !re_node_set_contains (dfa->eclosures + node,
+ cls_node))
+ {
+ /* It is against this limitation.
+ Remove it form the current sifted state. */
+ err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ --node_idx;
+ }
+ }
+ }
+ else /* (ent->subexp_to != str_idx) */
+ {
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ re_token_type_t type = dfa->nodes[node].type;
+ if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP)
+ {
+ if (subexp_idx != dfa->nodes[node].opr.idx)
+ continue;
+ /* It is against this limitation.
+ Remove it form the current sifted state. */
+ err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int str_idx, const re_node_set *candidates)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int node_idx, node;
+ re_sift_context_t local_sctx;
+ int first_idx = search_cur_bkref_entry (mctx, str_idx);
+
+ if (first_idx == -1)
+ return REG_NOERROR;
+
+ local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */
+
+ for (node_idx = 0; node_idx < candidates->nelem; ++node_idx)
+ {
+ int enabled_idx;
+ re_token_type_t type;
+ struct re_backref_cache_entry *entry;
+ node = candidates->elems[node_idx];
+ type = dfa->nodes[node].type;
+ /* Avoid infinite loop for the REs like "()\1+". */
+ if (node == sctx->last_node && str_idx == sctx->last_str_idx)
+ continue;
+ if (type != OP_BACK_REF)
+ continue;
+
+ entry = mctx->bkref_ents + first_idx;
+ enabled_idx = first_idx;
+ do
+ {
+ int subexp_len;
+ int to_idx;
+ int dst_node;
+ int ret;
+ re_dfastate_t *cur_state;
+
+ if (entry->node != node)
+ continue;
+ subexp_len = entry->subexp_to - entry->subexp_from;
+ to_idx = str_idx + subexp_len;
+ dst_node = (subexp_len ? dfa->nexts[node]
+ : dfa->edests[node].elems[0]);
+
+ if (to_idx > sctx->last_str_idx
+ || sctx->sifted_states[to_idx] == NULL
+ || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node)
+ || check_dst_limits (mctx, &sctx->limits, node,
+ str_idx, dst_node, to_idx))
+ continue;
+
+ if (local_sctx.sifted_states == NULL)
+ {
+ local_sctx = *sctx;
+ err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ local_sctx.last_node = node;
+ local_sctx.last_str_idx = str_idx;
+ ret = re_node_set_insert (&local_sctx.limits, enabled_idx);
+ if (BE (ret < 0, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ cur_state = local_sctx.sifted_states[str_idx];
+ err = sift_states_backward (mctx, &local_sctx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ if (sctx->limited_states != NULL)
+ {
+ err = merge_state_array (dfa, sctx->limited_states,
+ local_sctx.sifted_states,
+ str_idx + 1);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ local_sctx.sifted_states[str_idx] = cur_state;
+ re_node_set_remove (&local_sctx.limits, enabled_idx);
+
+ /* mctx->bkref_ents may have changed, reload the pointer. */
+ entry = mctx->bkref_ents + enabled_idx;
+ }
+ while (enabled_idx++, entry++->more);
+ }
+ err = REG_NOERROR;
+ free_return:
+ if (local_sctx.sifted_states != NULL)
+ {
+ re_node_set_free (&local_sctx.limits);
+ }
+
+ return err;
+}
+
+
+#ifdef RE_ENABLE_I18N
+static int
+internal_function
+sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int node_idx, int str_idx, int max_str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int naccepted;
+ /* Check the node can accept `multi byte'. */
+ naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx);
+ if (naccepted > 0 && str_idx + naccepted <= max_str_idx &&
+ !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted],
+ dfa->nexts[node_idx]))
+ /* The node can't accept the `multi byte', or the
+ destination was already thrown away, then the node
+ could't accept the current input `multi byte'. */
+ naccepted = 0;
+ /* Otherwise, it is sure that the node could accept
+ `naccepted' bytes input. */
+ return naccepted;
+}
+#endif /* RE_ENABLE_I18N */
+
+
+/* Functions for state transition. */
+
+/* Return the next state to which the current state STATE will transit by
+ accepting the current input byte, and update STATE_LOG if necessary.
+ If STATE can accept a multibyte char/collating element/back reference
+ update the destination of STATE_LOG. */
+
+static re_dfastate_t *
+internal_function
+transit_state (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *state)
+{
+ re_dfastate_t **trtable;
+ unsigned char ch;
+
+#ifdef RE_ENABLE_I18N
+ /* If the current state can accept multibyte. */
+ if (BE (state->accept_mb, 0))
+ {
+ *err = transit_state_mb (mctx, state);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+#endif /* RE_ENABLE_I18N */
+
+ /* Then decide the next state with the single byte. */
+#if 0
+ if (0)
+ /* don't use transition table */
+ return transit_state_sb (err, mctx, state);
+#endif
+
+ /* Use transition table */
+ ch = re_string_fetch_byte (&mctx->input);
+ for (;;)
+ {
+ trtable = state->trtable;
+ if (BE (trtable != NULL, 1))
+ return trtable[ch];
+
+ trtable = state->word_trtable;
+ if (BE (trtable != NULL, 1))
+ {
+ unsigned int context;
+ context
+ = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input) - 1,
+ mctx->eflags);
+ if (IS_WORD_CONTEXT (context))
+ return trtable[ch + SBC_MAX];
+ else
+ return trtable[ch];
+ }
+
+ if (!build_trtable (mctx->dfa, state))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ /* Retry, we now have a transition table. */
+ }
+}
+
+/* Update the state_log if we need */
+re_dfastate_t *
+internal_function
+merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *next_state)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int cur_idx = re_string_cur_idx (&mctx->input);
+
+ if (cur_idx > mctx->state_log_top)
+ {
+ mctx->state_log[cur_idx] = next_state;
+ mctx->state_log_top = cur_idx;
+ }
+ else if (mctx->state_log[cur_idx] == 0)
+ {
+ mctx->state_log[cur_idx] = next_state;
+ }
+ else
+ {
+ re_dfastate_t *pstate;
+ unsigned int context;
+ re_node_set next_nodes, *log_nodes, *table_nodes = NULL;
+ /* If (state_log[cur_idx] != 0), it implies that cur_idx is
+ the destination of a multibyte char/collating element/
+ back reference. Then the next state is the union set of
+ these destinations and the results of the transition table. */
+ pstate = mctx->state_log[cur_idx];
+ log_nodes = pstate->entrance_nodes;
+ if (next_state != NULL)
+ {
+ table_nodes = next_state->entrance_nodes;
+ *err = re_node_set_init_union (&next_nodes, table_nodes,
+ log_nodes);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+ else
+ next_nodes = *log_nodes;
+ /* Note: We already add the nodes of the initial state,
+ then we don't need to add them here. */
+
+ context = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input) - 1,
+ mctx->eflags);
+ next_state = mctx->state_log[cur_idx]
+ = re_acquire_state_context (err, dfa, &next_nodes, context);
+ /* We don't need to check errors here, since the return value of
+ this function is next_state and ERR is already set. */
+
+ if (table_nodes != NULL)
+ re_node_set_free (&next_nodes);
+ }
+
+ if (BE (dfa->nbackref, 0) && next_state != NULL)
+ {
+ /* Check OP_OPEN_SUBEXP in the current state in case that we use them
+ later. We must check them here, since the back references in the
+ next state might use them. */
+ *err = check_subexp_matching_top (mctx, &next_state->nodes,
+ cur_idx);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+
+ /* If the next state has back references. */
+ if (next_state->has_backref)
+ {
+ *err = transit_state_bkref (mctx, &next_state->nodes);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ next_state = mctx->state_log[cur_idx];
+ }
+ }
+
+ return next_state;
+}
+
+/* Skip bytes in the input that correspond to part of a
+ multi-byte match, then look in the log for a state
+ from which to restart matching. */
+re_dfastate_t *
+internal_function
+find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
+{
+ re_dfastate_t *cur_state;
+ do
+ {
+ int max = mctx->state_log_top;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+ do
+ {
+ if (++cur_str_idx > max)
+ return NULL;
+ re_string_skip_bytes (&mctx->input, 1);
+ }
+ while (mctx->state_log[cur_str_idx] == NULL);
+
+ cur_state = merge_state_with_log (err, mctx, NULL);
+ }
+ while (*err == REG_NOERROR && cur_state == NULL);
+ return cur_state;
+}
+
+/* Helper functions for transit_state. */
+
+/* From the node set CUR_NODES, pick up the nodes whose types are
+ OP_OPEN_SUBEXP and which have corresponding back references in the regular
+ expression. And register them to use them later for evaluating the
+ correspoding back references. */
+
+static reg_errcode_t
+internal_function
+check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes,
+ int str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int node_idx;
+ reg_errcode_t err;
+
+ /* TODO: This isn't efficient.
+ Because there might be more than one nodes whose types are
+ OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+ nodes.
+ E.g. RE: (a){2} */
+ for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx)
+ {
+ int node = cur_nodes->elems[node_idx];
+ if (dfa->nodes[node].type == OP_OPEN_SUBEXP
+ && dfa->nodes[node].opr.idx < BITSET_WORD_BITS
+ && (dfa->used_bkref_map
+ & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx)))
+ {
+ err = match_ctx_add_subtop (mctx, node, str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ return REG_NOERROR;
+}
+
+#if 0
+/* Return the next state to which the current state STATE will transit by
+ accepting the current input byte. */
+
+static re_dfastate_t *
+transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *state)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ re_node_set next_nodes;
+ re_dfastate_t *next_state;
+ int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input);
+ unsigned int context;
+
+ *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt)
+ {
+ int cur_node = state->nodes.elems[node_cnt];
+ if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx))
+ {
+ *err = re_node_set_merge (&next_nodes,
+ dfa->eclosures + dfa->nexts[cur_node]);
+ if (BE (*err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return NULL;
+ }
+ }
+ }
+ context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags);
+ next_state = re_acquire_state_context (err, dfa, &next_nodes, context);
+ /* We don't need to check errors here, since the return value of
+ this function is next_state and ERR is already set. */
+
+ re_node_set_free (&next_nodes);
+ re_string_skip_bytes (&mctx->input, 1);
+ return next_state;
+}
+#endif
+
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t
+internal_function
+transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int i;
+
+ for (i = 0; i < pstate->nodes.nelem; ++i)
+ {
+ re_node_set dest_nodes, *new_nodes;
+ int cur_node_idx = pstate->nodes.elems[i];
+ int naccepted, dest_idx;
+ unsigned int context;
+ re_dfastate_t *dest_state;
+
+ if (!dfa->nodes[cur_node_idx].accept_mb)
+ continue;
+
+ if (dfa->nodes[cur_node_idx].constraint)
+ {
+ context = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input),
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint,
+ context))
+ continue;
+ }
+
+ /* How many bytes the node can accept? */
+ naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input,
+ re_string_cur_idx (&mctx->input));
+ if (naccepted == 0)
+ continue;
+
+ /* The node can accepts `naccepted' bytes. */
+ dest_idx = re_string_cur_idx (&mctx->input) + naccepted;
+ mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted
+ : mctx->max_mb_elem_len);
+ err = clean_state_log_if_needed (mctx, dest_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+#ifdef DEBUG
+ assert (dfa->nexts[cur_node_idx] != -1);
+#endif
+ new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx];
+
+ dest_state = mctx->state_log[dest_idx];
+ if (dest_state == NULL)
+ dest_nodes = *new_nodes;
+ else
+ {
+ err = re_node_set_init_union (&dest_nodes,
+ dest_state->entrance_nodes, new_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ context = re_string_context_at (&mctx->input, dest_idx - 1,
+ mctx->eflags);
+ mctx->state_log[dest_idx]
+ = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+ if (dest_state != NULL)
+ re_node_set_free (&dest_nodes);
+ if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0))
+ return err;
+ }
+ return REG_NOERROR;
+}
+#endif /* RE_ENABLE_I18N */
+
+static reg_errcode_t
+internal_function
+transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int i;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+ for (i = 0; i < nodes->nelem; ++i)
+ {
+ int dest_str_idx, prev_nelem, bkc_idx;
+ int node_idx = nodes->elems[i];
+ unsigned int context;
+ const re_token_t *node = dfa->nodes + node_idx;
+ re_node_set *new_dest_nodes;
+
+ /* Check whether `node' is a backreference or not. */
+ if (node->type != OP_BACK_REF)
+ continue;
+
+ if (node->constraint)
+ {
+ context = re_string_context_at (&mctx->input, cur_str_idx,
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+ continue;
+ }
+
+ /* `node' is a backreference.
+ Check the substring which the substring matched. */
+ bkc_idx = mctx->nbkref_ents;
+ err = get_subexp (mctx, node_idx, cur_str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* And add the epsilon closures (which is `new_dest_nodes') of
+ the backreference to appropriate state_log. */
+#ifdef DEBUG
+ assert (dfa->nexts[node_idx] != -1);
+#endif
+ for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx)
+ {
+ int subexp_len;
+ re_dfastate_t *dest_state;
+ struct re_backref_cache_entry *bkref_ent;
+ bkref_ent = mctx->bkref_ents + bkc_idx;
+ if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx)
+ continue;
+ subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from;
+ new_dest_nodes = (subexp_len == 0
+ ? dfa->eclosures + dfa->edests[node_idx].elems[0]
+ : dfa->eclosures + dfa->nexts[node_idx]);
+ dest_str_idx = (cur_str_idx + bkref_ent->subexp_to
+ - bkref_ent->subexp_from);
+ context = re_string_context_at (&mctx->input, dest_str_idx - 1,
+ mctx->eflags);
+ dest_state = mctx->state_log[dest_str_idx];
+ prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0
+ : mctx->state_log[cur_str_idx]->nodes.nelem);
+ /* Add `new_dest_node' to state_log. */
+ if (dest_state == NULL)
+ {
+ mctx->state_log[dest_str_idx]
+ = re_acquire_state_context (&err, dfa, new_dest_nodes,
+ context);
+ if (BE (mctx->state_log[dest_str_idx] == NULL
+ && err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ else
+ {
+ re_node_set dest_nodes;
+ err = re_node_set_init_union (&dest_nodes,
+ dest_state->entrance_nodes,
+ new_dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&dest_nodes);
+ goto free_return;
+ }
+ mctx->state_log[dest_str_idx]
+ = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+ re_node_set_free (&dest_nodes);
+ if (BE (mctx->state_log[dest_str_idx] == NULL
+ && err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ /* We need to check recursively if the backreference can epsilon
+ transit. */
+ if (subexp_len == 0
+ && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem)
+ {
+ err = check_subexp_matching_top (mctx, new_dest_nodes,
+ cur_str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ err = transit_state_bkref (mctx, new_dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ }
+ }
+ err = REG_NOERROR;
+ free_return:
+ return err;
+}
+
+/* Enumerate all the candidates which the backreference BKREF_NODE can match
+ at BKREF_STR_IDX, and register them by match_ctx_add_entry().
+ Note that we might collect inappropriate candidates here.
+ However, the cost of checking them strictly here is too high, then we
+ delay these checking for prune_impossible_nodes(). */
+
+static reg_errcode_t
+internal_function
+get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int subexp_num, sub_top_idx;
+ const char *buf = (const char *) re_string_get_buffer (&mctx->input);
+ /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */
+ int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx);
+ if (cache_idx != -1)
+ {
+ const struct re_backref_cache_entry *entry
+ = mctx->bkref_ents + cache_idx;
+ do
+ if (entry->node == bkref_node)
+ return REG_NOERROR; /* We already checked it. */
+ while (entry++->more);
+ }
+
+ subexp_num = dfa->nodes[bkref_node].opr.idx;
+
+ /* For each sub expression */
+ for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx)
+ {
+ reg_errcode_t err;
+ re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx];
+ re_sub_match_last_t *sub_last;
+ int sub_last_idx, sl_str, bkref_str_off;
+
+ if (dfa->nodes[sub_top->node].opr.idx != subexp_num)
+ continue; /* It isn't related. */
+
+ sl_str = sub_top->str_idx;
+ bkref_str_off = bkref_str_idx;
+ /* At first, check the last node of sub expressions we already
+ evaluated. */
+ for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx)
+ {
+ int sl_str_diff;
+ sub_last = sub_top->lasts[sub_last_idx];
+ sl_str_diff = sub_last->str_idx - sl_str;
+ /* The matched string by the sub expression match with the substring
+ at the back reference? */
+ if (sl_str_diff > 0)
+ {
+ if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0))
+ {
+ /* Not enough chars for a successful match. */
+ if (bkref_str_off + sl_str_diff > mctx->input.len)
+ break;
+
+ err = clean_state_log_if_needed (mctx,
+ bkref_str_off
+ + sl_str_diff);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+ }
+ if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0)
+ /* We don't need to search this sub expression any more. */
+ break;
+ }
+ bkref_str_off += sl_str_diff;
+ sl_str += sl_str_diff;
+ err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+ bkref_str_idx);
+
+ /* Reload buf, since the preceding call might have reallocated
+ the buffer. */
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+
+ if (err == REG_NOMATCH)
+ continue;
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (sub_last_idx < sub_top->nlasts)
+ continue;
+ if (sub_last_idx > 0)
+ ++sl_str;
+ /* Then, search for the other last nodes of the sub expression. */
+ for (; sl_str <= bkref_str_idx; ++sl_str)
+ {
+ int cls_node, sl_str_off;
+ const re_node_set *nodes;
+ sl_str_off = sl_str - sub_top->str_idx;
+ /* The matched string by the sub expression match with the substring
+ at the back reference? */
+ if (sl_str_off > 0)
+ {
+ if (BE (bkref_str_off >= mctx->input.valid_len, 0))
+ {
+ /* If we are at the end of the input, we cannot match. */
+ if (bkref_str_off >= mctx->input.len)
+ break;
+
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+ }
+ if (buf [bkref_str_off++] != buf[sl_str - 1])
+ break; /* We don't need to search this sub expression
+ any more. */
+ }
+ if (mctx->state_log[sl_str] == NULL)
+ continue;
+ /* Does this state have a ')' of the sub expression? */
+ nodes = &mctx->state_log[sl_str]->nodes;
+ cls_node = find_subexp_node (dfa, nodes, subexp_num,
+ OP_CLOSE_SUBEXP);
+ if (cls_node == -1)
+ continue; /* No. */
+ if (sub_top->path == NULL)
+ {
+ sub_top->path = calloc (sizeof (state_array_t),
+ sl_str - sub_top->str_idx + 1);
+ if (sub_top->path == NULL)
+ return REG_ESPACE;
+ }
+ /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node
+ in the current context? */
+ err = check_arrival (mctx, sub_top->path, sub_top->node,
+ sub_top->str_idx, cls_node, sl_str,
+ OP_CLOSE_SUBEXP);
+ if (err == REG_NOMATCH)
+ continue;
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str);
+ if (BE (sub_last == NULL, 0))
+ return REG_ESPACE;
+ err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+ bkref_str_idx);
+ if (err == REG_NOMATCH)
+ continue;
+ }
+ }
+ return REG_NOERROR;
+}
+
+/* Helper functions for get_subexp(). */
+
+/* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR.
+ If it can arrive, register the sub expression expressed with SUB_TOP
+ and SUB_LAST. */
+
+static reg_errcode_t
+internal_function
+get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top,
+ re_sub_match_last_t *sub_last, int bkref_node, int bkref_str)
+{
+ reg_errcode_t err;
+ int to_idx;
+ /* Can the subexpression arrive the back reference? */
+ err = check_arrival (mctx, &sub_last->path, sub_last->node,
+ sub_last->str_idx, bkref_node, bkref_str,
+ OP_OPEN_SUBEXP);
+ if (err != REG_NOERROR)
+ return err;
+ err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx,
+ sub_last->str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx;
+ return clean_state_log_if_needed (mctx, to_idx);
+}
+
+/* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX.
+ Search '(' if FL_OPEN, or search ')' otherwise.
+ TODO: This function isn't efficient...
+ Because there might be more than one nodes whose types are
+ OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+ nodes.
+ E.g. RE: (a){2} */
+
+static int
+internal_function
+find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+ int subexp_idx, int type)
+{
+ int cls_idx;
+ for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx)
+ {
+ int cls_node = nodes->elems[cls_idx];
+ const re_token_t *node = dfa->nodes + cls_node;
+ if (node->type == type
+ && node->opr.idx == subexp_idx)
+ return cls_node;
+ }
+ return -1;
+}
+
+/* Check whether the node TOP_NODE at TOP_STR can arrive to the node
+ LAST_NODE at LAST_STR. We record the path onto PATH since it will be
+ heavily reused.
+ Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */
+
+static reg_errcode_t
+internal_function
+check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node,
+ int top_str, int last_node, int last_str, int type)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err = REG_NOERROR;
+ int subexp_num, backup_cur_idx, str_idx, null_cnt;
+ re_dfastate_t *cur_state = NULL;
+ re_node_set *cur_nodes, next_nodes;
+ re_dfastate_t **backup_state_log;
+ unsigned int context;
+
+ subexp_num = dfa->nodes[top_node].opr.idx;
+ /* Extend the buffer if we need. */
+ if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0))
+ {
+ re_dfastate_t **new_array;
+ int old_alloc = path->alloc;
+ path->alloc += last_str + mctx->max_mb_elem_len + 1;
+ new_array = re_realloc (path->array, re_dfastate_t *, path->alloc);
+ if (BE (new_array == NULL, 0))
+ {
+ path->alloc = old_alloc;
+ return REG_ESPACE;
+ }
+ path->array = new_array;
+ memset (new_array + old_alloc, '\0',
+ sizeof (re_dfastate_t *) * (path->alloc - old_alloc));
+ }
+
+ str_idx = path->next_idx ? path->next_idx : top_str;
+
+ /* Temporary modify MCTX. */
+ backup_state_log = mctx->state_log;
+ backup_cur_idx = mctx->input.cur_idx;
+ mctx->state_log = path->array;
+ mctx->input.cur_idx = str_idx;
+
+ /* Setup initial node set. */
+ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+ if (str_idx == top_str)
+ {
+ err = re_node_set_init_1 (&next_nodes, top_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ else
+ {
+ cur_state = mctx->state_log[str_idx];
+ if (cur_state && cur_state->has_backref)
+ {
+ err = re_node_set_init_copy (&next_nodes, &cur_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ re_node_set_init_empty (&next_nodes);
+ }
+ if (str_idx == top_str || (cur_state && cur_state->has_backref))
+ {
+ if (next_nodes.nelem)
+ {
+ err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+ subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+ if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ mctx->state_log[str_idx] = cur_state;
+ }
+
+ for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;)
+ {
+ re_node_set_empty (&next_nodes);
+ if (mctx->state_log[str_idx + 1])
+ {
+ err = re_node_set_merge (&next_nodes,
+ &mctx->state_log[str_idx + 1]->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ if (cur_state)
+ {
+ err = check_arrival_add_next_nodes (mctx, str_idx,
+ &cur_state->non_eps_nodes,
+ &next_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ ++str_idx;
+ if (next_nodes.nelem)
+ {
+ err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+ subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+ cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+ if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ mctx->state_log[str_idx] = cur_state;
+ null_cnt = cur_state == NULL ? null_cnt + 1 : 0;
+ }
+ re_node_set_free (&next_nodes);
+ cur_nodes = (mctx->state_log[last_str] == NULL ? NULL
+ : &mctx->state_log[last_str]->nodes);
+ path->next_idx = str_idx;
+
+ /* Fix MCTX. */
+ mctx->state_log = backup_state_log;
+ mctx->input.cur_idx = backup_cur_idx;
+
+ /* Then check the current node set has the node LAST_NODE. */
+ if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node))
+ return REG_NOERROR;
+
+ return REG_NOMATCH;
+}
+
+/* Helper functions for check_arrival. */
+
+/* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them
+ to NEXT_NODES.
+ TODO: This function is similar to the functions transit_state*(),
+ however this function has many additional works.
+ Can't we unify them? */
+
+static reg_errcode_t
+internal_function
+check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx,
+ re_node_set *cur_nodes, re_node_set *next_nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int result;
+ int cur_idx;
+#ifdef RE_ENABLE_I18N
+ reg_errcode_t err = REG_NOERROR;
+#endif
+ re_node_set union_set;
+ re_node_set_init_empty (&union_set);
+ for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx)
+ {
+ int naccepted = 0;
+ int cur_node = cur_nodes->elems[cur_idx];
+#ifdef DEBUG
+ re_token_type_t type = dfa->nodes[cur_node].type;
+ assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+ /* If the node may accept `multi byte'. */
+ if (dfa->nodes[cur_node].accept_mb)
+ {
+ naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input,
+ str_idx);
+ if (naccepted > 1)
+ {
+ re_dfastate_t *dest_state;
+ int next_node = dfa->nexts[cur_node];
+ int next_idx = str_idx + naccepted;
+ dest_state = mctx->state_log[next_idx];
+ re_node_set_empty (&union_set);
+ if (dest_state)
+ {
+ err = re_node_set_merge (&union_set, &dest_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&union_set);
+ return err;
+ }
+ }
+ result = re_node_set_insert (&union_set, next_node);
+ if (BE (result < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ return REG_ESPACE;
+ }
+ mctx->state_log[next_idx] = re_acquire_state (&err, dfa,
+ &union_set);
+ if (BE (mctx->state_log[next_idx] == NULL
+ && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&union_set);
+ return err;
+ }
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ if (naccepted
+ || check_node_accept (mctx, dfa->nodes + cur_node, str_idx))
+ {
+ result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]);
+ if (BE (result < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ return REG_ESPACE;
+ }
+ }
+ }
+ re_node_set_free (&union_set);
+ return REG_NOERROR;
+}
+
+/* For all the nodes in CUR_NODES, add the epsilon closures of them to
+ CUR_NODES, however exclude the nodes which are:
+ - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN.
+ - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN.
+*/
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes,
+ int ex_subexp, int type)
+{
+ reg_errcode_t err;
+ int idx, outside_node;
+ re_node_set new_nodes;
+#ifdef DEBUG
+ assert (cur_nodes->nelem);
+#endif
+ err = re_node_set_alloc (&new_nodes, cur_nodes->nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ /* Create a new node set NEW_NODES with the nodes which are epsilon
+ closures of the node in CUR_NODES. */
+
+ for (idx = 0; idx < cur_nodes->nelem; ++idx)
+ {
+ int cur_node = cur_nodes->elems[idx];
+ const re_node_set *eclosure = dfa->eclosures + cur_node;
+ outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type);
+ if (outside_node == -1)
+ {
+ /* There are no problematic nodes, just merge them. */
+ err = re_node_set_merge (&new_nodes, eclosure);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&new_nodes);
+ return err;
+ }
+ }
+ else
+ {
+ /* There are problematic nodes, re-calculate incrementally. */
+ err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node,
+ ex_subexp, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&new_nodes);
+ return err;
+ }
+ }
+ }
+ re_node_set_free (cur_nodes);
+ *cur_nodes = new_nodes;
+ return REG_NOERROR;
+}
+
+/* Helper function for check_arrival_expand_ecl.
+ Check incrementally the epsilon closure of TARGET, and if it isn't
+ problematic append it to DST_NODES. */
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes,
+ int target, int ex_subexp, int type)
+{
+ int cur_node;
+ for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);)
+ {
+ int err;
+
+ if (dfa->nodes[cur_node].type == type
+ && dfa->nodes[cur_node].opr.idx == ex_subexp)
+ {
+ if (type == OP_CLOSE_SUBEXP)
+ {
+ err = re_node_set_insert (dst_nodes, cur_node);
+ if (BE (err == -1, 0))
+ return REG_ESPACE;
+ }
+ break;
+ }
+ err = re_node_set_insert (dst_nodes, cur_node);
+ if (BE (err == -1, 0))
+ return REG_ESPACE;
+ if (dfa->edests[cur_node].nelem == 0)
+ break;
+ if (dfa->edests[cur_node].nelem == 2)
+ {
+ err = check_arrival_expand_ecl_sub (dfa, dst_nodes,
+ dfa->edests[cur_node].elems[1],
+ ex_subexp, type);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ cur_node = dfa->edests[cur_node].elems[0];
+ }
+ return REG_NOERROR;
+}
+
+
+/* For all the back references in the current state, calculate the
+ destination of the back references by the appropriate entry
+ in MCTX->BKREF_ENTS. */
+
+static reg_errcode_t
+internal_function
+expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes,
+ int cur_str, int subexp_num, int type)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int cache_idx_start = search_cur_bkref_entry (mctx, cur_str);
+ struct re_backref_cache_entry *ent;
+
+ if (cache_idx_start == -1)
+ return REG_NOERROR;
+
+ restart:
+ ent = mctx->bkref_ents + cache_idx_start;
+ do
+ {
+ int to_idx, next_node;
+
+ /* Is this entry ENT is appropriate? */
+ if (!re_node_set_contains (cur_nodes, ent->node))
+ continue; /* No. */
+
+ to_idx = cur_str + ent->subexp_to - ent->subexp_from;
+ /* Calculate the destination of the back reference, and append it
+ to MCTX->STATE_LOG. */
+ if (to_idx == cur_str)
+ {
+ /* The backreference did epsilon transit, we must re-check all the
+ node in the current state. */
+ re_node_set new_dests;
+ reg_errcode_t err2, err3;
+ next_node = dfa->edests[ent->node].elems[0];
+ if (re_node_set_contains (cur_nodes, next_node))
+ continue;
+ err = re_node_set_init_1 (&new_dests, next_node);
+ err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type);
+ err3 = re_node_set_merge (cur_nodes, &new_dests);
+ re_node_set_free (&new_dests);
+ if (BE (err != REG_NOERROR || err2 != REG_NOERROR
+ || err3 != REG_NOERROR, 0))
+ {
+ err = (err != REG_NOERROR ? err
+ : (err2 != REG_NOERROR ? err2 : err3));
+ return err;
+ }
+ /* TODO: It is still inefficient... */
+ goto restart;
+ }
+ else
+ {
+ re_node_set union_set;
+ next_node = dfa->nexts[ent->node];
+ if (mctx->state_log[to_idx])
+ {
+ int ret;
+ if (re_node_set_contains (&mctx->state_log[to_idx]->nodes,
+ next_node))
+ continue;
+ err = re_node_set_init_copy (&union_set,
+ &mctx->state_log[to_idx]->nodes);
+ ret = re_node_set_insert (&union_set, next_node);
+ if (BE (err != REG_NOERROR || ret < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ err = err != REG_NOERROR ? err : REG_ESPACE;
+ return err;
+ }
+ }
+ else
+ {
+ err = re_node_set_init_1 (&union_set, next_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set);
+ re_node_set_free (&union_set);
+ if (BE (mctx->state_log[to_idx] == NULL
+ && err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ while (ent++->more);
+ return REG_NOERROR;
+}
+
+/* Build transition table for the state.
+ Return 1 if succeeded, otherwise return NULL. */
+
+static int
+internal_function
+build_trtable (const re_dfa_t *dfa, re_dfastate_t *state)
+{
+ reg_errcode_t err;
+ int i, j, ch, need_word_trtable = 0;
+ bitset_word_t elem, mask;
+ bool dests_node_malloced = false;
+ bool dest_states_malloced = false;
+ int ndests; /* Number of the destination states from `state'. */
+ re_dfastate_t **trtable;
+ re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl;
+ re_node_set follows, *dests_node;
+ bitset_t *dests_ch;
+ bitset_t acceptable;
+
+ struct dests_alloc
+ {
+ re_node_set dests_node[SBC_MAX];
+ bitset_t dests_ch[SBC_MAX];
+ } *dests_alloc;
+
+ /* We build DFA states which corresponds to the destination nodes
+ from `state'. `dests_node[i]' represents the nodes which i-th
+ destination state contains, and `dests_ch[i]' represents the
+ characters which i-th destination state accepts. */
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca (sizeof (struct dests_alloc)))
+ dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc));
+ else
+#endif
+ {
+ dests_alloc = re_malloc (struct dests_alloc, 1);
+ if (BE (dests_alloc == NULL, 0))
+ return 0;
+ dests_node_malloced = true;
+ }
+ dests_node = dests_alloc->dests_node;
+ dests_ch = dests_alloc->dests_ch;
+
+ /* Initialize transiton table. */
+ state->word_trtable = state->trtable = NULL;
+
+ /* At first, group all nodes belonging to `state' into several
+ destinations. */
+ ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch);
+ if (BE (ndests <= 0, 0))
+ {
+ if (dests_node_malloced)
+ free (dests_alloc);
+ /* Return 0 in case of an error, 1 otherwise. */
+ if (ndests == 0)
+ {
+ state->trtable = (re_dfastate_t **)
+ calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ return 1;
+ }
+ return 0;
+ }
+
+ err = re_node_set_alloc (&follows, ndests + 1);
+ if (BE (err != REG_NOERROR, 0))
+ goto out_free;
+
+ /* Avoid arithmetic overflow in size calculation. */
+ if (BE ((((SIZE_MAX - (sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX)
+ / (3 * sizeof (re_dfastate_t *)))
+ < ndests),
+ 0))
+ goto out_free;
+
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX
+ + ndests * 3 * sizeof (re_dfastate_t *)))
+ dest_states = (re_dfastate_t **)
+ alloca (ndests * 3 * sizeof (re_dfastate_t *));
+ else
+#endif
+ {
+ dest_states = (re_dfastate_t **)
+ malloc (ndests * 3 * sizeof (re_dfastate_t *));
+ if (BE (dest_states == NULL, 0))
+ {
+out_free:
+ if (dest_states_malloced)
+ free (dest_states);
+ re_node_set_free (&follows);
+ for (i = 0; i < ndests; ++i)
+ re_node_set_free (dests_node + i);
+ if (dests_node_malloced)
+ free (dests_alloc);
+ return 0;
+ }
+ dest_states_malloced = true;
+ }
+ dest_states_word = dest_states + ndests;
+ dest_states_nl = dest_states_word + ndests;
+ bitset_empty (acceptable);
+
+ /* Then build the states for all destinations. */
+ for (i = 0; i < ndests; ++i)
+ {
+ int next_node;
+ re_node_set_empty (&follows);
+ /* Merge the follows of this destination states. */
+ for (j = 0; j < dests_node[i].nelem; ++j)
+ {
+ next_node = dfa->nexts[dests_node[i].elems[j]];
+ if (next_node != -1)
+ {
+ err = re_node_set_merge (&follows, dfa->eclosures + next_node);
+ if (BE (err != REG_NOERROR, 0))
+ goto out_free;
+ }
+ }
+ dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0);
+ if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+ /* If the new state has context constraint,
+ build appropriate states for these contexts. */
+ if (dest_states[i]->has_constraint)
+ {
+ dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows,
+ CONTEXT_WORD);
+ if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+
+ if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1)
+ need_word_trtable = 1;
+
+ dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows,
+ CONTEXT_NEWLINE);
+ if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+ }
+ else
+ {
+ dest_states_word[i] = dest_states[i];
+ dest_states_nl[i] = dest_states[i];
+ }
+ bitset_merge (acceptable, dests_ch[i]);
+ }
+
+ if (!BE (need_word_trtable, 0))
+ {
+ /* We don't care about whether the following character is a word
+ character, or we are in a single-byte character set so we can
+ discern by looking at the character code: allocate a
+ 256-entry transition table. */
+ trtable = state->trtable =
+ (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ if (BE (trtable == NULL, 0))
+ goto out_free;
+
+ /* For all characters ch...: */
+ for (i = 0; i < BITSET_WORDS; ++i)
+ for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+ elem;
+ mask <<= 1, elem >>= 1, ++ch)
+ if (BE (elem & 1, 0))
+ {
+ /* There must be exactly one destination which accepts
+ character ch. See group_nodes_into_DFAstates. */
+ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+ ;
+
+ /* j-th destination accepts the word character ch. */
+ if (dfa->word_char[i] & mask)
+ trtable[ch] = dest_states_word[j];
+ else
+ trtable[ch] = dest_states[j];
+ }
+ }
+ else
+ {
+ /* We care about whether the following character is a word
+ character, and we are in a multi-byte character set: discern
+ by looking at the character code: build two 256-entry
+ transition tables, one starting at trtable[0] and one
+ starting at trtable[SBC_MAX]. */
+ trtable = state->word_trtable =
+ (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX);
+ if (BE (trtable == NULL, 0))
+ goto out_free;
+
+ /* For all characters ch...: */
+ for (i = 0; i < BITSET_WORDS; ++i)
+ for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+ elem;
+ mask <<= 1, elem >>= 1, ++ch)
+ if (BE (elem & 1, 0))
+ {
+ /* There must be exactly one destination which accepts
+ character ch. See group_nodes_into_DFAstates. */
+ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+ ;
+
+ /* j-th destination accepts the word character ch. */
+ trtable[ch] = dest_states[j];
+ trtable[ch + SBC_MAX] = dest_states_word[j];
+ }
+ }
+
+ /* new line */
+ if (bitset_contain (acceptable, NEWLINE_CHAR))
+ {
+ /* The current state accepts newline character. */
+ for (j = 0; j < ndests; ++j)
+ if (bitset_contain (dests_ch[j], NEWLINE_CHAR))
+ {
+ /* k-th destination accepts newline character. */
+ trtable[NEWLINE_CHAR] = dest_states_nl[j];
+ if (need_word_trtable)
+ trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j];
+ /* There must be only one destination which accepts
+ newline. See group_nodes_into_DFAstates. */
+ break;
+ }
+ }
+
+ if (dest_states_malloced)
+ free (dest_states);
+
+ re_node_set_free (&follows);
+ for (i = 0; i < ndests; ++i)
+ re_node_set_free (dests_node + i);
+
+ if (dests_node_malloced)
+ free (dests_alloc);
+
+ return 1;
+}
+
+/* Group all nodes belonging to STATE into several destinations.
+ Then for all destinations, set the nodes belonging to the destination
+ to DESTS_NODE[i] and set the characters accepted by the destination
+ to DEST_CH[i]. This function return the number of destinations. */
+
+static int
+internal_function
+group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state,
+ re_node_set *dests_node, bitset_t *dests_ch)
+{
+ reg_errcode_t err;
+ int result;
+ int i, j, k;
+ int ndests; /* Number of the destinations from `state'. */
+ bitset_t accepts; /* Characters a node can accept. */
+ const re_node_set *cur_nodes = &state->nodes;
+ bitset_empty (accepts);
+ ndests = 0;
+
+ /* For all the nodes belonging to `state', */
+ for (i = 0; i < cur_nodes->nelem; ++i)
+ {
+ re_token_t *node = &dfa->nodes[cur_nodes->elems[i]];
+ re_token_type_t type = node->type;
+ unsigned int constraint = node->constraint;
+
+ /* Enumerate all single byte character this node can accept. */
+ if (type == CHARACTER)
+ bitset_set (accepts, node->opr.c);
+ else if (type == SIMPLE_BRACKET)
+ {
+ bitset_merge (accepts, node->opr.sbcset);
+ }
+ else if (type == OP_PERIOD)
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ bitset_merge (accepts, dfa->sb_char);
+ else
+#endif
+ bitset_set_all (accepts);
+ if (!(dfa->syntax & RE_DOT_NEWLINE))
+ bitset_clear (accepts, '\n');
+ if (dfa->syntax & RE_DOT_NOT_NULL)
+ bitset_clear (accepts, '\0');
+ }
+#ifdef RE_ENABLE_I18N
+ else if (type == OP_UTF8_PERIOD)
+ {
+ memset (accepts, '\xff', sizeof (bitset_t) / 2);
+ if (!(dfa->syntax & RE_DOT_NEWLINE))
+ bitset_clear (accepts, '\n');
+ if (dfa->syntax & RE_DOT_NOT_NULL)
+ bitset_clear (accepts, '\0');
+ }
+#endif
+ else
+ continue;
+
+ /* Check the `accepts' and sift the characters which are not
+ match it the context. */
+ if (constraint)
+ {
+ if (constraint & NEXT_NEWLINE_CONSTRAINT)
+ {
+ bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR);
+ bitset_empty (accepts);
+ if (accepts_newline)
+ bitset_set (accepts, NEWLINE_CHAR);
+ else
+ continue;
+ }
+ if (constraint & NEXT_ENDBUF_CONSTRAINT)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+
+ if (constraint & NEXT_WORD_CONSTRAINT)
+ {
+ bitset_word_t any_set = 0;
+ if (type == CHARACTER && !node->word_char)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j]));
+ else
+#endif
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= dfa->word_char[j]);
+ if (!any_set)
+ continue;
+ }
+ if (constraint & NEXT_NOTWORD_CONSTRAINT)
+ {
+ bitset_word_t any_set = 0;
+ if (type == CHARACTER && node->word_char)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j]));
+ else
+#endif
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= ~dfa->word_char[j]);
+ if (!any_set)
+ continue;
+ }
+ }
+
+ /* Then divide `accepts' into DFA states, or create a new
+ state. Above, we make sure that accepts is not empty. */
+ for (j = 0; j < ndests; ++j)
+ {
+ bitset_t intersec; /* Intersection sets, see below. */
+ bitset_t remains;
+ /* Flags, see below. */
+ bitset_word_t has_intersec, not_subset, not_consumed;
+
+ /* Optimization, skip if this state doesn't accept the character. */
+ if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c))
+ continue;
+
+ /* Enumerate the intersection set of this state and `accepts'. */
+ has_intersec = 0;
+ for (k = 0; k < BITSET_WORDS; ++k)
+ has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k];
+ /* And skip if the intersection set is empty. */
+ if (!has_intersec)
+ continue;
+
+ /* Then check if this state is a subset of `accepts'. */
+ not_subset = not_consumed = 0;
+ for (k = 0; k < BITSET_WORDS; ++k)
+ {
+ not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k];
+ not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k];
+ }
+
+ /* If this state isn't a subset of `accepts', create a
+ new group state, which has the `remains'. */
+ if (not_subset)
+ {
+ bitset_copy (dests_ch[ndests], remains);
+ bitset_copy (dests_ch[j], intersec);
+ err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]);
+ if (BE (err != REG_NOERROR, 0))
+ goto error_return;
+ ++ndests;
+ }
+
+ /* Put the position in the current group. */
+ result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]);
+ if (BE (result < 0, 0))
+ goto error_return;
+
+ /* If all characters are consumed, go to next node. */
+ if (!not_consumed)
+ break;
+ }
+ /* Some characters remain, create a new group. */
+ if (j == ndests)
+ {
+ bitset_copy (dests_ch[ndests], accepts);
+ err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]);
+ if (BE (err != REG_NOERROR, 0))
+ goto error_return;
+ ++ndests;
+ bitset_empty (accepts);
+ }
+ }
+ return ndests;
+ error_return:
+ for (j = 0; j < ndests; ++j)
+ re_node_set_free (dests_node + j);
+ return -1;
+}
+
+#ifdef RE_ENABLE_I18N
+/* Check how many bytes the node `dfa->nodes[node_idx]' accepts.
+ Return the number of the bytes the node accepts.
+ STR_IDX is the current index of the input string.
+
+ This function handles the nodes which can accept one character, or
+ one collating element like '.', '[a-z]', opposite to the other nodes
+ can only accept one byte. */
+
+static int
+internal_function
+check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+ const re_string_t *input, int str_idx)
+{
+ const re_token_t *node = dfa->nodes + node_idx;
+ int char_len, elem_len;
+ int i;
+ wint_t wc;
+
+ if (BE (node->type == OP_UTF8_PERIOD, 0))
+ {
+ unsigned char c = re_string_byte_at (input, str_idx), d;
+ if (BE (c < 0xc2, 1))
+ return 0;
+
+ if (str_idx + 2 > input->len)
+ return 0;
+
+ d = re_string_byte_at (input, str_idx + 1);
+ if (c < 0xe0)
+ return (d < 0x80 || d > 0xbf) ? 0 : 2;
+ else if (c < 0xf0)
+ {
+ char_len = 3;
+ if (c == 0xe0 && d < 0xa0)
+ return 0;
+ }
+ else if (c < 0xf8)
+ {
+ char_len = 4;
+ if (c == 0xf0 && d < 0x90)
+ return 0;
+ }
+ else if (c < 0xfc)
+ {
+ char_len = 5;
+ if (c == 0xf8 && d < 0x88)
+ return 0;
+ }
+ else if (c < 0xfe)
+ {
+ char_len = 6;
+ if (c == 0xfc && d < 0x84)
+ return 0;
+ }
+ else
+ return 0;
+
+ if (str_idx + char_len > input->len)
+ return 0;
+
+ for (i = 1; i < char_len; ++i)
+ {
+ d = re_string_byte_at (input, str_idx + i);
+ if (d < 0x80 || d > 0xbf)
+ return 0;
+ }
+ return char_len;
+ }
+
+ char_len = re_string_char_size_at (input, str_idx);
+ if (node->type == OP_PERIOD)
+ {
+ if (char_len <= 1)
+ return 0;
+ /* FIXME: I don't think this if is needed, as both '\n'
+ and '\0' are char_len == 1. */
+ /* '.' accepts any one character except the following two cases. */
+ if ((!(dfa->syntax & RE_DOT_NEWLINE) &&
+ re_string_byte_at (input, str_idx) == '\n') ||
+ ((dfa->syntax & RE_DOT_NOT_NULL) &&
+ re_string_byte_at (input, str_idx) == '\0'))
+ return 0;
+ return char_len;
+ }
+
+ elem_len = re_string_elem_size_at (input, str_idx);
+ wc = __btowc(*(input->mbs+str_idx));
+ if (((elem_len <= 1 && char_len <= 1) || char_len == 0) && (wc != WEOF && wc < SBC_MAX))
+ return 0;
+
+ if (node->type == COMPLEX_BRACKET)
+ {
+ const re_charset_t *cset = node->opr.mbcset;
+# ifdef _LIBC
+ const unsigned char *pin
+ = ((const unsigned char *) re_string_get_buffer (input) + str_idx);
+ int j;
+ uint32_t nrules;
+# endif /* _LIBC */
+ int match_len = 0;
+ wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars)
+ ? re_string_wchar_at (input, str_idx) : 0);
+
+ /* match with multibyte character? */
+ for (i = 0; i < cset->nmbchars; ++i)
+ if (wc == cset->mbchars[i])
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ /* match with character_class? */
+ for (i = 0; i < cset->nchar_classes; ++i)
+ {
+ wctype_t wt = cset->char_classes[i];
+ if (__iswctype (wc, wt))
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+
+# ifdef _LIBC
+ nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules != 0)
+ {
+ unsigned int in_collseq = 0;
+ const int32_t *table, *indirect;
+ const unsigned char *weights, *extra;
+ const char *collseqwc;
+ /* This #include defines a local function! */
+# include <locale/weight.h>
+
+ /* match with collating_symbol? */
+ if (cset->ncoll_syms)
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+ for (i = 0; i < cset->ncoll_syms; ++i)
+ {
+ const unsigned char *coll_sym = extra + cset->coll_syms[i];
+ /* Compare the length of input collating element and
+ the length of current collating element. */
+ if (*coll_sym != elem_len)
+ continue;
+ /* Compare each bytes. */
+ for (j = 0; j < *coll_sym; j++)
+ if (pin[j] != coll_sym[1 + j])
+ break;
+ if (j == *coll_sym)
+ {
+ /* Match if every bytes is equal. */
+ match_len = j;
+ goto check_node_accept_bytes_match;
+ }
+ }
+
+ if (cset->nranges)
+ {
+ if (elem_len <= char_len)
+ {
+ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+ in_collseq = __collseq_table_lookup (collseqwc, wc);
+ }
+ else
+ in_collseq = find_collation_sequence_value (pin, elem_len);
+ }
+ /* match with range expression? */
+ for (i = 0; i < cset->nranges; ++i)
+ if (cset->range_starts[i] <= in_collseq
+ && in_collseq <= cset->range_ends[i])
+ {
+ match_len = elem_len;
+ goto check_node_accept_bytes_match;
+ }
+
+ /* match with equivalence_class? */
+ if (cset->nequiv_classes)
+ {
+ const unsigned char *cp = pin;
+ table = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ weights = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB);
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB);
+ int32_t idx = findidx (&cp);
+ if (idx > 0)
+ for (i = 0; i < cset->nequiv_classes; ++i)
+ {
+ int32_t equiv_class_idx = cset->equiv_classes[i];
+ size_t weight_len = weights[idx & 0xffffff];
+ if (weight_len == weights[equiv_class_idx & 0xffffff]
+ && (idx >> 24) == (equiv_class_idx >> 24))
+ {
+ int cnt = 0;
+
+ idx &= 0xffffff;
+ equiv_class_idx &= 0xffffff;
+
+ while (cnt <= weight_len
+ && (weights[equiv_class_idx + 1 + cnt]
+ == weights[idx + 1 + cnt]))
+ ++cnt;
+ if (cnt > weight_len)
+ {
+ match_len = elem_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+ }
+ }
+ }
+ else
+# endif /* _LIBC */
+ {
+ /* match with range expression? */
+#if __GNUC__ >= 2
+ wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'};
+#else
+ wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+ cmp_buf[2] = wc;
+#endif
+ for (i = 0; i < cset->nranges; ++i)
+ {
+ cmp_buf[0] = cset->range_starts[i];
+ cmp_buf[4] = cset->range_ends[i];
+ if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+ && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+ }
+ check_node_accept_bytes_match:
+ if (!cset->non_match)
+ return match_len;
+ else
+ {
+ if (match_len > 0)
+ return 0;
+ else
+ return (elem_len > char_len) ? elem_len : char_len;
+ }
+ }
+ return 0;
+}
+
+# ifdef _LIBC
+static unsigned int
+internal_function
+find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len)
+{
+ uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules == 0)
+ {
+ if (mbs_len == 1)
+ {
+ /* No valid character. Match it as a single byte character. */
+ const unsigned char *collseq = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+ return collseq[mbs[0]];
+ }
+ return UINT_MAX;
+ }
+ else
+ {
+ int32_t idx;
+ const unsigned char *extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+ int32_t extrasize = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra;
+
+ for (idx = 0; idx < extrasize;)
+ {
+ int mbs_cnt, found = 0;
+ int32_t elem_mbs_len;
+ /* Skip the name of collating element name. */
+ idx = idx + extra[idx] + 1;
+ elem_mbs_len = extra[idx++];
+ if (mbs_len == elem_mbs_len)
+ {
+ for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt)
+ if (extra[idx + mbs_cnt] != mbs[mbs_cnt])
+ break;
+ if (mbs_cnt == elem_mbs_len)
+ /* Found the entry. */
+ found = 1;
+ }
+ /* Skip the byte sequence of the collating element. */
+ idx += elem_mbs_len;
+ /* Adjust for the alignment. */
+ idx = (idx + 3) & ~3;
+ /* Skip the collation sequence value. */
+ idx += sizeof (uint32_t);
+ /* Skip the wide char sequence of the collating element. */
+ idx = idx + sizeof (uint32_t) * (extra[idx] + 1);
+ /* If we found the entry, return the sequence value. */
+ if (found)
+ return *(uint32_t *) (extra + idx);
+ /* Skip the collation sequence value. */
+ idx += sizeof (uint32_t);
+ }
+ return UINT_MAX;
+ }
+}
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+
+/* Check whether the node accepts the byte which is IDX-th
+ byte of the INPUT. */
+
+static int
+internal_function
+check_node_accept (const re_match_context_t *mctx, const re_token_t *node,
+ int idx)
+{
+ unsigned char ch;
+ ch = re_string_byte_at (&mctx->input, idx);
+ switch (node->type)
+ {
+ case CHARACTER:
+ if (node->opr.c != ch)
+ return 0;
+ break;
+
+ case SIMPLE_BRACKET:
+ if (!bitset_contain (node->opr.sbcset, ch))
+ return 0;
+ break;
+
+#ifdef RE_ENABLE_I18N
+ case OP_UTF8_PERIOD:
+ if (ch >= 0x80)
+ return 0;
+ /* FALLTHROUGH */
+#endif
+ case OP_PERIOD:
+ if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE))
+ || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL)))
+ return 0;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (node->constraint)
+ {
+ /* The node has constraints. Check whether the current context
+ satisfies the constraints. */
+ unsigned int context = re_string_context_at (&mctx->input, idx,
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Extend the buffers, if the buffers have run out. */
+
+static reg_errcode_t
+internal_function
+extend_buffers (re_match_context_t *mctx)
+{
+ reg_errcode_t ret;
+ re_string_t *pstr = &mctx->input;
+
+ /* Avoid overflow. */
+ if (BE (INT_MAX / 2 / sizeof (re_dfastate_t *) <= pstr->bufs_len, 0))
+ return REG_ESPACE;
+
+ /* Double the lengthes of the buffers. */
+ ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ if (mctx->state_log != NULL)
+ {
+ /* And double the length of state_log. */
+ /* XXX We have no indication of the size of this buffer. If this
+ allocation fail we have no indication that the state_log array
+ does not have the right size. */
+ re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *,
+ pstr->bufs_len + 1);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ mctx->state_log = new_array;
+ }
+
+ /* Then reconstruct the buffers. */
+ if (pstr->icase)
+ {
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ build_upper_buffer (pstr);
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ build_wcs_buffer (pstr);
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ if (pstr->trans != NULL)
+ re_string_translate_buffer (pstr);
+ }
+ }
+ return REG_NOERROR;
+}
+
+
+/* Functions for matching context. */
+
+/* Initialize MCTX. */
+
+static reg_errcode_t
+internal_function
+match_ctx_init (re_match_context_t *mctx, int eflags, int n)
+{
+ mctx->eflags = eflags;
+ mctx->match_last = -1;
+ if (n > 0)
+ {
+ mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n);
+ mctx->sub_tops = re_malloc (re_sub_match_top_t *, n);
+ if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0))
+ return REG_ESPACE;
+ }
+ /* Already zero-ed by the caller.
+ else
+ mctx->bkref_ents = NULL;
+ mctx->nbkref_ents = 0;
+ mctx->nsub_tops = 0; */
+ mctx->abkref_ents = n;
+ mctx->max_mb_elem_len = 1;
+ mctx->asub_tops = n;
+ return REG_NOERROR;
+}
+
+/* Clean the entries which depend on the current input in MCTX.
+ This function must be invoked when the matcher changes the start index
+ of the input, or changes the input string. */
+
+static void
+internal_function
+match_ctx_clean (re_match_context_t *mctx)
+{
+ int st_idx;
+ for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx)
+ {
+ int sl_idx;
+ re_sub_match_top_t *top = mctx->sub_tops[st_idx];
+ for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx)
+ {
+ re_sub_match_last_t *last = top->lasts[sl_idx];
+ re_free (last->path.array);
+ re_free (last);
+ }
+ re_free (top->lasts);
+ if (top->path)
+ {
+ re_free (top->path->array);
+ re_free (top->path);
+ }
+ free (top);
+ }
+
+ mctx->nsub_tops = 0;
+ mctx->nbkref_ents = 0;
+}
+
+/* Free all the memory associated with MCTX. */
+
+static void
+internal_function
+match_ctx_free (re_match_context_t *mctx)
+{
+ /* First, free all the memory associated with MCTX->SUB_TOPS. */
+ match_ctx_clean (mctx);
+ re_free (mctx->sub_tops);
+ re_free (mctx->bkref_ents);
+}
+
+/* Add a new backreference entry to MCTX.
+ Note that we assume that caller never call this function with duplicate
+ entry, and call with STR_IDX which isn't smaller than any existing entry.
+*/
+
+static reg_errcode_t
+internal_function
+match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from,
+ int to)
+{
+ if (mctx->nbkref_ents >= mctx->abkref_ents)
+ {
+ struct re_backref_cache_entry* new_entry;
+ new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry,
+ mctx->abkref_ents * 2);
+ if (BE (new_entry == NULL, 0))
+ {
+ re_free (mctx->bkref_ents);
+ return REG_ESPACE;
+ }
+ mctx->bkref_ents = new_entry;
+ memset (mctx->bkref_ents + mctx->nbkref_ents, '\0',
+ sizeof (struct re_backref_cache_entry) * mctx->abkref_ents);
+ mctx->abkref_ents *= 2;
+ }
+ if (mctx->nbkref_ents > 0
+ && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx)
+ mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1;
+
+ mctx->bkref_ents[mctx->nbkref_ents].node = node;
+ mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx;
+ mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from;
+ mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to;
+
+ /* This is a cache that saves negative results of check_dst_limits_calc_pos.
+ If bit N is clear, means that this entry won't epsilon-transition to
+ an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If
+ it is set, check_dst_limits_calc_pos_1 will recurse and try to find one
+ such node.
+
+ A backreference does not epsilon-transition unless it is empty, so set
+ to all zeros if FROM != TO. */
+ mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map
+ = (from == to ? ~0 : 0);
+
+ mctx->bkref_ents[mctx->nbkref_ents++].more = 0;
+ if (mctx->max_mb_elem_len < to - from)
+ mctx->max_mb_elem_len = to - from;
+ return REG_NOERROR;
+}
+
+/* Search for the first entry which has the same str_idx, or -1 if none is
+ found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */
+
+static int
+internal_function
+search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+{
+ int left, right, mid, last;
+ last = right = mctx->nbkref_ents;
+ for (left = 0; left < right;)
+ {
+ mid = (left + right) / 2;
+ if (mctx->bkref_ents[mid].str_idx < str_idx)
+ left = mid + 1;
+ else
+ right = mid;
+ }
+ if (left < last && mctx->bkref_ents[left].str_idx == str_idx)
+ return left;
+ else
+ return -1;
+}
+
+/* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches
+ at STR_IDX. */
+
+static reg_errcode_t
+internal_function
+match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx)
+{
+#ifdef DEBUG
+ assert (mctx->sub_tops != NULL);
+ assert (mctx->asub_tops > 0);
+#endif
+ if (BE (mctx->nsub_tops == mctx->asub_tops, 0))
+ {
+ int new_asub_tops = mctx->asub_tops * 2;
+ re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops,
+ re_sub_match_top_t *,
+ new_asub_tops);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ mctx->sub_tops = new_array;
+ mctx->asub_tops = new_asub_tops;
+ }
+ mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t));
+ if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0))
+ return REG_ESPACE;
+ mctx->sub_tops[mctx->nsub_tops]->node = node;
+ mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx;
+ return REG_NOERROR;
+}
+
+/* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches
+ at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */
+
+static re_sub_match_last_t *
+internal_function
+match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx)
+{
+ re_sub_match_last_t *new_entry;
+ if (BE (subtop->nlasts == subtop->alasts, 0))
+ {
+ int new_alasts = 2 * subtop->alasts + 1;
+ re_sub_match_last_t **new_array = re_realloc (subtop->lasts,
+ re_sub_match_last_t *,
+ new_alasts);
+ if (BE (new_array == NULL, 0))
+ return NULL;
+ subtop->lasts = new_array;
+ subtop->alasts = new_alasts;
+ }
+ new_entry = calloc (1, sizeof (re_sub_match_last_t));
+ if (BE (new_entry != NULL, 1))
+ {
+ subtop->lasts[subtop->nlasts] = new_entry;
+ new_entry->node = node;
+ new_entry->str_idx = str_idx;
+ ++subtop->nlasts;
+ }
+ return new_entry;
+}
+
+static void
+internal_function
+sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+ re_dfastate_t **limited_sts, int last_node, int last_str_idx)
+{
+ sctx->sifted_states = sifted_sts;
+ sctx->limited_states = limited_sts;
+ sctx->last_node = last_node;
+ sctx->last_str_idx = last_str_idx;
+ re_node_set_init_empty (&sctx->limits);
+}
diff --git a/compat/strtok_r.c b/compat/strtok_r.c
new file mode 100644
index 0000000..7b5d568
--- /dev/null
+++ b/compat/strtok_r.c
@@ -0,0 +1,61 @@
+/* Reentrant string tokenizer. Generic version.
+ Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#include "../git-compat-util.h"
+
+/* Parse S into tokens separated by characters in DELIM.
+ If S is NULL, the saved pointer in SAVE_PTR is used as
+ the next starting point. For example:
+ char s[] = "-abc-=-def";
+ char *sp;
+ x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
+ x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
+ x = strtok_r(NULL, "=", &sp); // x = NULL
+ // s = "abc\0-def\0"
+*/
+char *
+gitstrtok_r (char *s, const char *delim, char **save_ptr)
+{
+ char *token;
+
+ if (s == NULL)
+ s = *save_ptr;
+
+ /* Scan leading delimiters. */
+ s += strspn (s, delim);
+ if (*s == '\0')
+ {
+ *save_ptr = s;
+ return NULL;
+ }
+
+ /* Find the end of the token. */
+ token = s;
+ s = strpbrk (token, delim);
+ if (s == NULL)
+ /* This token finishes the string. */
+ *save_ptr = token + strlen (token);
+ else
+ {
+ /* Terminate the token and make *SAVE_PTR point past it. */
+ *s = '\0';
+ *save_ptr = s + 1;
+ }
+ return token;
+}
diff --git a/compat/vcbuild/include/dirent.h b/compat/vcbuild/include/dirent.h
deleted file mode 100644
index 440618d..0000000
--- a/compat/vcbuild/include/dirent.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * DIRENT.H (formerly DIRLIB.H)
- * This file has no copyright assigned and is placed in the Public Domain.
- * This file is a part of the mingw-runtime package.
- *
- * The mingw-runtime package and its code is distributed in the hope that it
- * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
- * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
- * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- *
- * You are free to use this package and its code without limitation.
- */
-#ifndef _DIRENT_H_
-#define _DIRENT_H_
-#include <io.h>
-
-#define PATH_MAX 512
-
-#define __MINGW_NOTHROW
-
-#ifndef RC_INVOKED
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct dirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- char d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- * dd_stat field is now int (was short in older versions).
- */
-typedef struct
-{
- /* disk transfer area for this dir */
- struct _finddata_t dd_dta;
-
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct dirent dd_dir;
-
- /* _findnext handle */
- long dd_handle;
-
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
-
- /* given path for dir with search pattern (struct is extended) */
- char dd_name[PATH_MAX+3];
-} DIR;
-
-DIR* __cdecl __MINGW_NOTHROW opendir (const char*);
-struct dirent* __cdecl __MINGW_NOTHROW readdir (DIR*);
-int __cdecl __MINGW_NOTHROW closedir (DIR*);
-void __cdecl __MINGW_NOTHROW rewinddir (DIR*);
-long __cdecl __MINGW_NOTHROW telldir (DIR*);
-void __cdecl __MINGW_NOTHROW seekdir (DIR*, long);
-
-
-/* wide char versions */
-
-struct _wdirent
-{
- long d_ino; /* Always zero. */
- unsigned short d_reclen; /* Always zero. */
- unsigned short d_namlen; /* Length of name in d_name. */
- wchar_t d_name[FILENAME_MAX]; /* File name. */
-};
-
-/*
- * This is an internal data structure. Good programmers will not use it
- * except as an argument to one of the functions below.
- */
-typedef struct
-{
- /* disk transfer area for this dir */
- //struct _wfinddata_t dd_dta;
-
- /* dirent struct to return from dir (NOTE: this makes this thread
- * safe as long as only one thread uses a particular DIR struct at
- * a time) */
- struct _wdirent dd_dir;
-
- /* _findnext handle */
- long dd_handle;
-
- /*
- * Status of search:
- * 0 = not started yet (next entry to read is first entry)
- * -1 = off the end
- * positive = 0 based index of next entry
- */
- int dd_stat;
-
- /* given path for dir with search pattern (struct is extended) */
- wchar_t dd_name[1];
-} _WDIR;
-
-
-
-_WDIR* __cdecl __MINGW_NOTHROW _wopendir (const wchar_t*);
-struct _wdirent* __cdecl __MINGW_NOTHROW _wreaddir (_WDIR*);
-int __cdecl __MINGW_NOTHROW _wclosedir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wrewinddir (_WDIR*);
-long __cdecl __MINGW_NOTHROW _wtelldir (_WDIR*);
-void __cdecl __MINGW_NOTHROW _wseekdir (_WDIR*, long);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* Not RC_INVOKED */
-
-#endif /* Not _DIRENT_H_ */
diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h
index 2a4f276..b14fcf9 100644
--- a/compat/vcbuild/include/unistd.h
+++ b/compat/vcbuild/include/unistd.h
@@ -45,6 +45,10 @@ typedef unsigned long long uintmax_t;
typedef int64_t off64_t;
+#define INTMAX_MIN _I64_MIN
+#define INTMAX_MAX _I64_MAX
+#define UINTMAX_MAX _UI64_MAX
+
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c
new file mode 100644
index 0000000..7a0debe
--- /dev/null
+++ b/compat/win32/dirent.c
@@ -0,0 +1,108 @@
+#include "../git-compat-util.h"
+#include "dirent.h"
+
+struct DIR {
+ struct dirent dd_dir; /* includes d_type */
+ HANDLE dd_handle; /* FindFirstFile handle */
+ int dd_stat; /* 0-based index */
+ char dd_name[1]; /* extend struct */
+};
+
+DIR *opendir(const char *name)
+{
+ DWORD attrs = GetFileAttributesA(name);
+ int len;
+ DIR *p;
+
+ /* check for valid path */
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /* check if it's a directory */
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+ errno = ENOTDIR;
+ return NULL;
+ }
+
+ /* check that the pattern won't be too long for FindFirstFileA */
+ len = strlen(name);
+ if (is_dir_sep(name[len - 1]))
+ len--;
+ if (len + 2 >= MAX_PATH) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ p = malloc(sizeof(DIR) + len + 2);
+ if (!p)
+ return NULL;
+
+ memset(p, 0, sizeof(DIR) + len + 2);
+ strcpy(p->dd_name, name);
+ p->dd_name[len] = '/';
+ p->dd_name[len+1] = '*';
+
+ p->dd_handle = INVALID_HANDLE_VALUE;
+ return p;
+}
+
+struct dirent *readdir(DIR *dir)
+{
+ WIN32_FIND_DATAA buf;
+ HANDLE handle;
+
+ if (!dir || !dir->dd_handle) {
+ errno = EBADF; /* No set_errno for mingw */
+ return NULL;
+ }
+
+ if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) {
+ DWORD lasterr;
+ handle = FindFirstFileA(dir->dd_name, &buf);
+ lasterr = GetLastError();
+ dir->dd_handle = handle;
+ if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) {
+ errno = err_win_to_posix(lasterr);
+ return NULL;
+ }
+ } else if (dir->dd_handle == INVALID_HANDLE_VALUE) {
+ return NULL;
+ } else if (!FindNextFileA(dir->dd_handle, &buf)) {
+ DWORD lasterr = GetLastError();
+ FindClose(dir->dd_handle);
+ dir->dd_handle = INVALID_HANDLE_VALUE;
+ /* POSIX says you shouldn't set errno when readdir can't
+ find any more files; so, if another error we leave it set. */
+ if (lasterr != ERROR_NO_MORE_FILES)
+ errno = err_win_to_posix(lasterr);
+ return NULL;
+ }
+
+ /* We get here if `buf' contains valid data. */
+ strcpy(dir->dd_dir.d_name, buf.cFileName);
+ ++dir->dd_stat;
+
+ /* Set file type, based on WIN32_FIND_DATA */
+ dir->dd_dir.d_type = 0;
+ if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ dir->dd_dir.d_type |= DT_DIR;
+ else
+ dir->dd_dir.d_type |= DT_REG;
+
+ return &dir->dd_dir;
+}
+
+int closedir(DIR *dir)
+{
+ if (!dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (dir->dd_handle != INVALID_HANDLE_VALUE)
+ FindClose(dir->dd_handle);
+ free(dir);
+ return 0;
+}
diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h
new file mode 100644
index 0000000..927a25c
--- /dev/null
+++ b/compat/win32/dirent.h
@@ -0,0 +1,24 @@
+#ifndef DIRENT_H
+#define DIRENT_H
+
+typedef struct DIR DIR;
+
+#define DT_UNKNOWN 0
+#define DT_DIR 1
+#define DT_REG 2
+#define DT_LNK 3
+
+struct dirent {
+ long d_ino; /* Always zero. */
+ char d_name[FILENAME_MAX]; /* File name. */
+ union {
+ unsigned short d_reclen; /* Always zero. */
+ unsigned char d_type; /* Reimplementation adds this */
+ };
+};
+
+DIR *opendir(const char *dirname);
+struct dirent *readdir(DIR *dir);
+int closedir(DIR *dir);
+
+#endif /* DIRENT_H */
diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c
new file mode 100644
index 0000000..708a6c9
--- /dev/null
+++ b/compat/win32/sys/poll.c
@@ -0,0 +1,599 @@
+/* Emulation for poll(2)
+ Contributed by Paolo Bonzini.
+
+ Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc.
+
+ This file is part of gnulib.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* Tell gcc not to warn about the (nfd < 0) tests, below. */
+#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <malloc.h>
+
+#include <sys/types.h>
+#include "poll.h"
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_NATIVE
+# if defined (_MSC_VER)
+# define _WIN32_WINNT 0x0502
+# endif
+# include <winsock2.h>
+# include <windows.h>
+# include <io.h>
+# include <stdio.h>
+# include <conio.h>
+#else
+# include <sys/time.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include <time.h>
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* BeOS does not have MSG_PEEK. */
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+#ifdef WIN32_NATIVE
+
+#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
+
+static BOOL
+IsSocketHandle (HANDLE h)
+{
+ WSANETWORKEVENTS ev;
+
+ if (IsConsoleHandle (h))
+ return FALSE;
+
+ /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+ WSAEnumNetworkEvents instead distinguishes the two correctly. */
+ ev.lNetworkEvents = 0xDEADBEEF;
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ return ev.lNetworkEvents != 0xDEADBEEF;
+}
+
+/* Declare data structures for ntdll functions. */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+ ULONG NamedPipeType;
+ ULONG NamedPipeConfiguration;
+ ULONG MaximumInstances;
+ ULONG CurrentInstances;
+ ULONG InboundQuota;
+ ULONG ReadDataAvailable;
+ ULONG OutboundQuota;
+ ULONG WriteQuotaAvailable;
+ ULONG NamedPipeState;
+ ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+ union {
+ DWORD Status;
+ PVOID Pointer;
+ } u;
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+ FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+ (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+# ifndef PIPE_BUF
+# define PIPE_BUF 512
+# endif
+
+/* Compute revents values for file handle H. If some events cannot happen
+ for the handle, eliminate them from *P_SOUGHT. */
+
+static int
+win32_compute_revents (HANDLE h, int *p_sought)
+{
+ int i, ret, happened;
+ INPUT_RECORD *irbuffer;
+ DWORD avail, nbuffer;
+ BOOL bRet;
+ IO_STATUS_BLOCK iosb;
+ FILE_PIPE_LOCAL_INFORMATION fpli;
+ static PNtQueryInformationFile NtQueryInformationFile;
+ static BOOL once_only;
+
+ switch (GetFileType (h))
+ {
+ case FILE_TYPE_PIPE:
+ if (!once_only)
+ {
+ NtQueryInformationFile = (PNtQueryInformationFile)
+ GetProcAddress (GetModuleHandle ("ntdll.dll"),
+ "NtQueryInformationFile");
+ once_only = TRUE;
+ }
+
+ happened = 0;
+ if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+ {
+ if (avail)
+ happened |= *p_sought & (POLLIN | POLLRDNORM);
+ }
+ else if (GetLastError () == ERROR_BROKEN_PIPE)
+ happened |= POLLHUP;
+
+ else
+ {
+ /* It was the write-end of the pipe. Check if it is writable.
+ If NtQueryInformationFile fails, optimistically assume the pipe is
+ writable. This could happen on Win9x, where NtQueryInformationFile
+ is not available, or if we inherit a pipe that doesn't permit
+ FILE_READ_ATTRIBUTES access on the write end (I think this should
+ not happen since WinXP SP2; WINE seems fine too). Otherwise,
+ ensure that enough space is available for atomic writes. */
+ memset (&iosb, 0, sizeof (iosb));
+ memset (&fpli, 0, sizeof (fpli));
+
+ if (!NtQueryInformationFile
+ || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+ FilePipeLocalInformation)
+ || fpli.WriteQuotaAvailable >= PIPE_BUF
+ || (fpli.OutboundQuota < PIPE_BUF &&
+ fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+ happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+ return happened;
+
+ case FILE_TYPE_CHAR:
+ ret = WaitForSingleObject (h, 0);
+ if (!IsConsoleHandle (h))
+ return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
+
+ nbuffer = avail = 0;
+ bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+ if (bRet)
+ {
+ /* Input buffer. */
+ *p_sought &= POLLIN | POLLRDNORM;
+ if (nbuffer == 0)
+ return POLLHUP;
+ if (!*p_sought)
+ return 0;
+
+ irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+ bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+ if (!bRet || avail == 0)
+ return POLLHUP;
+
+ for (i = 0; i < avail; i++)
+ if (irbuffer[i].EventType == KEY_EVENT)
+ return *p_sought;
+ return 0;
+ }
+ else
+ {
+ /* Screen buffer. */
+ *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
+ return *p_sought;
+ }
+
+ default:
+ ret = WaitForSingleObject (h, 0);
+ if (ret == WAIT_OBJECT_0)
+ return *p_sought & ~(POLLPRI | POLLRDBAND);
+
+ return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+}
+
+/* Convert fd_sets returned by select into revents values. */
+
+static int
+win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+ int happened = 0;
+
+ if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+ {
+ int r, error;
+
+ char data[64];
+ WSASetLastError (0);
+ r = recv (h, data, sizeof (data), MSG_PEEK);
+ error = WSAGetLastError ();
+ WSASetLastError (0);
+
+ if (r > 0 || error == WSAENOTCONN)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+ || error == WSAECONNABORTED || error == WSAENETRESET)
+ happened |= POLLHUP;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (lNetworkEvents & FD_OOB)
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values. */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ int happened = 0;
+ if (FD_ISSET (fd, rfds))
+ {
+ int r;
+ int socket_errno;
+
+# if defined __MACH__ && defined __APPLE__
+ /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+ for some kinds of descriptors. Detect if this descriptor is a
+ connected socket, a server socket, or something else using a
+ 0-byte recv, and use ioctl(2) to detect POLLHUP. */
+ r = recv (fd, NULL, 0, MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+ if (r == 0 || socket_errno == ENOTSOCK)
+ ioctl (fd, FIONREAD, &r);
+# else
+ char data[64];
+ r = recv (fd, data, sizeof (data), MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+# endif
+ if (r == 0)
+ happened |= POLLHUP;
+
+ /* If the event happened on an unconnected server socket,
+ that's fine. */
+ else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+ || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+ happened |= POLLHUP;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (FD_ISSET (fd, wfds))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (FD_ISSET (fd, efds))
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+#endif /* !MinGW */
+
+int
+poll (pfd, nfd, timeout)
+ struct pollfd *pfd;
+ nfds_t nfd;
+ int timeout;
+{
+#ifndef WIN32_NATIVE
+ fd_set rfds, wfds, efds;
+ struct timeval tv;
+ struct timeval *ptv;
+ int maxfd, rc;
+ nfds_t i;
+
+# ifdef _SC_OPEN_MAX
+ static int sc_open_max = -1;
+
+ if (nfd < 0
+ || (nfd > sc_open_max
+ && (sc_open_max != -1
+ || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# else /* !_SC_OPEN_MAX */
+# ifdef OPEN_MAX
+ if (nfd < 0 || nfd > OPEN_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# endif /* OPEN_MAX -- else, no check is needed */
+# endif /* !_SC_OPEN_MAX */
+
+ /* EFAULT is not necessary to implement, but let's do it in the
+ simplest case. */
+ if (!pfd)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+
+ /* convert timeout number into a timeval structure */
+ if (timeout == 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = 0;
+ ptv->tv_usec = 0;
+ }
+ else if (timeout > 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = timeout / 1000;
+ ptv->tv_usec = (timeout % 1000) * 1000;
+ }
+ else if (timeout == INFTIM)
+ /* wait forever */
+ ptv = NULL;
+ else
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* create fd sets and determine max fd */
+ maxfd = -1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&efds);
+ for (i = 0; i < nfd; i++)
+ {
+ if (pfd[i].fd < 0)
+ continue;
+
+ if (pfd[i].events & (POLLIN | POLLRDNORM))
+ FD_SET (pfd[i].fd, &rfds);
+
+ /* see select(2): "the only exceptional condition detectable
+ is out-of-band data received on a socket", hence we push
+ POLLWRBAND events onto wfds instead of efds. */
+ if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ FD_SET (pfd[i].fd, &wfds);
+ if (pfd[i].events & (POLLPRI | POLLRDBAND))
+ FD_SET (pfd[i].fd, &efds);
+ if (pfd[i].fd >= maxfd
+ && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
+ | POLLRDNORM | POLLRDBAND
+ | POLLWRNORM | POLLWRBAND)))
+ {
+ maxfd = pfd[i].fd;
+ if (maxfd > FD_SETSIZE)
+ {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ }
+
+ /* examine fd sets */
+ rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
+ if (rc < 0)
+ return rc;
+
+ /* establish results */
+ rc = 0;
+ for (i = 0; i < nfd; i++)
+ if (pfd[i].fd < 0)
+ pfd[i].revents = 0;
+ else
+ {
+ int happened = compute_revents (pfd[i].fd, pfd[i].events,
+ &rfds, &wfds, &efds);
+ if (happened)
+ {
+ pfd[i].revents = happened;
+ rc++;
+ }
+ }
+
+ return rc;
+#else
+ static struct timeval tv0;
+ static HANDLE hEvent;
+ WSANETWORKEVENTS ev;
+ HANDLE h, handle_array[FD_SETSIZE + 2];
+ DWORD ret, wait_timeout, nhandles;
+ fd_set rfds, wfds, xfds;
+ BOOL poll_again;
+ MSG msg;
+ int rc = 0;
+ nfds_t i;
+
+ if (nfd < 0 || timeout < -1)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!hEvent)
+ hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+ handle_array[0] = hEvent;
+ nhandles = 1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&xfds);
+
+ /* Classify socket handles and create fd sets. */
+ for (i = 0; i < nfd; i++)
+ {
+ int sought = pfd[i].events;
+ pfd[i].revents = 0;
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
+ | POLLPRI | POLLRDBAND)))
+ continue;
+
+ h = (HANDLE) _get_osfhandle (pfd[i].fd);
+ assert (h != NULL);
+ if (IsSocketHandle (h))
+ {
+ int requested = FD_CLOSE;
+
+ /* see above; socket handles are mapped onto select. */
+ if (sought & (POLLIN | POLLRDNORM))
+ {
+ requested |= FD_READ | FD_ACCEPT;
+ FD_SET ((SOCKET) h, &rfds);
+ }
+ if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ {
+ requested |= FD_WRITE | FD_CONNECT;
+ FD_SET ((SOCKET) h, &wfds);
+ }
+ if (sought & (POLLPRI | POLLRDBAND))
+ {
+ requested |= FD_OOB;
+ FD_SET ((SOCKET) h, &xfds);
+ }
+
+ if (requested)
+ WSAEventSelect ((SOCKET) h, hEvent, requested);
+ }
+ else
+ {
+ /* Poll now. If we get an event, do not poll again. Also,
+ screen buffer handles are waitable, and they'll block until
+ a character is available. win32_compute_revents eliminates
+ bits for the "wrong" direction. */
+ pfd[i].revents = win32_compute_revents (h, &sought);
+ if (sought)
+ handle_array[nhandles++] = h;
+ if (pfd[i].revents)
+ timeout = 0;
+ }
+ }
+
+ if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
+ {
+ /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
+ no need to call select again. */
+ poll_again = FALSE;
+ wait_timeout = 0;
+ }
+ else
+ {
+ poll_again = TRUE;
+ if (timeout == INFTIM)
+ wait_timeout = INFINITE;
+ else
+ wait_timeout = timeout;
+ }
+
+ for (;;)
+ {
+ ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+ wait_timeout, QS_ALLINPUT);
+
+ if (ret == WAIT_OBJECT_0 + nhandles)
+ {
+ /* new input of some other kind */
+ BOOL bRet;
+ while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+ {
+ TranslateMessage (&msg);
+ DispatchMessage (&msg);
+ }
+ }
+ else
+ break;
+ }
+
+ if (poll_again)
+ select (0, &rfds, &wfds, &xfds, &tv0);
+
+ /* Place a sentinel at the end of the array. */
+ handle_array[nhandles] = NULL;
+ nhandles = 1;
+ for (i = 0; i < nfd; i++)
+ {
+ int happened;
+
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(pfd[i].events & (POLLIN | POLLRDNORM |
+ POLLOUT | POLLWRNORM | POLLWRBAND)))
+ continue;
+
+ h = (HANDLE) _get_osfhandle (pfd[i].fd);
+ if (h != handle_array[nhandles])
+ {
+ /* It's a socket. */
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ WSAEventSelect ((SOCKET) h, 0, 0);
+
+ /* If we're lucky, WSAEnumNetworkEvents already provided a way
+ to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
+ if (FD_ISSET ((SOCKET) h, &rfds)
+ && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
+ ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
+ if (FD_ISSET ((SOCKET) h, &wfds))
+ ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
+ if (FD_ISSET ((SOCKET) h, &xfds))
+ ev.lNetworkEvents |= FD_OOB;
+
+ happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
+ ev.lNetworkEvents);
+ }
+ else
+ {
+ /* Not a socket. */
+ int sought = pfd[i].events;
+ happened = win32_compute_revents (h, &sought);
+ nhandles++;
+ }
+
+ if ((pfd[i].revents |= happened) != 0)
+ rc++;
+ }
+
+ return rc;
+#endif
+}
diff --git a/compat/win32/sys/poll.h b/compat/win32/sys/poll.h
new file mode 100644
index 0000000..b7aa59d
--- /dev/null
+++ b/compat/win32/sys/poll.h
@@ -0,0 +1,53 @@
+/* Header for poll(2) emulation
+ Contributed by Paolo Bonzini.
+
+ Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
+
+ This file is part of gnulib.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _GL_POLL_H
+#define _GL_POLL_H
+
+/* fake a poll(2) environment */
+#define POLLIN 0x0001 /* any readable data available */
+#define POLLPRI 0x0002 /* OOB/Urgent readable data */
+#define POLLOUT 0x0004 /* file descriptor is writeable */
+#define POLLERR 0x0008 /* some poll error occurred */
+#define POLLHUP 0x0010 /* file descriptor was "hung up" */
+#define POLLNVAL 0x0020 /* requested events "invalid" */
+#define POLLRDNORM 0x0040
+#define POLLRDBAND 0x0080
+#define POLLWRNORM 0x0100
+#define POLLWRBAND 0x0200
+
+struct pollfd
+{
+ int fd; /* which file descriptor to poll */
+ short events; /* events we are interested in */
+ short revents; /* events found on return */
+};
+
+typedef unsigned long nfds_t;
+
+extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
+
+/* Define INFTIM only if doing so conforms to POSIX. */
+#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
+#define INFTIM (-1)
+#endif
+
+#endif /* _GL_POLL_H */
diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c
new file mode 100644
index 0000000..42b95a9
--- /dev/null
+++ b/compat/win32/syslog.c
@@ -0,0 +1,72 @@
+#include "../../git-compat-util.h"
+#include "../../strbuf.h"
+
+static HANDLE ms_eventlog;
+
+void openlog(const char *ident, int logopt, int facility)
+{
+ if (ms_eventlog)
+ return;
+
+ ms_eventlog = RegisterEventSourceA(NULL, ident);
+
+ if (!ms_eventlog)
+ warning("RegisterEventSource() failed: %lu", GetLastError());
+}
+
+void syslog(int priority, const char *fmt, ...)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf_expand_dict_entry dict[] = {
+ {"1", "% 1"},
+ {NULL, NULL}
+ };
+ WORD logtype;
+ char *str;
+ int str_len;
+ va_list ap;
+
+ if (!ms_eventlog)
+ return;
+
+ va_start(ap, fmt);
+ str_len = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (str_len < 0) {
+ warning("vsnprintf failed: '%s'", strerror(errno));
+ return;
+ }
+
+ str = malloc(str_len + 1);
+ va_start(ap, fmt);
+ vsnprintf(str, str_len + 1, fmt, ap);
+ va_end(ap);
+ strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict);
+ free(str);
+
+ switch (priority) {
+ case LOG_EMERG:
+ case LOG_ALERT:
+ case LOG_CRIT:
+ case LOG_ERR:
+ logtype = EVENTLOG_ERROR_TYPE;
+ break;
+
+ case LOG_WARNING:
+ logtype = EVENTLOG_WARNING_TYPE;
+ break;
+
+ case LOG_NOTICE:
+ case LOG_INFO:
+ case LOG_DEBUG:
+ default:
+ logtype = EVENTLOG_INFORMATION_TYPE;
+ break;
+ }
+
+ ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0,
+ (const char **)&sb.buf, NULL);
+
+ strbuf_release(&sb);
+}
diff --git a/compat/win32/syslog.h b/compat/win32/syslog.h
new file mode 100644
index 0000000..70daa7c
--- /dev/null
+++ b/compat/win32/syslog.h
@@ -0,0 +1,20 @@
+#ifndef SYSLOG_H
+#define SYSLOG_H
+
+#define LOG_PID 0x01
+
+#define LOG_EMERG 0
+#define LOG_ALERT 1
+#define LOG_CRIT 2
+#define LOG_ERR 3
+#define LOG_WARNING 4
+#define LOG_NOTICE 5
+#define LOG_INFO 6
+#define LOG_DEBUG 7
+
+#define LOG_DAEMON (3<<3)
+
+void openlog(const char *ident, int logopt, int facility);
+void syslog(int priority, const char *fmt, ...);
+
+#endif /* SYSLOG_H */
diff --git a/config.c b/config.c
index cdcf583..e42c59b 100644
--- a/config.c
+++ b/config.c
@@ -8,66 +8,106 @@
#include "cache.h"
#include "exec_cmd.h"
#include "strbuf.h"
+#include "quote.h"
#define MAXNAME (256)
-static FILE *config_file;
-static const char *config_file_name;
-static int config_linenr;
-static int config_file_eof;
+typedef struct config_file {
+ struct config_file *prev;
+ FILE *f;
+ const char *name;
+ int linenr;
+ int eof;
+ struct strbuf value;
+ char var[MAXNAME];
+} config_file;
+
+static config_file *cf;
+
static int zlib_compression_seen;
const char *config_exclusive_filename = NULL;
-struct config_item
-{
- struct config_item *next;
- char *name;
- char *value;
-};
-static struct config_item *config_parameters;
-static struct config_item **config_parameters_tail = &config_parameters;
-
static void lowercase(char *p)
{
for (; *p; p++)
*p = tolower(*p);
}
-int git_config_parse_parameter(const char *text)
+void git_config_push_parameter(const char *text)
+{
+ struct strbuf env = STRBUF_INIT;
+ const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
+ if (old) {
+ strbuf_addstr(&env, old);
+ strbuf_addch(&env, ' ');
+ }
+ sq_quote_buf(&env, text);
+ setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);
+ strbuf_release(&env);
+}
+
+int git_config_parse_parameter(const char *text,
+ config_fn_t fn, void *data)
{
- struct config_item *ct;
- struct strbuf tmp = STRBUF_INIT;
struct strbuf **pair;
- strbuf_addstr(&tmp, text);
- pair = strbuf_split(&tmp, '=');
+ pair = strbuf_split_str(text, '=', 2);
+ if (!pair[0])
+ return error("bogus config parameter: %s", text);
if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
strbuf_setlen(pair[0], pair[0]->len - 1);
strbuf_trim(pair[0]);
if (!pair[0]->len) {
strbuf_list_free(pair);
- return -1;
+ return error("bogus config parameter: %s", text);
}
- ct = xcalloc(1, sizeof(struct config_item));
- ct->name = strbuf_detach(pair[0], NULL);
- if (pair[1]) {
- strbuf_trim(pair[1]);
- ct->value = strbuf_detach(pair[1], NULL);
+ lowercase(pair[0]->buf);
+ if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) {
+ strbuf_list_free(pair);
+ return -1;
}
strbuf_list_free(pair);
- lowercase(ct->name);
- *config_parameters_tail = ct;
- config_parameters_tail = &ct->next;
return 0;
}
+int git_config_from_parameters(config_fn_t fn, void *data)
+{
+ const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
+ char *envw;
+ const char **argv = NULL;
+ int nr = 0, alloc = 0;
+ int i;
+
+ if (!env)
+ return 0;
+ /* sq_dequote will write over it */
+ envw = xstrdup(env);
+
+ if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
+ free(envw);
+ return error("bogus format in " CONFIG_DATA_ENVIRONMENT);
+ }
+
+ for (i = 0; i < nr; i++) {
+ if (git_config_parse_parameter(argv[i], fn, data) < 0) {
+ free(argv);
+ free(envw);
+ return -1;
+ }
+ }
+
+ free(argv);
+ free(envw);
+ return nr > 0;
+}
+
static int get_next_char(void)
{
int c;
FILE *f;
c = '\n';
- if ((f = config_file) != NULL) {
+ if (cf && ((f = cf->f) != NULL)) {
c = fgetc(f);
if (c == '\r') {
/* DOS like systems */
@@ -78,9 +118,9 @@ static int get_next_char(void)
}
}
if (c == '\n')
- config_linenr++;
+ cf->linenr++;
if (c == EOF) {
- config_file_eof = 1;
+ cf->eof = 1;
c = '\n';
}
}
@@ -89,23 +129,20 @@ static int get_next_char(void)
static char *parse_value(void)
{
- static char value[1024];
- int quote = 0, comment = 0, len = 0, space = 0;
+ int quote = 0, comment = 0, space = 0;
+ strbuf_reset(&cf->value);
for (;;) {
int c = get_next_char();
- if (len >= sizeof(value) - 1)
- return NULL;
if (c == '\n') {
if (quote)
return NULL;
- value[len] = 0;
- return value;
+ return cf->value.buf;
}
if (comment)
continue;
if (isspace(c) && !quote) {
- if (len)
+ if (cf->value.len)
space++;
continue;
}
@@ -116,7 +153,7 @@ static char *parse_value(void)
}
}
for (; space; space--)
- value[len++] = ' ';
+ strbuf_addch(&cf->value, ' ');
if (c == '\\') {
c = get_next_char();
switch (c) {
@@ -138,14 +175,14 @@ static char *parse_value(void)
default:
return NULL;
}
- value[len++] = c;
+ strbuf_addch(&cf->value, c);
continue;
}
if (c == '"') {
quote = 1-quote;
continue;
}
- value[len++] = c;
+ strbuf_addch(&cf->value, c);
}
}
@@ -162,7 +199,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
/* Get the full name */
for (;;) {
c = get_next_char();
- if (config_file_eof)
+ if (cf->eof)
break;
if (!iskeychar(c))
break;
@@ -226,7 +263,7 @@ static int get_base_var(char *name)
for (;;) {
int c = get_next_char();
- if (config_file_eof)
+ if (cf->eof)
return -1;
if (c == ']')
return baselen;
@@ -244,7 +281,7 @@ static int git_parse_file(config_fn_t fn, void *data)
{
int comment = 0;
int baselen = 0;
- static char var[MAXNAME];
+ char *var = cf->var;
/* U+FEFF Byte Order Mark in UTF8 */
static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
@@ -268,7 +305,7 @@ static int git_parse_file(config_fn_t fn, void *data)
}
}
if (c == '\n') {
- if (config_file_eof)
+ if (cf->eof)
return 0;
comment = 0;
continue;
@@ -293,7 +330,7 @@ static int git_parse_file(config_fn_t fn, void *data)
if (get_value(fn, data, var, baselen+1) < 0)
break;
}
- die("bad config file line %d in %s", config_linenr, config_file_name);
+ die("bad config file line %d in %s", cf->linenr, cf->name);
}
static int parse_unit_factor(const char *end, unsigned long *val)
@@ -344,8 +381,8 @@ int git_parse_ulong(const char *value, unsigned long *ret)
static void die_bad_config(const char *name)
{
- if (config_file_name)
- die("bad config value for '%s' in %s", name, config_file_name);
+ if (cf && cf->name)
+ die("bad config value for '%s' in %s", name, cf->name);
die("bad config value for '%s'", name);
}
@@ -365,7 +402,7 @@ unsigned long git_config_ulong(const char *name, const char *value)
return ret;
}
-int git_config_maybe_bool(const char *name, const char *value)
+static int git_config_maybe_bool_text(const char *name, const char *value)
{
if (!value)
return 1;
@@ -382,9 +419,19 @@ int git_config_maybe_bool(const char *name, const char *value)
return -1;
}
+int git_config_maybe_bool(const char *name, const char *value)
+{
+ long v = git_config_maybe_bool_text(name, value);
+ if (0 <= v)
+ return v;
+ if (git_parse_long(value, &v))
+ return !!v;
+ return -1;
+}
+
int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
{
- int v = git_config_maybe_bool(name, value);
+ int v = git_config_maybe_bool_text(name, value);
if (0 <= v) {
*is_bool = 1;
return v;
@@ -469,6 +516,14 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.abbrev")) {
+ int abbrev = git_config_int(var, value);
+ if (abbrev < minimum_abbrev || abbrev > 40)
+ return -1;
+ default_abbrev = abbrev;
+ return 0;
+ }
+
if (!strcmp(var, "core.loosecompression")) {
int level = git_config_int(var, value);
if (level == -1)
@@ -505,6 +560,12 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.bigfilethreshold")) {
+ long n = git_config_int(var, value);
+ big_file_threshold = 0 < n ? n : 0;
+ return 0;
+ }
+
if (!strcmp(var, "core.packedgitlimit")) {
packed_git_limit = git_config_int(var, value);
return 0;
@@ -517,7 +578,7 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.autocrlf")) {
if (value && !strcasecmp(value, "input")) {
- if (eol == EOL_CRLF)
+ if (core_eol == EOL_CRLF)
return error("core.autocrlf=input conflicts with core.eol=crlf");
auto_crlf = AUTO_CRLF_INPUT;
return 0;
@@ -537,14 +598,14 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.eol")) {
if (value && !strcasecmp(value, "lf"))
- eol = EOL_LF;
+ core_eol = EOL_LF;
else if (value && !strcasecmp(value, "crlf"))
- eol = EOL_CRLF;
+ core_eol = EOL_CRLF;
else if (value && !strcasecmp(value, "native"))
- eol = EOL_NATIVE;
+ core_eol = EOL_NATIVE;
else
- eol = EOL_UNSET;
- if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
+ core_eol = EOL_UNSET;
+ if (core_eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
return error("core.autocrlf=input conflicts with core.eol=crlf");
return 0;
}
@@ -560,6 +621,9 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.editor"))
return git_config_string(&editor_program, var, value);
+ if (!strcmp(var, "core.askpass"))
+ return git_config_string(&askpass_program, var, value);
+
if (!strcmp(var, "core.excludesfile"))
return git_config_pathname(&excludes_file, var, value);
@@ -672,8 +736,10 @@ static int git_default_push_config(const char *var, const char *value)
push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
- else if (!strcmp(value, "tracking"))
- push_default = PUSH_DEFAULT_TRACKING;
+ else if (!strcmp(value, "upstream"))
+ push_default = PUSH_DEFAULT_UPSTREAM;
+ else if (!strcmp(value, "tracking")) /* deprecated */
+ push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "current"))
push_default = PUSH_DEFAULT_CURRENT;
else {
@@ -736,13 +802,24 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
ret = -1;
if (f) {
- config_file = f;
- config_file_name = filename;
- config_linenr = 1;
- config_file_eof = 0;
+ config_file top;
+
+ /* push config-file parsing state stack */
+ top.prev = cf;
+ top.f = f;
+ top.name = filename;
+ top.linenr = 1;
+ top.eof = 0;
+ strbuf_init(&top.value, 1024);
+ cf = &top;
+
ret = git_parse_file(fn, data);
+
+ /* pop config-file parsing state stack */
+ strbuf_release(&top.value);
+ cf = top.prev;
+
fclose(f);
- config_file_name = NULL;
}
return ret;
}
@@ -766,24 +843,9 @@ int git_config_system(void)
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}
-int git_config_global(void)
-{
- return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
-}
-
-int git_config_from_parameters(config_fn_t fn, void *data)
-{
- const struct config_item *ct;
- for (ct = config_parameters; ct; ct = ct->next)
- if (fn(ct->name, ct->value, data) < 0)
- return -1;
- return 0;
-}
-
-int git_config(config_fn_t fn, void *data)
+int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- char *repo_config = NULL;
const char *home = NULL;
/* Setting $GIT_CONFIG makes git read _only_ the given config file. */
@@ -796,7 +858,7 @@ int git_config(config_fn_t fn, void *data)
}
home = getenv("HOME");
- if (git_config_global() && home) {
+ if (home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
if (!access(user_config, R_OK)) {
ret += git_config_from_file(fn, user_config, data);
@@ -805,20 +867,34 @@ int git_config(config_fn_t fn, void *data)
free(user_config);
}
- repo_config = git_pathdup("config");
- if (!access(repo_config, R_OK)) {
+ if (repo_config && !access(repo_config, R_OK)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
- free(repo_config);
- if (config_parameters) {
- ret += git_config_from_parameters(fn, data);
- found += 1;
+ switch (git_config_from_parameters(fn, data)) {
+ case -1: /* error */
+ die("unable to parse command-line config");
+ break;
+ case 0: /* found nothing */
+ break;
+ default: /* found at least one item */
+ found++;
+ break;
}
- if (found == 0)
- return -1;
+ return ret == 0 ? found : ret;
+}
+
+int git_config(config_fn_t fn, void *data)
+{
+ char *repo_config = NULL;
+ int ret;
+
+ repo_config = git_pathdup("config");
+ ret = git_config_early(fn, data, repo_config);
+ if (repo_config)
+ free(repo_config);
return ret;
}
@@ -851,6 +927,7 @@ static int store_aux(const char *key, const char *value, void *cb)
{
const char *ep;
size_t section_len;
+ FILE *f = cf->f;
switch (store.state) {
case KEY_SEEN:
@@ -862,7 +939,7 @@ static int store_aux(const char *key, const char *value, void *cb)
return 1;
}
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
store.seen++;
}
break;
@@ -889,19 +966,19 @@ static int store_aux(const char *key, const char *value, void *cb)
* Do not increment matches: this is no match, but we
* just made sure we are in the desired section.
*/
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
/* fallthru */
case SECTION_END_SEEN:
case START:
if (matches(key, value)) {
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
store.state = KEY_SEEN;
store.seen++;
} else {
if (strrchr(key, '.') - key == store.baselen &&
!strncmp(key, store.key, store.baselen)) {
store.state = SECTION_SEEN;
- store.offset[store.seen] = ftell(config_file);
+ store.offset[store.seen] = ftell(f);
}
}
}
@@ -1021,6 +1098,75 @@ int git_config_set(const char *key, const char *value)
}
/*
+ * Auxiliary function to sanity-check and split the key into the section
+ * identifier and variable name.
+ *
+ * Returns 0 on success, -1 when there is an invalid character in the key and
+ * -2 if there is no section name in the key.
+ *
+ * store_key - pointer to char* which will hold a copy of the key with
+ * lowercase section and variable name
+ * baselen - pointer to int which will hold the length of the
+ * section + subsection part, can be NULL
+ */
+int git_config_parse_key(const char *key, char **store_key, int *baselen_)
+{
+ int i, dot, baselen;
+ const char *last_dot = strrchr(key, '.');
+
+ /*
+ * Since "key" actually contains the section name and the real
+ * key name separated by a dot, we have to know where the dot is.
+ */
+
+ if (last_dot == NULL || last_dot == key) {
+ error("key does not contain a section: %s", key);
+ return -CONFIG_NO_SECTION_OR_NAME;
+ }
+
+ if (!last_dot[1]) {
+ error("key does not contain variable name: %s", key);
+ return -CONFIG_NO_SECTION_OR_NAME;
+ }
+
+ baselen = last_dot - key;
+ if (baselen_)
+ *baselen_ = baselen;
+
+ /*
+ * Validate the key and while at it, lower case it for matching.
+ */
+ *store_key = xmalloc(strlen(key) + 1);
+
+ dot = 0;
+ for (i = 0; key[i]; i++) {
+ unsigned char c = key[i];
+ if (c == '.')
+ dot = 1;
+ /* Leave the extended basename untouched.. */
+ if (!dot || i > baselen) {
+ if (!iskeychar(c) ||
+ (i == baselen + 1 && !isalpha(c))) {
+ error("invalid key: %s", key);
+ goto out_free_ret_1;
+ }
+ c = tolower(c);
+ } else if (c == '\n') {
+ error("invalid key (newline): %s", key);
+ goto out_free_ret_1;
+ }
+ (*store_key)[i] = c;
+ }
+ (*store_key)[i] = 0;
+
+ return 0;
+
+out_free_ret_1:
+ free(*store_key);
+ return -CONFIG_INVALID_KEY;
+}
+
+/*
* If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match.
* if multi_replace==0, nothing, or only one matching key/value is replaced,
@@ -1046,59 +1192,23 @@ int git_config_set(const char *key, const char *value)
int git_config_set_multivar(const char *key, const char *value,
const char *value_regex, int multi_replace)
{
- int i, dot;
int fd = -1, in_fd;
int ret;
char *config_filename;
struct lock_file *lock = NULL;
- const char *last_dot = strrchr(key, '.');
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
else
config_filename = git_pathdup("config");
- /*
- * Since "key" actually contains the section name and the real
- * key name separated by a dot, we have to know where the dot is.
- */
-
- if (last_dot == NULL) {
- error("key does not contain a section: %s", key);
- ret = 2;
+ /* parse-key returns negative; flip the sign to feed exit(3) */
+ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
+ if (ret)
goto out_free;
- }
- store.baselen = last_dot - key;
store.multi_replace = multi_replace;
- /*
- * Validate the key and while at it, lower case it for matching.
- */
- store.key = xmalloc(strlen(key) + 1);
- dot = 0;
- for (i = 0; key[i]; i++) {
- unsigned char c = key[i];
- if (c == '.')
- dot = 1;
- /* Leave the extended basename untouched.. */
- if (!dot || i > store.baselen) {
- if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
- error("invalid key: %s", key);
- free(store.key);
- ret = 1;
- goto out_free;
- }
- c = tolower(c);
- } else if (c == '\n') {
- error("invalid key (newline): %s", key);
- free(store.key);
- ret = 1;
- goto out_free;
- }
- store.key[i] = c;
- }
- store.key[i] = 0;
/*
* The lock serves a purpose in addition to locking: the new
@@ -1109,7 +1219,7 @@ int git_config_set_multivar(const char *key, const char *value,
if (fd < 0) {
error("could not lock config file %s: %s", config_filename, strerror(errno));
free(store.key);
- ret = -1;
+ ret = CONFIG_NO_LOCK;
goto out_free;
}
@@ -1123,12 +1233,12 @@ int git_config_set_multivar(const char *key, const char *value,
if ( ENOENT != errno ) {
error("opening %s: %s", config_filename,
strerror(errno));
- ret = 3; /* same as "invalid config file" */
+ ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
goto out_free;
}
/* if nothing to unset, error out */
if (value == NULL) {
- ret = 5;
+ ret = CONFIG_NOTHING_SET;
goto out_free;
}
@@ -1156,7 +1266,7 @@ int git_config_set_multivar(const char *key, const char *value,
REG_EXTENDED)) {
error("invalid pattern: %s", value_regex);
free(store.value_regex);
- ret = 6;
+ ret = CONFIG_INVALID_PATTERN;
goto out_free;
}
}
@@ -1178,7 +1288,7 @@ int git_config_set_multivar(const char *key, const char *value,
regfree(store.value_regex);
free(store.value_regex);
}
- ret = 3;
+ ret = CONFIG_INVALID_FILE;
goto out_free;
}
@@ -1191,7 +1301,7 @@ int git_config_set_multivar(const char *key, const char *value,
/* if nothing to unset, or too many matches, error out */
if ((store.seen == 0 && value == NULL) ||
(store.seen > 1 && multi_replace == 0)) {
- ret = 5;
+ ret = CONFIG_NOTHING_SET;
goto out_free;
}
@@ -1252,7 +1362,7 @@ int git_config_set_multivar(const char *key, const char *value,
if (commit_lock_file(lock) < 0) {
error("could not commit config file %s", config_filename);
- ret = 4;
+ ret = CONFIG_NO_WRITE;
goto out_free;
}
@@ -1324,6 +1434,7 @@ int git_config_rename_section(const char *old_name, const char *new_name)
struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
int out_fd;
char buf[1024];
+ FILE *config_file;
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
@@ -1388,10 +1499,10 @@ int git_config_rename_section(const char *old_name, const char *new_name)
}
}
fclose(config_file);
- unlock_and_out:
+unlock_and_out:
if (commit_lock_file(lock) < 0)
ret = error("could not commit config file %s", config_filename);
- out:
+out:
free(config_filename);
return ret;
}
diff --git a/config.mak.in b/config.mak.in
index b4e65c3..ab37101 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -18,6 +18,7 @@ bindir = @bindir@
gitexecdir = @libexecdir@/git-core
datarootdir = @datarootdir@
template_dir = @datadir@/git-core/templates
+sysconfdir = @sysconfdir@
mandir=@mandir@
@@ -27,7 +28,7 @@ VPATH = @srcdir@
export exec_prefix mandir
export srcdir VPATH
-ASCIIDOC8=@ASCIIDOC8@
+ASCIIDOC7=@ASCIIDOC7@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@
@@ -43,9 +44,11 @@ NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@
NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@
NO_IPV6=@NO_IPV6@
-NO_C99_FORMAT=@NO_C99_FORMAT@
NO_HSTRERROR=@NO_HSTRERROR@
NO_STRCASESTR=@NO_STRCASESTR@
+NO_STRTOK_R=@NO_STRTOK_R@
+NO_FNMATCH=@NO_FNMATCH@
+NO_FNMATCH_CASEFOLD=@NO_FNMATCH_CASEFOLD@
NO_MEMMEM=@NO_MEMMEM@
NO_STRLCPY=@NO_STRLCPY@
NO_UINTMAX_T=@NO_UINTMAX_T@
@@ -58,6 +61,8 @@ NO_INET_NTOP=@NO_INET_NTOP@
NO_INET_PTON=@NO_INET_PTON@
NO_ICONV=@NO_ICONV@
OLD_ICONV=@OLD_ICONV@
+NO_REGEX=@NO_REGEX@
+USE_LIBPCRE=@USE_LIBPCRE@
NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
INLINE=@INLINE@
SOCKLEN_T=@SOCKLEN_T@
diff --git a/configure.ac b/configure.ac
index 5601e8b..048a1d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -220,6 +220,27 @@ AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\
GIT_PARSE_WITH(openssl))
#
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+AC_ARG_WITH(libpcre,
+AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
+AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]),
+if test "$withval" = "no"; then \
+ USE_LIBPCRE=; \
+elif test "$withval" = "yes"; then \
+ USE_LIBPCRE=YesPlease; \
+else
+ USE_LIBPCRE=YesPlease; \
+ LIBPCREDIR=$withval; \
+ AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \
+ GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \
+fi \
+)
+#
# Define NO_CURL if you do not have curl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
# transports.
@@ -282,7 +303,15 @@ GIT_PARSE_WITH(iconv))
GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG,
Use VALUE instead of /etc/gitconfig as the
global git configuration file.
- If VALUE is not fully qualified it will be interpretted
+ If VALUE is not fully qualified it will be interpreted
+ as a path relative to the computed prefix at runtime.)
+
+#
+# Allow user to set ETC_GITATTRIBUTES variable
+GIT_PARSE_WITH_SET_MAKE_VAR(gitattributes, ETC_GITATTRIBUTES,
+ Use VALUE instead of /etc/gitattributes as the
+ global git attributes file.
+ If VALUE is not fully qualified it will be interpreted
as a path relative to the computed prefix at runtime.)
#
@@ -337,7 +366,7 @@ esac
AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -R /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_dashr" = "yes"; then
@@ -346,7 +375,7 @@ else
AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_wl_rpath" = "yes"; then
@@ -355,7 +384,7 @@ else
AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -rpath /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
if test "$git_cv_ld_rpath" = "yes"; then
@@ -390,21 +419,21 @@ if test -n "$ASCIIDOC"; then
AC_MSG_CHECKING([for asciidoc version])
asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
case "${asciidoc_version}" in
- asciidoc' '8*)
- ASCIIDOC8=YesPlease
+ asciidoc' '7*)
+ ASCIIDOC7=YesPlease
AC_MSG_RESULT([${asciidoc_version} > 7])
;;
- asciidoc' '7*)
- ASCIIDOC8=
+ asciidoc' '8*)
+ ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version}])
;;
*)
- ASCIIDOC8=
+ ASCIIDOC7=
AC_MSG_RESULT([${asciidoc_version} (unknown)])
;;
esac
fi
-AC_SUBST(ASCIIDOC8)
+AC_SUBST(ASCIIDOC7)
## Checks for libraries.
@@ -427,6 +456,25 @@ AC_SUBST(NEEDS_SSL_WITH_CRYPTO)
AC_SUBST(NO_OPENSSL)
#
+# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
+# able to use Perl-compatible regular expressions.
+#
+
+if test -n "$USE_LIBPCRE"; then
+
+GIT_STASH_FLAGS($LIBPCREDIR)
+
+AC_CHECK_LIB([pcre], [pcre_version],
+[USE_LIBPCRE=YesPlease],
+[USE_LIBPCRE=])
+
+GIT_UNSTASH_FLAGS($LIBPCREDIR)
+
+AC_SUBST(USE_LIBPCRE)
+
+fi
+
+#
# Define NO_CURL if you do not have libcurl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
# transports.
@@ -464,15 +512,9 @@ if test -z "$NO_ICONV"; then
GIT_STASH_FLAGS($ICONVDIR)
-AC_DEFUN([ICONVTEST_SRC], [
-#include <iconv.h>
-
-int main(void)
-{
- iconv_open("", "");
- return 0;
-}
-])
+AC_DEFUN([ICONVTEST_SRC],
+[AC_LANG_PROGRAM([#include <iconv.h>],
+ [iconv_open("", "");])])
if test -n "$ICONVDIR"; then
lib_order="-liconv -lc"
@@ -492,7 +534,7 @@ for l in $lib_order; do
old_LIBS="$LIBS"
LIBS="$LIBS $l"
AC_MSG_CHECKING([for iconv in $l])
- AC_LINK_IFELSE(ICONVTEST_SRC,
+ AC_LINK_IFELSE([ICONVTEST_SRC],
[AC_MSG_RESULT([yes])
NO_ICONV=
break],
@@ -520,18 +562,12 @@ fi
GIT_STASH_FLAGS($ZLIB_PATH)
AC_DEFUN([ZLIBTEST_SRC], [
-#include <zlib.h>
-
-int main(void)
-{
- deflateBound(0, 0);
- return 0;
-}
-])
+AC_LANG_PROGRAM([#include <zlib.h>],
+ [deflateBound(0, 0);])])
AC_MSG_CHECKING([for deflateBound in -lz])
old_LIBS="$LIBS"
LIBS="$LIBS -lz"
-AC_LINK_IFELSE(ZLIBTEST_SRC,
+AC_LINK_IFELSE([ZLIBTEST_SRC],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
NO_DEFLATE_BOUND=yes])
@@ -609,25 +645,33 @@ AC_CHECK_HEADER([sys/select.h],
[NO_SYS_SELECT_H=UnfortunatelyYes])
AC_SUBST(NO_SYS_SELECT_H)
#
+# Define NO_SYS_POLL_H if you don't have sys/poll.h
+AC_CHECK_HEADER([sys/poll.h],
+[NO_SYS_POLL_H=],
+[NO_SYS_POLL_H=UnfortunatelyYes])
+AC_SUBST(NO_SYS_POLL_H)
+#
+# Define NO_INTTYPES_H if you don't have inttypes.h
+AC_CHECK_HEADER([inttypes.h],
+[NO_INTTYPES_H=],
+[NO_INTTYPES_H=UnfortunatelyYes])
+AC_SUBST(NO_INTTYPES_H)
+#
# Define OLD_ICONV if your library has an old iconv(), where the second
# (input buffer pointer) parameter is declared with type (const char **).
-AC_DEFUN([OLDICONVTEST_SRC], [[
+AC_DEFUN([OLDICONVTEST_SRC], [
+AC_LANG_PROGRAM([[
#include <iconv.h>
extern size_t iconv(iconv_t cd,
char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft);
-
-int main(void)
-{
- return 0;
-}
-]])
+]], [])])
GIT_STASH_FLAGS($ICONVDIR)
AC_MSG_CHECKING([for old iconv()])
-AC_COMPILE_IFELSE(OLDICONVTEST_SRC,
+AC_COMPILE_IFELSE([OLDICONVTEST_SRC],
[AC_MSG_RESULT([no])],
[AC_MSG_RESULT([yes])
OLD_ICONV=UnfortunatelyYes])
@@ -682,29 +726,26 @@ AC_CHECK_TYPE([struct addrinfo],[
])
AC_SUBST(NO_IPV6)
#
-# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
-# do not support the 'size specifiers' introduced by C99, namely ll, hh,
-# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
-# some C compilers supported these specifiers prior to C99 as an extension.
-AC_CACHE_CHECK([whether formatted IO functions support C99 size specifiers],
- [ac_cv_c_c99_format],
-[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c
-AC_RUN_IFELSE(
- [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
- [[char buf[64];
- if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5)
- return 1;
- else if (strcmp(buf, "12345"))
- return 2;]])],
- [ac_cv_c_c99_format=yes],
- [ac_cv_c_c99_format=no])
+# Define NO_REGEX if you have no or inferior regex support in your C library.
+AC_CACHE_CHECK([whether the platform regex can handle null bytes],
+ [ac_cv_c_excellent_regex], [
+AC_EGREP_CPP(yippeeyeswehaveit,
+ AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT
+#include <regex.h>
+],
+[#ifdef REG_STARTEND
+yippeeyeswehaveit
+#endif
+]),
+ [ac_cv_c_excellent_regex=yes],
+ [ac_cv_c_excellent_regex=no])
])
-if test $ac_cv_c_c99_format = no; then
- NO_C99_FORMAT=YesPlease
+if test $ac_cv_c_excellent_regex = yes; then
+ NO_REGEX=
else
- NO_C99_FORMAT=
+ NO_REGEX=YesPlease
fi
-AC_SUBST(NO_C99_FORMAT)
+AC_SUBST(NO_REGEX)
#
# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
# when attempting to read from an fopen'ed directory.
@@ -783,6 +824,40 @@ GIT_CHECK_FUNC(strcasestr,
[NO_STRCASESTR=YesPlease])
AC_SUBST(NO_STRCASESTR)
#
+# Define NO_STRTOK_R if you don't have strtok_r
+GIT_CHECK_FUNC(strtok_r,
+[NO_STRTOK_R=],
+[NO_STRTOK_R=YesPlease])
+AC_SUBST(NO_STRTOK_R)
+#
+# Define NO_FNMATCH if you don't have fnmatch
+GIT_CHECK_FUNC(fnmatch,
+[NO_FNMATCH=],
+[NO_FNMATCH=YesPlease])
+AC_SUBST(NO_FNMATCH)
+#
+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
+# FNM_CASEFOLD GNU extension.
+AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension],
+ [ac_cv_c_excellent_fnmatch], [
+AC_EGREP_CPP(yippeeyeswehaveit,
+ AC_LANG_PROGRAM([
+#include <fnmatch.h>
+],
+[#ifdef FNM_CASEFOLD
+yippeeyeswehaveit
+#endif
+]),
+ [ac_cv_c_excellent_fnmatch=yes],
+ [ac_cv_c_excellent_fnmatch=no])
+])
+if test $ac_cv_c_excellent_fnmatch = yes; then
+ NO_FNMATCH_CASEFOLD=
+else
+ NO_FNMATCH_CASEFOLD=YesPlease
+fi
+AC_SUBST(NO_FNMATCH_CASEFOLD)
+#
# Define NO_MEMMEM if you don't have memmem.
GIT_CHECK_FUNC(memmem,
[NO_MEMMEM=],
@@ -833,6 +908,12 @@ GIT_CHECK_FUNC(mkstemps,
[NO_MKSTEMPS=YesPlease])
AC_SUBST(NO_MKSTEMPS)
#
+# Define NO_INITGROUPS if you don't have initgroups in the C library.
+GIT_CHECK_FUNC(initgroups,
+[NO_INITGROUPS=],
+[NO_INITGROUPS=YesPlease])
+AC_SUBST(NO_INITGROUPS)
+#
#
# Define NO_MMAP if you want to avoid mmap.
#
@@ -850,18 +931,18 @@ AC_SUBST(NO_MKSTEMPS)
#
# Define PTHREAD_LIBS to the linker flag used for Pthread support.
AC_DEFUN([PTHREADTEST_SRC], [
+AC_LANG_PROGRAM([[
#include <pthread.h>
-
-int main(void)
-{
+]], [[
pthread_mutex_t test_mutex;
+ pthread_key_t test_key;
int retcode = 0;
+ retcode |= pthread_key_create(&test_key, (void *)0);
retcode |= pthread_mutex_init(&test_mutex,(void *)0);
retcode |= pthread_mutex_lock(&test_mutex);
retcode |= pthread_mutex_unlock(&test_mutex);
return retcode;
-}
-])
+]])])
dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM(
dnl [[#include <pthread.h>]],
@@ -881,7 +962,7 @@ elif test -z "$PTHREAD_CFLAGS"; then
old_CFLAGS="$CFLAGS"
CFLAGS="$opt $CFLAGS"
AC_MSG_CHECKING([Checking for POSIX Threads with '$opt'])
- AC_LINK_IFELSE(PTHREADTEST_SRC,
+ AC_LINK_IFELSE([PTHREADTEST_SRC],
[AC_MSG_RESULT([yes])
NO_PTHREADS=
PTHREAD_LIBS="$opt"
@@ -901,7 +982,7 @@ else
old_CFLAGS="$CFLAGS"
CFLAGS="$PTHREAD_CFLAGS $CFLAGS"
AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS'])
- AC_LINK_IFELSE(PTHREADTEST_SRC,
+ AC_LINK_IFELSE([PTHREADTEST_SRC],
[AC_MSG_RESULT([yes])
NO_PTHREADS=
PTHREAD_LIBS="$PTHREAD_CFLAGS"
diff --git a/connect.c b/connect.c
index 02e738a..844107e 100644
--- a/connect.c
+++ b/connect.c
@@ -192,7 +192,8 @@ static const char *ai_name(const struct addrinfo *ai)
*/
static int git_tcp_connect_sock(char *host, int flags)
{
- int sockfd = -1, saved_errno = 0;
+ struct strbuf error_message = STRBUF_INIT;
+ int sockfd = -1;
const char *port = STR(DEFAULT_GIT_PORT);
struct addrinfo hints, *ai0, *ai;
int gai;
@@ -219,18 +220,12 @@ static int git_tcp_connect_sock(char *host, int flags)
for (ai0 = ai; ai; ai = ai->ai_next) {
sockfd = socket(ai->ai_family,
ai->ai_socktype, ai->ai_protocol);
- if (sockfd < 0) {
- saved_errno = errno;
- continue;
- }
- if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
- saved_errno = errno;
- fprintf(stderr, "%s[%d: %s]: errno=%s\n",
- host,
- cnt,
- ai_name(ai),
- strerror(saved_errno));
- close(sockfd);
+ if ((sockfd < 0) ||
+ (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) {
+ strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
+ host, cnt, ai_name(ai), strerror(errno));
+ if (0 <= sockfd)
+ close(sockfd);
sockfd = -1;
continue;
}
@@ -242,11 +237,13 @@ static int git_tcp_connect_sock(char *host, int flags)
freeaddrinfo(ai0);
if (sockfd < 0)
- die("unable to connect a socket (%s)", strerror(saved_errno));
+ die("unable to connect to %s:\n%s", host, error_message.buf);
if (flags & CONNECT_VERBOSE)
fprintf(stderr, "done.\n");
+ strbuf_release(&error_message);
+
return sockfd;
}
@@ -395,26 +392,28 @@ static int git_use_proxy(const char *host)
return (git_proxy_command && *git_proxy_command);
}
-static void git_proxy_connect(int fd[2], char *host)
+static struct child_process *git_proxy_connect(int fd[2], char *host)
{
const char *port = STR(DEFAULT_GIT_PORT);
- const char *argv[4];
- struct child_process proxy;
+ const char **argv;
+ struct child_process *proxy;
get_host_and_port(&host, &port);
+ argv = xmalloc(sizeof(*argv) * 4);
argv[0] = git_proxy_command;
argv[1] = host;
argv[2] = port;
argv[3] = NULL;
- memset(&proxy, 0, sizeof(proxy));
- proxy.argv = argv;
- proxy.in = -1;
- proxy.out = -1;
- if (start_command(&proxy))
+ proxy = xcalloc(1, sizeof(*proxy));
+ proxy->argv = argv;
+ proxy->in = -1;
+ proxy->out = -1;
+ if (start_command(proxy))
die("cannot start proxy %s", argv[0]);
- fd[0] = proxy.out; /* read from proxy stdout */
- fd[1] = proxy.in; /* write to proxy stdin */
+ fd[0] = proxy->out; /* read from proxy stdout */
+ fd[1] = proxy->in; /* write to proxy stdin */
+ return proxy;
}
#define MAX_CMD_LEN 1024
@@ -455,7 +454,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
char *host, *path;
char *end;
int c;
- struct child_process *conn;
+ struct child_process *conn = &no_fork;
enum protocol protocol = PROTO_LOCAL;
int free_path = 0;
char *port = NULL;
@@ -540,7 +539,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
*/
char *target_host = xstrdup(host);
if (git_use_proxy(host))
- git_proxy_connect(fd, host);
+ conn = git_proxy_connect(fd, host);
else
git_tcp_connect(fd, host, flags);
/*
@@ -558,7 +557,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
free(url);
if (free_path)
free(path);
- return &no_fork;
+ return conn;
}
conn = xcalloc(1, sizeof(*conn));
@@ -607,10 +606,15 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
return conn;
}
+int git_connection_is_socket(struct child_process *conn)
+{
+ return conn == &no_fork;
+}
+
int finish_connect(struct child_process *conn)
{
int code;
- if (!conn || conn == &no_fork)
+ if (!conn || git_connection_is_socket(conn))
return 0;
code = finish_command(conn);
@@ -621,15 +625,22 @@ int finish_connect(struct child_process *conn)
char *git_getpass(const char *prompt)
{
- char *askpass;
+ const char *askpass;
struct child_process pass;
const char *args[3];
static struct strbuf buffer = STRBUF_INIT;
askpass = getenv("GIT_ASKPASS");
-
- if (!askpass || !(*askpass))
- return getpass(prompt);
+ if (!askpass)
+ askpass = askpass_program;
+ if (!askpass)
+ askpass = getenv("SSH_ASKPASS");
+ if (!askpass || !(*askpass)) {
+ char *result = getpass(prompt);
+ if (!result)
+ die_errno("Could not read password");
+ return result;
+ }
args[0] = askpass;
args[1] = prompt;
diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py
index d0627e0..9775dff 100755
--- a/contrib/ciabot/ciabot.py
+++ b/contrib/ciabot/ciabot.py
@@ -122,7 +122,7 @@ def report(refname, merged):
branch = os.path.basename(refname)
# Compute a shortnane for the revision
- rev = do("git describe ${merged} 2>/dev/null") or merged[:12]
+ rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12]
# Extract the neta-information for the commit
rawcommit = do("git cat-file commit " + merged)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 6756990..5a83090 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1,6 +1,6 @@
#!bash
#
-# bash completion support for core Git.
+# bash/zsh completion support for core Git.
#
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
@@ -18,11 +18,12 @@
# To use these routines:
#
# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
-# 2) Added the following line to your .bashrc:
+# 2) Add the following line to your .bashrc/.zshrc:
# source ~/.git-completion.sh
#
# 3) Consider changing your PS1 to also show the current branch:
-# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
#
# The argument to __git_ps1 will be displayed only if you
# are currently in a git repository. The %s token will be
@@ -72,6 +73,10 @@
# git@vger.kernel.org
#
+if [[ -n ${ZSH_VERSION-} ]]; then
+ autoload -U +X bashcompinit && bashcompinit
+fi
+
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
@@ -138,11 +143,12 @@ __git_ps1_show_upstream ()
# get the upstream from the "git-svn-id: ..." in a commit message
# (git-svn uses essentially the same procedure internally)
local svn_upstream=($(git log --first-parent -1 \
- --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
svn_upstream=${svn_upstream%@*}
- for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+ local n_stop="${#svn_remote[@]}"
+ for ((n=1; n <= n_stop; ++n)); do
svn_upstream=${svn_upstream#${svn_remote[$n]}}
done
@@ -240,6 +246,8 @@ __git_ps1 ()
fi
elif [ -f "$g/MERGE_HEAD" ]; then
r="|MERGING"
+ elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+ r="|CHERRY-PICKING"
elif [ -f "$g/BISECT_LOG" ]; then
r="|BISECTING"
fi
@@ -255,7 +263,7 @@ __git_ps1 ()
(describe)
git describe HEAD ;;
(* | default)
- git describe --exact-match HEAD ;;
+ git describe --tags --exact-match HEAD ;;
esac 2>/dev/null)" ||
b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
@@ -321,15 +329,172 @@ __gitcomp_1 ()
done
}
+# The following function is based on code from:
+#
+# bash_completion - programmable completion functions for bash 3.2+
+#
+# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
+# © 2009-2010, Bash Completion Maintainers
+# <bash-completion-devel@lists.alioth.debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# 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, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# The latest version of this software can be obtained here:
+#
+# http://bash-completion.alioth.debian.org/
+#
+# RELEASE: 2.x
+
+# This function can be used to access a tokenized list of words
+# on the command line:
+#
+# __git_reassemble_comp_words_by_ref '=:'
+# if test "${words_[cword_-1]}" = -w
+# then
+# ...
+# fi
+#
+# The argument should be a collection of characters from the list of
+# word completion separators (COMP_WORDBREAKS) to treat as ordinary
+# characters.
+#
+# This is roughly equivalent to going back in time and setting
+# COMP_WORDBREAKS to exclude those characters. The intent is to
+# make option types like --date=<type> and <rev>:<path> easy to
+# recognize by treating each shell word as a single token.
+#
+# It is best not to set COMP_WORDBREAKS directly because the value is
+# shared with other completion scripts. By the time the completion
+# function gets called, COMP_WORDS has already been populated so local
+# changes to COMP_WORDBREAKS have no effect.
+#
+# Output: words_, cword_, cur_.
+
+__git_reassemble_comp_words_by_ref()
+{
+ local exclude i j first
+ # Which word separators to exclude?
+ exclude="${1//[^$COMP_WORDBREAKS]}"
+ cword_=$COMP_CWORD
+ if [ -z "$exclude" ]; then
+ words_=("${COMP_WORDS[@]}")
+ return
+ fi
+ # List of word completion separators has shrunk;
+ # re-assemble words to complete.
+ for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+ # Append each nonempty word consisting of just
+ # word separator characters to the current word.
+ first=t
+ while
+ [ $i -gt 0 ] &&
+ [ -n "${COMP_WORDS[$i]}" ] &&
+ # word consists of excluded word separators
+ [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
+ do
+ # Attach to the previous token,
+ # unless the previous token is the command name.
+ if [ $j -ge 2 ] && [ -n "$first" ]; then
+ ((j--))
+ fi
+ first=
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ if (($i < ${#COMP_WORDS[@]} - 1)); then
+ ((i++))
+ else
+ # Done.
+ return
+ fi
+ done
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ done
+}
+
+if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
+if [[ -z ${ZSH_VERSION:+set} ]]; then
+_get_comp_words_by_ref ()
+{
+ local exclude cur_ words_ cword_
+ if [ "$1" = "-n" ]; then
+ exclude=$2
+ shift 2
+ fi
+ __git_reassemble_comp_words_by_ref "$exclude"
+ cur_=${words_[cword_]}
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=$cur_
+ ;;
+ prev)
+ prev=${words_[$cword_-1]}
+ ;;
+ words)
+ words=("${words_[@]}")
+ ;;
+ cword)
+ cword=$cword_
+ ;;
+ esac
+ shift
+ done
+}
+else
+_get_comp_words_by_ref ()
+{
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=${COMP_WORDS[COMP_CWORD]}
+ ;;
+ prev)
+ prev=${COMP_WORDS[COMP_CWORD-1]}
+ ;;
+ words)
+ words=("${COMP_WORDS[@]}")
+ ;;
+ cword)
+ cword=$COMP_CWORD
+ ;;
+ -n)
+ # assume COMP_WORDBREAKS is already set sanely
+ shift
+ ;;
+ esac
+ shift
+ done
+}
+fi
+fi
+
# __gitcomp accepts 1, 2, 3, or 4 arguments
# generates completion reply with compgen
__gitcomp ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur_="$cur"
+
if [ $# -gt 2 ]; then
- cur="$3"
+ cur_="$3"
fi
- case "$cur" in
+ case "$cur_" in
--*=)
COMPREPLY=()
;;
@@ -337,7 +502,7 @@ __gitcomp ()
local IFS=$'\n'
COMPREPLY=($(compgen -P "${2-}" \
-W "$(__gitcomp_1 "${1-}" "${4-}")" \
- -- "$cur"))
+ -- "$cur_"))
;;
esac
}
@@ -380,16 +545,19 @@ __git_tags ()
done
}
-# __git_refs accepts 0 or 1 arguments (to pass to __gitdir)
+# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
+# presence of 2nd argument means use the guess heuristic employed
+# by checkout for tracking branches
__git_refs ()
{
- local i is_hash=y dir="$(__gitdir "${1-}")"
- local cur="${COMP_WORDS[COMP_CWORD]}" format refs
+ local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}"
+ local format refs
if [ -d "$dir" ]; then
case "$cur" in
refs|refs/*)
format="refname"
refs="${cur%/*}"
+ track=""
;;
*)
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
@@ -401,6 +569,21 @@ __git_refs ()
esac
git --git-dir="$dir" for-each-ref --format="%($format)" \
$refs
+ if [ -n "$track" ]; then
+ # employ the heuristic used by git checkout
+ # Try to find a remote branch that matches the completion word
+ # but only output if the branch name is unique
+ local ref entry
+ git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
+ "refs/remotes/" | \
+ while read entry; do
+ eval "$entry"
+ ref="${ref#*/}"
+ if [[ "$ref" == "$cur"* ]]; then
+ echo "$ref"
+ fi
+ done | uniq -u
+ fi
return
fi
for i in $(git ls-remote "$dir" 2>/dev/null); do
@@ -445,12 +628,12 @@ __git_refs_remotes ()
__git_remotes ()
{
local i ngoff IFS=$'\n' d="$(__gitdir)"
- shopt -q nullglob || ngoff=1
- shopt -s nullglob
+ __git_shopt -q nullglob || ngoff=1
+ __git_shopt -s nullglob
for i in "$d/remotes"/*; do
echo ${i#$d/remotes/}
done
- [ "$ngoff" ] && shopt -u nullglob
+ [ "$ngoff" ] && __git_shopt -u nullglob
for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
i="${i#remote.}"
echo "${i/.url*/}"
@@ -480,24 +663,27 @@ __git_compute_merge_strategies ()
: ${__git_merge_strategies:=$(__git_list_merge_strategies)}
}
-__git_complete_file ()
+__git_complete_revlist_file ()
{
- local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
- case "$cur" in
+ local pfx ls ref cur_="$cur"
+ case "$cur_" in
+ *..?*:*)
+ return
+ ;;
?*:*)
- ref="${cur%%:*}"
- cur="${cur#*:}"
- case "$cur" in
+ ref="${cur_%%:*}"
+ cur_="${cur_#*:}"
+ case "$cur_" in
?*/*)
- pfx="${cur%/*}"
- cur="${cur##*/}"
+ pfx="${cur_%/*}"
+ cur_="${cur_##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
*)
ls="$ref"
;;
- esac
+ esac
case "$COMP_WORDBREAKS" in
*:*) : great ;;
@@ -520,27 +706,17 @@ __git_complete_file ()
s,$,/,
}
s/^.* //')" \
- -- "$cur"))
- ;;
- *)
- __gitcomp "$(__git_refs)"
+ -- "$cur_"))
;;
- esac
-}
-
-__git_complete_revlist ()
-{
- local pfx cur="${COMP_WORDS[COMP_CWORD]}"
- case "$cur" in
*...*)
- pfx="${cur%...*}..."
- cur="${cur#*...}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ pfx="${cur_%...*}..."
+ cur_="${cur_#*...}"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
;;
*..*)
- pfx="${cur%..*}.."
- cur="${cur#*..}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ pfx="${cur_%..*}.."
+ cur_="${cur_#*..}"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
;;
*)
__gitcomp "$(__git_refs)"
@@ -548,13 +724,23 @@ __git_complete_revlist ()
esac
}
+
+__git_complete_file ()
+{
+ __git_complete_revlist_file
+}
+
+__git_complete_revlist ()
+{
+ __git_complete_revlist_file
+}
+
__git_complete_remote_or_refspec ()
{
- local cmd="${COMP_WORDS[1]}"
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur_="$cur" cmd="${words[1]}"
local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
--all)
@@ -581,40 +767,40 @@ __git_complete_remote_or_refspec ()
return
fi
[ "$remote" = "." ] && remote=
- case "$cur" in
+ case "$cur_" in
*:*)
case "$COMP_WORDBREAKS" in
*:*) : great ;;
- *) pfx="${cur%%:*}:" ;;
+ *) pfx="${cur_%%:*}:" ;;
esac
- cur="${cur#*:}"
+ cur_="${cur_#*:}"
lhs=0
;;
+*)
pfx="+"
- cur="${cur#+}"
+ cur_="${cur_#+}"
;;
esac
case "$cmd" in
fetch)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
fi
;;
pull)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
fi
;;
push)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
fi
;;
esac
@@ -623,12 +809,11 @@ __git_complete_remote_or_refspec ()
__git_complete_strategy ()
{
__git_compute_merge_strategies
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-s|--strategy)
__gitcomp "$__git_merge_strategies"
return 0
esac
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--strategy=*)
__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
@@ -711,7 +896,6 @@ __git_list_porcelain_commands ()
quiltimport) : import;;
read-tree) : plumbing;;
receive-pack) : plumbing;;
- reflog) : plumbing;;
remote-*) : transport;;
repo-config) : deprecated;;
rerere) : plumbing;;
@@ -750,6 +934,19 @@ __git_compute_porcelain_commands ()
: ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
}
+__git_pretty_aliases ()
+{
+ local i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
+ case "$i" in
+ pretty.*)
+ i="${i#pretty.}"
+ echo "${i/ */}"
+ ;;
+ esac
+ done
+}
+
__git_aliases ()
{
local i IFS=$'\n'
@@ -789,9 +986,8 @@ __git_aliased_command ()
__git_find_on_cmdline ()
{
local word subcommand c=1
-
- while [ $c -lt $COMP_CWORD ]; do
- word="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ word="${words[c]}"
for subcommand in $1; do
if [ "$subcommand" = "$word" ]; then
echo "$subcommand"
@@ -805,8 +1001,8 @@ __git_find_on_cmdline ()
__git_has_doubledash ()
{
local c=1
- while [ $c -lt $COMP_CWORD ]; do
- if [ "--" = "${COMP_WORDS[c]}" ]; then
+ while [ $c -lt $cword ]; do
+ if [ "--" = "${words[c]}" ]; then
return 0
fi
c=$((++c))
@@ -818,7 +1014,7 @@ __git_whitespacelist="nowarn warn error error-all fix"
_git_am ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local dir="$(__gitdir)"
if [ -d "$dir"/rebase-apply ]; then
__gitcomp "--skip --continue --resolved --abort"
return
@@ -842,7 +1038,6 @@ _git_am ()
_git_apply ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--whitespace=*)
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
@@ -865,7 +1060,6 @@ _git_add ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -879,7 +1073,6 @@ _git_add ()
_git_archive ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--format=*)
__gitcomp "$(git archive --list)" "" "${cur##--format=}"
@@ -907,12 +1100,16 @@ _git_bisect ()
local subcommands="start bad good skip reset visualize replay log run"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- __gitcomp "$subcommands"
+ if [ -f "$(__gitdir)"/BISECT_START ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "replay start"
+ fi
return
fi
case "$subcommand" in
- bad|good|reset|skip)
+ bad|good|reset|skip|start)
__gitcomp "$(__git_refs)"
;;
*)
@@ -925,8 +1122,8 @@ _git_branch ()
{
local i c=1 only_local_ref="n" has_r="n"
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-m) only_local_ref="y" ;;
-r) has_r="y" ;;
@@ -934,7 +1131,7 @@ _git_branch ()
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*)
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
@@ -954,8 +1151,8 @@ _git_branch ()
_git_bundle ()
{
- local cmd="${COMP_WORDS[2]}"
- case "$COMP_CWORD" in
+ local cmd="${words[2]}"
+ case "$cword" in
2)
__gitcomp "create list-heads verify unbundle"
;;
@@ -976,7 +1173,6 @@ _git_checkout ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
@@ -988,7 +1184,13 @@ _git_checkout ()
"
;;
*)
- __gitcomp "$(__git_refs)"
+ # check if --track, --no-track, or --no-guess was specified
+ # if so, disable DWIM mode
+ local flags="--track --no-track --no-guess" track=1
+ if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
+ track=''
+ fi
+ __gitcomp "$(__git_refs '' $track)"
;;
esac
}
@@ -1000,7 +1202,6 @@ _git_cherry ()
_git_cherry_pick ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--edit --no-commit"
@@ -1015,7 +1216,6 @@ _git_clean ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--dry-run --quiet"
@@ -1027,7 +1227,6 @@ _git_clean ()
_git_clone ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1054,7 +1253,6 @@ _git_commit ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--cleanup=*)
__gitcomp "default strip verbatim whitespace
@@ -1089,7 +1287,6 @@ _git_commit ()
_git_describe ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1121,28 +1318,26 @@ _git_diff ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
- --base --ours --theirs
+ --base --ours --theirs --no-index
$__git_diff_common_options
"
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis p4merge
+ tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3
"
_git_difftool ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
@@ -1167,7 +1362,6 @@ __git_fetch_options="
_git_fetch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "$__git_fetch_options"
@@ -1179,7 +1373,6 @@ _git_fetch ()
_git_format_patch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--thread=*)
__gitcomp "
@@ -1211,7 +1404,6 @@ _git_format_patch ()
_git_fsck ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1226,7 +1418,6 @@ _git_fsck ()
_git_gc ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--prune --aggressive"
@@ -1245,14 +1436,14 @@ _git_grep ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
--cached
--text --ignore-case --word-regexp --invert-match
- --full-name
+ --full-name --line-number
--extended-regexp --basic-regexp --fixed-strings
+ --perl-regexp
--files-with-matches --name-only
--files-without-match
--max-depth
@@ -1268,7 +1459,6 @@ _git_grep ()
_git_help ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--all --info --man --web"
@@ -1276,7 +1466,7 @@ _git_help ()
;;
esac
__git_compute_all_commands
- __gitcomp "$__git_all_commands
+ __gitcomp "$__git_all_commands $(__git_aliases)
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
repository-layout tutorial tutorial-2
@@ -1286,7 +1476,6 @@ _git_help ()
_git_init ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--shared=*)
__gitcomp "
@@ -1306,7 +1495,6 @@ _git_ls_files ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --deleted --modified --others --ignored
@@ -1340,12 +1528,14 @@ __git_log_common_options="
--max-count=
--max-age= --since= --after=
--min-age= --until= --before=
+ --min-parents= --max-parents=
+ --no-min-parents --no-max-parents
"
# Options that go well for log and gitk (not shortlog)
__git_log_gitk_options="
--dense --sparse --full-history
--simplify-merges --simplify-by-decoration
- --left-right
+ --left-right --notes --no-notes
"
# Options that go well for log and shortlog (not gitk)
__git_log_shortlog_options="
@@ -1360,7 +1550,6 @@ _git_log ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
local g="$(git rev-parse --git-dir 2>/dev/null)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
@@ -1368,12 +1557,12 @@ _git_log ()
fi
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
@@ -1419,7 +1608,6 @@ _git_merge ()
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "$__git_merge_options"
@@ -1430,7 +1618,6 @@ _git_merge ()
_git_mergetool ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
@@ -1451,7 +1638,6 @@ _git_merge_base ()
_git_mv ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--dry-run"
@@ -1468,18 +1654,49 @@ _git_name_rev ()
_git_notes ()
{
- local subcommands="edit show"
- if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- __gitcomp "$subcommands"
- return
- fi
+ local subcommands='add append copy edit list prune remove show'
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
- case "${COMP_WORDS[COMP_CWORD-1]}" in
- -m|-F)
- COMPREPLY=()
+ case "$subcommand,$cur" in
+ ,--*)
+ __gitcomp '--ref'
+ ;;
+ ,*)
+ case "${words[cword-1]}" in
+ --ref)
+ __gitcomp "$(__git_refs)"
+ ;;
+ *)
+ __gitcomp "$subcommands --ref"
+ ;;
+ esac
+ ;;
+ add,--reuse-message=*|append,--reuse-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
+ ;;
+ add,--reedit-message=*|append,--reedit-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+ ;;
+ add,--*|append,--*)
+ __gitcomp '--file= --message= --reedit-message=
+ --reuse-message='
+ ;;
+ copy,--*)
+ __gitcomp '--stdin'
+ ;;
+ prune,--*)
+ __gitcomp '--dry-run --verbose'
+ ;;
+ prune,*)
;;
*)
- __gitcomp "$(__git_refs)"
+ case "${words[cword-1]}" in
+ -m|-F)
+ ;;
+ *)
+ __gitcomp "$(__git_refs)"
+ ;;
+ esac
;;
esac
}
@@ -1488,7 +1705,6 @@ _git_pull ()
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1504,8 +1720,7 @@ _git_pull ()
_git_push ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
--repo)
__gitcomp "$(__git_remotes)"
return
@@ -1528,7 +1743,7 @@ _git_push ()
_git_rebase ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local dir="$(__gitdir)"
if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
__gitcomp "--continue --skip --abort"
return
@@ -1553,12 +1768,23 @@ _git_rebase ()
__gitcomp "$(__git_refs)"
}
+_git_reflog ()
+{
+ local subcommands="show delete expire"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "$(__git_refs)"
+ fi
+}
+
__git_send_email_confirm_options="always never auto cc compose"
__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
_git_send_email ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--confirm=*)
__gitcomp "
@@ -1600,9 +1826,9 @@ _git_stage ()
__git_config_get_set_variables ()
{
- local prevword word config_file= c=$COMP_CWORD
+ local prevword word config_file= c=$cword
while [ $c -gt 1 ]; do
- word="${COMP_WORDS[c]}"
+ word="${words[c]}"
case "$word" in
--global|--system|--file=*)
config_file="$word"
@@ -1630,9 +1856,7 @@ __git_config_get_set_variables ()
_git_config ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local prv="${COMP_WORDS[COMP_CWORD-1]}"
- case "$prv" in
+ case "$prev" in
branch.*.remote)
__gitcomp "$(__git_remotes)"
return
@@ -1642,13 +1866,13 @@ _git_config ()
return
;;
remote.*.fetch)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.fetch}"
__gitcomp "$(__git_refs_remotes "$remote")"
return
;;
remote.*.push)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.push}"
__gitcomp "$(git --git-dir="$(__gitdir)" \
for-each-ref --format='%(refname):%(refname)' \
@@ -1717,98 +1941,108 @@ _git_config ()
return
;;
branch.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_"
return
;;
branch.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
- __gitcomp "$(__git_heads)" "$pfx" "$cur" "."
+ local pfx="${cur%.*}." cur_="${cur#*.}"
+ __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
return
;;
guitool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
__gitcomp "
argprompt cmd confirm needsfile noconsole norescan
prompt revprompt revunmerged title
- " "$pfx" "$cur"
+ " "$pfx" "$cur_"
return
;;
difftool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_"
return
;;
man.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_"
return
;;
mergetool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path trustExitCode" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path trustExitCode" "$pfx" "$cur_"
return
;;
pager.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
+ local pfx="${cur%.*}." cur_="${cur#*.}"
__git_compute_all_commands
- __gitcomp "$__git_all_commands" "$pfx" "$cur"
+ __gitcomp "$__git_all_commands" "$pfx" "$cur_"
return
;;
remote.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
__gitcomp "
url proxy fetch push mirror skipDefaultUpdate
receivepack uploadpack tagopt pushurl
- " "$pfx" "$cur"
+ " "$pfx" "$cur_"
return
;;
remote.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
- __gitcomp "$(__git_remotes)" "$pfx" "$cur" "."
+ local pfx="${cur%.*}." cur_="${cur#*.}"
+ __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
return
;;
url.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
return
;;
esac
__gitcomp "
- add.ignore-errors
+ add.ignoreErrors
+ advice.commitBeforeMerge
+ advice.detachedHead
+ advice.implicitIdentity
+ advice.pushNonFastForward
+ advice.resolveConflict
+ advice.statusHints
alias.
+ am.keepcr
apply.ignorewhitespace
apply.whitespace
branch.autosetupmerge
branch.autosetuprebase
+ browser.
clean.requireForce
color.branch
color.branch.current
color.branch.local
color.branch.plain
color.branch.remote
+ color.decorate.HEAD
+ color.decorate.branch
+ color.decorate.remoteBranch
+ color.decorate.stash
+ color.decorate.tag
color.diff
color.diff.commit
color.diff.frag
+ color.diff.func
color.diff.meta
color.diff.new
color.diff.old
color.diff.plain
color.diff.whitespace
color.grep
- color.grep.external
+ color.grep.context
+ color.grep.filename
+ color.grep.function
+ color.grep.linenumber
color.grep.match
+ color.grep.selected
+ color.grep.separator
color.interactive
+ color.interactive.error
color.interactive.header
color.interactive.help
color.interactive.prompt
@@ -1822,21 +2056,29 @@ _git_config ()
color.status.untracked
color.status.updated
color.ui
+ commit.status
commit.template
+ core.abbrev
+ core.askpass
+ core.attributesfile
core.autocrlf
core.bare
+ core.bigFileThreshold
core.compression
core.createObject
core.deltaBaseCacheLimit
core.editor
+ core.eol
core.excludesfile
core.fileMode
core.fsyncobjectfiles
core.gitProxy
core.ignoreCygwinFSTricks
core.ignoreStat
+ core.ignorecase
core.logAllRefUpdates
core.loosecompression
+ core.notesRef
core.packedGitLimit
core.packedGitWindowSize
core.pager
@@ -1846,6 +2088,7 @@ _git_config ()
core.repositoryFormatVersion
core.safecrlf
core.sharedRepository
+ core.sparseCheckout
core.symlinks
core.trustctime
core.warnAmbiguousRefs
@@ -1853,15 +2096,17 @@ _git_config ()
core.worktree
diff.autorefreshindex
diff.external
+ diff.ignoreSubmodules
diff.mnemonicprefix
+ diff.noprefix
diff.renameLimit
- diff.renameLimit.
diff.renames
diff.suppressBlankEmpty
diff.tool
diff.wordRegex
difftool.
difftool.prompt
+ fetch.recurseSubmodules
fetch.unpackLimit
format.attach
format.cc
@@ -1873,6 +2118,8 @@ _git_config ()
format.subjectprefix
format.suffix
format.thread
+ format.to
+ gc.
gc.aggressiveWindow
gc.auto
gc.autopacklimit
@@ -1910,15 +2157,20 @@ _git_config ()
http.lowSpeedLimit
http.lowSpeedTime
http.maxRequests
+ http.minSessions
http.noEPSV
+ http.postBuffer
http.proxy
http.sslCAInfo
http.sslCAPath
http.sslCert
+ http.sslCertPasswordProtected
http.sslKey
http.sslVerify
+ http.useragent
i18n.commitEncoding
i18n.logOutputEncoding
+ imap.authMethod
imap.folder
imap.host
imap.pass
@@ -1927,6 +2179,7 @@ _git_config ()
imap.sslverify
imap.tunnel
imap.user
+ init.templatedir
instaweb.browser
instaweb.httpd
instaweb.local
@@ -1934,19 +2187,29 @@ _git_config ()
instaweb.port
interactive.singlekey
log.date
+ log.decorate
log.showroot
mailmap.file
man.
man.viewer
+ merge.
merge.conflictstyle
merge.log
merge.renameLimit
+ merge.renormalize
merge.stat
merge.tool
merge.verbosity
mergetool.
mergetool.keepBackup
+ mergetool.keepTemporaries
mergetool.prompt
+ notes.displayRef
+ notes.rewrite.
+ notes.rewrite.amend
+ notes.rewrite.rebase
+ notes.rewriteMode
+ notes.rewriteRef
pack.compression
pack.deltaCacheLimit
pack.deltaCacheSize
@@ -1957,31 +2220,42 @@ _git_config ()
pack.window
pack.windowMemory
pager.
+ pretty.
pull.octopus
pull.twohead
push.default
+ rebase.autosquash
rebase.stat
+ receive.autogc
receive.denyCurrentBranch
+ receive.denyDeleteCurrent
receive.denyDeletes
receive.denyNonFastForwards
receive.fsckObjects
receive.unpackLimit
+ receive.updateserverinfo
+ remotes.
repack.usedeltabaseoffset
rerere.autoupdate
rerere.enabled
+ sendemail.
sendemail.aliasesfile
- sendemail.aliasesfiletype
+ sendemail.aliasfiletype
sendemail.bcc
sendemail.cc
sendemail.cccmd
sendemail.chainreplyto
sendemail.confirm
sendemail.envelopesender
+ sendemail.from
+ sendemail.identity
sendemail.multiedit
sendemail.signedoffbycc
+ sendemail.smtpdomain
sendemail.smtpencryption
sendemail.smtppass
sendemail.smtpserver
+ sendemail.smtpserveroption
sendemail.smtpserverport
sendemail.smtpuser
sendemail.suppresscc
@@ -1992,6 +2266,8 @@ _git_config ()
showbranch.default
status.relativePaths
status.showUntrackedFiles
+ status.submodulesummary
+ submodule.
tar.umask
transfer.unpackLimit
url.
@@ -2039,7 +2315,6 @@ _git_reset ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--merge --mixed --hard --soft --patch"
@@ -2051,7 +2326,6 @@ _git_reset ()
_git_revert ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--edit --mainline --no-edit --no-commit --signoff"
@@ -2065,7 +2339,6 @@ _git_rm ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --dry-run --ignore-unmatch --quiet"
@@ -2079,7 +2352,6 @@ _git_shortlog ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -2097,15 +2369,14 @@ _git_show ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
@@ -2121,7 +2392,6 @@ _git_show ()
_git_show_branch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -2138,7 +2408,6 @@ _git_show_branch ()
_git_stash ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
local save_opts='--keep-index --no-keep-index --quiet --patch'
local subcommands='save list show apply clear drop pop create branch'
local subcommand="$(__git_find_on_cmdline "$subcommands")"
@@ -2183,7 +2452,6 @@ _git_submodule ()
local subcommands="add status init update summary foreach sync"
if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--quiet --cached"
@@ -2227,7 +2495,6 @@ _git_svn ()
--edit --rmdir --find-copies-harder --copy-similarity=
"
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$subcommand,$cur" in
fetch,--*)
__gitcomp "--revision= --fetch-all $fc_opts"
@@ -2299,8 +2566,8 @@ _git_svn ()
_git_tag ()
{
local i c=1 f=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-v)
__gitcomp "$(__git_tags)"
@@ -2313,7 +2580,7 @@ _git_tag ()
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-m|-F)
COMPREPLY=()
;;
@@ -2339,8 +2606,19 @@ _git ()
{
local i c=1 command __git_dir
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+ fi
+
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;;
@@ -2352,7 +2630,7 @@ _git ()
done
if [ -z "$command" ]; then
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*) __gitcomp "
--paginate
--no-pager
@@ -2372,20 +2650,31 @@ _git ()
fi
local completion_func="_git_${command//-/_}"
- declare -F $completion_func >/dev/null && $completion_func && return
+ declare -f $completion_func >/dev/null && $completion_func && return
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
completion_func="_git_${expansion//-/_}"
- declare -F $completion_func >/dev/null && $completion_func
+ declare -f $completion_func >/dev/null && $completion_func
fi
}
_gitk ()
{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+ fi
+
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
local g="$(__gitdir)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
@@ -2417,3 +2706,33 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
|| complete -o default -o nospace -F _git git.exe
fi
+
+if [[ -n ${ZSH_VERSION-} ]]; then
+ __git_shopt () {
+ local option
+ if [ $# -ne 2 ]; then
+ echo "USAGE: $0 (-q|-s|-u) <option>" >&2
+ return 1
+ fi
+ case "$2" in
+ nullglob)
+ option="$2"
+ ;;
+ *)
+ echo "$0: invalid option: $2" >&2
+ return 1
+ esac
+ case "$1" in
+ -q) setopt | grep -q "$option" ;;
+ -u) unsetopt "$option" ;;
+ -s) setopt "$option" ;;
+ *)
+ echo "$0: invalid flag: $1" >&2
+ return 1
+ esac
+ }
+else
+ __git_shopt () {
+ shopt "$@"
+ }
+fi
diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt
index 9718abf..0565d83 100644
--- a/contrib/convert-objects/git-convert-objects.txt
+++ b/contrib/convert-objects/git-convert-objects.txt
@@ -8,6 +8,7 @@ git-convert-objects - Converts old-style git repository
SYNOPSIS
--------
+[verse]
'git-convert-objects'
DESCRIPTION
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index 7f4c792..d351cfb 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -79,6 +79,7 @@
;;; Code:
(eval-when-compile (require 'cl)) ; to use `push', `pop'
+(require 'format-spec)
(defface git-blame-prefix-face
'((((background dark)) (:foreground "gray"
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 214930a..65c95d9 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1310,6 +1310,13 @@ The FILES list must be sorted."
(when sign-off (git-append-sign-off committer-name committer-email)))
buffer))
+(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit"
+ "Major mode for editing git log messages.
+
+Set up git-specific `font-lock-keywords' for `log-edit-mode'."
+ (set (make-local-variable 'font-lock-defaults)
+ '(git-log-edit-font-lock-keywords t t)))
+
(defun git-commit-file ()
"Commit the marked file(s), asking for a commit message."
(interactive)
@@ -1335,9 +1342,9 @@ The FILES list must be sorted."
(git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date))
(if (boundp 'log-edit-diff-function)
(log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
- (log-edit-diff-function . git-log-edit-diff)) buffer)
- (log-edit 'git-do-commit nil 'git-log-edit-files buffer))
- (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+ (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode)
+ (log-edit 'git-do-commit nil 'git-log-edit-files buffer
+ 'git-log-edit-mode))
(setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$"))
(setq buffer-file-coding-system coding-system)
(re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
index cd10dbc..3140e40 100644
--- a/contrib/examples/builtin-fetch--tool.c
+++ b/contrib/examples/builtin-fetch--tool.c
@@ -148,7 +148,7 @@ static int append_fetch_head(FILE *fp,
what = remote_name + 10;
}
else if (!strncmp(remote_name, "refs/remotes/", 13)) {
- kind = "remote branch";
+ kind = "remote-tracking branch";
what = remote_name + 13;
}
else {
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
index 5c72f65..23ffb02 100755
--- a/contrib/examples/git-commit.sh
+++ b/contrib/examples/git-commit.sh
@@ -631,7 +631,7 @@ then
if test -z "$quiet"
then
commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
- --summary --root HEAD --`
+ --abbrev --summary --root HEAD --`
echo "Created${initial_commit:+ initial} commit $commit"
fi
fi
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
index 8f617fc..7b922c3 100755
--- a/contrib/examples/git-merge.sh
+++ b/contrib/examples/git-merge.sh
@@ -15,7 +15,10 @@ log add list of one-line log to merge commit message
squash create a single commit instead of doing a merge
commit perform a commit if the merge succeeds (default)
ff allow fast-forward (default)
+ff-only abort if fast-forward is not possible
+rerere-autoupdate update index with any reused conflict resolution
s,strategy= merge strategy to use
+X= option for selected merge strategy
m,message= message to be used for the merge commit (if any)
"
@@ -25,26 +28,32 @@ require_work_tree
cd_to_toplevel
test -z "$(git ls-files -u)" ||
- die "You are in the middle of a conflicted merge."
+ die "Merge is not possible because you have unmerged files."
+
+! test -e "$GIT_DIR/MERGE_HEAD" ||
+ die 'You have not concluded your merge (MERGE_HEAD exists).'
LF='
'
all_strategies='recur recursive octopus resolve stupid ours subtree'
all_strategies="$all_strategies recursive-ours recursive-theirs"
+not_strategies='base file index tree'
default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_fast_forward_strategies='subtree ours'
no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
use_strategies=
+xopt=
allow_fast_forward=t
+fast_forward_only=
allow_trivial_merge=t
-squash= no_commit= log_arg=
+squash= no_commit= log_arg= rr_arg=
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_STASH" || exit 1
+ "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1
}
savestate() {
@@ -131,21 +140,34 @@ finish () {
merge_name () {
remote="$1"
rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
- bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
- if test "$rh" = "$bh"
- then
- echo "$rh branch '$remote' of ."
- elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+ if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') &&
git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
then
echo "$rh branch '$truname' (early part) of ."
- elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+ return
+ fi
+ if found_ref=$(git rev-parse --symbolic-full-name --verify \
+ "$remote" 2>/dev/null)
+ then
+ expanded=$(git check-ref-format --branch "$remote") ||
+ exit
+ if test "${found_ref#refs/heads/}" != "$found_ref"
+ then
+ echo "$rh branch '$expanded' of ."
+ return
+ elif test "${found_ref#refs/remotes/}" != "$found_ref"
+ then
+ echo "$rh remote branch '$expanded' of ."
+ return
+ fi
+ fi
+ if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
then
sed -e 's/ not-for-merge / /' -e 1q \
"$GIT_DIR/FETCH_HEAD"
- else
- echo "$rh commit '$remote'"
+ return
fi
+ echo "$rh commit '$remote'"
}
parse_config () {
@@ -172,16 +194,36 @@ parse_config () {
--no-ff)
test "$squash" != t ||
die "You cannot combine --squash with --no-ff."
+ test "$fast_forward_only" != t ||
+ die "You cannot combine --ff-only with --no-ff."
allow_fast_forward=f ;;
+ --ff-only)
+ test "$allow_fast_forward" != f ||
+ die "You cannot combine --ff-only with --no-ff."
+ fast_forward_only=t ;;
+ --rerere-autoupdate|--no-rerere-autoupdate)
+ rr_arg=$1 ;;
-s|--strategy)
shift
case " $all_strategies " in
*" $1 "*)
- use_strategies="$use_strategies$1 " ;;
+ use_strategies="$use_strategies$1 "
+ ;;
*)
- die "available strategies are: $all_strategies" ;;
+ case " $not_strategies " in
+ *" $1 "*)
+ false
+ esac &&
+ type "git-merge-$1" >/dev/null 2>&1 ||
+ die "available strategies are: $all_strategies"
+ use_strategies="$use_strategies$1 "
+ ;;
esac
;;
+ -X)
+ shift
+ xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")"
+ ;;
-m|--message)
shift
merge_msg="$1"
@@ -245,6 +287,10 @@ then
exit 1
fi
+ test "$squash" != t ||
+ die "Squash commit into empty head not supported yet"
+ test "$allow_fast_forward" = t ||
+ die "Non-fast-forward into an empty head does not make sense"
rh=$(git rev-parse --verify "$1^0") ||
die "$1 - not something we can merge"
@@ -261,12 +307,18 @@ else
# the given message. If remote is invalid we will die
# later in the common codepath so we discard the error
# in this loop.
- merge_name=$(for remote
+ merge_msg="$(
+ for remote
do
merge_name "$remote"
- done | git fmt-merge-msg $log_arg
- )
- merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+ done |
+ if test "$have_message" = t
+ then
+ git fmt-merge-msg -m "$merge_msg" $log_arg
+ else
+ git fmt-merge-msg $log_arg
+ fi
+ )"
fi
head=$(git rev-parse --verify "$head_arg"^0) || usage
@@ -335,7 +387,7 @@ case "$#" in
common=$(git merge-base --all $head "$@")
;;
*)
- common=$(git show-branch --merge-base $head "$@")
+ common=$(git merge-base --all --octopus $head "$@")
;;
esac
echo "$head" >"$GIT_DIR/ORIG_HEAD"
@@ -373,8 +425,8 @@ t,1,"$head",*)
# We are not doing octopus, not fast-forward, and have only
# one common.
git update-index --refresh 2>/dev/null
- case "$allow_trivial_merge" in
- t)
+ case "$allow_trivial_merge,$fast_forward_only" in
+ t,)
# See if it is really trivial.
git var GIT_COMMITTER_IDENT >/dev/null || exit
echo "Trying really trivial in-index merge..."
@@ -413,6 +465,11 @@ t,1,"$head",*)
;;
esac
+if test "$fast_forward_only" = t
+then
+ die "Not possible to fast-forward, aborting."
+fi
+
# We are going to make a new commit.
git var GIT_COMMITTER_IDENT >/dev/null || exit
@@ -451,7 +508,7 @@ do
# Remember which strategy left the state in the working tree
wt_strategy=$strategy
- git-merge-$strategy $common -- "$head_arg" "$@"
+ eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"'
exit=$?
if test "$no_commit" = t && test "$exit" = 0
then
@@ -489,9 +546,9 @@ if test '' != "$result_tree"
then
if test "$allow_fast_forward" = "t"
then
- parents=$(git show-branch --independent "$head" "$@")
+ parents=$(git merge-base --independent "$head" "$@")
else
- parents=$(git rev-parse "$head" "$@")
+ parents=$(git rev-parse "$head" "$@")
fi
parents=$(echo "$parents" | sed -e 's/^/-p /')
result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
@@ -533,7 +590,15 @@ else
do
echo $remote
done >"$GIT_DIR/MERGE_HEAD"
- printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+ printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" ||
+ die "Could not write to $GIT_DIR/MERGE_MSG"
+ if test "$allow_fast_forward" != t
+ then
+ printf "%s" no-ff
+ else
+ :
+ fi >"$GIT_DIR/MERGE_MODE" ||
+ die "Could not write to $GIT_DIR/MERGE_MODE"
fi
if test "$merge_was_ok" = t
@@ -550,6 +615,6 @@ Conflicts:
sed -e 's/^[^ ]* / /' |
uniq
} >>"$GIT_DIR/MERGE_MSG"
- git rerere
+ git rerere $rr_arg
die "Automatic merge failed; fix conflicts and then commit the result."
fi
diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh
index 49f0032..6bf155c 100755
--- a/contrib/examples/git-revert.sh
+++ b/contrib/examples/git-revert.sh
@@ -26,6 +26,7 @@ require_work_tree
cd_to_toplevel
no_commit=
+xopt=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -44,6 +45,16 @@ do
-x|--i-really-want-to-expose-my-private-commit-object-name)
replay=
;;
+ -X?*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")"
+ ;;
+ --strategy-option=*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")"
+ ;;
+ -X|--strategy-option)
+ shift
+ xopt="$xopt$(git rev-parse --sq-quote "--$1")"
+ ;;
-*)
usage
;;
@@ -159,7 +170,7 @@ export GITHEAD_$head GITHEAD_$next
# and $prev on top of us (when reverting), or the change between
# $prev and $commit on top of us (when cherry-picking or replaying).
-git-merge-recursive $base -- $head $next &&
+eval "git merge-recursive $xopt $base -- $head $next" &&
result=$(git-write-tree 2>/dev/null) || {
mv -f .msg "$GIT_DIR/MERGE_MSG"
{
@@ -181,7 +192,6 @@ Conflicts:
esac
exit 1
}
-echo >&2 "Finished one $me."
# If we are cherry-pick, and if the merge did not result in
# hand-editing, we will hit this commit and inherit the original
diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl
index 4576c4a..b09ff8f 100755
--- a/contrib/examples/git-svnimport.perl
+++ b/contrib/examples/git-svnimport.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
# This tool is copyright (c) 2005, Matthias Urlichs.
# It is released under the Gnu Public License, version 2.
@@ -289,7 +289,7 @@ my $current_rev = $opt_s || 1;
unless(-d $git_dir) {
system("git init");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git read-tree");
+ system("git read-tree --empty");
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index c1ea643..98d2aee 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -222,10 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
try:
while True:
entry = marshal.load(p4.stdout)
- if cb is not None:
- cb(entry)
- else:
- result.append(entry)
+ if cb is not None:
+ cb(entry)
+ else:
+ result.append(entry)
except EOFError:
pass
exitCode = p4.wait()
@@ -333,9 +333,13 @@ def gitBranchExists(branch):
return proc.wait() == 0;
_gitConfig = {}
-def gitConfig(key):
+def gitConfig(key, args = None): # set args to "--bool", for instance
if not _gitConfig.has_key(key):
- _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
+ argsFilter = ""
+ if args != None:
+ argsFilter = "%s " % args
+ cmd = "git config %s%s" % (argsFilter, key)
+ _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
return _gitConfig[key]
def p4BranchesInGit(branchesAreInRemotes = True):
@@ -445,18 +449,72 @@ def p4ChangesForPaths(depotPaths, changeRange):
changes = {}
for line in output:
- changeNum = int(line.split(" ")[1])
- changes[changeNum] = True
+ changeNum = int(line.split(" ")[1])
+ changes[changeNum] = True
changelist = changes.keys()
changelist.sort()
return changelist
+def p4PathStartsWith(path, prefix):
+ # This method tries to remedy a potential mixed-case issue:
+ #
+ # If UserA adds //depot/DirA/file1
+ # and UserB adds //depot/dira/file2
+ #
+ # we may or may not have a problem. If you have core.ignorecase=true,
+ # we treat DirA and dira as the same directory
+ ignorecase = gitConfig("core.ignorecase", "--bool") == "true"
+ if ignorecase:
+ return path.lower().startswith(prefix.lower())
+ return path.startswith(prefix)
+
class Command:
def __init__(self):
self.usage = "usage: %prog [options]"
self.needsGit = True
+class P4UserMap:
+ def __init__(self):
+ self.userMapFromPerforceServer = False
+
+ def getUserCacheFilename(self):
+ home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+ return home + "/.gitp4-usercache.txt"
+
+ def getUserMapFromPerforceServer(self):
+ if self.userMapFromPerforceServer:
+ return
+ self.users = {}
+ self.emails = {}
+
+ for output in p4CmdList("users"):
+ if not output.has_key("User"):
+ continue
+ self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+ self.emails[output["Email"]] = output["User"]
+
+
+ s = ''
+ for (key, val) in self.users.items():
+ s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+
+ open(self.getUserCacheFilename(), "wb").write(s)
+ self.userMapFromPerforceServer = True
+
+ def loadUserMapFromCache(self):
+ self.users = {}
+ self.userMapFromPerforceServer = False
+ try:
+ cache = open(self.getUserCacheFilename(), "rb")
+ lines = cache.readlines()
+ cache.close()
+ for line in lines:
+ entry = line.strip().split("\t")
+ self.users[entry[0]] = entry[1]
+ except IOError:
+ self.getUserMapFromPerforceServer()
+
class P4Debug(Command):
def __init__(self):
Command.__init__(self)
@@ -537,21 +595,26 @@ class P4RollBack(Command):
return True
-class P4Submit(Command):
+class P4Submit(Command, P4UserMap):
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
- optparse.make_option("-M", dest="detectRename", action="store_true"),
+ optparse.make_option("-M", dest="detectRenames", action="store_true"),
+ # preserve the user, requires relevant p4 permissions
+ optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.interactive = True
self.origin = ""
- self.detectRename = False
+ self.detectRenames = False
self.verbose = False
+ self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
self.isWindows = (platform.system() == "Windows")
+ self.myP4UserId = None
def check(self):
if len(p4CmdList("opened ...")) > 0:
@@ -570,7 +633,7 @@ class P4Submit(Command):
continue
if inDescriptionSection:
- if line.startswith("Files:"):
+ if line.startswith("Files:") or line.startswith("Jobs:"):
inDescriptionSection = False
else:
continue
@@ -585,6 +648,99 @@ class P4Submit(Command):
return result
+ def p4UserForCommit(self,id):
+ # Return the tuple (perforce user,git email) for a given git commit id
+ self.getUserMapFromPerforceServer()
+ gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
+ gitEmail = gitEmail.strip()
+ if not self.emails.has_key(gitEmail):
+ return (None,gitEmail)
+ else:
+ return (self.emails[gitEmail],gitEmail)
+
+ def checkValidP4Users(self,commits):
+ # check if any git authors cannot be mapped to p4 users
+ for id in commits:
+ (user,email) = self.p4UserForCommit(id)
+ if not user:
+ msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
+ if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
+ print "%s" % msg
+ else:
+ die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
+
+ def lastP4Changelist(self):
+ # Get back the last changelist number submitted in this client spec. This
+ # then gets used to patch up the username in the change. If the same
+ # client spec is being used by multiple processes then this might go
+ # wrong.
+ results = p4CmdList("client -o") # find the current client
+ client = None
+ for r in results:
+ if r.has_key('Client'):
+ client = r['Client']
+ break
+ if not client:
+ die("could not get client spec")
+ results = p4CmdList("changes -c %s -m 1" % client)
+ for r in results:
+ if r.has_key('change'):
+ return r['change']
+ die("Could not get changelist number for last submit - cannot patch up user details")
+
+ def modifyChangelistUser(self, changelist, newUser):
+ # fixup the user field of a changelist after it has been submitted.
+ changes = p4CmdList("change -o %s" % changelist)
+ if len(changes) != 1:
+ die("Bad output from p4 change modifying %s to user %s" %
+ (changelist, newUser))
+
+ c = changes[0]
+ if c['User'] == newUser: return # nothing to do
+ c['User'] = newUser
+ input = marshal.dumps(c)
+
+ result = p4CmdList("change -f -i", stdin=input)
+ for r in result:
+ if r.has_key('code'):
+ if r['code'] == 'error':
+ die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
+ if r.has_key('data'):
+ print("Updated user field for changelist %s to %s" % (changelist, newUser))
+ return
+ die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
+
+ def canChangeChangelists(self):
+ # check to see if we have p4 admin or super-user permissions, either of
+ # which are required to modify changelists.
+ results = p4CmdList("protects %s" % self.depotPath)
+ for r in results:
+ if r.has_key('perm'):
+ if r['perm'] == 'admin':
+ return 1
+ if r['perm'] == 'super':
+ return 1
+ return 0
+
+ def p4UserId(self):
+ if self.myP4UserId:
+ return self.myP4UserId
+
+ results = p4CmdList("user -o")
+ for r in results:
+ if r.has_key('User'):
+ self.myP4UserId = r['User']
+ return r['User']
+ die("Could not find your p4 user id")
+
+ def p4UserIsMe(self, p4User):
+ # return True if the given p4 user is actually me
+ me = self.p4UserId()
+ if not p4User or p4User != me:
+ return False
+ else:
+ return True
+
def prepareSubmitTemplate(self):
# remove lines in the Files section that show changes to files outside the depot path we're committing into
template = ""
@@ -599,7 +755,7 @@ class P4Submit(Command):
lastTab = path.rfind("\t")
if lastTab != -1:
path = path[:lastTab]
- if not path.startswith(self.depotPath):
+ if not p4PathStartsWith(path, self.depotPath):
continue
else:
inFilesSection = False
@@ -613,7 +769,24 @@ class P4Submit(Command):
def applyCommit(self, id):
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diffOpts = ("", "-M")[self.detectRename]
+
+ (p4User, gitEmail) = self.p4UserForCommit(id)
+
+ if not self.detectRenames:
+ # If not explicitly set check the config variable
+ self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+
+ if self.detectRenames:
+ diffOpts = "-M"
+ else:
+ diffOpts = ""
+
+ if gitConfig("git-p4.detectCopies").lower() == "true":
+ diffOpts += " -C"
+
+ if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+ diffOpts += " --find-copies-harder"
+
diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
@@ -637,11 +810,23 @@ class P4Submit(Command):
filesToDelete.add(path)
if path in filesToAdd:
filesToAdd.remove(path)
+ elif modifier == "C":
+ src, dest = diff['src'], diff['dst']
+ p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
- p4_system("edit \"%s\"" % (dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
@@ -704,9 +889,15 @@ class P4Submit(Command):
if self.interactive:
submitTemplate = self.prepareLogMessage(template, logMessage)
+
+ if self.preserveUser:
+ submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+
if os.environ.has_key("P4DIFF"):
del(os.environ["P4DIFF"])
- diff = p4_read_pipe("diff -du ...")
+ diff = ""
+ for editedFile in editedFiles:
+ diff += p4_read_pipe("diff -du %r" % editedFile)
newdiff = ""
for newFile in filesToAdd:
@@ -718,6 +909,11 @@ class P4Submit(Command):
newdiff += "+" + line
f.close()
+ if self.checkAuthorship and not self.p4UserIsMe(p4User):
+ submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+ submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
+ submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
+
separatorLine = "######## everything below this line is just the diff #######\n"
[handle, fileName] = tempfile.mkstemp()
@@ -735,8 +931,13 @@ class P4Submit(Command):
editor = read_pipe("git var GIT_EDITOR").strip()
system(editor + " " + fileName)
+ if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+ checkModTime = False
+ else:
+ checkModTime = True
+
response = "y"
- if os.stat(fileName).st_mtime <= mtime:
+ if checkModTime and (os.stat(fileName).st_mtime <= mtime):
response = "x"
while response != "y" and response != "n":
response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
@@ -749,6 +950,14 @@ class P4Submit(Command):
if self.isWindows:
submitTemplate = submitTemplate.replace("\r\n", "\n")
p4_write_pipe("submit -i", submitTemplate)
+
+ if self.preserveUser:
+ if p4User:
+ # Get last changelist number. Cannot easily get it from
+ # the submit command output as the output is unmarshalled.
+ changelist = self.lastP4Changelist()
+ self.modifyChangelistUser(changelist, p4User)
+
else:
for f in editedFiles:
p4_system("revert \"%s\"" % f);
@@ -785,6 +994,10 @@ class P4Submit(Command):
if len(self.origin) == 0:
self.origin = upstream
+ if self.preserveUser:
+ if not self.canChangeChangelists():
+ die("Cannot preserve user names without p4 super-user or admin permissions")
+
if self.verbose:
print "Origin branch is " + self.origin
@@ -812,6 +1025,14 @@ class P4Submit(Command):
commits.append(line.strip())
commits.reverse()
+ if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
+ self.checkAuthorship = False
+ else:
+ self.checkAuthorship = True
+
+ if self.preserveUser:
+ self.checkValidP4Users(commits)
+
while len(commits) > 0:
commit = commits[0]
commits = commits[1:]
@@ -831,9 +1052,12 @@ class P4Submit(Command):
return True
-class P4Sync(Command):
+class P4Sync(Command, P4UserMap):
+ delete_actions = ( "delete", "move/delete", "purge" )
+
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--branch", dest="branch"),
optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
@@ -880,6 +1104,23 @@ class P4Sync(Command):
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
+ #
+ # P4 wildcards are not allowed in filenames. P4 complains
+ # if you simply add them, but you can force it with "-f", in
+ # which case it translates them into %xx encoding internally.
+ # Search for and fix just these four characters. Do % last so
+ # that fixing it does not inadvertently create new %-escapes.
+ #
+ def wildcard_decode(self, path):
+ # Cannot have * in a filename in windows; untested as to
+ # what p4 would do in such a case.
+ if not self.isWindows:
+ path = path.replace("%2A", "*")
+ path = path.replace("%23", "#") \
+ .replace("%40", "@") \
+ .replace("%25", "%")
+ return path
+
def extractFilesFromCommit(self, commit):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
@@ -889,11 +1130,11 @@ class P4Sync(Command):
path = commit["depotFile%s" % fnum]
if [p for p in self.cloneExclude
- if path.startswith (p)]:
+ if p4PathStartsWith(path, p)]:
found = False
else:
found = [p for p in self.depotPaths
- if path.startswith (p)]
+ if p4PathStartsWith(path, p)]
if not found:
fnum = fnum + 1
continue
@@ -908,11 +1149,27 @@ class P4Sync(Command):
return files
def stripRepoPath(self, path, prefixes):
+ if self.useClientSpec:
+
+ # if using the client spec, we use the output directory
+ # specified in the client. For example, a view
+ # //depot/foo/branch/... //client/branch/foo/...
+ # will end up putting all foo/branch files into
+ # branch/foo/
+ for val in self.clientSpecDirs:
+ if path.startswith(val[0]):
+ # replace the depot path with the client path
+ path = path.replace(val[0], val[1][1])
+ # now strip out the client (//client/...)
+ path = re.sub("^(//[^/]+/)", '', path)
+ # the rest is all path
+ return path
+
if self.keepRepoPath:
prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
for p in prefixes:
- if path.startswith(p):
+ if p4PathStartsWith(path, p):
path = path[len(p):]
return path
@@ -923,7 +1180,7 @@ class P4Sync(Command):
while commit.has_key("depotFile%s" % fnum):
path = commit["depotFile%s" % fnum]
found = [p for p in self.depotPaths
- if path.startswith (p)]
+ if p4PathStartsWith(path, p)]
if not found:
fnum = fnum + 1
continue
@@ -952,12 +1209,13 @@ class P4Sync(Command):
# - helper for streamP4Files
def streamOneP4File(self, file, contents):
- if file["type"] == "apple":
- print "\nfile %s is a strange apple file that forks. Ignoring" % \
- file['depotFile']
- return
+ if file["type"] == "apple":
+ print "\nfile %s is a strange apple file that forks. Ignoring" % \
+ file['depotFile']
+ return
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+ relPath = self.wildcard_decode(relPath)
if verbose:
sys.stderr.write("%s\n" % relPath)
@@ -1003,22 +1261,22 @@ class P4Sync(Command):
# handle another chunk of streaming data
def streamP4FilesCb(self, marshalled):
- if marshalled.has_key('depotFile') and self.stream_have_file_info:
- # start of a new file - output the old one first
- self.streamOneP4File(self.stream_file, self.stream_contents)
- self.stream_file = {}
- self.stream_contents = []
- self.stream_have_file_info = False
+ if marshalled.has_key('depotFile') and self.stream_have_file_info:
+ # start of a new file - output the old one first
+ self.streamOneP4File(self.stream_file, self.stream_contents)
+ self.stream_file = {}
+ self.stream_contents = []
+ self.stream_have_file_info = False
- # pick up the new file information... for the
- # 'data' field we need to append to our array
- for k in marshalled.keys():
- if k == 'data':
- self.stream_contents.append(marshalled['data'])
- else:
- self.stream_file[k] = marshalled[k]
+ # pick up the new file information... for the
+ # 'data' field we need to append to our array
+ for k in marshalled.keys():
+ if k == 'data':
+ self.stream_contents.append(marshalled['data'])
+ else:
+ self.stream_file[k] = marshalled[k]
- self.stream_have_file_info = True
+ self.stream_have_file_info = True
# Stream directly from "p4 files" into "git fast-import"
def streamP4Files(self, files):
@@ -1030,16 +1288,16 @@ class P4Sync(Command):
includeFile = True
for val in self.clientSpecDirs:
if f['path'].startswith(val[0]):
- if val[1] <= 0:
+ if val[1][0] <= 0:
includeFile = False
break
if includeFile:
filesForCommit.append(f)
- if f['action'] not in ('delete', 'move/delete', 'purge'):
- filesToRead.append(f)
- else:
+ if f['action'] in self.delete_actions:
filesToDelete.append(f)
+ else:
+ filesToRead.append(f)
# deleted files...
for f in filesToDelete:
@@ -1050,14 +1308,14 @@ class P4Sync(Command):
self.stream_contents = []
self.stream_have_file_info = False
- # curry self argument
- def streamP4FilesCbSelf(entry):
- self.streamP4FilesCb(entry)
+ # curry self argument
+ def streamP4FilesCbSelf(entry):
+ self.streamP4FilesCb(entry)
- p4CmdList("-x - print",
- '\n'.join(['%s#%s' % (f['path'], f['rev'])
+ p4CmdList("-x - print",
+ '\n'.join(['%s#%s' % (f['path'], f['rev'])
for f in filesToRead]),
- cb=streamP4FilesCbSelf)
+ cb=streamP4FilesCbSelf)
# do the last chunk
if self.stream_file.has_key('depotFile'):
@@ -1066,7 +1324,7 @@ class P4Sync(Command):
def commit(self, details, files, branch, branchPrefixes, parent = ""):
epoch = details["time"]
author = details["user"]
- self.branchPrefixes = branchPrefixes
+ self.branchPrefixes = branchPrefixes
if self.verbose:
print "commit into %s" % branch
@@ -1075,10 +1333,10 @@ class P4Sync(Command):
# create a commit.
new_files = []
for f in files:
- if [p for p in branchPrefixes if f['path'].startswith(p)]:
+ if [p for p in branchPrefixes if p4PathStartsWith(f['path'], p)]:
new_files.append (f)
else:
- sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
+ sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
self.gitStream.write("commit %s\n" % branch)
# gitStream.write("mark :%s\n" % details["change"])
@@ -1125,7 +1383,7 @@ class P4Sync(Command):
cleanedFiles = {}
for info in files:
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
continue
cleanedFiles[info["depotFile"]] = info["rev"]
@@ -1154,41 +1412,6 @@ class P4Sync(Command):
print ("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
- def getUserCacheFilename(self):
- home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
- return home + "/.gitp4-usercache.txt"
-
- def getUserMapFromPerforceServer(self):
- if self.userMapFromPerforceServer:
- return
- self.users = {}
-
- for output in p4CmdList("users"):
- if not output.has_key("User"):
- continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
-
-
- s = ''
- for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
-
- open(self.getUserCacheFilename(), "wb").write(s)
- self.userMapFromPerforceServer = True
-
- def loadUserMapFromCache(self):
- self.users = {}
- self.userMapFromPerforceServer = False
- try:
- cache = open(self.getUserCacheFilename(), "rb")
- lines = cache.readlines()
- cache.close()
- for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
- except IOError:
- self.getUserMapFromPerforceServer()
-
def getLabels(self):
self.labels = {}
@@ -1239,7 +1462,7 @@ class P4Sync(Command):
source = paths[0]
destination = paths[1]
## HACK
- if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
+ if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
source = source[len(self.depotPaths[0]):-4]
destination = destination[len(self.depotPaths[0]):-4]
@@ -1427,7 +1650,7 @@ class P4Sync(Command):
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
details = { "user" : "git perforce import user", "time" : int(time.time()) }
- details["desc"] = ("Initial import of %s from the state at revision %s"
+ details["desc"] = ("Initial import of %s from the state at revision %s\n"
% (' '.join(self.depotPaths), revision))
details["change"] = revision
newestRevision = 0
@@ -1438,9 +1661,16 @@ class P4Sync(Command):
% (p, revision)
for p in self.depotPaths])):
- if info['code'] == 'error':
+ if 'code' in info and info['code'] == 'error':
sys.stderr.write("p4 returned an error: %s\n"
% info['data'])
+ if info['data'].find("must refer to client") >= 0:
+ sys.stderr.write("This particular p4 error is misleading.\n")
+ sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
+ sys.exit(1)
+ if 'p4ExitCode' in info:
+ sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
sys.exit(1)
@@ -1448,7 +1678,7 @@ class P4Sync(Command):
if change > newestRevision:
newestRevision = change
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
# don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
#fileCnt = fileCnt + 1
continue
@@ -1473,19 +1703,45 @@ class P4Sync(Command):
for entry in specList:
for k,v in entry.iteritems():
if k.startswith("View"):
+
+ # p4 has these %%1 to %%9 arguments in specs to
+ # reorder paths; which we can't handle (yet :)
+ if re.match('%%\d', v) != None:
+ print "Sorry, can't handle %%n arguments in client specs"
+ sys.exit(1)
+
if v.startswith('"'):
start = 1
else:
start = 0
index = v.find("...")
+
+ # save the "client view"; i.e the RHS of the view
+ # line that tells the client where to put the
+ # files for this view.
+ cv = v[index+3:].strip() # +3 to remove previous '...'
+
+ # if the client view doesn't end with a
+ # ... wildcard, then we're going to mess up the
+ # output directory, so fail gracefully.
+ if not cv.endswith('...'):
+ print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
+ sys.exit(1)
+ cv=cv[:-3]
+
+ # now save the view; +index means included, -index
+ # means it should be filtered out.
v = v[start:index]
if v.startswith("-"):
v = v[1:]
- temp[v] = -len(v)
+ include = -len(v)
else:
- temp[v] = len(v)
+ include = len(v)
+
+ temp[v] = (include, cv)
+
self.clientSpecDirs = temp.items()
- self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+ self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
def run(self, args):
self.depotPaths = []
@@ -1665,6 +1921,10 @@ class P4Sync(Command):
changes.sort()
else:
+ # catch "git-p4 sync" with no new branches, in a repo that
+ # does not have any existing git-p4 branches
+ if len(args) == 0 and not self.p4BranchesInGit:
+ die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange)
@@ -1745,10 +2005,13 @@ class P4Clone(P4Sync):
help="where to leave result of the clone"),
optparse.make_option("-/", dest="cloneExclude",
action="append", type="string",
- help="exclude depot path")
+ help="exclude depot path"),
+ optparse.make_option("--bare", dest="cloneBare",
+ action="store_true", default=False),
]
self.cloneDestination = None
self.needsGit = False
+ self.cloneBare = False
# This is required for the "append" cloneExclude action
def ensure_value(self, attr, value):
@@ -1788,11 +2051,16 @@ class P4Clone(P4Sync):
self.cloneDestination = self.defaultDestination(args)
print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+
if not os.path.exists(self.cloneDestination):
os.makedirs(self.cloneDestination)
chdir(self.cloneDestination)
- system("git init")
- self.gitdir = os.getcwd() + "/.git"
+
+ init_cmd = [ "git", "init" ]
+ if self.cloneBare:
+ init_cmd.append("--bare")
+ subprocess.check_call(init_cmd)
+
if not P4Sync.run(self, depotPaths):
return False
if self.branch != "master":
@@ -1802,7 +2070,8 @@ class P4Clone(P4Sync):
masterbranch = "refs/heads/p4/master"
if gitBranchExists(masterbranch):
system("git branch master %s" % masterbranch)
- system("git checkout -f")
+ if not self.cloneBare:
+ system("git checkout -f")
else:
print "Could not detect main branch. No checkout/master branch created."
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
index 49b3359..caa4bb3 100644
--- a/contrib/fast-import/git-p4.txt
+++ b/contrib/fast-import/git-p4.txt
@@ -110,6 +110,12 @@ is not your current git branch you can also pass that as an argument:
You can override the reference branch with the --origin=mysourcebranch option.
+The Perforce changelists will be created with the user who ran git-p4. If you
+use --preserve-user then git-p4 will attempt to create Perforce changelists
+with the Perforce user corresponding to the git commit author. You need to
+have sufficient permissions within Perforce, and the git users need to have
+Perforce accounts. Permissions can be granted using 'p4 protect'.
+
If a submit fails you may have to "p4 resolve" and submit manually. You can
continue importing the remaining changes with
@@ -191,6 +197,41 @@ git-p4.useclientspec
git config [--global] git-p4.useclientspec false
+The P4CLIENT environment variable should be correctly set for p4 to be
+able to find the relevant client. This client spec will be used to
+both filter the files cloned by git and set the directory layout as
+specified in the client (this implies --keep-path style semantics).
+
+git-p4.skipSubmitModTimeCheck
+
+ git config [--global] git-p4.skipSubmitModTimeCheck false
+
+If true, submit will not check if the p4 change template has been modified.
+
+git-p4.preserveUser
+
+ git config [--global] git-p4.preserveUser false
+
+If true, attempt to preserve user names by modifying the p4 changelists. See
+the "--preserve-user" submit option.
+
+git-p4.allowMissingPerforceUsers
+
+ git config [--global] git-p4.allowMissingP4Users false
+
+If git-p4 is setting the perforce user for a commit (--preserve-user) then
+if there is no perforce user corresponding to the git author, git-p4 will
+stop. With allowMissingPerforceUsers set to true, git-p4 will use the
+current user (i.e. the behavior without --preserve-user) and carry on with
+the perforce commit.
+
+git-p4.skipUserNameCheck
+
+ git config [--global] git-p4.skipUserNameCheck false
+
+When submitting, git-p4 checks that the git commits are authored by the current
+p4 user, and warns if they are not. This disables the check.
+
Implementation Details...
=========================
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
index 3a5da4a..7f3afa5 100755
--- a/contrib/fast-import/import-directories.perl
+++ b/contrib/fast-import/import-directories.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se>
#
@@ -140,6 +140,7 @@ by whitespace or other characters.
# Globals
use strict;
+use warnings;
use integer;
my $crlfmode = 0;
my @revs;
diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README
new file mode 100644
index 0000000..438463b
--- /dev/null
+++ b/contrib/git-shell-commands/README
@@ -0,0 +1,18 @@
+Sample programs callable through git-shell. Place a directory named
+'git-shell-commands' in the home directory of a user whose shell is
+git-shell. Then anyone logging in as that user will be able to run
+executables in the 'git-shell-commands' directory.
+
+Provided commands:
+
+help: Prints out the names of available commands. When run
+interactively, git-shell will automatically run 'help' on startup,
+provided it exists.
+
+list: Displays any bare repository whose name ends with ".git" under
+user's home directory. No other git repositories are visible,
+although they might be clonable through git-shell. 'list' is designed
+to minimize the number of calls to git that must be made in finding
+available repositories; if your setup has additional repositories that
+should be user-discoverable, you may wish to modify 'list'
+accordingly.
diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help
new file mode 100755
index 0000000..535770c
--- /dev/null
+++ b/contrib/git-shell-commands/help
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if tty -s
+then
+ echo "Run 'help' for help, or 'exit' to leave. Available commands:"
+else
+ echo "Run 'help' for help. Available commands:"
+fi
+
+cd "$(dirname "$0")"
+
+for cmd in *
+do
+ case "$cmd" in
+ help) ;;
+ *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
+ esac
+done
diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list
new file mode 100755
index 0000000..6f89938
--- /dev/null
+++ b/contrib/git-shell-commands/list
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+print_if_bare_repo='
+ if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
+ then
+ printf "%s\n" "${1#./}"
+ fi
+'
+
+find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
index 77c29de..9e12f97 100644
--- a/contrib/gitview/gitview.txt
+++ b/contrib/gitview/gitview.txt
@@ -7,6 +7,7 @@ gitview - A GTK based repository browser for git
SYNOPSIS
--------
+[verse]
'gitview' [options] [args]
DESCRIPTION
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 30ae63d..21989fc 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -55,6 +55,11 @@
# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
# Be careful if "..." contains things that will be expanded by shell "eval"
# or printf.
+# hooks.emailmaxlines
+# The maximum number of lines that should be included in the generated
+# email body. 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.
#
# Notes
# -----
@@ -66,24 +71,16 @@
# ---------------------------- Functions
#
-# Top level email generation function. This decides what type of update
-# this is and calls the appropriate body-generation routine after outputting
-# the common header
+# Function to prepare for email generation. This decides what type
+# of update this is and whether an email should even be generated.
#
-# Note this function doesn't actually generate any email output, that is
-# taken care of by the functions it calls:
-# - generate_email_header
-# - generate_create_XXXX_email
-# - generate_update_XXXX_email
-# - generate_delete_XXXX_email
-# - generate_email_footer
-#
-generate_email()
+prep_for_email()
{
# --- Arguments
oldrev=$(git rev-parse $1)
newrev=$(git rev-parse $2)
refname="$3"
+ maxlines=$4
# --- Interpret
# 0000->1234 (create)
@@ -147,13 +144,13 @@ generate_email()
short_refname=${refname##refs/remotes/}
echo >&2 "*** Push-update of tracking branch, $refname"
echo >&2 "*** - no email generated."
- exit 0
+ return 1
;;
*)
# Anything else (is there anything else?)
echo >&2 "*** Unknown type of update to $refname ($rev_type)"
echo >&2 "*** - no email generated"
- exit 1
+ return 1
;;
esac
@@ -169,9 +166,32 @@ generate_email()
esac
echo >&2 "*** $config_name is not set so no email will be sent"
echo >&2 "*** for $refname update $oldrev->$newrev"
- exit 0
+ return 1
fi
+ return 0
+}
+
+#
+# Top level email generation function. This calls the appropriate
+# body-generation routine after outputting the common header.
+#
+# Note this function doesn't actually generate any email output, that is
+# taken care of by the functions it calls:
+# - generate_email_header
+# - generate_create_XXXX_email
+# - generate_update_XXXX_email
+# - generate_delete_XXXX_email
+# - generate_email_footer
+#
+# Note also that this function cannot 'exit' from the script; when this
+# function is running (in hook script mode), the send_mail() function
+# is already executing in another process, connected via a pipe, and
+# if this function exits without, whatever has been generated to that
+# point will be sent as an email... even if nothing has been generated.
+#
+generate_email()
+{
# Email parameters
# The email subject will contain the best description of the ref
# that we can build from the parameters
@@ -192,7 +212,12 @@ generate_email()
fn_name=atag
;;
esac
- generate_${change_type}_${fn_name}_email
+
+ if [ -z "$maxlines" ]; then
+ generate_${change_type}_${fn_name}_email
+ else
+ generate_${change_type}_${fn_name}_email | limit_lines $maxlines
+ fi
generate_email_footer
}
@@ -203,7 +228,7 @@ generate_email_header()
# Generate header
cat <<-EOF
To: $recipients
- Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe
+ Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
X-Git-Refname: $refname
X-Git-Reftype: $refname_type
X-Git-Oldrev: $oldrev
@@ -642,6 +667,24 @@ show_new_revisions()
}
+limit_lines()
+{
+ lines=0
+ skipped=0
+ while IFS="" read -r line; do
+ lines=$((lines + 1))
+ if [ $lines -gt $1 ]; then
+ skipped=$((skipped + 1))
+ else
+ printf "%s\n" "$line"
+ fi
+ done
+ if [ $skipped -ne 0 ]; then
+ echo "... $skipped lines suppressed ..."
+ fi
+}
+
+
send_mail()
{
if [ -n "$envelopesender" ]; then
@@ -666,7 +709,7 @@ if [ -z "$GIT_DIR" ]; then
exit 1
fi
-projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
+projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null)
# Check if the description is unchanged from it's default, and shorten it to
# a more manageable length if it is
if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
@@ -679,6 +722,7 @@ announcerecipients=$(git config hooks.announcelist)
envelopesender=$(git config hooks.envelopesender)
emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
custom_showrev=$(git config hooks.showrev)
+maxlines=$(git config hooks.emailmaxlines)
# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
@@ -687,10 +731,11 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# Output to the terminal in command line mode - if someone wanted to
# resend an email; they could redirect the output to sendmail
# themselves
- PAGER= generate_email $2 $3 $1
+ prep_for_email $2 $3 $1 && PAGER= generate_email
else
while read oldrev newrev refname
do
- generate_email $oldrev $newrev $refname | send_mail
+ prep_for_email $oldrev $newrev $refname || continue
+ generate_email $maxlines | send_mail
done
fi
diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore
new file mode 100644
index 0000000..02a7791
--- /dev/null
+++ b/contrib/svn-fe/.gitignore
@@ -0,0 +1,4 @@
+/*.xml
+/*.1
+/*.html
+/svn-fe
diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile
new file mode 100644
index 0000000..360d8da
--- /dev/null
+++ b/contrib/svn-fe/Makefile
@@ -0,0 +1,63 @@
+all:: svn-fe$X
+
+CC = gcc
+RM = rm -f
+MV = mv
+
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+ALL_CFLAGS = $(CFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+EXTLIBS =
+
+GIT_LIB = ../../libgit.a
+VCSSVN_LIB = ../../vcs-svn/lib.a
+LIBS = $(VCSSVN_LIB) $(GIT_LIB) $(EXTLIBS)
+
+QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+ QUIET_CC = @echo ' ' CC $@;
+ QUIET_LINK = @echo ' ' LINK $@;
+ QUIET_SUBDIR0 = +@subdir=
+ QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
+ $(MAKE) $(PRINT_DIR) -C $$subdir
+endif
+endif
+
+svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \
+ $(ALL_LDFLAGS) $(LIBS)
+
+svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h
+ $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<
+
+svn-fe.html: svn-fe.txt
+ $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+ MAN_TXT=../contrib/svn-fe/svn-fe.txt \
+ ../contrib/svn-fe/$@
+
+svn-fe.1: svn-fe.txt
+ $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
+ MAN_TXT=../contrib/svn-fe/svn-fe.txt \
+ ../contrib/svn-fe/$@
+ $(MV) ../../Documentation/svn-fe.1 .
+
+../../vcs-svn/lib.a: FORCE
+ $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a
+
+../../libgit.a: FORCE
+ $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a
+
+clean:
+ $(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1
+
+.PHONY: all clean FORCE
diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c
new file mode 100644
index 0000000..35db24f
--- /dev/null
+++ b/contrib/svn-fe/svn-fe.c
@@ -0,0 +1,17 @@
+/*
+ * This file is in the public domain.
+ * You may freely use, modify, distribute, and relicense it.
+ */
+
+#include <stdlib.h>
+#include "svndump.h"
+
+int main(int argc, char **argv)
+{
+ if (svndump_init(NULL))
+ return 1;
+ svndump_read((argc > 1) ? argv[1] : NULL);
+ svndump_deinit();
+ svndump_reset();
+ return 0;
+}
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
new file mode 100644
index 0000000..72ffea0
--- /dev/null
+++ b/contrib/svn-fe/svn-fe.txt
@@ -0,0 +1,71 @@
+svn-fe(1)
+=========
+
+NAME
+----
+svn-fe - convert an SVN "dumpfile" to a fast-import stream
+
+SYNOPSIS
+--------
+[verse]
+svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
+
+DESCRIPTION
+-----------
+
+Converts a Subversion dumpfile into input suitable for
+git-fast-import(1) and similar importers. REPO is a path to a
+Subversion repository mirrored on the local disk. Remote Subversion
+repositories can be mirrored on local disk using the `svnsync`
+command.
+
+Note: this tool is very young. The details of its commandline
+interface may change in backward incompatible ways.
+
+INPUT FORMAT
+------------
+Subversion's repository dump format is documented in full in
+`notes/dump-load-format.txt` from the Subversion source tree.
+Files in this format can be generated using the 'svnadmin dump' or
+'svk admin dump' command.
+
+Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
+are not supported.
+
+OUTPUT FORMAT
+-------------
+The fast-import format is documented by the git-fast-import(1)
+manual page.
+
+NOTES
+-----
+Subversion dumps do not record a separate author and committer for
+each revision, nor a separate display name and email address for
+each author. Like git-svn(1), 'svn-fe' will use the name
+
+---------
+user <user@UUID>
+---------
+
+as committer, where 'user' is the value of the `svn:author` property
+and 'UUID' the repository's identifier.
+
+To support incremental imports, 'svn-fe' puts a `git-svn-id` line at
+the end of each commit log message if passed an url on the command
+line. This line has the form `git-svn-id: URL@REVNO UUID`.
+
+The resulting repository will generally require further processing
+to put each project in its own repository and to separate the history
+of each branch. The 'git filter-branch --subdirectory-filter' command
+may be useful for this purpose.
+
+BUGS
+----
+Empty directories and unknown properties are silently discarded.
+
+The exit status does not reflect whether an error was detected.
+
+SEE ALSO
+--------
+git-svn(1), svn2git(1), svk(1), git-filter-branch(1), git-fast-import(1),
+https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt
diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh
index cc518f3..5eb4a51 100755
--- a/contrib/thunderbird-patch-inline/appp.sh
+++ b/contrib/thunderbird-patch-inline/appp.sh
@@ -1,8 +1,8 @@
-#!/bin/bash
+#!/bin/sh
# Copyright 2008 Lukas Sandström <luksan@gmail.com>
#
# AppendPatch - A script to be used together with ExternalEditor
-# for Mozilla Thunderbird to properly include pathes inline i e-mails.
+# for Mozilla Thunderbird to properly include patches inline in e-mails.
# ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 993cacf..75e8b25 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -42,7 +42,7 @@ then
fi
# don't link to a workdir
-if test -L "$git_dir/config"
+if test -h "$git_dir/config"
then
die "\"$orig_git\" is a working directory only, please specify" \
"a complete repository."
@@ -54,13 +54,13 @@ then
die "destination directory '$new_workdir' already exists."
fi
-# make sure the the links use full paths
+# make sure the links use full paths
git_dir=$(cd "$git_dir"; pwd)
# create the workdir
mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
-# create the links to the original repo. explictly exclude index, HEAD and
+# create the links to the original repo. explicitly exclude index, HEAD and
# logs/HEAD from the list since they are purely related to the current working
# directory, and should not be shared.
for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn
diff --git a/convert.c b/convert.c
index e41a31e..85939c2 100644
--- a/convert.c
+++ b/convert.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "attr.h"
#include "run-command.h"
+#include "quote.h"
/*
* convert.c - convert a file when checking it out and checking it in.
@@ -11,13 +12,13 @@
* translation when the "text" attribute or "auto_crlf" option is set.
*/
-enum action {
+enum crlf_action {
CRLF_GUESS = -1,
CRLF_BINARY = 0,
CRLF_TEXT,
CRLF_INPUT,
CRLF_CRLF,
- CRLF_AUTO,
+ CRLF_AUTO
};
struct text_stat {
@@ -93,8 +94,9 @@ static int is_binary(unsigned long size, struct text_stat *stats)
return 0;
}
-static enum eol determine_output_conversion(enum action action) {
- switch (action) {
+static enum eol output_eol(enum crlf_action crlf_action)
+{
+ switch (crlf_action) {
case CRLF_BINARY:
return EOL_UNSET;
case CRLF_CRLF:
@@ -111,19 +113,19 @@ static enum eol determine_output_conversion(enum action action) {
return EOL_CRLF;
else if (auto_crlf == AUTO_CRLF_INPUT)
return EOL_LF;
- else if (eol == EOL_UNSET)
+ else if (core_eol == EOL_UNSET)
return EOL_NATIVE;
}
- return eol;
+ return core_eol;
}
-static void check_safe_crlf(const char *path, enum action action,
+static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
struct text_stat *stats, enum safe_crlf checksafe)
{
if (!checksafe)
return;
- if (determine_output_conversion(action) == EOL_LF) {
+ if (output_eol(crlf_action) == EOL_LF) {
/*
* CRLFs would not be restored by checkout:
* check if we'd remove CRLFs
@@ -134,7 +136,7 @@ static void check_safe_crlf(const char *path, enum action action,
else /* i.e. SAFE_CRLF_FAIL */
die("CRLF would be replaced by LF in %s.", path);
}
- } else if (determine_output_conversion(action) == EOL_CRLF) {
+ } else if (output_eol(crlf_action) == EOL_CRLF) {
/*
* CRLFs would be added by checkout:
* check if we have "naked" LFs
@@ -186,18 +188,19 @@ static int has_cr_in_index(const char *path)
}
static int crlf_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, enum action action, enum safe_crlf checksafe)
+ struct strbuf *buf,
+ enum crlf_action crlf_action, enum safe_crlf checksafe)
{
struct text_stat stats;
char *dst;
- if (action == CRLF_BINARY ||
- (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
+ if (crlf_action == CRLF_BINARY ||
+ (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
return 0;
gather_stats(src, len, &stats);
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
/*
* We're currently not going to even try to convert stuff
* that has bare CR characters. Does anybody do that crazy
@@ -212,7 +215,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (is_binary(len, &stats))
return 0;
- if (action == CRLF_GUESS) {
+ if (crlf_action == CRLF_GUESS) {
/*
* If the file in the index has any CR in it, do not convert.
* This is the new safer autocrlf handling.
@@ -222,7 +225,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
}
}
- check_safe_crlf(path, action, &stats, checksafe);
+ check_safe_crlf(path, crlf_action, &stats, checksafe);
/* Optimization: No CR? Nothing to convert, regardless. */
if (!stats.cr)
@@ -232,7 +235,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (strbuf_avail(buf) + buf->len < len)
strbuf_grow(buf, len - buf->len);
dst = buf->buf;
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
/*
* If we guessed, we already know we rejected a file with
* lone CR, and we can strip a CR without looking at what
@@ -255,12 +258,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
}
static int crlf_to_worktree(const char *path, const char *src, size_t len,
- struct strbuf *buf, enum action action)
+ struct strbuf *buf, enum crlf_action crlf_action)
{
char *to_free = NULL;
struct text_stat stats;
- if (!len || determine_output_conversion(action) != EOL_CRLF)
+ if (!len || output_eol(crlf_action) != EOL_CRLF)
return 0;
gather_stats(src, len, &stats);
@@ -273,8 +276,8 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
if (stats.lf == stats.crlf)
return 0;
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
- if (action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
+ if (crlf_action == CRLF_GUESS) {
/* If we have any CR or CRLF line endings, we do not touch it */
/* This is the new safer autocrlf-handling */
if (stats.cr > 0 || stats.crlf > 0)
@@ -317,6 +320,7 @@ struct filter_params {
const char *src;
unsigned long size;
const char *cmd;
+ const char *path;
};
static int filter_buffer(int in, int out, void *data)
@@ -329,7 +333,23 @@ static int filter_buffer(int in, int out, void *data)
int write_err, status;
const char *argv[] = { NULL, NULL };
- argv[0] = params->cmd;
+ /* apply % substitution to cmd */
+ struct strbuf cmd = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf_expand_dict_entry dict[] = {
+ { "f", NULL, },
+ { NULL, NULL, },
+ };
+
+ /* quote the path to preserve spaces, etc. */
+ sq_quote_buf(&path, params->path);
+ dict[0].value = path.buf;
+
+ /* expand all %f with the quoted path */
+ strbuf_expand(&cmd, params->cmd, strbuf_expand_dict_cb, &dict);
+ strbuf_release(&path);
+
+ argv[0] = cmd.buf;
memset(&child_process, 0, sizeof(child_process));
child_process.argv = argv;
@@ -349,6 +369,8 @@ static int filter_buffer(int in, int out, void *data)
status = finish_command(&child_process);
if (status)
error("external filter %s failed %d", params->cmd, status);
+
+ strbuf_release(&cmd);
return (write_err || status);
}
@@ -376,6 +398,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
params.src = src;
params.size = len;
params.cmd = cmd;
+ params.path = path;
fflush(NULL);
if (start_async(&async))
@@ -452,30 +475,6 @@ static int read_convert_config(const char *var, const char *value, void *cb)
return 0;
}
-static void setup_convert_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_text;
- static struct git_attr *attr_crlf;
- static struct git_attr *attr_eol;
- static struct git_attr *attr_ident;
- static struct git_attr *attr_filter;
-
- if (!attr_text) {
- attr_text = git_attr("text");
- attr_crlf = git_attr("crlf");
- attr_eol = git_attr("eol");
- attr_ident = git_attr("ident");
- attr_filter = git_attr("filter");
- user_convert_tail = &user_convert;
- git_config(read_convert_config, NULL);
- }
- check[0].attr = attr_crlf;
- check[1].attr = attr_ident;
- check[2].attr = attr_filter;
- check[3].attr = attr_eol;
- check[4].attr = attr_text;
-}
-
static int count_ident(const char *cp, unsigned long size)
{
/*
@@ -693,7 +692,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
return !!ATTR_TRUE(value);
}
-enum action determine_action(enum action text_attr, enum eol eol_attr) {
+static enum crlf_action input_crlf_action(enum crlf_action text_attr, enum eol eol_attr)
+{
if (text_attr == CRLF_BINARY)
return CRLF_BINARY;
if (eol_attr == EOL_LF)
@@ -703,73 +703,510 @@ enum action determine_action(enum action text_attr, enum eol eol_attr) {
return text_attr;
}
+struct conv_attrs {
+ struct convert_driver *drv;
+ enum crlf_action crlf_action;
+ enum eol eol_attr;
+ int ident;
+};
+
+static const char *conv_attr_name[] = {
+ "crlf", "ident", "filter", "eol", "text",
+};
+#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
+
+static void convert_attrs(struct conv_attrs *ca, const char *path)
+{
+ int i;
+ static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+
+ if (!ccheck[0].attr) {
+ for (i = 0; i < NUM_CONV_ATTRS; i++)
+ ccheck[i].attr = git_attr(conv_attr_name[i]);
+ user_convert_tail = &user_convert;
+ git_config(read_convert_config, NULL);
+ }
+
+ if (!git_checkattr(path, NUM_CONV_ATTRS, ccheck)) {
+ ca->crlf_action = git_path_check_crlf(path, ccheck + 4);
+ if (ca->crlf_action == CRLF_GUESS)
+ ca->crlf_action = git_path_check_crlf(path, ccheck + 0);
+ ca->ident = git_path_check_ident(path, ccheck + 1);
+ ca->drv = git_path_check_convert(path, ccheck + 2);
+ ca->eol_attr = git_path_check_eol(path, ccheck + 3);
+ } else {
+ ca->drv = NULL;
+ ca->crlf_action = CRLF_GUESS;
+ ca->eol_attr = EOL_UNSET;
+ ca->ident = 0;
+ }
+}
+
int convert_to_git(const char *path, const char *src, size_t len,
struct strbuf *dst, enum safe_crlf checksafe)
{
- struct git_attr_check check[5];
- enum action action = CRLF_GUESS;
- enum eol eol_attr = EOL_UNSET;
- int ident = 0, ret = 0;
+ int ret = 0;
const char *filter = NULL;
+ struct conv_attrs ca;
- setup_convert_check(check);
- if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
- struct convert_driver *drv;
- action = git_path_check_crlf(path, check + 4);
- if (action == CRLF_GUESS)
- action = git_path_check_crlf(path, check + 0);
- ident = git_path_check_ident(path, check + 1);
- drv = git_path_check_convert(path, check + 2);
- eol_attr = git_path_check_eol(path, check + 3);
- if (drv && drv->clean)
- filter = drv->clean;
- }
+ convert_attrs(&ca, path);
+ if (ca.drv)
+ filter = ca.drv->clean;
ret |= apply_filter(path, src, len, dst, filter);
if (ret) {
src = dst->buf;
len = dst->len;
}
- action = determine_action(action, eol_attr);
- ret |= crlf_to_git(path, src, len, dst, action, checksafe);
+ ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+ ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
if (ret) {
src = dst->buf;
len = dst->len;
}
- return ret | ident_to_git(path, src, len, dst, ident);
+ return ret | ident_to_git(path, src, len, dst, ca.ident);
}
-int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+static int convert_to_working_tree_internal(const char *path, const char *src,
+ size_t len, struct strbuf *dst,
+ int normalizing)
{
- struct git_attr_check check[5];
- enum action action = CRLF_GUESS;
- enum eol eol_attr = EOL_UNSET;
- int ident = 0, ret = 0;
+ int ret = 0;
const char *filter = NULL;
+ struct conv_attrs ca;
- setup_convert_check(check);
- if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
- struct convert_driver *drv;
- action = git_path_check_crlf(path, check + 4);
- if (action == CRLF_GUESS)
- action = git_path_check_crlf(path, check + 0);
- ident = git_path_check_ident(path, check + 1);
- drv = git_path_check_convert(path, check + 2);
- eol_attr = git_path_check_eol(path, check + 3);
- if (drv && drv->smudge)
- filter = drv->smudge;
- }
+ convert_attrs(&ca, path);
+ if (ca.drv)
+ filter = ca.drv->smudge;
- ret |= ident_to_worktree(path, src, len, dst, ident);
+ ret |= ident_to_worktree(path, src, len, dst, ca.ident);
if (ret) {
src = dst->buf;
len = dst->len;
}
- action = determine_action(action, eol_attr);
- ret |= crlf_to_worktree(path, src, len, dst, action);
+ /*
+ * CRLF conversion can be skipped if normalizing, unless there
+ * is a smudge filter. The filter might expect CRLFs.
+ */
+ if (filter || !normalizing) {
+ ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+ ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action);
+ if (ret) {
+ src = dst->buf;
+ len = dst->len;
+ }
+ }
+ return ret | apply_filter(path, src, len, dst, filter);
+}
+
+int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
+{
+ return convert_to_working_tree_internal(path, src, len, dst, 0);
+}
+
+int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst)
+{
+ int ret = convert_to_working_tree_internal(path, src, len, dst, 1);
if (ret) {
src = dst->buf;
len = dst->len;
}
- return ret | apply_filter(path, src, len, dst, filter);
+ return ret | convert_to_git(path, src, len, dst, 0);
+}
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+typedef int (*filter_fn)(struct stream_filter *,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p);
+typedef void (*free_fn)(struct stream_filter *);
+
+struct stream_filter_vtbl {
+ filter_fn filter;
+ free_fn free;
+};
+
+struct stream_filter {
+ struct stream_filter_vtbl *vtbl;
+};
+
+static int null_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ size_t count;
+
+ if (!input)
+ return 0; /* we do not keep any states */
+ count = *isize_p;
+ if (*osize_p < count)
+ count = *osize_p;
+ if (count) {
+ memmove(output, input, count);
+ *isize_p -= count;
+ *osize_p -= count;
+ }
+ return 0;
+}
+
+static void null_free_fn(struct stream_filter *filter)
+{
+ ; /* nothing -- null instances are shared */
+}
+
+static struct stream_filter_vtbl null_vtbl = {
+ null_filter_fn,
+ null_free_fn,
+};
+
+static struct stream_filter null_filter_singleton = {
+ &null_vtbl,
+};
+
+int is_null_stream_filter(struct stream_filter *filter)
+{
+ return filter == &null_filter_singleton;
+}
+
+
+/*
+ * LF-to-CRLF filter
+ */
+static int lf_to_crlf_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ size_t count;
+
+ if (!input)
+ return 0; /* we do not keep any states */
+ count = *isize_p;
+ if (count) {
+ size_t i, o;
+ for (i = o = 0; o < *osize_p && i < count; i++) {
+ char ch = input[i];
+ if (ch == '\n') {
+ if (o + 1 < *osize_p)
+ output[o++] = '\r';
+ else
+ break;
+ }
+ output[o++] = ch;
+ }
+
+ *osize_p -= o;
+ *isize_p -= i;
+ }
+ return 0;
+}
+
+static struct stream_filter_vtbl lf_to_crlf_vtbl = {
+ lf_to_crlf_filter_fn,
+ null_free_fn,
+};
+
+static struct stream_filter lf_to_crlf_filter_singleton = {
+ &lf_to_crlf_vtbl,
+};
+
+
+/*
+ * Cascade filter
+ */
+#define FILTER_BUFFER 1024
+struct cascade_filter {
+ struct stream_filter filter;
+ struct stream_filter *one;
+ struct stream_filter *two;
+ char buf[FILTER_BUFFER];
+ int end, ptr;
+};
+
+static int cascade_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ struct cascade_filter *cas = (struct cascade_filter *) filter;
+ size_t filled = 0;
+ size_t sz = *osize_p;
+ size_t to_feed, remaining;
+
+ /*
+ * input -- (one) --> buf -- (two) --> output
+ */
+ while (filled < sz) {
+ remaining = sz - filled;
+
+ /* do we already have something to feed two with? */
+ if (cas->ptr < cas->end) {
+ to_feed = cas->end - cas->ptr;
+ if (stream_filter(cas->two,
+ cas->buf + cas->ptr, &to_feed,
+ output + filled, &remaining))
+ return -1;
+ cas->ptr += (cas->end - cas->ptr) - to_feed;
+ filled = sz - remaining;
+ continue;
+ }
+
+ /* feed one from upstream and have it emit into our buffer */
+ to_feed = input ? *isize_p : 0;
+ if (input && !to_feed)
+ break;
+ remaining = sizeof(cas->buf);
+ if (stream_filter(cas->one,
+ input, &to_feed,
+ cas->buf, &remaining))
+ return -1;
+ cas->end = sizeof(cas->buf) - remaining;
+ cas->ptr = 0;
+ if (input) {
+ size_t fed = *isize_p - to_feed;
+ *isize_p -= fed;
+ input += fed;
+ }
+
+ /* do we know that we drained one completely? */
+ if (input || cas->end)
+ continue;
+
+ /* tell two to drain; we have nothing more to give it */
+ to_feed = 0;
+ remaining = sz - filled;
+ if (stream_filter(cas->two,
+ NULL, &to_feed,
+ output + filled, &remaining))
+ return -1;
+ if (remaining == (sz - filled))
+ break; /* completely drained two */
+ filled = sz - remaining;
+ }
+ *osize_p -= filled;
+ return 0;
+}
+
+static void cascade_free_fn(struct stream_filter *filter)
+{
+ struct cascade_filter *cas = (struct cascade_filter *)filter;
+ free_stream_filter(cas->one);
+ free_stream_filter(cas->two);
+ free(filter);
+}
+
+static struct stream_filter_vtbl cascade_vtbl = {
+ cascade_filter_fn,
+ cascade_free_fn,
+};
+
+static struct stream_filter *cascade_filter(struct stream_filter *one,
+ struct stream_filter *two)
+{
+ struct cascade_filter *cascade;
+
+ if (!one || is_null_stream_filter(one))
+ return two;
+ if (!two || is_null_stream_filter(two))
+ return one;
+
+ cascade = xmalloc(sizeof(*cascade));
+ cascade->one = one;
+ cascade->two = two;
+ cascade->end = cascade->ptr = 0;
+ cascade->filter.vtbl = &cascade_vtbl;
+ return (struct stream_filter *)cascade;
+}
+
+/*
+ * ident filter
+ */
+#define IDENT_DRAINING (-1)
+#define IDENT_SKIPPING (-2)
+struct ident_filter {
+ struct stream_filter filter;
+ struct strbuf left;
+ int state;
+ char ident[45]; /* ": x40 $" */
+};
+
+static int is_foreign_ident(const char *str)
+{
+ int i;
+
+ if (prefixcmp(str, "$Id: "))
+ return 0;
+ for (i = 5; str[i]; i++) {
+ if (isspace(str[i]) && str[i+1] != '$')
+ return 1;
+ }
+ return 0;
+}
+
+static void ident_drain(struct ident_filter *ident, char **output_p, size_t *osize_p)
+{
+ size_t to_drain = ident->left.len;
+
+ if (*osize_p < to_drain)
+ to_drain = *osize_p;
+ if (to_drain) {
+ memcpy(*output_p, ident->left.buf, to_drain);
+ strbuf_remove(&ident->left, 0, to_drain);
+ *output_p += to_drain;
+ *osize_p -= to_drain;
+ }
+ if (!ident->left.len)
+ ident->state = 0;
+}
+
+static int ident_filter_fn(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ struct ident_filter *ident = (struct ident_filter *)filter;
+ static const char head[] = "$Id";
+
+ if (!input) {
+ /* drain upon eof */
+ switch (ident->state) {
+ default:
+ strbuf_add(&ident->left, head, ident->state);
+ case IDENT_SKIPPING:
+ /* fallthru */
+ case IDENT_DRAINING:
+ ident_drain(ident, &output, osize_p);
+ }
+ return 0;
+ }
+
+ while (*isize_p || (ident->state == IDENT_DRAINING)) {
+ int ch;
+
+ if (ident->state == IDENT_DRAINING) {
+ ident_drain(ident, &output, osize_p);
+ if (!*osize_p)
+ break;
+ continue;
+ }
+
+ ch = *(input++);
+ (*isize_p)--;
+
+ if (ident->state == IDENT_SKIPPING) {
+ /*
+ * Skipping until '$' or LF, but keeping them
+ * in case it is a foreign ident.
+ */
+ strbuf_addch(&ident->left, ch);
+ if (ch != '\n' && ch != '$')
+ continue;
+ if (ch == '$' && !is_foreign_ident(ident->left.buf)) {
+ strbuf_setlen(&ident->left, sizeof(head) - 1);
+ strbuf_addstr(&ident->left, ident->ident);
+ }
+ ident->state = IDENT_DRAINING;
+ continue;
+ }
+
+ if (ident->state < sizeof(head) &&
+ head[ident->state] == ch) {
+ ident->state++;
+ continue;
+ }
+
+ if (ident->state)
+ strbuf_add(&ident->left, head, ident->state);
+ if (ident->state == sizeof(head) - 1) {
+ if (ch != ':' && ch != '$') {
+ strbuf_addch(&ident->left, ch);
+ ident->state = 0;
+ continue;
+ }
+
+ if (ch == ':') {
+ strbuf_addch(&ident->left, ch);
+ ident->state = IDENT_SKIPPING;
+ } else {
+ strbuf_addstr(&ident->left, ident->ident);
+ ident->state = IDENT_DRAINING;
+ }
+ continue;
+ }
+
+ strbuf_addch(&ident->left, ch);
+ ident->state = IDENT_DRAINING;
+ }
+ return 0;
+}
+
+static void ident_free_fn(struct stream_filter *filter)
+{
+ struct ident_filter *ident = (struct ident_filter *)filter;
+ strbuf_release(&ident->left);
+ free(filter);
+}
+
+static struct stream_filter_vtbl ident_vtbl = {
+ ident_filter_fn,
+ ident_free_fn,
+};
+
+static struct stream_filter *ident_filter(const unsigned char *sha1)
+{
+ struct ident_filter *ident = xmalloc(sizeof(*ident));
+
+ sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+ strbuf_init(&ident->left, 0);
+ ident->filter.vtbl = &ident_vtbl;
+ ident->state = 0;
+ return (struct stream_filter *)ident;
+}
+
+/*
+ * Return an appropriately constructed filter for the path, or NULL if
+ * the contents cannot be filtered without reading the whole thing
+ * in-core.
+ *
+ * Note that you would be crazy to set CRLF, smuge/clean or ident to a
+ * large binary blob you would want us not to slurp into the memory!
+ */
+struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1)
+{
+ struct conv_attrs ca;
+ enum crlf_action crlf_action;
+ struct stream_filter *filter = NULL;
+
+ convert_attrs(&ca, path);
+
+ if (ca.drv && (ca.drv->smudge || ca.drv->clean))
+ return filter;
+
+ if (ca.ident)
+ filter = ident_filter(sha1);
+
+ crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+
+ if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) ||
+ (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE))
+ filter = cascade_filter(filter, &null_filter_singleton);
+
+ else if (output_eol(crlf_action) == EOL_CRLF &&
+ !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
+ filter = cascade_filter(filter, &lf_to_crlf_filter_singleton);
+
+ return filter;
+}
+
+void free_stream_filter(struct stream_filter *filter)
+{
+ filter->vtbl->free(filter);
+}
+
+int stream_filter(struct stream_filter *filter,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p)
+{
+ return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
}
diff --git a/convert.h b/convert.h
new file mode 100644
index 0000000..d799a16
--- /dev/null
+++ b/convert.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef CONVERT_H
+#define CONVERT_H
+
+enum safe_crlf {
+ SAFE_CRLF_FALSE = 0,
+ SAFE_CRLF_FAIL = 1,
+ SAFE_CRLF_WARN = 2
+};
+
+extern enum safe_crlf safe_crlf;
+
+enum auto_crlf {
+ AUTO_CRLF_FALSE = 0,
+ AUTO_CRLF_TRUE = 1,
+ AUTO_CRLF_INPUT = -1
+};
+
+extern enum auto_crlf auto_crlf;
+
+enum eol {
+ EOL_UNSET,
+ EOL_CRLF,
+ EOL_LF,
+#ifdef NATIVE_CRLF
+ EOL_NATIVE = EOL_CRLF
+#else
+ EOL_NATIVE = EOL_LF
+#endif
+};
+
+extern enum eol core_eol;
+
+/* returns 1 if *dst was used */
+extern int convert_to_git(const char *path, const char *src, size_t len,
+ struct strbuf *dst, enum safe_crlf checksafe);
+extern int convert_to_working_tree(const char *path, const char *src,
+ size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len,
+ struct strbuf *dst);
+
+/*****************************************************************
+ *
+ * Streaming converison support
+ *
+ *****************************************************************/
+
+struct stream_filter; /* opaque */
+
+extern struct stream_filter *get_stream_filter(const char *path, const unsigned char *);
+extern void free_stream_filter(struct stream_filter *);
+extern int is_null_stream_filter(struct stream_filter *);
+
+/*
+ * Use as much input up to *isize_p and fill output up to *osize_p;
+ * update isize_p and osize_p to indicate how much buffer space was
+ * consumed and filled. Return 0 on success, non-zero on error.
+ *
+ * Some filters may need to buffer the input and look-ahead inside it
+ * to decide what to output, and they may consume more than zero bytes
+ * of input and still not produce any output. After feeding all the
+ * input, pass NULL as input and keep calling this function, to let
+ * such filters know there is no more input coming and it is time for
+ * them to produce the remaining output based on the buffered input.
+ */
+extern int stream_filter(struct stream_filter *,
+ const char *input, size_t *isize_p,
+ char *output, size_t *osize_p);
+
+#endif /* CONVERT_H */
diff --git a/csum-file.c b/csum-file.c
index 4d50cc5..fc97d6e 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -11,8 +11,20 @@
#include "progress.h"
#include "csum-file.h"
-static void flush(struct sha1file *f, void * buf, unsigned int count)
+static void flush(struct sha1file *f, void *buf, unsigned int count)
{
+ if (0 <= f->check_fd && count) {
+ unsigned char check_buffer[8192];
+ ssize_t ret = read_in_full(f->check_fd, check_buffer, count);
+
+ if (ret < 0)
+ die_errno("%s: sha1 file read error", f->name);
+ if (ret < count)
+ die("%s: sha1 file truncated", f->name);
+ if (memcmp(buf, check_buffer, count))
+ die("sha1 file '%s' validation error", f->name);
+ }
+
for (;;) {
int ret = xwrite(f->fd, buf, count);
if (ret > 0) {
@@ -59,6 +71,17 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
fd = 0;
} else
fd = f->fd;
+ if (0 <= f->check_fd) {
+ char discard;
+ int cnt = read_in_full(f->check_fd, &discard, 1);
+ if (cnt < 0)
+ die_errno("%s: error when reading the tail of sha1 file",
+ f->name);
+ if (cnt)
+ die("%s: sha1 file has trailing garbage", f->name);
+ if (close(f->check_fd))
+ die_errno("%s: sha1 file error on close", f->name);
+ }
free(f);
return fd;
}
@@ -101,10 +124,31 @@ struct sha1file *sha1fd(int fd, const char *name)
return sha1fd_throughput(fd, name, NULL);
}
+struct sha1file *sha1fd_check(const char *name)
+{
+ int sink, check;
+ struct sha1file *f;
+
+ sink = open("/dev/null", O_WRONLY);
+ if (sink < 0)
+ return NULL;
+ check = open(name, O_RDONLY);
+ if (check < 0) {
+ int saved_errno = errno;
+ close(sink);
+ errno = saved_errno;
+ return NULL;
+ }
+ f = sha1fd(sink, name);
+ f->check_fd = check;
+ return f;
+}
+
struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
{
struct sha1file *f = xmalloc(sizeof(*f));
f->fd = fd;
+ f->check_fd = -1;
f->offset = 0;
f->total = 0;
f->tp = tp;
@@ -116,7 +160,7 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp
void crc32_begin(struct sha1file *f)
{
- f->crc32 = crc32(0, Z_NULL, 0);
+ f->crc32 = crc32(0, NULL, 0);
f->do_crc = 1;
}
diff --git a/csum-file.h b/csum-file.h
index 294add2..6a7967c 100644
--- a/csum-file.h
+++ b/csum-file.h
@@ -6,6 +6,7 @@ struct progress;
/* A SHA1-protected file */
struct sha1file {
int fd;
+ int check_fd;
unsigned int offset;
git_SHA_CTX ctx;
off_t total;
@@ -21,6 +22,7 @@ struct sha1file {
#define CSUM_FSYNC 2
extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1fd_check(const char *name);
extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp);
extern int sha1close(struct sha1file *, unsigned char *, unsigned int);
extern int sha1write(struct sha1file *, void *, unsigned int);
diff --git a/ctype.c b/ctype.c
index de60027..b5d856f 100644
--- a/ctype.c
+++ b/ctype.c
@@ -10,17 +10,18 @@ enum {
A = GIT_ALPHA,
D = GIT_DIGIT,
G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
- R = GIT_REGEX_SPECIAL /* $, (, ), +, ., ^, {, | */
+ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */
+ P = GIT_PATHSPEC_MAGIC /* other non-alnum, except for ] and } */
};
unsigned char sane_ctype[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */
- S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0, /* 32.. 47 */
- D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G, /* 48.. 63 */
- 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
- A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0, /* 80.. 95 */
- 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
- A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0, /* 112..127 */
+ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
+ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
+ A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, P, /* 80.. 95 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
+ A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0, /* 112..127 */
/* Nothing in the 128.. range */
};
diff --git a/daemon.c b/daemon.c
index e22a2b7..4c8346d 100644
--- a/daemon.c
+++ b/daemon.c
@@ -3,8 +3,7 @@
#include "exec_cmd.h"
#include "run-command.h"
#include "strbuf.h"
-
-#include <syslog.h>
+#include "string-list.h"
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
@@ -14,21 +13,25 @@
#define NI_MAXSERV 32
#endif
+#ifdef NO_INITGROUPS
+#define initgroups(x, y) (0) /* nothing */
+#endif
+
static int log_syslog;
static int verbose;
static int reuseaddr;
static const char daemon_usage[] =
"git daemon [--verbose] [--syslog] [--export-all]\n"
-" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n"
-" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n"
-" [--user-path | --user-path=path]\n"
-" [--interpolated-path=path]\n"
-" [--reuseaddr] [--detach] [--pid-file=file]\n"
-" [--[enable|disable|allow-override|forbid-override]=service]\n"
-" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n"
-" [--user=user [--group=group]]\n"
-" [directory...]";
+" [--timeout=<n>] [--init-timeout=<n>] [--max-connections=<n>]\n"
+" [--strict-paths] [--base-path=<path>] [--base-path-relaxed]\n"
+" [--user-path | --user-path=<path>]\n"
+" [--interpolated-path=<path>]\n"
+" [--reuseaddr] [--pid-file=<file>]\n"
+" [--(enable|disable|allow-override|forbid-override)=<service>]\n"
+" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
+" [--detach] [--user=<user> [--group=<group>]]\n"
+" [<directory>...]";
/* List of acceptable pathname prefixes */
static char **ok_paths;
@@ -68,12 +71,14 @@ static void logreport(int priority, const char *err, va_list params)
syslog(priority, "%s", buf);
} else {
/*
- * Since stderr is set to linebuffered mode, the
+ * Since stderr is set to buffered mode, the
* logging of different processes will not overlap
+ * unless they overflow the (rather big) buffers.
*/
fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid());
vfprintf(stderr, err, params);
fputc('\n', stderr);
+ fflush(stderr);
}
}
@@ -515,37 +520,14 @@ static void parse_host_arg(char *extra_args, int buflen)
}
-static int execute(struct sockaddr *addr)
+static int execute(void)
{
static char line[1000];
int pktlen, len, i;
+ char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
- if (addr) {
- char addrbuf[256] = "";
- int port = -1;
-
- if (addr->sa_family == AF_INET) {
- struct sockaddr_in *sin_addr = (void *) addr;
- inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
- port = ntohs(sin_addr->sin_port);
-#ifndef NO_IPV6
- } else if (addr && addr->sa_family == AF_INET6) {
- struct sockaddr_in6 *sin6_addr = (void *) addr;
-
- char *buf = addrbuf;
- *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
- inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
- strcat(buf, "]");
-
- port = ntohs(sin6_addr->sin6_port);
-#endif
- }
- loginfo("Connection from %s:%d", addrbuf, port);
- setenv("REMOTE_ADDR", addrbuf, 1);
- }
- else {
- unsetenv("REMOTE_ADDR");
- }
+ if (addr)
+ loginfo("Connection from %s:%s", addr, port);
alarm(init_timeout ? init_timeout : timeout);
pktlen = packet_read_line(0, line, sizeof(line));
@@ -615,17 +597,17 @@ static unsigned int live_children;
static struct child {
struct child *next;
- pid_t pid;
+ struct child_process cld;
struct sockaddr_storage address;
} *firstborn;
-static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
+static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_t addrlen)
{
struct child *newborn, **cradle;
newborn = xcalloc(1, sizeof(*newborn));
live_children++;
- newborn->pid = pid;
+ memcpy(&newborn->cld, cld, sizeof(*cld));
memcpy(&newborn->address, addr, addrlen);
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
if (!addrcmp(&(*cradle)->address, &newborn->address))
@@ -634,19 +616,6 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
*cradle = newborn;
}
-static void remove_child(pid_t pid)
-{
- struct child **cradle, *blanket;
-
- for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
- if (blanket->pid == pid) {
- *cradle = blanket->next;
- live_children--;
- free(blanket);
- break;
- }
-}
-
/*
* This gets called if the number of connections grows
* past "max_connections".
@@ -662,7 +631,7 @@ static void kill_some_child(void)
for (; (next = blanket->next); blanket = next)
if (!addrcmp(&blanket->address, &next->address)) {
- kill(blanket->pid, SIGTERM);
+ kill(blanket->cld.pid, SIGTERM);
break;
}
}
@@ -672,18 +641,28 @@ static void check_dead_children(void)
int status;
pid_t pid;
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- const char *dead = "";
- remove_child(pid);
- if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
- dead = " (with error)";
- loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
- }
+ struct child **cradle, *blanket;
+ for (cradle = &firstborn; (blanket = *cradle);)
+ if ((pid = waitpid(blanket->cld.pid, &status, WNOHANG)) > 1) {
+ const char *dead = "";
+ if (status)
+ dead = " (with error)";
+ loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
+
+ /* remove the child */
+ *cradle = blanket->next;
+ live_children--;
+ free(blanket);
+ } else
+ cradle = &blanket->next;
}
-static void handle(int incoming, struct sockaddr *addr, int addrlen)
+static char **cld_argv;
+static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
{
- pid_t pid;
+ struct child_process cld = { NULL };
+ char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
+ char *env[] = { addrbuf, portbuf, NULL };
if (max_connections && live_children >= max_connections) {
kill_some_child();
@@ -696,22 +675,37 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
}
}
- if ((pid = fork())) {
- close(incoming);
- if (pid < 0) {
- logerror("Couldn't fork %s", strerror(errno));
- return;
- }
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin_addr = (void *) addr;
+ inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12,
+ sizeof(addrbuf) - 12);
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin_addr->sin_port));
+#ifndef NO_IPV6
+ } else if (addr && addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6_addr = (void *) addr;
- add_child(pid, addr, addrlen);
- return;
+ char *buf = addrbuf + 12;
+ *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+ inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf,
+ sizeof(addrbuf) - 13);
+ strcat(buf, "]");
+
+ snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
+ ntohs(sin6_addr->sin6_port));
+#endif
}
- dup2(incoming, 0);
- dup2(incoming, 1);
- close(incoming);
+ cld.env = (const char **)env;
+ cld.argv = (const char **)cld_argv;
+ cld.in = incoming;
+ cld.out = dup(incoming);
- exit(execute(addr));
+ if (start_command(&cld))
+ logerror("unable to fork");
+ else
+ add_child(&cld, addr, addrlen);
+ close(incoming);
}
static void child_handler(int signo)
@@ -734,11 +728,17 @@ static int set_reuse_addr(int sockfd)
&on, sizeof(on));
}
+struct socketlist {
+ int *list;
+ size_t nr;
+ size_t alloc;
+};
+
#ifndef NO_IPV6
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
- int socknum = 0, *socklist = NULL;
+ int socknum = 0;
int maxfd = -1;
char pbuf[NI_MAXSERV];
struct addrinfo hints, *ai0, *ai;
@@ -753,8 +753,10 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
hints.ai_flags = AI_PASSIVE;
gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
- if (gai)
- die("getaddrinfo() failed: %s", gai_strerror(gai));
+ if (gai) {
+ logerror("getaddrinfo() for %s failed: %s", listen_addr, gai_strerror(gai));
+ return 0;
+ }
for (ai = ai0; ai; ai = ai->ai_next) {
int sockfd;
@@ -795,8 +797,9 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- socklist = xrealloc(socklist, sizeof(int) * (socknum + 1));
- socklist[socknum++] = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
+ socknum++;
if (maxfd < sockfd)
maxfd = sockfd;
@@ -804,13 +807,12 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
freeaddrinfo(ai0);
- *socklist_p = socklist;
return socknum;
}
#else /* NO_IPV6 */
-static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
+static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
struct sockaddr_in sin;
int sockfd;
@@ -851,22 +853,39 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
if (flags >= 0)
fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
- *socklist_p = xmalloc(sizeof(int));
- **socklist_p = sockfd;
+ ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
+ socklist->list[socklist->nr++] = sockfd;
return 1;
}
#endif
-static int service_loop(int socknum, int *socklist)
+static void socksetup(struct string_list *listen_addr, int listen_port, struct socketlist *socklist)
+{
+ if (!listen_addr->nr)
+ setup_named_sock(NULL, listen_port, socklist);
+ else {
+ int i, socknum;
+ for (i = 0; i < listen_addr->nr; i++) {
+ socknum = setup_named_sock(listen_addr->items[i].string,
+ listen_port, socklist);
+
+ if (socknum == 0)
+ logerror("unable to allocate any listen sockets for host %s on port %u",
+ listen_addr->items[i].string, listen_port);
+ }
+ }
+}
+
+static int service_loop(struct socketlist *socklist)
{
struct pollfd *pfd;
int i;
- pfd = xcalloc(socknum, sizeof(struct pollfd));
+ pfd = xcalloc(socklist->nr, sizeof(struct pollfd));
- for (i = 0; i < socknum; i++) {
- pfd[i].fd = socklist[i];
+ for (i = 0; i < socklist->nr; i++) {
+ pfd[i].fd = socklist->list[i];
pfd[i].events = POLLIN;
}
@@ -877,7 +896,7 @@ static int service_loop(int socknum, int *socklist)
check_dead_children();
- if (poll(pfd, socknum, -1) < 0) {
+ if (poll(pfd, socklist->nr, -1) < 0) {
if (errno != EINTR) {
logerror("Poll failed, resuming: %s",
strerror(errno));
@@ -886,11 +905,17 @@ static int service_loop(int socknum, int *socklist)
continue;
}
- for (i = 0; i < socknum; i++) {
+ for (i = 0; i < socklist->nr; i++) {
if (pfd[i].revents & POLLIN) {
- struct sockaddr_storage ss;
- unsigned int sslen = sizeof(ss);
- int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sai;
+#ifndef NO_IPV6
+ struct sockaddr_in6 sai6;
+#endif
+ } ss;
+ socklen_t sslen = sizeof(ss);
+ int incoming = accept(pfd[i].fd, &ss.sa, &sslen);
if (incoming < 0) {
switch (errno) {
case EAGAIN:
@@ -901,7 +926,7 @@ static int service_loop(int socknum, int *socklist)
die_errno("accept returned");
}
}
- handle(incoming, (struct sockaddr *)&ss, sslen);
+ handle(incoming, &ss.sa, sslen);
}
}
}
@@ -919,6 +944,62 @@ static void sanitize_stdfds(void)
close(fd);
}
+#ifdef NO_POSIX_GOODIES
+
+struct credentials;
+
+static void drop_privileges(struct credentials *cred)
+{
+ /* nothing */
+}
+
+static void daemonize(void)
+{
+ die("--detach not supported on this platform");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+ const char *group_name)
+{
+ die("--user not supported on this platform");
+}
+
+#else
+
+struct credentials {
+ struct passwd *pass;
+ gid_t gid;
+};
+
+static void drop_privileges(struct credentials *cred)
+{
+ if (cred && (initgroups(cred->pass->pw_name, cred->gid) ||
+ setgid (cred->gid) || setuid(cred->pass->pw_uid)))
+ die("cannot drop privileges");
+}
+
+static struct credentials *prepare_credentials(const char *user_name,
+ const char *group_name)
+{
+ static struct credentials c;
+
+ c.pass = getpwnam(user_name);
+ if (!c.pass)
+ die("user not found - %s", user_name);
+
+ if (!group_name)
+ c.gid = c.pass->pw_gid;
+ else {
+ struct group *group = getgrnam(group_name);
+ if (!group)
+ die("group not found - %s", group_name);
+
+ c.gid = group->gr_gid;
+ }
+
+ return &c;
+}
+
static void daemonize(void)
{
switch (fork()) {
@@ -936,6 +1017,7 @@ static void daemonize(void)
close(2);
sanitize_stdfds();
}
+#endif
static void store_pid(const char *path)
{
@@ -946,33 +1028,29 @@ static void store_pid(const char *path)
die_errno("failed to write pid file '%s'", path);
}
-static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
+static int serve(struct string_list *listen_addr, int listen_port,
+ struct credentials *cred)
{
- int socknum, *socklist;
+ struct socketlist socklist = { NULL, 0, 0 };
- socknum = socksetup(listen_addr, listen_port, &socklist);
- if (socknum == 0)
- die("unable to allocate any listen sockets on host %s port %u",
- listen_addr, listen_port);
+ socksetup(listen_addr, listen_port, &socklist);
+ if (socklist.nr == 0)
+ die("unable to allocate any listen sockets on port %u",
+ listen_port);
- if (pass && gid &&
- (initgroups(pass->pw_name, gid) || setgid (gid) ||
- setuid(pass->pw_uid)))
- die("cannot drop privileges");
+ drop_privileges(cred);
- return service_loop(socknum, socklist);
+ return service_loop(&socklist);
}
int main(int argc, char **argv)
{
int listen_port = 0;
- char *listen_addr = NULL;
- int inetd_mode = 0;
+ struct string_list listen_addr = STRING_LIST_INIT_NODUP;
+ int serve_mode = 0, inetd_mode = 0;
const char *pid_file = NULL, *user_name = NULL, *group_name = NULL;
int detach = 0;
- struct passwd *pass = NULL;
- struct group *group;
- gid_t gid = 0;
+ struct credentials *cred = NULL;
int i;
git_extract_argv0_path(argv[0]);
@@ -981,7 +1059,7 @@ int main(int argc, char **argv)
char *arg = argv[i];
if (!prefixcmp(arg, "--listen=")) {
- listen_addr = xstrdup_tolower(arg + 9);
+ string_list_append(&listen_addr, xstrdup_tolower(arg + 9));
continue;
}
if (!prefixcmp(arg, "--port=")) {
@@ -993,6 +1071,10 @@ int main(int argc, char **argv)
continue;
}
}
+ if (!strcmp(arg, "--serve")) {
+ serve_mode = 1;
+ continue;
+ }
if (!strcmp(arg, "--inetd")) {
inetd_mode = 1;
log_syslog = 1;
@@ -1101,12 +1183,12 @@ int main(int argc, char **argv)
set_die_routine(daemon_die);
} else
/* avoid splitting a message in the middle */
- setvbuf(stderr, NULL, _IOLBF, 0);
+ setvbuf(stderr, NULL, _IOFBF, 4096);
- if (inetd_mode && (group_name || user_name))
- die("--user and --group are incompatible with --inetd");
+ if (inetd_mode && (detach || group_name || user_name))
+ die("--detach, --user and --group are incompatible with --inetd");
- if (inetd_mode && (listen_port || listen_addr))
+ if (inetd_mode && (listen_port || (listen_addr.nr > 0)))
die("--listen= and --port= are incompatible with --inetd");
else if (listen_port == 0)
listen_port = DEFAULT_GIT_PORT;
@@ -1114,21 +1196,8 @@ int main(int argc, char **argv)
if (group_name && !user_name)
die("--group supplied without --user");
- if (user_name) {
- pass = getpwnam(user_name);
- if (!pass)
- die("user not found - %s", user_name);
-
- if (!group_name)
- gid = pass->pw_gid;
- else {
- group = getgrnam(group_name);
- if (!group)
- die("group not found - %s", group_name);
-
- gid = group->gr_gid;
- }
- }
+ if (user_name)
+ cred = prepare_credentials(user_name, group_name);
if (strict_paths && (!ok_paths || !*ok_paths))
die("option --strict-paths requires a whitelist");
@@ -1138,19 +1207,13 @@ int main(int argc, char **argv)
base_path);
if (inetd_mode) {
- struct sockaddr_storage ss;
- struct sockaddr *peer = (struct sockaddr *)&ss;
- socklen_t slen = sizeof(ss);
-
if (!freopen("/dev/null", "w", stderr))
die_errno("failed to redirect stderr to /dev/null");
-
- if (getpeername(0, peer, &slen))
- peer = NULL;
-
- return execute(peer);
}
+ if (inetd_mode || serve_mode)
+ return execute();
+
if (detach) {
daemonize();
loginfo("Ready to rumble");
@@ -1161,5 +1224,13 @@ int main(int argc, char **argv)
if (pid_file)
store_pid(pid_file);
- return serve(listen_addr, listen_port, pass, gid);
+ /* prepare argv for serving-processes */
+ cld_argv = xmalloc(sizeof (char *) * (argc + 2));
+ cld_argv[0] = argv[0]; /* git-daemon */
+ cld_argv[1] = "--serve";
+ for (i = 1; i < argc; ++i)
+ cld_argv[i+1] = argv[i];
+ cld_argv[argc+1] = NULL;
+
+ return serve(&listen_addr, listen_port, cred);
}
diff --git a/date.c b/date.c
index 68cdcaa..896fbb4 100644
--- a/date.c
+++ b/date.c
@@ -129,8 +129,9 @@ const char *show_date_relative(unsigned long time, int tz,
}
/* Give years and months for 5 years or so */
if (diff < 1825) {
- unsigned long years = diff / 365;
- unsigned long months = (diff % 365 + 15) / 30;
+ unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
+ unsigned long years = totalmonths / 12;
+ unsigned long months = totalmonths % 12;
int n;
n = snprintf(timebuf, timebuf_size, "%lu year%s",
years, (years > 1 ? "s" : ""));
@@ -586,7 +587,7 @@ static int date_string(unsigned long date, int offset, char *buf, int len)
/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
(i.e. English) day/month names, and it doesn't work correctly with %z. */
-int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset)
+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)
{
struct tm tm;
int tm_gmt;
@@ -635,24 +636,23 @@ int parse_date_toffset(const char *date, unsigned long *timestamp, int *offset)
/* mktime uses local timezone */
*timestamp = tm_to_time_t(&tm);
if (*offset == -1)
- *offset = (*timestamp - mktime(&tm)) / 60;
+ *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
if (*timestamp == -1)
return -1;
if (!tm_gmt)
*timestamp -= *offset * 60;
- return 1; /* success */
+ return 0; /* success */
}
int parse_date(const char *date, char *result, int maxlen)
{
unsigned long timestamp;
int offset;
- if (parse_date_toffset(date, &timestamp, &offset) > 0)
- return date_string(timestamp, offset, result, maxlen);
- else
+ if (parse_date_basic(date, &timestamp, &offset))
return -1;
+ return date_string(timestamp, offset, result, maxlen);
}
enum date_mode parse_date_format(const char *format)
@@ -1004,9 +1004,8 @@ unsigned long approxidate_relative(const char *date, const struct timeval *tv)
int offset;
int errors = 0;
- if (parse_date_toffset(date, &timestamp, &offset) > 0)
+ if (!parse_date_basic(date, &timestamp, &offset))
return timestamp;
-
return approxidate_str(date, tv, &errors);
}
@@ -1019,7 +1018,7 @@ unsigned long approxidate_careful(const char *date, int *error_ret)
if (!error_ret)
error_ret = &dummy;
- if (parse_date_toffset(date, &timestamp, &offset) > 0) {
+ if (!parse_date_basic(date, &timestamp, &offset)) {
*error_ret = 0;
return timestamp;
}
diff --git a/diff-delta.c b/diff-delta.c
index 464ac3f..93385e1 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -146,7 +146,14 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
/* Determine index hash size. Note that indexing skips the
first byte to allow for optimizing the Rabin's polynomial
initialization in create_delta(). */
- entries = (bufsize - 1) / RABIN_WINDOW;
+ entries = (bufsize - 1) / RABIN_WINDOW;
+ if (bufsize >= 0xffffffffUL) {
+ /*
+ * Current delta format can't encode offsets into
+ * reference buffer with more than 32 bits.
+ */
+ entries = 0xfffffffeU / RABIN_WINDOW;
+ }
hsize = entries / 4;
for (i = 4; (1u << i) < hsize && i < 31; i++);
hsize = 1 << i;
diff --git a/diff-lib.c b/diff-lib.c
index 8b8978ae..2e09500 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -68,11 +68,16 @@ static int match_stat_with_submodule(struct diff_options *diffopt,
unsigned ce_option, unsigned *dirty_submodule)
{
int changed = ce_match_stat(ce, st, ce_option);
- if (S_ISGITLINK(ce->ce_mode)
- && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)
- && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
- && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) {
- *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
+ if (S_ISGITLINK(ce->ce_mode)) {
+ unsigned orig_flags = diffopt->flags;
+ if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG))
+ set_diffopt_flags_from_submodule_config(diffopt, ce->name);
+ if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES))
+ changed = 0;
+ else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
+ && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES)))
+ *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
+ diffopt->flags = orig_flags;
}
return changed;
}
@@ -97,15 +102,16 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
int changed;
unsigned dirty_submodule = 0;
- if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
- DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
+ if (diff_can_quit_early(&revs->diffopt))
break;
- if (!ce_path_match(ce, revs->prune_data))
+ if (!ce_path_match(ce, &revs->prune_data))
continue;
if (ce_stage(ce)) {
struct combine_diff_path *dpath;
+ struct diff_filepair *pair;
+ unsigned int wt_mode = 0;
int num_compare_stages = 0;
size_t path_len;
@@ -124,7 +130,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
changed = check_removed(ce, &st);
if (!changed)
- dpath->mode = ce_mode_from_stat(ce, st.st_mode);
+ wt_mode = ce_mode_from_stat(ce, st.st_mode);
else {
if (changed < 0) {
perror(ce->name);
@@ -132,7 +138,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
}
if (silent_on_removed)
continue;
+ wt_mode = 0;
}
+ dpath->mode = wt_mode;
while (i < entries) {
struct cache_entry *nce = active_cache[i];
@@ -178,7 +186,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
* Show the diff for the 'ce' if we found the one
* from the desired stage.
*/
- diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1);
+ pair = diff_unmerge(&revs->diffopt, ce->name);
+ if (wt_mode)
+ pair->two->mode = wt_mode;
if (ce_stage(ce) != diff_unmerged_stage)
continue;
}
@@ -367,8 +377,9 @@ static void do_oneway_diff(struct unpack_trees_options *o,
match_missing = !revs->ignore_merges;
if (cached && idx && ce_stage(idx)) {
- diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode,
- idx->sha1);
+ struct diff_filepair *pair;
+ pair = diff_unmerge(&revs->diffopt, idx->name);
+ fill_filespec(pair->one, idx->sha1, idx->ce_mode);
return;
}
@@ -422,8 +433,13 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
if (tree == o->df_conflict_entry)
tree = NULL;
- if (ce_path_match(idx ? idx : tree, revs->prune_data))
+ if (ce_path_match(idx ? idx : tree, &revs->prune_data)) {
do_oneway_diff(o, idx, tree);
+ if (diff_can_quit_early(&revs->diffopt)) {
+ o->exiting_early = 1;
+ return -1;
+ }
+ }
return 0;
}
@@ -496,7 +512,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
active_nr = dst - active_cache;
init_revisions(&revs, NULL);
- revs.prune_data = opt->paths;
+ init_pathspec(&revs.prune_data, opt->pathspec.raw);
tree = parse_tree_indirect(tree_sha1);
if (!tree)
die("bad tree object %s", sha1_to_hex(tree_sha1));
diff --git a/diff-no-index.c b/diff-no-index.c
index 43aeeba..3a36144 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -64,7 +64,8 @@ static int queue_diff(struct diff_options *o,
if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
char buffer1[PATH_MAX], buffer2[PATH_MAX];
- struct string_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
+ struct string_list p1 = STRING_LIST_INIT_DUP;
+ struct string_list p2 = STRING_LIST_INIT_DUP;
int len1 = 0, len2 = 0, i1, i2, ret = 0;
if (name1 && read_directory(name1, &p1))
@@ -230,8 +231,9 @@ void diff_no_index(struct rev_info *revs,
if (prefix) {
int len = strlen(prefix);
+ const char *paths[3];
+ memset(paths, 0, sizeof(paths));
- revs->diffopt.paths = xcalloc(2, sizeof(char *));
for (i = 0; i < 2; i++) {
const char *p = argv[argc - 2 + i];
/*
@@ -241,12 +243,12 @@ void diff_no_index(struct rev_info *revs,
p = (strcmp(p, "-")
? xstrdup(prefix_filename(prefix, len, p))
: p);
- revs->diffopt.paths[i] = p;
+ paths[i] = p;
}
+ diff_tree_setup_paths(paths, &revs->diffopt);
}
else
- revs->diffopt.paths = argv + argc - 2;
- revs->diffopt.nr_paths = 2;
+ diff_tree_setup_paths(argv + argc - 2, &revs->diffopt);
revs->diffopt.skip_stat_unmatch = 1;
if (!revs->diffopt.output_format)
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
@@ -258,8 +260,8 @@ void diff_no_index(struct rev_info *revs,
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
- if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
- revs->diffopt.paths[1]))
+ if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
+ revs->diffopt.pathspec.raw[1]))
exit(1);
diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
diffcore_std(&revs->diffopt);
diff --git a/diff.c b/diff.c
index 3aa695d..93ef9a2 100644
--- a/diff.c
+++ b/diff.c
@@ -23,7 +23,7 @@
#endif
static int diff_detect_rename_default;
-static int diff_rename_limit_default = 200;
+static int diff_rename_limit_default = 400;
static int diff_suppress_blank_empty;
int diff_use_color_default = -1;
static const char *diff_word_regex_cfg;
@@ -31,6 +31,8 @@ static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
static int diff_no_prefix;
+static int diff_dirstat_permille_default = 30;
+static struct diff_options default_diff_options;
static char diff_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
@@ -65,6 +67,58 @@ static int parse_diff_color_slot(const char *var, int ofs)
return -1;
}
+static int parse_dirstat_params(struct diff_options *options, const char *params,
+ struct strbuf *errmsg)
+{
+ const char *p = params;
+ int p_len, ret = 0;
+
+ while (*p) {
+ p_len = strchrnul(p, ',') - p;
+ if (!memcmp(p, "changes", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "lines", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "files", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "noncumulative", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+ } else if (!memcmp(p, "cumulative", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+ } else if (isdigit(*p)) {
+ char *end;
+ int permille = strtoul(p, &end, 10) * 10;
+ if (*end == '.' && isdigit(*++end)) {
+ /* only use first digit */
+ permille += *end - '0';
+ /* .. and ignore any further digits */
+ while (isdigit(*++end))
+ ; /* nothing */
+ }
+ if (end - p == p_len)
+ options->dirstat_permille = permille;
+ else {
+ strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+ } else {
+ strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+
+ p += p_len;
+
+ if (*p)
+ p++; /* more parameters, swallow separator */
+ }
+ return ret;
+}
+
static int git_config_rename(const char *var, const char *value)
{
if (!value)
@@ -107,6 +161,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "diff.wordregex"))
return git_config_string(&diff_word_regex_cfg, var, value);
+ if (!strcmp(var, "diff.ignoresubmodules"))
+ handle_ignore_submodules_arg(&default_diff_options, value);
+
return git_diff_basic_config(var, value, cb);
}
@@ -141,6 +198,20 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp(var, "diff.dirstat")) {
+ struct strbuf errmsg = STRBUF_INIT;
+ default_diff_options.dirstat_permille = diff_dirstat_permille_default;
+ if (parse_dirstat_params(&default_diff_options, value, &errmsg))
+ warning(_("Found errors in 'diff.dirstat' config variable:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ diff_dirstat_permille_default = default_diff_options.dirstat_permille;
+ return 0;
+ }
+
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+
return git_color_default_config(var, value, cb);
}
@@ -238,6 +309,15 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
return 0;
}
+/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
+static unsigned long diff_filespec_size(struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one))
+ return 0;
+ diff_populate_filespec(one, 1);
+ return one->size;
+}
+
static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
{
char *ptr = mf->ptr;
@@ -565,11 +645,14 @@ static void emit_rewrite_diff(const char *name_a,
line_prefix, metainfo, a_name.buf, name_a_tab, reset,
line_prefix, metainfo, b_name.buf, name_b_tab, reset,
line_prefix, fraginfo);
- print_line_count(o->file, lc_a);
+ if (!o->irreversible_delete)
+ print_line_count(o->file, lc_a);
+ else
+ fprintf(o->file, "?,?");
fprintf(o->file, " +");
print_line_count(o->file, lc_b);
fprintf(o->file, " @@%s\n", reset);
- if (lc_a)
+ if (lc_a && !o->irreversible_delete)
emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
if (lc_b)
emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
@@ -599,22 +682,20 @@ static void diff_words_append(char *line, unsigned long len,
buffer->text.ptr[buffer->text.size] = '\0';
}
-struct diff_words_style_elem
-{
+struct diff_words_style_elem {
const char *prefix;
const char *suffix;
const char *color; /* NULL; filled in by the setup code if
* color is enabled */
};
-struct diff_words_style
-{
+struct diff_words_style {
enum diff_words_type type;
struct diff_words_style_elem new, old, ctx;
const char *newline;
};
-struct diff_words_style diff_words_styles[] = {
+static struct diff_words_style diff_words_styles[] = {
{ DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
{ DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
{ DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
@@ -912,7 +993,10 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
free (ecbdata->diff_words->plus.orig);
- free(ecbdata->diff_words->word_regex);
+ if (ecbdata->diff_words->word_regex) {
+ regfree(ecbdata->diff_words->word_regex);
+ free(ecbdata->diff_words->word_regex);
+ }
free(ecbdata->diff_words);
ecbdata->diff_words = NULL;
}
@@ -1033,8 +1117,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
emit_line(ecbdata->opt, plain, reset, line, len);
fputs("~\n", ecbdata->opt->file);
} else {
- /* don't print the prefix character */
- emit_line(ecbdata->opt, plain, reset, line+1, len-1);
+ /*
+ * Skip the prefix character, if any. With
+ * diff_suppress_blank_empty, there may be
+ * none.
+ */
+ if (line[0] != '\n') {
+ line++;
+ len--;
+ }
+ emit_line(ecbdata->opt, plain, reset, line, len);
}
return;
}
@@ -1224,9 +1316,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0;
int total_files = data->nr;
- int width, name_width;
- const char *reset, *set, *add_c, *del_c;
+ int width, name_width, count;
+ const char *reset, *add_c, *del_c;
const char *line_prefix = "";
+ int extra_shown = 0;
struct strbuf *msg = NULL;
if (data->nr == 0)
@@ -1239,6 +1332,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
width = options->stat_width ? options->stat_width : 80;
name_width = options->stat_name_width ? options->stat_name_width : 50;
+ count = options->stat_count ? options->stat_count : data->nr;
/* Sanity: give at least 5 columns to the graph,
* but leave at least 10 columns for the name.
@@ -1252,13 +1346,17 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
/* Find the longest filename and max number of changes */
reset = diff_get_color_opt(options, DIFF_RESET);
- set = diff_get_color_opt(options, DIFF_PLAIN);
add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
- for (i = 0; i < data->nr; i++) {
+ for (i = 0; (i < count) && (i < data->nr); i++) {
struct diffstat_file *file = data->files[i];
uintmax_t change = file->added + file->deleted;
+ if (!data->files[i]->is_renamed &&
+ (change == 0)) {
+ count++; /* not shown == room for one more */
+ continue;
+ }
fill_print_name(file);
len = strlen(file->print_name);
if (max_len < len)
@@ -1269,6 +1367,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
if (max_change < change)
max_change = change;
}
+ count = i; /* min(count, data->nr) */
/* Compute the width of the graph part;
* 10 is for one blank at the beginning of the line plus
@@ -1283,13 +1382,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
else
width = max_change;
- for (i = 0; i < data->nr; i++) {
+ for (i = 0; i < count; i++) {
const char *prefix = "";
char *name = data->files[i]->print_name;
uintmax_t added = data->files[i]->added;
uintmax_t deleted = data->files[i]->deleted;
int name_len;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
/*
* "scale" the filename
*/
@@ -1324,11 +1428,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
fprintf(options->file, " Unmerged\n");
continue;
}
- else if (!data->files[i]->is_renamed &&
- (added + deleted == 0)) {
- total_files--;
- continue;
- }
/*
* scale the add/delete
@@ -1350,6 +1449,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
show_graph(options->file, '-', del, del_c, reset);
fprintf(options->file, "\n");
}
+ for (i = count; i < data->nr; i++) {
+ uintmax_t added = data->files[i]->added;
+ uintmax_t deleted = data->files[i]->deleted;
+ if (!data->files[i]->is_renamed &&
+ (added + deleted == 0)) {
+ total_files--;
+ continue;
+ }
+ adds += added;
+ dels += deleted;
+ if (!extra_shown)
+ fprintf(options->file, "%s ...\n", line_prefix);
+ extra_shown = 1;
+ }
fprintf(options->file, "%s", line_prefix);
fprintf(options->file,
" %d files changed, %d insertions(+), %d deletions(-)\n",
@@ -1436,7 +1549,7 @@ struct dirstat_file {
struct dirstat_dir {
struct dirstat_file *files;
- int alloc, nr, percent, cumulative;
+ int alloc, nr, permille, cumulative;
};
static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
@@ -1483,12 +1596,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
* under this directory (sources == 1).
*/
if (baselen && sources != 1) {
- int permille = this_dir * 1000 / changed;
- if (permille) {
- int percent = permille / 10;
- if (percent >= dir->percent) {
+ if (this_dir) {
+ int permille = this_dir * 1000 / changed;
+ if (permille >= dir->permille) {
fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
- percent, permille % 10, baselen, base);
+ permille / 10, permille % 10, baselen, base);
if (!dir->cumulative)
return 0;
}
@@ -1514,7 +1626,7 @@ static void show_dirstat(struct diff_options *options)
dir.files = NULL;
dir.alloc = 0;
dir.nr = 0;
- dir.percent = options->dirstat_percent;
+ dir.permille = options->dirstat_permille;
dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
changed = 0;
@@ -1522,8 +1634,36 @@ static void show_dirstat(struct diff_options *options)
struct diff_filepair *p = q->queue[i];
const char *name;
unsigned long copied, added, damage;
+ int content_changed;
+
+ name = p->two->path ? p->two->path : p->one->path;
+
+ if (p->one->sha1_valid && p->two->sha1_valid)
+ content_changed = hashcmp(p->one->sha1, p->two->sha1);
+ else
+ content_changed = 1;
+
+ if (!content_changed) {
+ /*
+ * The SHA1 has not changed, so pre-/post-content is
+ * identical. We can therefore skip looking at the
+ * file contents altogether.
+ */
+ damage = 0;
+ goto found_damage;
+ }
- name = p->one->path ? p->one->path : p->two->path;
+ if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) {
+ /*
+ * In --dirstat-by-file mode, we don't really need to
+ * look at the actual file contents at all.
+ * The fact that the SHA1 changed is enough for us to
+ * add this file to the list of results
+ * (with each file contributing equal damage).
+ */
+ damage = 1;
+ goto found_damage;
+ }
if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
diff_populate_filespec(p->one, 0);
@@ -1547,14 +1687,18 @@ static void show_dirstat(struct diff_options *options)
/*
* Original minus copied is the removed material,
* added is the new material. They are both damages
- * made to the preimage. In --dirstat-by-file mode, count
- * damaged files, not damaged lines. This is done by
- * counting only a single damaged line per file.
+ * made to the preimage.
+ * If the resulting damage is zero, we know that
+ * diffcore_count_changes() considers the two entries to
+ * be identical, but since content_changed is true, we
+ * know that there must have been _some_ kind of change,
+ * so we force all entries to have damage > 0.
*/
damage = (p->one->size - copied) + added;
- if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0)
+ if (!damage)
damage = 1;
+found_damage:
ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
dir.files[dir.nr].name = name;
dir.files[dir.nr].changed = damage;
@@ -1571,6 +1715,50 @@ static void show_dirstat(struct diff_options *options)
gather_dirstat(options, &dir, changed, "", 0);
}
+static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options)
+{
+ int i;
+ unsigned long changed;
+ struct dirstat_dir dir;
+
+ if (data->nr == 0)
+ return;
+
+ dir.files = NULL;
+ dir.alloc = 0;
+ dir.nr = 0;
+ dir.permille = options->dirstat_permille;
+ dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+
+ changed = 0;
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+ unsigned long damage = file->added + file->deleted;
+ if (file->is_binary)
+ /*
+ * binary files counts bytes, not lines. Must find some
+ * way to normalize binary bytes vs. textual lines.
+ * The following heuristic assumes that there are 64
+ * bytes per "line".
+ * This is stupid and ugly, but very cheap...
+ */
+ damage = (damage + 63) / 64;
+ ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
+ dir.files[dir.nr].name = file->name;
+ dir.files[dir.nr].changed = damage;
+ changed += damage;
+ dir.nr++;
+ }
+
+ /* This can happen even with many files, if everything was renames */
+ if (!changed)
+ return;
+
+ /* Show all directories with more than x% of the changes */
+ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+ gather_dirstat(options, &dir, changed, "", 0);
+}
+
static void free_diffstat_info(struct diffstat_t *diffstat)
{
int i;
@@ -1673,20 +1861,20 @@ static unsigned char *deflate_it(char *data,
{
int bound;
unsigned char *deflated;
- z_stream stream;
+ git_zstream stream;
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
- bound = deflateBound(&stream, size);
+ git_deflate_init(&stream, zlib_compression_level);
+ bound = git_deflate_bound(&stream, size);
deflated = xmalloc(bound);
stream.next_out = deflated;
stream.avail_out = bound;
stream.next_in = (unsigned char *)data;
stream.avail_in = size;
- while (deflate(&stream, Z_FINISH) == Z_OK)
+ while (git_deflate(&stream, Z_FINISH) == Z_OK)
; /* nothing */
- deflateEnd(&stream);
+ git_deflate_end(&stream);
*result_size = stream.total_out;
return deflated;
}
@@ -1761,8 +1949,14 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre
static void diff_filespec_load_driver(struct diff_filespec *one)
{
- if (!one->driver)
+ /* Use already-loaded driver */
+ if (one->driver)
+ return;
+
+ if (S_ISREG(one->mode))
one->driver = userdiff_find_by_path(one->path);
+
+ /* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
@@ -1810,22 +2004,9 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one))
return NULL;
- if (!S_ISREG(one->mode))
- return NULL;
- diff_filespec_load_driver(one);
- if (!one->driver->textconv)
- return NULL;
-
- if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
- struct notes_cache *c = xmalloc(sizeof(*c));
- struct strbuf name = STRBUF_INIT;
-
- strbuf_addf(&name, "textconv/%s", one->driver->name);
- notes_cache_init(c, name.buf, one->driver->textconv);
- one->driver->textconv_cache = c;
- }
- return one->driver;
+ diff_filespec_load_driver(one);
+ return userdiff_get_textconv(one->driver);
}
static void builtin_diff(const char *name_a,
@@ -1928,7 +2109,11 @@ static void builtin_diff(const char *name_a,
}
}
- if (!DIFF_OPT_TST(o, TEXT) &&
+ if (o->irreversible_delete && lbl[1][0] == '/') {
+ fprintf(o->file, "%s", header.buf);
+ strbuf_reset(&header);
+ goto free_ab_and_return;
+ } else if (!DIFF_OPT_TST(o, TEXT) &&
( (!textconv_one && diff_filespec_is_binary(one)) ||
(!textconv_two && diff_filespec_is_binary(two)) )) {
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
@@ -1948,8 +2133,7 @@ static void builtin_diff(const char *name_a,
fprintf(o->file, "%sBinary files %s and %s differ\n",
line_prefix, lbl[0], lbl[1]);
o->found_changes = 1;
- }
- else {
+ } else {
/* Crazy xdl interfaces.. */
const char *diffopts = getenv("GIT_DIFF_OPTS");
xpparam_t xpp;
@@ -2064,25 +2248,28 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
data->is_unmerged = 1;
return;
}
- if (complete_rewrite) {
+
+ if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
+ data->is_binary = 1;
+ data->added = diff_filespec_size(two);
+ data->deleted = diff_filespec_size(one);
+ }
+
+ else if (complete_rewrite) {
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
- goto free_and_return;
}
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
- if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
- data->is_binary = 1;
- data->added = mf2.size;
- data->deleted = mf1.size;
- } else {
+ else {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = o->xdl_opts;
@@ -2090,7 +2277,6 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
&xpp, &xecfg);
}
- free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
}
@@ -2143,7 +2329,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
ecbdata.ws_rule = data.ws_rule;
check_blank_at_eof(&mf1, &mf2, &ecbdata);
- blank_at_eof = ecbdata.blank_at_eof_in_preimage;
+ blank_at_eof = ecbdata.blank_at_eof_in_postimage;
if (blank_at_eof) {
static char *err;
@@ -2376,10 +2562,14 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
}
else {
enum object_type type;
- if (size_only)
+ if (size_only) {
type = sha1_object_info(s->sha1, &s->size);
- else {
+ if (type < 0)
+ die("unable to read %s", sha1_to_hex(s->sha1));
+ } else {
s->data = read_sha1_file(s->sha1, &type, &s->size);
+ if (!s->data)
+ die("unable to read %s", sha1_to_hex(s->sha1));
s->should_free = 1;
}
}
@@ -2627,8 +2817,7 @@ static void fill_metainfo(struct strbuf *msg,
(!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
abbrev = 40;
}
- strbuf_addf(msg, "%s%sindex %s..", set,
- line_prefix,
+ strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
find_unique_abbrev(one->sha1, abbrev));
strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
if (one->mode == two->mode)
@@ -2705,10 +2894,16 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
{
/* Strip the prefix but do not molest /dev/null and absolute paths */
- if (*namep && **namep != '/')
+ if (*namep && **namep != '/') {
*namep += prefix_length;
- if (*otherp && **otherp != '/')
+ if (**namep == '/')
+ ++*namep;
+ }
+ if (*otherp && **otherp != '/') {
*otherp += prefix_length;
+ if (**otherp == '/')
+ ++*otherp;
+ }
}
static void run_diff(struct diff_filepair *p, struct diff_options *o)
@@ -2814,15 +3009,14 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
void diff_setup(struct diff_options *options)
{
- memset(options, 0, sizeof(*options));
- memset(&diff_queued_diff, 0, sizeof(diff_queued_diff));
+ memcpy(options, &default_diff_options, sizeof(*options));
options->file = stdout;
options->line_termination = '\n';
options->break_opt = -1;
options->rename_limit = -1;
- options->dirstat_percent = 3;
+ options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
options->change = diff_change;
@@ -2991,9 +3185,129 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
static int diff_scoreopt_parse(const char *opt);
+static inline int short_opt(char opt, const char **argv,
+ const char **optarg)
+{
+ const char *arg = argv[0];
+ if (arg[0] != '-' || arg[1] != opt)
+ return 0;
+ if (arg[2] != '\0') {
+ *optarg = arg + 2;
+ return 1;
+ }
+ if (!argv[1])
+ die("Option '%c' requires a value", opt);
+ *optarg = argv[1];
+ return 2;
+}
+
+int parse_long_opt(const char *opt, const char **argv,
+ const char **optarg)
+{
+ const char *arg = argv[0];
+ if (arg[0] != '-' || arg[1] != '-')
+ return 0;
+ arg += strlen("--");
+ if (prefixcmp(arg, opt))
+ return 0;
+ arg += strlen(opt);
+ if (*arg == '=') { /* sticked form: --option=value */
+ *optarg = arg + 1;
+ return 1;
+ }
+ if (*arg != '\0')
+ return 0;
+ /* separate form: --option value */
+ if (!argv[1])
+ die("Option '--%s' requires a value", opt);
+ *optarg = argv[1];
+ return 2;
+}
+
+static int stat_opt(struct diff_options *options, const char **av)
+{
+ const char *arg = av[0];
+ char *end;
+ int width = options->stat_width;
+ int name_width = options->stat_name_width;
+ int count = options->stat_count;
+ int argcount = 1;
+
+ arg += strlen("--stat");
+ end = (char *)arg;
+
+ switch (*arg) {
+ case '-':
+ if (!prefixcmp(arg, "-width")) {
+ arg += strlen("-width");
+ if (*arg == '=')
+ width = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-width' requires a value");
+ else if (!*arg) {
+ width = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
+ } else if (!prefixcmp(arg, "-name-width")) {
+ arg += strlen("-name-width");
+ if (*arg == '=')
+ name_width = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-name-width' requires a value");
+ else if (!*arg) {
+ name_width = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
+ } else if (!prefixcmp(arg, "-count")) {
+ arg += strlen("-count");
+ if (*arg == '=')
+ count = strtoul(arg + 1, &end, 10);
+ else if (!*arg && !av[1])
+ die("Option '--stat-count' requires a value");
+ else if (!*arg) {
+ count = strtoul(av[1], &end, 10);
+ argcount = 2;
+ }
+ }
+ break;
+ case '=':
+ width = strtoul(arg+1, &end, 10);
+ if (*end == ',')
+ name_width = strtoul(end+1, &end, 10);
+ if (*end == ',')
+ count = strtoul(end+1, &end, 10);
+ }
+
+ /* Important! This checks all the error cases! */
+ if (*end)
+ return 0;
+ options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ options->stat_name_width = name_width;
+ options->stat_width = width;
+ options->stat_count = count;
+ return argcount;
+}
+
+static int parse_dirstat_opt(struct diff_options *options, const char *params)
+{
+ struct strbuf errmsg = STRBUF_INIT;
+ if (parse_dirstat_params(options, params, &errmsg))
+ die(_("Failed to parse --dirstat/-X option parameter:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ /*
+ * The caller knows a dirstat-related option is given from the command
+ * line; allow it to say "return this_function();"
+ */
+ options->output_format |= DIFF_FORMAT_DIRSTAT;
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
+ const char *optarg;
+ int argcount;
/* Output format options */
if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
@@ -3008,15 +3322,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->output_format |= DIFF_FORMAT_NUMSTAT;
else if (!strcmp(arg, "--shortstat"))
options->output_format |= DIFF_FORMAT_SHORTSTAT;
- else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- else if (!strcmp(arg, "--cumulative")) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
- } else if (opt_arg(arg, 0, "dirstat-by-file",
- &options->dirstat_percent)) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
+ return parse_dirstat_opt(options, "");
+ else if (!prefixcmp(arg, "-X"))
+ return parse_dirstat_opt(options, arg + 2);
+ else if (!prefixcmp(arg, "--dirstat="))
+ return parse_dirstat_opt(options, arg + 10);
+ else if (!strcmp(arg, "--cumulative"))
+ return parse_dirstat_opt(options, "cumulative");
+ else if (!strcmp(arg, "--dirstat-by-file"))
+ return parse_dirstat_opt(options, "files");
+ else if (!prefixcmp(arg, "--dirstat-by-file=")) {
+ parse_dirstat_opt(options, "files");
+ return parse_dirstat_opt(options, arg + 18);
}
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
@@ -3030,49 +3348,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->output_format |= DIFF_FORMAT_NAME_STATUS;
else if (!strcmp(arg, "-s"))
options->output_format |= DIFF_FORMAT_NO_OUTPUT;
- else if (!prefixcmp(arg, "--stat")) {
- char *end;
- int width = options->stat_width;
- int name_width = options->stat_name_width;
- arg += 6;
- end = (char *)arg;
-
- switch (*arg) {
- case '-':
- if (!prefixcmp(arg, "-width="))
- width = strtoul(arg + 7, &end, 10);
- else if (!prefixcmp(arg, "-name-width="))
- name_width = strtoul(arg + 12, &end, 10);
- break;
- case '=':
- width = strtoul(arg+1, &end, 10);
- if (*end == ',')
- name_width = strtoul(end+1, &end, 10);
- }
-
- /* Important! This checks all the error cases! */
- if (*end)
- return 0;
- options->output_format |= DIFF_FORMAT_DIFFSTAT;
- options->stat_name_width = name_width;
- options->stat_width = width;
- }
+ else if (!prefixcmp(arg, "--stat"))
+ /* --stat, --stat-width, --stat-name-width, or --stat-count */
+ return stat_opt(options, av);
/* renames options */
- else if (!prefixcmp(arg, "-B")) {
+ else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
+ !strcmp(arg, "--break-rewrites")) {
if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -B: %s", arg+2);
}
- else if (!prefixcmp(arg, "-M")) {
+ else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") ||
+ !strcmp(arg, "--find-renames")) {
if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -M: %s", arg+2);
options->detect_rename = DIFF_DETECT_RENAME;
}
- else if (!prefixcmp(arg, "-C")) {
+ else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
+ options->irreversible_delete = 1;
+ }
+ else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
+ !strcmp(arg, "--find-copies")) {
if (options->detect_rename == DIFF_DETECT_COPY)
DIFF_OPT_SET(options, FIND_COPIES_HARDER);
if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
- return -1;
+ return error("invalid argument to -C: %s", arg+2);
options->detect_rename = DIFF_DETECT_COPY;
}
else if (!strcmp(arg, "--no-renames"))
@@ -3150,10 +3450,11 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else
die("bad --word-diff argument: %s", type);
}
- else if (!prefixcmp(arg, "--word-diff-regex=")) {
+ else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
if (options->word_diff == DIFF_WORDS_NONE)
options->word_diff = DIFF_WORDS_PLAIN;
- options->word_regex = arg + 18;
+ options->word_regex = optarg;
+ return argcount;
}
else if (!strcmp(arg, "--exit-code"))
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
@@ -3167,11 +3468,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
DIFF_OPT_SET(options, ALLOW_TEXTCONV);
else if (!strcmp(arg, "--no-textconv"))
DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
- else if (!strcmp(arg, "--ignore-submodules"))
+ else if (!strcmp(arg, "--ignore-submodules")) {
+ DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
handle_ignore_submodules_arg(options, "all");
- else if (!prefixcmp(arg, "--ignore-submodules="))
+ } else if (!prefixcmp(arg, "--ignore-submodules=")) {
+ DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
handle_ignore_submodules_arg(options, arg + 20);
- else if (!strcmp(arg, "--submodule"))
+ } else if (!strcmp(arg, "--submodule"))
DIFF_OPT_SET(options, SUBMODULE_LOG);
else if (!prefixcmp(arg, "--submodule=")) {
if (!strcmp(arg + 12, "log"))
@@ -3181,18 +3484,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
/* misc options */
else if (!strcmp(arg, "-z"))
options->line_termination = 0;
- else if (!prefixcmp(arg, "-l"))
- options->rename_limit = strtoul(arg+2, NULL, 10);
- else if (!prefixcmp(arg, "-S"))
- options->pickaxe = arg + 2;
+ else if ((argcount = short_opt('l', av, &optarg))) {
+ options->rename_limit = strtoul(optarg, NULL, 10);
+ return argcount;
+ }
+ else if ((argcount = short_opt('S', av, &optarg))) {
+ options->pickaxe = optarg;
+ options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
+ return argcount;
+ } else if ((argcount = short_opt('G', av, &optarg))) {
+ options->pickaxe = optarg;
+ options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
+ return argcount;
+ }
else if (!strcmp(arg, "--pickaxe-all"))
- options->pickaxe_opts = DIFF_PICKAXE_ALL;
+ options->pickaxe_opts |= DIFF_PICKAXE_ALL;
else if (!strcmp(arg, "--pickaxe-regex"))
- options->pickaxe_opts = DIFF_PICKAXE_REGEX;
- else if (!prefixcmp(arg, "-O"))
- options->orderfile = arg + 2;
- else if (!prefixcmp(arg, "--diff-filter="))
- options->filter = arg + 14;
+ options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
+ else if ((argcount = short_opt('O', av, &optarg))) {
+ options->orderfile = optarg;
+ return argcount;
+ }
+ else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
+ options->filter = optarg;
+ return argcount;
+ }
else if (!strcmp(arg, "--abbrev"))
options->abbrev = DEFAULT_ABBREV;
else if (!prefixcmp(arg, "--abbrev=")) {
@@ -3202,26 +3518,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (40 < options->abbrev)
options->abbrev = 40;
}
- else if (!prefixcmp(arg, "--src-prefix="))
- options->a_prefix = arg + 13;
- else if (!prefixcmp(arg, "--dst-prefix="))
- options->b_prefix = arg + 13;
+ else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
+ options->a_prefix = optarg;
+ return argcount;
+ }
+ else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
+ options->b_prefix = optarg;
+ return argcount;
+ }
else if (!strcmp(arg, "--no-prefix"))
options->a_prefix = options->b_prefix = "";
else if (opt_arg(arg, '\0', "inter-hunk-context",
&options->interhunkcontext))
;
- else if (!prefixcmp(arg, "--output=")) {
- options->file = fopen(arg + strlen("--output="), "w");
+ else if ((argcount = parse_long_opt("output", av, &optarg))) {
+ options->file = fopen(optarg, "w");
if (!options->file)
- die_errno("Could not open '%s'", arg + strlen("--output="));
+ die_errno("Could not open '%s'", optarg);
options->close_file = 1;
+ return argcount;
} else
return 0;
return 1;
}
-static int parse_num(const char **cp_p)
+int parse_rename_score(const char **cp_p)
{
unsigned long num, scale;
int ch, dot;
@@ -3264,10 +3585,26 @@ static int diff_scoreopt_parse(const char *opt)
if (*opt++ != '-')
return -1;
cmd = *opt++;
+ if (cmd == '-') {
+ /* convert the long-form arguments into short-form versions */
+ if (!prefixcmp(opt, "break-rewrites")) {
+ opt += strlen("break-rewrites");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'B';
+ } else if (!prefixcmp(opt, "find-copies")) {
+ opt += strlen("find-copies");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'C';
+ } else if (!prefixcmp(opt, "find-renames")) {
+ opt += strlen("find-renames");
+ if (*opt == 0 || *opt++ == '=')
+ cmd = 'M';
+ }
+ }
if (cmd != 'M' && cmd != 'C' && cmd != 'B')
return -1; /* that is not a -M, -C nor -B option */
- opt1 = parse_num(&opt);
+ opt1 = parse_rename_score(&opt);
if (cmd != 'B')
opt2 = 0;
else {
@@ -3277,7 +3614,7 @@ static int diff_scoreopt_parse(const char *opt)
return -1; /* we expect -B80/99 or -B80 */
else {
opt++;
- opt2 = parse_num(&opt);
+ opt2 = parse_rename_score(&opt);
}
}
if (*opt != 0)
@@ -3430,7 +3767,7 @@ static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return; /* no useful stat for tree diffs */
run_diffstat(p, o, diffstat);
}
@@ -3443,7 +3780,7 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return; /* nothing to check in tree diffs */
run_checkdiff(p, o);
}
@@ -3759,9 +4096,16 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
len2, p->two->path);
git_SHA1_Update(&ctx, buffer, len1);
+ if (diff_filespec_is_binary(p->one) ||
+ diff_filespec_is_binary(p->two)) {
+ git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40);
+ git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40);
+ continue;
+ }
+
xpp.flags = 0;
xecfg.ctxlen = 3;
- xecfg.flags = XDL_EMIT_FUNCNAMES;
+ xecfg.flags = 0;
xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
&xpp, &xecfg);
}
@@ -3810,11 +4154,34 @@ static int is_summary_empty(const struct diff_queue_struct *q)
return 1;
}
+static const char rename_limit_warning[] =
+"inexact rename detection was skipped due to too many files.";
+
+static const char degrade_cc_to_c_warning[] =
+"only found copies from modified paths due to too many files.";
+
+static const char rename_limit_advice[] =
+"you may want to set your %s variable to at least "
+"%d and retry the command.";
+
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
+{
+ if (degraded_cc)
+ warning(degrade_cc_to_c_warning);
+ else if (needed)
+ warning(rename_limit_warning);
+ else
+ return;
+ if (0 < needed && needed < 32767)
+ warning(rename_limit_advice, varname, needed);
+}
+
void diff_flush(struct diff_options *options)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i, output_format = options->output_format;
int separator = 0;
+ int dirstat_by_line = 0;
/*
* Order: raw, stat, summary, patch
@@ -3835,7 +4202,11 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
+ if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
+ dirstat_by_line = 1;
+
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
+ dirstat_by_line) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -3850,10 +4221,12 @@ void diff_flush(struct diff_options *options)
show_stats(&diffstat, options);
if (output_format & DIFF_FORMAT_SHORTSTAT)
show_shortstats(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIRSTAT)
+ show_dirstat_by_line(&diffstat, options);
free_diffstat_info(&diffstat);
separator++;
}
- if (output_format & DIFF_FORMAT_DIRSTAT)
+ if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line)
show_dirstat(options);
if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
@@ -4060,25 +4433,24 @@ void diffcore_fix_diff_index(struct diff_options *options)
void diffcore_std(struct diff_options *options)
{
- /* We never run this function more than one time, because the
- * rename/copy detection logic can only run once.
- */
- if (diff_queued_diff.run)
- return;
-
if (options->skip_stat_unmatch)
diffcore_skip_stat_unmatch(options);
- if (options->break_opt != -1)
- diffcore_break(options->break_opt);
- if (options->detect_rename)
- diffcore_rename(options);
- if (options->break_opt != -1)
- diffcore_merge_broken();
+ if (!options->found_follow) {
+ /* See try_to_follow_renames() in tree-diff.c */
+ if (options->break_opt != -1)
+ diffcore_break(options->break_opt);
+ if (options->detect_rename)
+ diffcore_rename(options);
+ if (options->break_opt != -1)
+ diffcore_merge_broken();
+ }
if (options->pickaxe)
- diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ diffcore_pickaxe(options);
if (options->orderfile)
diffcore_order(options->orderfile);
- diff_resolve_rename_copy();
+ if (!options->found_follow)
+ /* See try_to_follow_renames() in tree-diff.c */
+ diff_resolve_rename_copy();
diffcore_apply_filter(options->filter);
if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
@@ -4086,12 +4458,16 @@ void diffcore_std(struct diff_options *options)
else
DIFF_OPT_CLR(options, HAS_CHANGES);
- diff_queued_diff.run = 1;
+ options->found_follow = 0;
}
int diff_result_code(struct diff_options *opt, int status)
{
int result = 0;
+
+ diff_warn_rename_limit("diff.renamelimit",
+ opt->needed_rename_limit,
+ opt->degraded_cc_to_c);
if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
!(opt->output_format & DIFF_FORMAT_CHECKDIFF))
return status;
@@ -4104,6 +4480,31 @@ int diff_result_code(struct diff_options *opt, int status)
return result;
}
+int diff_can_quit_early(struct diff_options *opt)
+{
+ return (DIFF_OPT_TST(opt, QUICK) &&
+ !opt->filter &&
+ DIFF_OPT_TST(opt, HAS_CHANGES));
+}
+
+/*
+ * Shall changes to this submodule be ignored?
+ *
+ * Submodule changes can be configured to be ignored separately for each path,
+ * but that configuration can be overridden from the command line.
+ */
+static int is_submodule_ignored(const char *path, struct diff_options *options)
+{
+ int ignored = 0;
+ unsigned orig_flags = options->flags;
+ if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
+ set_diffopt_flags_from_submodule_config(options, path);
+ if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
+ ignored = 1;
+ options->flags = orig_flags;
+ return ignored;
+}
+
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
@@ -4111,7 +4512,7 @@ void diff_addremove(struct diff_options *options,
{
struct diff_filespec *one, *two;
- if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
+ if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
return;
/* This may look odd, but it is a preparation for
@@ -4158,8 +4559,8 @@ void diff_change(struct diff_options *options,
{
struct diff_filespec *one, *two;
- if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
- && S_ISGITLINK(new_mode))
+ if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
+ is_submodule_ignored(concatpath, options))
return;
if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
@@ -4187,20 +4588,20 @@ void diff_change(struct diff_options *options,
DIFF_OPT_SET(options, HAS_CHANGES);
}
-void diff_unmerge(struct diff_options *options,
- const char *path,
- unsigned mode, const unsigned char *sha1)
+struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
{
+ struct diff_filepair *pair;
struct diff_filespec *one, *two;
if (options->prefix &&
strncmp(path, options->prefix, options->prefix_length))
- return;
+ return NULL;
one = alloc_filespec(path);
two = alloc_filespec(path);
- fill_filespec(one, sha1, mode);
- diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
+ pair = diff_queue(&diff_queued_diff, one, two);
+ pair->is_unmerged = 1;
+ return pair;
}
static char *run_textconv(const char *pgm, struct diff_filespec *spec,
@@ -4258,7 +4659,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
return df->size;
}
- if (driver->textconv_cache) {
+ if (driver->textconv_cache && df->sha1_valid) {
*outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
&size);
if (*outbuf)
@@ -4269,7 +4670,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
if (!*outbuf)
die("unable to read files to diff");
- if (driver->textconv_cache) {
+ if (driver->textconv_cache && df->sha1_valid) {
/* ignore errors, as we might be in a readonly repository */
notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
size);
diff --git a/diff.h b/diff.h
index 063d10a..b920a20 100644
--- a/diff.h
+++ b/diff.h
@@ -77,6 +77,8 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24)
#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
+#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
+#define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
@@ -103,14 +105,17 @@ struct diff_options {
int interhunkcontext;
int break_opt;
int detect_rename;
+ int irreversible_delete;
int skip_stat_unmatch;
int line_termination;
int output_format;
int pickaxe_opts;
int rename_score;
int rename_limit;
- int warn_on_too_large_rename;
- int dirstat_percent;
+ int needed_rename_limit;
+ int degraded_cc_to_c;
+ int show_rename_progress;
+ int dirstat_permille;
int setup;
int abbrev;
const char *prefix;
@@ -120,18 +125,20 @@ struct diff_options {
int stat_width;
int stat_name_width;
+ int stat_count;
const char *word_regex;
enum diff_words_type word_diff;
/* this is set by diffcore for DIFF_FORMAT_PATCH */
int found_changes;
+ /* to support internal diff recursion by --follow hack*/
+ int found_follow;
+
FILE *file;
int close_file;
- int nr_paths;
- const char **paths;
- int *pathlens;
+ struct pathspec pathspec;
change_fn_t change;
add_remove_fn_t add_remove;
diff_format_fn_t format_callback;
@@ -192,6 +199,8 @@ extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
+extern int diff_can_quit_early(struct diff_options *);
+
extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
@@ -205,15 +214,19 @@ extern void diff_change(struct diff_options *,
const char *fullpath,
unsigned dirty_submodule1, unsigned dirty_submodule2);
-extern void diff_unmerge(struct diff_options *,
- const char *path,
- unsigned mode,
- const unsigned char *sha1);
+extern struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
#define DIFF_SETUP_REVERSE 1
#define DIFF_SETUP_USE_CACHE 2
#define DIFF_SETUP_USE_SIZE_CACHE 4
+/*
+ * Poor man's alternative to parse-option, to allow both sticked form
+ * (--option=value) and separate form (--option value).
+ */
+extern int parse_long_opt(const char *opt, const char **argv,
+ const char **optarg);
+
extern int git_diff_basic_config(const char *var, const char *value, void *cb);
extern int git_diff_ui_config(const char *var, const char *value, void *cb);
extern int diff_use_color_default;
@@ -227,6 +240,9 @@ extern int diff_setup_done(struct diff_options *);
#define DIFF_PICKAXE_ALL 1
#define DIFF_PICKAXE_REGEX 2
+#define DIFF_PICKAXE_KIND_S 4 /* traditional plumbing counter */
+#define DIFF_PICKAXE_KIND_G 8 /* grep in the patch */
+
extern void diffcore_std(struct diff_options *);
extern void diffcore_fix_diff_index(struct diff_options *);
@@ -260,6 +276,7 @@ extern void diffcore_fix_diff_index(struct diff_options *);
extern int diff_queue_is_empty(void);
extern void diff_flush(struct diff_options*);
+extern void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
/* diff-raw status letters */
#define DIFF_STATUS_ADDED 'A'
@@ -301,4 +318,6 @@ extern size_t fill_textconv(struct userdiff_driver *driver,
extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
+extern int parse_rename_score(const char **cp_p);
+
#endif /* DIFF_H */
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
index 929de15..ea03b91 100644
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
@@ -1,9 +1,148 @@
/*
* Copyright (C) 2005 Junio C Hamano
+ * Copyright (C) 2010 Google Inc.
*/
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
+#include "xdiff-interface.h"
+
+struct diffgrep_cb {
+ regex_t *regexp;
+ int hit;
+};
+
+static void diffgrep_consume(void *priv, char *line, unsigned long len)
+{
+ struct diffgrep_cb *data = priv;
+ regmatch_t regmatch;
+ int hold;
+
+ if (line[0] != '+' && line[0] != '-')
+ return;
+ if (data->hit)
+ /*
+ * NEEDSWORK: we should have a way to terminate the
+ * caller early.
+ */
+ return;
+ /* Yuck -- line ought to be "const char *"! */
+ hold = line[len];
+ line[len] = '\0';
+ data->hit = !regexec(data->regexp, line + 1, 1, &regmatch, 0);
+ line[len] = hold;
+}
+
+static void fill_one(struct diff_filespec *one,
+ mmfile_t *mf, struct userdiff_driver **textconv)
+{
+ if (DIFF_FILE_VALID(one)) {
+ *textconv = get_textconv(one);
+ mf->size = fill_textconv(*textconv, one, &mf->ptr);
+ } else {
+ memset(mf, 0, sizeof(*mf));
+ }
+}
+
+static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
+{
+ regmatch_t regmatch;
+ struct userdiff_driver *textconv_one = NULL;
+ struct userdiff_driver *textconv_two = NULL;
+ mmfile_t mf1, mf2;
+ int hit;
+
+ if (diff_unmodified_pair(p))
+ return 0;
+
+ fill_one(p->one, &mf1, &textconv_one);
+ fill_one(p->two, &mf2, &textconv_two);
+
+ if (!mf1.ptr) {
+ if (!mf2.ptr)
+ return 0; /* ignore unmerged */
+ /* created "two" -- does it have what we are looking for? */
+ hit = !regexec(regexp, p->two->data, 1, &regmatch, 0);
+ } else if (!mf2.ptr) {
+ /* removed "one" -- did it have what we are looking for? */
+ hit = !regexec(regexp, p->one->data, 1, &regmatch, 0);
+ } else {
+ /*
+ * We have both sides; need to run textual diff and see if
+ * the pattern appears on added/deleted lines.
+ */
+ struct diffgrep_cb ecbdata;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ ecbdata.regexp = regexp;
+ ecbdata.hit = 0;
+ xecfg.ctxlen = o->context;
+ xecfg.interhunkctxlen = o->interhunkcontext;
+ xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata,
+ &xpp, &xecfg);
+ hit = ecbdata.hit;
+ }
+ if (textconv_one)
+ free(mf1.ptr);
+ if (textconv_two)
+ free(mf2.ptr);
+ return hit;
+}
+
+static void diffcore_pickaxe_grep(struct diff_options *o)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i, has_changes, err;
+ regex_t regex;
+ struct diff_queue_struct outq;
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ err = regcomp(&regex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
+ if (err) {
+ char errbuf[1024];
+ regerror(err, &regex, errbuf, 1024);
+ regfree(&regex);
+ die("invalid log-grep regex: %s", errbuf);
+ }
+
+ if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
+ /* Showing the whole changeset if needle exists */
+ for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (diff_grep(p, &regex, o))
+ has_changes++;
+ }
+ if (has_changes)
+ return; /* do not munge the queue */
+
+ /*
+ * Otherwise we will clear the whole queue by copying
+ * the empty outq at the end of this function, but
+ * first clear the current entries in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ } else {
+ /* Showing only the filepairs that has the needle */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (diff_grep(p, &regex, o))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ }
+
+ regfree(&regex);
+
+ free(q->queue);
+ *q = outq;
+ return;
+}
static unsigned int contains(struct diff_filespec *one,
const char *needle, unsigned long len,
@@ -48,8 +187,10 @@ static unsigned int contains(struct diff_filespec *one,
return cnt;
}
-void diffcore_pickaxe(const char *needle, int opts)
+static void diffcore_pickaxe_count(struct diff_options *o)
{
+ const char *needle = o->pickaxe;
+ int opts = o->pickaxe_opts;
struct diff_queue_struct *q = &diff_queued_diff;
unsigned long len = strlen(needle);
int i, has_changes;
@@ -128,11 +269,19 @@ void diffcore_pickaxe(const char *needle, int opts)
diff_free_filepair(p);
}
- if (opts & DIFF_PICKAXE_REGEX) {
+ if (opts & DIFF_PICKAXE_REGEX)
regfree(&regex);
- }
free(q->queue);
*q = outq;
return;
}
+
+void diffcore_pickaxe(struct diff_options *o)
+{
+ /* Might want to warn when both S and G are on; I don't care... */
+ if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G)
+ diffcore_pickaxe_grep(o);
+ else
+ diffcore_pickaxe_count(o);
+}
diff --git a/diffcore-rename.c b/diffcore-rename.c
index df41be5..f639601 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -5,6 +5,7 @@
#include "diff.h"
#include "diffcore.h"
#include "hash.h"
+#include "progress.h"
/* Table of rename/copy destinations */
@@ -54,22 +55,23 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
/* Table of rename/copy src files */
static struct diff_rename_src {
- struct diff_filespec *one;
+ struct diff_filepair *p;
unsigned short score; /* to remember the break score */
} *rename_src;
static int rename_src_nr, rename_src_alloc;
-static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
- unsigned short score)
+static struct diff_rename_src *register_rename_src(struct diff_filepair *p)
{
int first, last;
+ struct diff_filespec *one = p->one;
+ unsigned short score = p->score;
first = 0;
last = rename_src_nr;
while (last > first) {
int next = (last + first) >> 1;
struct diff_rename_src *src = &(rename_src[next]);
- int cmp = strcmp(one->path, src->one->path);
+ int cmp = strcmp(one->path, src->p->one->path);
if (!cmp)
return src;
if (cmp < 0) {
@@ -89,7 +91,7 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
if (first < rename_src_nr)
memmove(rename_src + first + 1, rename_src + first,
(rename_src_nr - first - 1) * sizeof(*rename_src));
- rename_src[first].one = one;
+ rename_src[first].p = p;
rename_src[first].score = score;
return &(rename_src[first]);
}
@@ -170,7 +172,7 @@ static int estimate_similarity(struct diff_filespec *src,
* and the final score computation below would not have a
* divide-by-zero issue.
*/
- if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+ if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
if (!src->cnt_data && diff_populate_filespec(src, 0))
@@ -204,7 +206,7 @@ static void record_rename_pair(int dst_index, int src_index, int score)
if (rename_dst[dst_index].pair)
die("internal error: dst already matched.");
- src = rename_src[src_index].one;
+ src = rename_src[src_index].p->one;
src->rename_used++;
src->count++;
@@ -247,7 +249,8 @@ struct file_similarity {
};
static int find_identical_files(struct file_similarity *src,
- struct file_similarity *dst)
+ struct file_similarity *dst,
+ struct diff_options *options)
{
int renames = 0;
@@ -277,6 +280,8 @@ static int find_identical_files(struct file_similarity *src,
}
/* Give higher scores to sources that haven't been used already */
score = !source->rename_used;
+ if (source->rename_used && options->detect_rename != DIFF_DETECT_COPY)
+ continue;
score += basename_same(source, target);
if (score > best_score) {
best = p;
@@ -306,11 +311,12 @@ static void free_similarity_list(struct file_similarity *p)
}
}
-static int find_same_files(void *ptr)
+static int find_same_files(void *ptr, void *data)
{
int ret;
struct file_similarity *p = ptr;
struct file_similarity *src = NULL, *dst = NULL;
+ struct diff_options *options = data;
/* Split the hash list up into sources and destinations */
do {
@@ -329,7 +335,7 @@ static int find_same_files(void *ptr)
* If we have both sources *and* destinations, see if
* we can match them up
*/
- ret = (src && dst) ? find_identical_files(src, dst) : 0;
+ ret = (src && dst) ? find_identical_files(src, dst, options) : 0;
/* Free the hashes and return the number of renames found */
free_similarity_list(src);
@@ -377,20 +383,20 @@ static void insert_file_table(struct hash_table *table, int src_dst, int index,
* and then during the second round we try to match
* cache-dirty entries as well.
*/
-static int find_exact_renames(void)
+static int find_exact_renames(struct diff_options *options)
{
int i;
struct hash_table file_table;
init_hash(&file_table);
for (i = 0; i < rename_src_nr; i++)
- insert_file_table(&file_table, -1, i, rename_src[i].one);
+ insert_file_table(&file_table, -1, i, rename_src[i].p->one);
for (i = 0; i < rename_dst_nr; i++)
insert_file_table(&file_table, 1, i, rename_dst[i].two);
/* Find the renames */
- i = for_each_hash(&file_table, find_same_files);
+ i = for_each_hash(&file_table, find_same_files, options);
/* .. and free the hash data structure */
free_hash(&file_table);
@@ -414,16 +420,86 @@ static void record_if_better(struct diff_score m[], struct diff_score *o)
m[worst] = *o;
}
+/*
+ * Returns:
+ * 0 if we are under the limit;
+ * 1 if we need to disable inexact rename detection;
+ * 2 if we would be under the limit if we were given -C instead of -C -C.
+ */
+static int too_many_rename_candidates(int num_create,
+ struct diff_options *options)
+{
+ int rename_limit = options->rename_limit;
+ int num_src = rename_src_nr;
+ int i;
+
+ options->needed_rename_limit = 0;
+
+ /*
+ * This basically does a test for the rename matrix not
+ * growing larger than a "rename_limit" square matrix, ie:
+ *
+ * num_create * num_src > rename_limit * rename_limit
+ *
+ * but handles the potential overflow case specially (and we
+ * assume at least 32-bit integers)
+ */
+ if (rename_limit <= 0 || rename_limit > 32767)
+ rename_limit = 32767;
+ if ((num_create <= rename_limit || num_src <= rename_limit) &&
+ (num_create * num_src <= rename_limit * rename_limit))
+ return 0;
+
+ options->needed_rename_limit =
+ num_src > num_create ? num_src : num_create;
+
+ /* Are we running under -C -C? */
+ if (!DIFF_OPT_TST(options, FIND_COPIES_HARDER))
+ return 1;
+
+ /* Would we bust the limit if we were running under -C? */
+ for (num_src = i = 0; i < rename_src_nr; i++) {
+ if (diff_unmodified_pair(rename_src[i].p))
+ continue;
+ num_src++;
+ }
+ if ((num_create <= rename_limit || num_src <= rename_limit) &&
+ (num_create * num_src <= rename_limit * rename_limit))
+ return 2;
+ return 1;
+}
+
+static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies)
+{
+ int count = 0, i;
+
+ for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
+ struct diff_rename_dst *dst;
+
+ if ((mx[i].dst < 0) ||
+ (mx[i].score < minimum_score))
+ break; /* there is no more usable pair. */
+ dst = &rename_dst[mx[i].dst];
+ if (dst->pair)
+ continue; /* already done, either exact or fuzzy. */
+ if (!copies && rename_src[mx[i].src].p->one->rename_used)
+ continue;
+ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+ count++;
+ }
+ return count;
+}
+
void diffcore_rename(struct diff_options *options)
{
int detect_rename = options->detect_rename;
int minimum_score = options->rename_score;
- int rename_limit = options->rename_limit;
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_queue_struct outq;
struct diff_score *mx;
- int i, j, rename_count;
- int num_create, num_src, dst_cnt;
+ int i, j, rename_count, skip_unmodified = 0;
+ int num_create, dst_cnt;
+ struct progress *progress = NULL;
if (!minimum_score)
minimum_score = DEFAULT_RENAME_SCORE;
@@ -439,7 +515,7 @@ void diffcore_rename(struct diff_options *options)
else
locate_rename_dst(p->two, 1);
}
- else if (!DIFF_FILE_VALID(p->two)) {
+ else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
/*
* If the source is a broken "delete", and
* they did not really want to get broken,
@@ -449,7 +525,7 @@ void diffcore_rename(struct diff_options *options)
*/
if (p->broken_pair && !p->score)
p->one->rename_used++;
- register_rename_src(p->one, p->score);
+ register_rename_src(p);
}
else if (detect_rename == DIFF_DETECT_COPY) {
/*
@@ -457,7 +533,7 @@ void diffcore_rename(struct diff_options *options)
* one, to indicate ourselves as a user.
*/
p->one->rename_used++;
- register_rename_src(p->one, p->score);
+ register_rename_src(p);
}
}
if (rename_dst_nr == 0 || rename_src_nr == 0)
@@ -467,7 +543,7 @@ void diffcore_rename(struct diff_options *options)
* We really want to cull the candidates list early
* with cheap tests in order to avoid doing deltas.
*/
- rename_count = find_exact_renames();
+ rename_count = find_exact_renames(options);
/* Did we only want exact renames? */
if (minimum_score == MAX_SCORE)
@@ -478,28 +554,26 @@ void diffcore_rename(struct diff_options *options)
* files still remain as options for rename/copies!)
*/
num_create = (rename_dst_nr - rename_count);
- num_src = rename_src_nr;
/* All done? */
if (!num_create)
goto cleanup;
- /*
- * This basically does a test for the rename matrix not
- * growing larger than a "rename_limit" square matrix, ie:
- *
- * num_create * num_src > rename_limit * rename_limit
- *
- * but handles the potential overflow case specially (and we
- * assume at least 32-bit integers)
- */
- if (rename_limit <= 0 || rename_limit > 32767)
- rename_limit = 32767;
- if ((num_create > rename_limit && num_src > rename_limit) ||
- (num_create * num_src > rename_limit * rename_limit)) {
- if (options->warn_on_too_large_rename)
- warning("too many files (created: %d deleted: %d), skipping inexact rename detection", num_create, num_src);
+ switch (too_many_rename_candidates(num_create, options)) {
+ case 1:
goto cleanup;
+ case 2:
+ options->degraded_cc_to_c = 1;
+ skip_unmodified = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (options->show_rename_progress) {
+ progress = start_progress_delay(
+ "Performing inexact rename detection",
+ rename_dst_nr * rename_src_nr, 50, 1);
}
mx = xcalloc(num_create * NUM_CANDIDATE_PER_DST, sizeof(*mx));
@@ -515,8 +589,13 @@ void diffcore_rename(struct diff_options *options)
m[j].dst = -1;
for (j = 0; j < rename_src_nr; j++) {
- struct diff_filespec *one = rename_src[j].one;
+ struct diff_filespec *one = rename_src[j].p->one;
struct diff_score this_src;
+
+ if (skip_unmodified &&
+ diff_unmodified_pair(rename_src[j].p))
+ continue;
+
this_src.score = estimate_similarity(one, two,
minimum_score);
this_src.name_score = basename_same(one, two);
@@ -531,38 +610,16 @@ void diffcore_rename(struct diff_options *options)
diff_free_filespec_blob(two);
}
dst_cnt++;
+ display_progress(progress, (i+1)*rename_src_nr);
}
+ stop_progress(&progress);
/* cost matrix sorted by most to least similar pair */
qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare);
- for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
- struct diff_rename_dst *dst;
-
- if ((mx[i].dst < 0) ||
- (mx[i].score < minimum_score))
- break; /* there is no more usable pair. */
- dst = &rename_dst[mx[i].dst];
- if (dst->pair)
- continue; /* already done, either exact or fuzzy. */
- if (rename_src[mx[i].src].one->rename_used)
- continue;
- record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
- rename_count++;
- }
-
- for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
- struct diff_rename_dst *dst;
-
- if ((mx[i].dst < 0) ||
- (mx[i].score < minimum_score))
- break; /* there is no more usable pair. */
- dst = &rename_dst[mx[i].dst];
- if (dst->pair)
- continue; /* already done, either exact or fuzzy. */
- record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
- rename_count++;
- }
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 0);
+ if (detect_rename == DIFF_DETECT_COPY)
+ rename_count += find_renames(mx, dst_cnt, minimum_score, 1);
free(mx);
cleanup:
@@ -574,7 +631,10 @@ void diffcore_rename(struct diff_options *options)
struct diff_filepair *p = q->queue[i];
struct diff_filepair *pair_to_free = NULL;
- if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
+ if (DIFF_PAIR_UNMERGED(p)) {
+ diff_q(&outq, p);
+ }
+ else if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
/*
* Creation
*
diff --git a/diffcore.h b/diffcore.h
index 491bea0..b8f1fde 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -18,7 +18,7 @@
#define MAX_SCORE 60000.0
#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */
-#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */
+#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen (60%) */
#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */
@@ -91,14 +91,12 @@ struct diff_queue_struct {
struct diff_filepair **queue;
int alloc;
int nr;
- int run;
};
#define DIFF_QUEUE_CLEAR(q) \
do { \
(q)->queue = NULL; \
(q)->nr = (q)->alloc = 0; \
- (q)->run = 0; \
- } while(0);
+ } while (0)
extern struct diff_queue_struct diff_queued_diff;
extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
@@ -109,7 +107,7 @@ extern void diff_q(struct diff_queue_struct *, struct diff_filepair *);
extern void diffcore_break(int);
extern void diffcore_rename(struct diff_options *);
extern void diffcore_merge_broken(void);
-extern void diffcore_pickaxe(const char *needle, int opts);
+extern void diffcore_pickaxe(struct diff_options *);
extern void diffcore_order(const char *orderfile);
#define DIFF_DEBUG 0
@@ -118,9 +116,9 @@ void diff_debug_filespec(struct diff_filespec *, int, const char *);
void diff_debug_filepair(const struct diff_filepair *, int);
void diff_debug_queue(const char *, struct diff_queue_struct *);
#else
-#define diff_debug_filespec(a,b,c) do {} while(0)
-#define diff_debug_filepair(a,b) do {} while(0)
-#define diff_debug_queue(a,b) do {} while(0)
+#define diff_debug_filespec(a,b,c) do { /* nothing */ } while (0)
+#define diff_debug_filepair(a,b) do { /* nothing */ } while (0)
+#define diff_debug_queue(a,b) do { /* nothing */ } while (0)
#endif
extern int diffcore_count_changes(struct diff_filespec *src,
diff --git a/dir.c b/dir.c
index 151ea67..08281d2 100644
--- a/dir.c
+++ b/dir.c
@@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
+/* helper string functions with support for the ignore_case flag */
+int strcmp_icase(const char *a, const char *b)
+{
+ return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+}
+
+int strncmp_icase(const char *a, const char *b, size_t count)
+{
+ return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
+}
+
static int common_prefix(const char **pathspec)
{
const char *path, *slash, *next;
@@ -71,6 +87,21 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
return len;
}
+int within_depth(const char *name, int namelen,
+ int depth, int max_depth)
+{
+ const char *cp = name, *cpe = name + namelen;
+
+ while (cp < cpe) {
+ if (*cp++ != '/')
+ continue;
+ depth++;
+ if (depth > max_depth)
+ return 0;
+ }
+ return 1;
+}
+
/*
* Does 'match' match the given name?
* A match is found if
@@ -91,16 +122,30 @@ static int match_one(const char *match, const char *name, int namelen)
if (!*match)
return MATCHED_RECURSIVELY;
- for (;;) {
- unsigned char c1 = *match;
- unsigned char c2 = *name;
- if (c1 == '\0' || is_glob_special(c1))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
+ if (ignore_case) {
+ for (;;) {
+ unsigned char c1 = tolower(*match);
+ unsigned char c2 = tolower(*name);
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
+ } else {
+ for (;;) {
+ unsigned char c1 = *match;
+ unsigned char c2 = *name;
+ if (c1 == '\0' || is_glob_special(c1))
+ break;
+ if (c1 != c2)
+ return 0;
+ match++;
+ name++;
+ namelen--;
+ }
}
@@ -109,8 +154,8 @@ static int match_one(const char *match, const char *name, int namelen)
* we need to match by fnmatch
*/
matchlen = strlen(match);
- if (strncmp(match, name, matchlen))
- return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
+ if (strncmp_icase(match, name, matchlen))
+ return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
if (namelen == matchlen)
return MATCHED_EXACTLY;
@@ -154,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen,
return retval;
}
+/*
+ * Does 'match' match the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
+static int match_pathspec_item(const struct pathspec_item *item, int prefix,
+ const char *name, int namelen)
+{
+ /* name/namelen has prefix cut off by caller */
+ const char *match = item->match + prefix;
+ int matchlen = item->len - prefix;
+
+ /* If the match was just the prefix, we matched */
+ if (!*match)
+ return MATCHED_RECURSIVELY;
+
+ if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+ if (matchlen == namelen)
+ return MATCHED_EXACTLY;
+
+ if (match[matchlen-1] == '/' || name[matchlen] == '/')
+ return MATCHED_RECURSIVELY;
+ }
+
+ if (item->use_wildcard && !fnmatch(match, name, 0))
+ return MATCHED_FNMATCH;
+
+ return 0;
+}
+
+/*
+ * Given a name and a list of pathspecs, see if the name matches
+ * any of the pathspecs. The caller is also interested in seeing
+ * all pathspec matches some names it calls this function with
+ * (otherwise the user could have mistyped the unmatched pathspec),
+ * and a mark is left in seen[] array for pathspec element that
+ * actually matched anything.
+ */
+int match_pathspec_depth(const struct pathspec *ps,
+ const char *name, int namelen,
+ int prefix, char *seen)
+{
+ int i, retval = 0;
+
+ if (!ps->nr) {
+ if (!ps->recursive || ps->max_depth == -1)
+ return MATCHED_RECURSIVELY;
+
+ if (within_depth(name, namelen, 0, ps->max_depth))
+ return MATCHED_EXACTLY;
+ else
+ return 0;
+ }
+
+ name += prefix;
+ namelen -= prefix;
+
+ for (i = ps->nr - 1; i >= 0; i--) {
+ int how;
+ if (seen && seen[i] == MATCHED_EXACTLY)
+ continue;
+ how = match_pathspec_item(ps->items+i, prefix, name, namelen);
+ if (ps->recursive && ps->max_depth != -1 &&
+ how && how != MATCHED_FNMATCH) {
+ int len = ps->items[i].len;
+ if (name[len] == '/')
+ len++;
+ if (within_depth(name+len, namelen-len, 0, ps->max_depth))
+ how = MATCHED_EXACTLY;
+ else
+ how = 0;
+ }
+ if (how) {
+ if (retval < how)
+ retval = how;
+ if (seen && seen[i] < how)
+ seen[i] = how;
+ }
+ }
+ return retval;
+}
+
static int no_wildcard(const char *string)
{
return string[strcspn(string, "*?[{\\")] == '\0';
@@ -223,6 +357,18 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
return data;
}
+void free_excludes(struct exclude_list *el)
+{
+ int i;
+
+ for (i = 0; i < el->nr; i++)
+ free(el->excludes[i]);
+ free(el->excludes);
+
+ el->nr = 0;
+ el->excludes = NULL;
+}
+
int add_excludes_from_file_to_list(const char *fname,
const char *base,
int baselen,
@@ -232,7 +378,7 @@ int add_excludes_from_file_to_list(const char *fname,
{
struct stat st;
int fd, i;
- size_t size;
+ size_t size = 0;
char *buf, *entry;
fd = open(fname, O_RDONLY);
@@ -359,12 +505,6 @@ int excluded_from_list(const char *pathname,
int to_exclude = x->to_exclude;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
- if (!dtype) {
- if (!prefixcmp(pathname, exclude))
- return to_exclude;
- else
- continue;
- }
if (*dtype == DT_UNKNOWN)
*dtype = get_dtype(NULL, pathname, pathlen);
if (*dtype != DT_DIR)
@@ -374,14 +514,14 @@ int excluded_from_list(const char *pathname,
if (x->flags & EXC_FLAG_NODIR) {
/* match basename */
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, basename))
+ if (!strcmp_icase(exclude, basename))
return to_exclude;
} else if (x->flags & EXC_FLAG_ENDSWITH) {
if (x->patternlen - 1 <= pathlen &&
- !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+ !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
return to_exclude;
} else {
- if (fnmatch(exclude, basename, 0) == 0)
+ if (fnmatch_icase(exclude, basename, 0) == 0)
return to_exclude;
}
}
@@ -396,14 +536,14 @@ int excluded_from_list(const char *pathname,
if (pathlen < baselen ||
(baselen && pathname[baselen-1] != '/') ||
- strncmp(pathname, x->base, baselen))
+ strncmp_icase(pathname, x->base, baselen))
continue;
if (x->flags & EXC_FLAG_NOWILDCARD) {
- if (!strcmp(exclude, pathname + baselen))
+ if (!strcmp_icase(exclude, pathname + baselen))
return to_exclude;
} else {
- if (fnmatch(exclude, pathname+baselen,
+ if (fnmatch_icase(exclude, pathname+baselen,
FNM_PATHNAME) == 0)
return to_exclude;
}
@@ -453,7 +593,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna
return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
}
-static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
{
if (!cache_name_is_other(pathname, len))
return NULL;
@@ -469,6 +609,39 @@ enum exist_status {
};
/*
+ * Do not use the alphabetically stored index to look up
+ * the directory name; instead, use the case insensitive
+ * name hash.
+ */
+static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
+{
+ struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+ unsigned char endchar;
+
+ if (!ce)
+ return index_nonexistent;
+ endchar = ce->name[len];
+
+ /*
+ * The cache_entry structure returned will contain this dirname
+ * and possibly additional path components.
+ */
+ if (endchar == '/')
+ return index_directory;
+
+ /*
+ * If there are no additional path components, then this cache_entry
+ * represents a submodule. Submodules, despite being directories,
+ * are stored in the cache without a closing slash.
+ */
+ if (!endchar && S_ISGITLINK(ce->ce_mode))
+ return index_gitdir;
+
+ /* This should never be hit, but it exists just in case. */
+ return index_nonexistent;
+}
+
+/*
* The index sorts alphabetically by entry name, which
* means that a gitlink sorts as '\0' at the end, while
* a directory (which is defined not as an entry, but as
@@ -477,7 +650,12 @@ enum exist_status {
*/
static enum exist_status directory_exists_in_index(const char *dirname, int len)
{
- int pos = cache_name_pos(dirname, len);
+ int pos;
+
+ if (ignore_case)
+ return directory_exists_in_index_icase(dirname, len);
+
+ pos = cache_name_pos(dirname, len);
if (pos < 0)
pos = -pos-1;
while (pos < active_nr) {
@@ -927,51 +1105,45 @@ int file_exists(const char *f)
}
/*
- * get_relative_cwd() gets the prefix of the current working directory
- * relative to 'dir'. If we are not inside 'dir', it returns NULL.
- *
- * As a convenience, it also returns NULL if 'dir' is already NULL. The
- * reason for this behaviour is that it is natural for functions returning
- * directory names to return NULL to say "this directory does not exist"
- * or "this directory is invalid". These cases are usually handled the
- * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
- * returns NULL for both of them.
- *
- * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
- * unifies the handling of "outside work tree" with "no work tree at all".
+ * Given two normalized paths (a trailing slash is ok), if subdir is
+ * outside dir, return -1. Otherwise return the offset in subdir that
+ * can be used as relative path to dir.
*/
-char *get_relative_cwd(char *buffer, int size, const char *dir)
+int dir_inside_of(const char *subdir, const char *dir)
{
- char *cwd = buffer;
-
- if (!dir)
- return NULL;
- if (!getcwd(buffer, size))
- die_errno("can't find the current directory");
+ int offset = 0;
- if (!is_absolute_path(dir))
- dir = make_absolute_path(dir);
+ assert(dir && subdir && *dir && *subdir);
- while (*dir && *dir == *cwd) {
+ while (*dir && *subdir && *dir == *subdir) {
dir++;
- cwd++;
- }
- if (*dir)
- return NULL;
- switch (*cwd) {
- case '\0':
- return cwd;
- case '/':
- return cwd + 1;
- default:
- return NULL;
+ subdir++;
+ offset++;
}
+
+ /* hel[p]/me vs hel[l]/yeah */
+ if (*dir && *subdir)
+ return -1;
+
+ if (!*subdir)
+ return !*dir ? offset : -1; /* same dir */
+
+ /* foo/[b]ar vs foo/[] */
+ if (is_dir_sep(dir[-1]))
+ return is_dir_sep(subdir[-1]) ? offset : -1;
+
+ /* foo[/]bar vs foo[] */
+ return is_dir_sep(*subdir) ? offset + 1 : -1;
}
int is_inside_dir(const char *dir)
{
- char buffer[PATH_MAX];
- return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+ char cwd[PATH_MAX];
+ if (!dir)
+ return 0;
+ if (!getcwd(cwd, sizeof(cwd)))
+ die_errno("can't find the current directory");
+ return dir_inside_of(cwd, dir) >= 0;
}
int is_empty_dir(const char *path)
@@ -1008,7 +1180,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
dir = opendir(path->buf);
if (!dir)
- return -1;
+ return rmdir(path->buf);
if (path->buf[original_len - 1] != '/')
strbuf_addch(path, '/');
@@ -1071,3 +1243,50 @@ int remove_path(const char *name)
return 0;
}
+static int pathspec_item_cmp(const void *a_, const void *b_)
+{
+ struct pathspec_item *a, *b;
+
+ a = (struct pathspec_item *)a_;
+ b = (struct pathspec_item *)b_;
+ return strcmp(a->match, b->match);
+}
+
+int init_pathspec(struct pathspec *pathspec, const char **paths)
+{
+ const char **p = paths;
+ int i;
+
+ memset(pathspec, 0, sizeof(*pathspec));
+ if (!p)
+ return 0;
+ while (*p)
+ p++;
+ pathspec->raw = paths;
+ pathspec->nr = p - paths;
+ if (!pathspec->nr)
+ return 0;
+
+ pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
+ for (i = 0; i < pathspec->nr; i++) {
+ struct pathspec_item *item = pathspec->items+i;
+ const char *path = paths[i];
+
+ item->match = path;
+ item->len = strlen(path);
+ item->use_wildcard = !no_wildcard(path);
+ if (item->use_wildcard)
+ pathspec->has_wildcard = 1;
+ }
+
+ qsort(pathspec->items, pathspec->nr,
+ sizeof(struct pathspec_item), pathspec_item_cmp);
+
+ return 0;
+}
+
+void free_pathspec(struct pathspec *pathspec)
+{
+ free(pathspec->items);
+ pathspec->items = NULL;
+}
diff --git a/dir.h b/dir.h
index 3bead5f..433b5b4 100644
--- a/dir.h
+++ b/dir.h
@@ -65,6 +65,10 @@ struct dir_struct {
#define MATCHED_FNMATCH 2
#define MATCHED_EXACTLY 3
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
+extern int match_pathspec_depth(const struct pathspec *pathspec,
+ const char *name, int namelen,
+ int prefix, char *seen);
+extern int within_depth(const char *name, int namelen, int depth, int max_depth);
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
@@ -72,15 +76,17 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const
extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
int *dtype, struct exclude_list *el);
extern int excluded(struct dir_struct *, const char *, int *);
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
char **buf_p, struct exclude_list *which, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
+extern void free_excludes(struct exclude_list *el);
extern int file_exists(const char *);
-extern char *get_relative_cwd(char *buffer, int size, const char *dir);
extern int is_inside_dir(const char *dir);
+extern int dir_inside_of(const char *subdir, const char *dir);
static inline int is_dot_or_dotdot(const char *name)
{
@@ -100,4 +106,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */
extern int remove_path(const char *path);
+extern int strcmp_icase(const char *a, const char *b);
+extern int strncmp_icase(const char *a, const char *b, size_t count);
+extern int fnmatch_icase(const char *pattern, const char *string, int flags);
+
#endif
diff --git a/entry.c b/entry.c
index 004182c..852fea1 100644
--- a/entry.c
+++ b/entry.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "blob.h"
#include "dir.h"
+#include "streaming.h"
static void create_directories(const char *path, int path_len,
const struct checkout *state)
@@ -91,6 +92,91 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
return NULL;
}
+static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile)
+{
+ int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
+ if (to_tempfile) {
+ strcpy(path, symlink
+ ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+ return mkstemp(path);
+ } else {
+ return create_file(path, !symlink ? ce->ce_mode : 0666);
+ }
+}
+
+static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+{
+ /* use fstat() only when path == ce->name */
+ if (fstat_is_reliable() &&
+ state->refresh_cache && !state->base_dir_len) {
+ fstat(fd, st);
+ return 1;
+ }
+ return 0;
+}
+
+static int streaming_write_entry(struct cache_entry *ce, char *path,
+ struct stream_filter *filter,
+ const struct checkout *state, int to_tempfile,
+ int *fstat_done, struct stat *statbuf)
+{
+ struct git_istream *st;
+ enum object_type type;
+ unsigned long sz;
+ int result = -1;
+ ssize_t kept = 0;
+ int fd = -1;
+
+ st = open_istream(ce->sha1, &type, &sz, filter);
+ if (!st)
+ return -1;
+ if (type != OBJ_BLOB)
+ goto close_and_exit;
+
+ fd = open_output_fd(path, ce, to_tempfile);
+ if (fd < 0)
+ goto close_and_exit;
+
+ for (;;) {
+ char buf[1024 * 16];
+ ssize_t wrote, holeto;
+ ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+ if (!readlen)
+ break;
+ if (sizeof(buf) == readlen) {
+ for (holeto = 0; holeto < readlen; holeto++)
+ if (buf[holeto])
+ break;
+ if (readlen == holeto) {
+ kept += holeto;
+ continue;
+ }
+ }
+
+ if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+ goto close_and_exit;
+ else
+ kept = 0;
+ wrote = write_in_full(fd, buf, readlen);
+
+ if (wrote != readlen)
+ goto close_and_exit;
+ }
+ if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+ write(fd, "", 1) != 1))
+ goto close_and_exit;
+ *fstat_done = fstat_output(fd, state, statbuf);
+
+close_and_exit:
+ close_istream(st);
+ if (0 <= fd)
+ result = close(fd);
+ if (result && 0 <= fd)
+ unlink(path);
+ return result;
+}
+
static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
{
unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
@@ -101,19 +187,28 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
size_t wrote, newsize = 0;
struct stat st;
+ if (ce_mode_s_ifmt == S_IFREG) {
+ struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+ if (filter &&
+ !streaming_write_entry(ce, path, filter,
+ state, to_tempfile,
+ &fstat_done, &st))
+ goto finish;
+ }
+
switch (ce_mode_s_ifmt) {
case S_IFREG:
case S_IFLNK:
new = read_blob_entry(ce, &size);
if (!new)
- return error("git checkout-index: unable to read sha1 file of %s (%s)",
+ return error("unable to read sha1 file of %s (%s)",
path, sha1_to_hex(ce->sha1));
if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
ret = symlink(new, path);
free(new);
if (ret)
- return error("git checkout-index: unable to create symlink %s (%s)",
+ return error("unable to create symlink %s (%s)",
path, strerror(errno));
break;
}
@@ -128,45 +223,32 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
size = newsize;
}
- if (to_tempfile) {
- if (ce_mode_s_ifmt == S_IFREG)
- strcpy(path, ".merge_file_XXXXXX");
- else
- strcpy(path, ".merge_link_XXXXXX");
- fd = mkstemp(path);
- } else if (ce_mode_s_ifmt == S_IFREG) {
- fd = create_file(path, ce->ce_mode);
- } else {
- fd = create_file(path, 0666);
- }
+ fd = open_output_fd(path, ce, to_tempfile);
if (fd < 0) {
free(new);
- return error("git checkout-index: unable to create file %s (%s)",
+ return error("unable to create file %s (%s)",
path, strerror(errno));
}
wrote = write_in_full(fd, new, size);
- /* use fstat() only when path == ce->name */
- if (fstat_is_reliable() &&
- state->refresh_cache && !to_tempfile && !state->base_dir_len) {
- fstat(fd, &st);
- fstat_done = 1;
- }
+ if (!to_tempfile)
+ fstat_done = fstat_output(fd, state, &st);
close(fd);
free(new);
if (wrote != size)
- return error("git checkout-index: unable to write file %s", path);
+ return error("unable to write file %s", path);
break;
case S_IFGITLINK:
if (to_tempfile)
- return error("git checkout-index: cannot create temporary subproject %s", path);
+ return error("cannot create temporary subproject %s", path);
if (mkdir(path, 0777) < 0)
- return error("git checkout-index: cannot create subproject directory %s", path);
+ return error("cannot create subproject directory %s", path);
break;
default:
- return error("git checkout-index: unknown file mode for %s", path);
+ return error("unknown file mode for %s in index", path);
}
+finish:
if (state->refresh_cache) {
if (!fstat_done)
lstat(ce->name, &st);
@@ -211,7 +293,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
return 0;
if (!state->force) {
if (!state->quiet)
- fprintf(stderr, "git-checkout-index: %s already exists\n", path);
+ fprintf(stderr, "%s already exists, no checkout\n", path);
return -1;
}
diff --git a/environment.c b/environment.c
index 83d38d3..94d58fd 100644
--- a/environment.c
+++ b/environment.c
@@ -15,6 +15,7 @@ int user_ident_explicitly_given;
int trust_executable_bit = 1;
int trust_ctime = 1;
int has_symlinks = 1;
+int minimum_abbrev = 4, default_abbrev = 7;
int ignore_case;
int assume_unchanged;
int prefer_symlink_refs;
@@ -34,13 +35,15 @@ int fsync_object_files;
size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
size_t delta_base_cache_limit = 16 * 1024 * 1024;
+unsigned long big_file_threshold = 512 * 1024 * 1024;
const char *pager_program;
int pager_use_color = 1;
const char *editor_program;
+const char *askpass_program;
const char *excludes_file;
enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
-int read_replace_refs = 1;
-enum eol eol = EOL_UNSET;
+int read_replace_refs = 1; /* NEEDSWORK: rename to use_replace_refs */
+enum eol core_eol = EOL_UNSET;
enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
@@ -53,6 +56,7 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
char *notes_ref_name;
int grafts_replace_parents = 1;
int core_apply_sparse_checkout;
+struct startup_info *startup_info;
/* Parallel index stat data preload? */
int core_preload_index = 0;
@@ -62,7 +66,7 @@ char *git_work_tree_cfg;
static char *work_tree;
static const char *git_dir;
-static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
+static char *git_object_dir, *git_index_file, *git_graft_file;
/*
* Repository-local GIT_* environment variables
@@ -72,6 +76,7 @@ static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
ALTERNATE_DB_ENVIRONMENT,
CONFIG_ENVIRONMENT,
+ CONFIG_DATA_ENVIRONMENT,
DB_ENVIRONMENT,
GIT_DIR_ENVIRONMENT,
GIT_WORK_TREE_ENVIRONMENT,
@@ -84,8 +89,11 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
static void setup_git_env(void)
{
git_dir = getenv(GIT_DIR_ENVIRONMENT);
- if (!git_dir)
+ git_dir = git_dir ? xstrdup(git_dir) : NULL;
+ if (!git_dir) {
git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+ git_dir = git_dir ? xstrdup(git_dir) : NULL;
+ }
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
git_object_dir = getenv(DB_ENVIRONMENT);
@@ -93,8 +101,6 @@ static void setup_git_env(void)
git_object_dir = xmalloc(strlen(git_dir) + 9);
sprintf(git_object_dir, "%s/objects", git_dir);
}
- git_refs_dir = xmalloc(strlen(git_dir) + 6);
- sprintf(git_refs_dir, "%s/refs", git_dir);
git_index_file = getenv(INDEX_ENVIRONMENT);
if (!git_index_file) {
git_index_file = xmalloc(strlen(git_dir) + 7);
@@ -134,30 +140,20 @@ static int git_work_tree_initialized;
*/
void set_git_work_tree(const char *new_work_tree)
{
- if (is_bare_repository_cfg >= 0)
- die("cannot set work tree after initialization");
+ if (git_work_tree_initialized) {
+ new_work_tree = real_path(new_work_tree);
+ if (strcmp(new_work_tree, work_tree))
+ die("internal error: work tree has already been set\n"
+ "Current worktree: %s\nNew worktree: %s",
+ work_tree, new_work_tree);
+ return;
+ }
git_work_tree_initialized = 1;
- free(work_tree);
- work_tree = xstrdup(make_absolute_path(new_work_tree));
- is_bare_repository_cfg = 0;
+ work_tree = xstrdup(real_path(new_work_tree));
}
const char *get_git_work_tree(void)
{
- if (!git_work_tree_initialized) {
- work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
- /* core.bare = true overrides implicit and config work tree */
- if (!work_tree && is_bare_repository_cfg < 1) {
- work_tree = git_work_tree_cfg;
- /* make_absolute_path also normalizes the path */
- if (work_tree && !is_absolute_path(work_tree))
- work_tree = xstrdup(make_absolute_path(git_path("%s", work_tree)));
- } else if (work_tree)
- work_tree = xstrdup(make_absolute_path(work_tree));
- git_work_tree_initialized = 1;
- if (work_tree)
- is_bare_repository_cfg = 0;
- }
return work_tree;
}
@@ -168,6 +164,43 @@ char *get_object_directory(void)
return git_object_dir;
}
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+ int fd;
+ /*
+ * we let the umask do its job, don't try to be more
+ * restrictive except to remove write permission.
+ */
+ int mode = 0444;
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ fd = git_mkstemp_mode(template, mode);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ /* some mkstemp implementations erase template on failure */
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ safe_create_leading_directories(template);
+ return xmkstemp_mode(template, mode);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+ int fd;
+
+ snprintf(name, namesz, "%s/pack/pack-%s.keep",
+ get_object_directory(), sha1_to_hex(sha1));
+ fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ safe_create_leading_directories(name);
+ return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}
+
char *get_index_file(void)
{
if (!git_index_file)
@@ -189,3 +222,14 @@ int set_git_dir(const char *path)
setup_git_env();
return 0;
}
+
+const char *get_log_output_encoding(void)
+{
+ return git_log_output_encoding ? git_log_output_encoding
+ : get_commit_output_encoding();
+}
+
+const char *get_commit_output_encoding(void)
+{
+ return git_commit_encoding ? git_commit_encoding : "UTF-8";
+}
diff --git a/exec_cmd.c b/exec_cmd.c
index bf22570..171e841 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -3,7 +3,6 @@
#include "quote.h"
#define MAX_ARGS 32
-extern char **environ;
static const char *argv_exec_path;
static const char *argv0_path;
@@ -90,7 +89,7 @@ static void add_path(struct strbuf *out, const char *path)
if (is_absolute_path(path))
strbuf_addstr(out, path);
else
- strbuf_addstr(out, make_nonrelative_path(path));
+ strbuf_addstr(out, absolute_path(path));
strbuf_addch(out, PATH_SEP);
}
diff --git a/fast-import.c b/fast-import.c
index 1e5d66e..9e8d186 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -24,10 +24,12 @@ Format of STDIN stream:
commit_msg
('from' sp committish lf)?
('merge' sp committish lf)*
- file_change*
+ (file_change | ls)*
lf?;
commit_msg ::= data;
+ ls ::= 'ls' sp '"' quoted(path) '"' lf;
+
file_change ::= file_clr
| file_del
| file_rnm
@@ -132,14 +134,19 @@ Format of STDIN stream:
ts ::= # time since the epoch in seconds, ascii base10 notation;
tz ::= # GIT style timezone;
- # note: comments may appear anywhere in the input, except
- # within a data command. Any form of the data command
- # always escapes the related input from comment processing.
+ # note: comments, ls and cat requests may appear anywhere
+ # in the input, except within a data command. Any form
+ # of the data command always escapes the related input
+ # from comment processing.
#
# In case it is not clear, the '#' that starts the comment
# must be the first character on that line (an lf
# preceded it).
#
+
+ cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
+ ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
+
comment ::= '#' not_lf* lf;
not_lf ::= # Any byte that is not ASCII newline (LF);
*/
@@ -156,14 +163,14 @@ Format of STDIN stream:
#include "csum-file.h"
#include "quote.h"
#include "exec_cmd.h"
+#include "dir.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
#define DEPTH_BITS 13
#define MAX_DEPTH ((1<<DEPTH_BITS)-1)
-struct object_entry
-{
+struct object_entry {
struct pack_idx_entry idx;
struct object_entry *next;
uint32_t type : TYPE_BITS,
@@ -171,16 +178,14 @@ struct object_entry
depth : DEPTH_BITS;
};
-struct object_entry_pool
-{
+struct object_entry_pool {
struct object_entry_pool *next_pool;
struct object_entry *next_free;
struct object_entry *end;
struct object_entry entries[FLEX_ARRAY]; /* more */
};
-struct mark_set
-{
+struct mark_set {
union {
struct object_entry *marked[1024];
struct mark_set *sets[1024];
@@ -188,57 +193,49 @@ struct mark_set
unsigned int shift;
};
-struct last_object
-{
+struct last_object {
struct strbuf data;
off_t offset;
unsigned int depth;
unsigned no_swap : 1;
};
-struct mem_pool
-{
+struct mem_pool {
struct mem_pool *next_pool;
char *next_free;
char *end;
uintmax_t space[FLEX_ARRAY]; /* more */
};
-struct atom_str
-{
+struct atom_str {
struct atom_str *next_atom;
unsigned short str_len;
char str_dat[FLEX_ARRAY]; /* more */
};
struct tree_content;
-struct tree_entry
-{
+struct tree_entry {
struct tree_content *tree;
struct atom_str *name;
- struct tree_entry_ms
- {
+ struct tree_entry_ms {
uint16_t mode;
unsigned char sha1[20];
} versions[2];
};
-struct tree_content
-{
+struct tree_content {
unsigned int entry_capacity; /* must match avail_tree_content */
unsigned int entry_count;
unsigned int delta_depth;
struct tree_entry *entries[FLEX_ARRAY]; /* more */
};
-struct avail_tree_content
-{
+struct avail_tree_content {
unsigned int entry_capacity; /* must match tree_content */
struct avail_tree_content *next_avail;
};
-struct branch
-{
+struct branch {
struct branch *table_next_branch;
struct branch *active_next_branch;
const char *name;
@@ -250,16 +247,14 @@ struct branch
unsigned char sha1[20];
};
-struct tag
-{
+struct tag {
struct tag *next_tag;
const char *name;
unsigned int pack_id;
unsigned char sha1[20];
};
-struct hash_list
-{
+struct hash_list {
struct hash_list *next;
unsigned char sha1[20];
};
@@ -270,8 +265,7 @@ typedef enum {
WHENSPEC_NOW
} whenspec_type;
-struct recent_command
-{
+struct recent_command {
struct recent_command *prev;
struct recent_command *next;
char *buf;
@@ -280,7 +274,6 @@ struct recent_command
/* Configured limits on output */
static unsigned long max_depth = 10;
static off_t max_packsize;
-static uintmax_t big_file_threshold = 512 * 1024 * 1024;
static int force_update;
static int pack_compression_level = Z_DEFAULT_COMPRESSION;
static int pack_compression_seen;
@@ -311,6 +304,7 @@ static unsigned int atom_cnt;
static struct atom_str **atom_table;
/* The .pack file being generated */
+static struct pack_idx_option pack_idx_opts;
static unsigned int pack_id;
static struct sha1file *pack_file;
static struct packed_git *pack_data;
@@ -325,6 +319,7 @@ static struct mark_set *marks;
static const char *export_marks_file;
static const char *import_marks_file;
static int import_marks_file_from_stream;
+static int import_marks_file_ignore_missing;
static int relative_marks_paths;
/* Our last blob */
@@ -361,7 +356,15 @@ static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
static int seen_data_command;
+/* Signal handling */
+static volatile sig_atomic_t checkpoint_requested;
+
+/* Where to write output of cat-blob commands */
+static int cat_blob_fd = STDOUT_FILENO;
+
static void parse_argv(void);
+static void parse_cat_blob(void);
+static void parse_ls(struct branch *b);
static void write_branch_report(FILE *rpt, struct branch *b)
{
@@ -500,6 +503,32 @@ static NORETURN void die_nicely(const char *err, va_list params)
exit(128);
}
+#ifndef SIGUSR1 /* Windows, for example */
+
+static void set_checkpoint_signal(void)
+{
+}
+
+#else
+
+static void checkpoint_signal(int signo)
+{
+ checkpoint_requested = 1;
+}
+
+static void set_checkpoint_signal(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = checkpoint_signal;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGUSR1, &sa, NULL);
+}
+
+#endif
+
static void alloc_objects(unsigned int cnt)
{
struct object_entry_pool *b;
@@ -539,22 +568,17 @@ static struct object_entry *insert_object(unsigned char *sha1)
{
unsigned int h = sha1[0] << 8 | sha1[1];
struct object_entry *e = object_table[h];
- struct object_entry *p = NULL;
while (e) {
if (!hashcmp(sha1, e->idx.sha1))
return e;
- p = e;
e = e->next;
}
e = new_object(sha1);
- e->next = NULL;
+ e->next = object_table[h];
e->idx.offset = 0;
- if (p)
- p->next = e;
- else
- object_table[h] = e;
+ object_table[h] = e;
return e;
}
@@ -839,6 +863,7 @@ static void start_packfile(void)
p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
strcpy(p->pack_name, tmpfile);
p->pack_fd = pack_fd;
+ p->do_not_close = 1;
pack_file = sha1fd(pack_fd, p->pack_name);
hdr.hdr_signature = htonl(PACK_SIGNATURE);
@@ -872,7 +897,7 @@ static const char *create_index(void)
if (c != last)
die("internal consistency error creating the index");
- tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1);
+ tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, pack_data->sha1);
free(idx);
return tmpfile;
}
@@ -993,7 +1018,7 @@ static int store_object(
unsigned char sha1[20];
unsigned long hdrlen, deltalen;
git_SHA_CTX c;
- z_stream s;
+ git_zstream s;
hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
(unsigned long)dat->len) + 1;
@@ -1026,7 +1051,7 @@ static int store_object(
delta = NULL;
memset(&s, 0, sizeof(s));
- deflateInit(&s, pack_compression_level);
+ git_deflate_init(&s, pack_compression_level);
if (delta) {
s.next_in = delta;
s.avail_in = deltalen;
@@ -1034,11 +1059,11 @@ static int store_object(
s.next_in = (void *)dat->buf;
s.avail_in = dat->len;
}
- s.avail_out = deflateBound(&s, s.avail_in);
+ s.avail_out = git_deflate_bound(&s, s.avail_in);
s.next_out = out = xmalloc(s.avail_out);
- while (deflate(&s, Z_FINISH) == Z_OK)
- /* nothing */;
- deflateEnd(&s);
+ while (git_deflate(&s, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ git_deflate_end(&s);
/* Determine if we should auto-checkpoint. */
if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize)
@@ -1054,14 +1079,14 @@ static int store_object(
delta = NULL;
memset(&s, 0, sizeof(s));
- deflateInit(&s, pack_compression_level);
+ git_deflate_init(&s, pack_compression_level);
s.next_in = (void *)dat->buf;
s.avail_in = dat->len;
- s.avail_out = deflateBound(&s, s.avail_in);
+ s.avail_out = git_deflate_bound(&s, s.avail_in);
s.next_out = out = xrealloc(out, s.avail_out);
- while (deflate(&s, Z_FINISH) == Z_OK)
- /* nothing */;
- deflateEnd(&s);
+ while (git_deflate(&s, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ git_deflate_end(&s);
}
}
@@ -1139,7 +1164,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
off_t offset;
git_SHA_CTX c;
git_SHA_CTX pack_file_ctx;
- z_stream s;
+ git_zstream s;
int status = Z_OK;
/* Determine if we should auto-checkpoint. */
@@ -1163,7 +1188,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
crc32_begin(pack_file);
memset(&s, 0, sizeof(s));
- deflateInit(&s, pack_compression_level);
+ git_deflate_init(&s, pack_compression_level);
hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
if (out_sz <= hdrlen)
@@ -1185,7 +1210,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
len -= n;
}
- status = deflate(&s, len ? 0 : Z_FINISH);
+ status = git_deflate(&s, len ? 0 : Z_FINISH);
if (!s.avail_out || status == Z_STREAM_END) {
size_t n = s.next_out - out_buf;
@@ -1204,7 +1229,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
die("unexpected deflate failure: %d", status);
}
}
- deflateEnd(&s);
+ git_deflate_end(&s);
git_SHA1_Final(sha1, &c);
if (sha1out)
@@ -1437,6 +1462,20 @@ static void store_tree(struct tree_entry *root)
t->entry_count -= del;
}
+static void tree_content_replace(
+ struct tree_entry *root,
+ const unsigned char *sha1,
+ const uint16_t mode,
+ struct tree_content *newtree)
+{
+ if (!S_ISDIR(mode))
+ die("Root cannot be a non-directory");
+ hashcpy(root->versions[1].sha1, sha1);
+ if (root->tree)
+ release_tree_content_recursive(root->tree);
+ root->tree = newtree;
+}
+
static int tree_content_set(
struct tree_entry *root,
const char *p,
@@ -1444,7 +1483,7 @@ static int tree_content_set(
const uint16_t mode,
struct tree_content *subtree)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
@@ -1459,9 +1498,12 @@ static int tree_content_set(
if (!slash1 && !S_ISDIR(mode) && subtree)
die("Non-directories cannot have subtrees");
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
if (!slash1) {
if (!S_ISDIR(mode)
&& e->versions[1].mode == mode
@@ -1514,7 +1556,7 @@ static int tree_content_remove(
const char *p,
struct tree_entry *backup_leaf)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
@@ -1525,9 +1567,20 @@ static int tree_content_remove(
else
n = strlen(p);
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+ if (slash1 && !S_ISDIR(e->versions[1].mode))
+ /*
+ * If p names a file in some subdirectory, and a
+ * file or symlink matching the name of the
+ * parent directory of p exists, then p cannot
+ * exist and need not be deleted.
+ */
+ return 1;
if (!slash1 || !S_ISDIR(e->versions[1].mode))
goto del_entry;
if (!e->tree)
@@ -1564,7 +1617,7 @@ static int tree_content_get(
const char *p,
struct tree_entry *leaf)
{
- struct tree_content *t = root->tree;
+ struct tree_content *t;
const char *slash1;
unsigned int i, n;
struct tree_entry *e;
@@ -1575,9 +1628,12 @@ static int tree_content_get(
else
n = strlen(p);
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
- if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
if (!slash1) {
memcpy(leaf, e, sizeof(*leaf));
if (e->tree && is_null_sha1(e->versions[1].sha1))
@@ -1666,7 +1722,7 @@ static void dump_marks_helper(FILE *f,
if (m->shift) {
for (k = 0; k < 1024; k++) {
if (m->data.sets[k])
- dump_marks_helper(f, (base + k) << m->shift,
+ dump_marks_helper(f, base + (k << m->shift),
m->data.sets[k]);
}
} else {
@@ -1732,7 +1788,11 @@ static void read_marks(void)
{
char line[512];
FILE *f = fopen(import_marks_file, "r");
- if (!f)
+ if (f)
+ ;
+ else if (import_marks_file_ignore_missing && errno == ENOENT)
+ return; /* Marks file does not exist */
+ else
die_errno("cannot read '%s'", import_marks_file);
while (fgets(line, sizeof(line), f)) {
uintmax_t mark;
@@ -1773,7 +1833,7 @@ static int read_next_command(void)
return EOF;
}
- do {
+ for (;;) {
if (unread_command_buf) {
unread_command_buf = 0;
} else {
@@ -1806,9 +1866,14 @@ static int read_next_command(void)
rc->prev->next = rc;
cmd_tail = rc;
}
- } while (command_buf.buf[0] == '#');
-
- return 0;
+ if (!prefixcmp(command_buf.buf, "cat-blob ")) {
+ parse_cat_blob();
+ continue;
+ }
+ if (command_buf.buf[0] == '#')
+ continue;
+ return 0;
+ }
}
static void skip_optional_lf(void)
@@ -2131,6 +2196,7 @@ static void file_change_m(struct branch *b)
case S_IFREG | 0644:
case S_IFREG | 0755:
case S_IFLNK:
+ case S_IFDIR:
case S_IFGITLINK:
/* ok */
break;
@@ -2162,6 +2228,12 @@ static void file_change_m(struct branch *b)
p = uq.buf;
}
+ /* Git does not track empty, non-toplevel directories. */
+ if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
+ tree_content_remove(&b->branch_tree, p, NULL);
+ return;
+ }
+
if (S_ISGITLINK(mode)) {
if (inline_data)
die("Git links cannot be specified 'inline': %s",
@@ -2176,25 +2248,34 @@ static void file_change_m(struct branch *b)
* another repository.
*/
} else if (inline_data) {
+ if (S_ISDIR(mode))
+ die("Directories cannot be specified 'inline': %s",
+ command_buf.buf);
if (p != uq.buf) {
strbuf_addstr(&uq, p);
p = uq.buf;
}
read_next_command();
parse_and_store_blob(&last_blob, sha1, 0);
- } else if (oe) {
- if (oe->type != OBJ_BLOB)
- die("Not a blob (actually a %s): %s",
- typename(oe->type), command_buf.buf);
} else {
- enum object_type type = sha1_object_info(sha1, NULL);
+ enum object_type expected = S_ISDIR(mode) ?
+ OBJ_TREE: OBJ_BLOB;
+ enum object_type type = oe ? oe->type :
+ sha1_object_info(sha1, NULL);
if (type < 0)
- die("Blob not found: %s", command_buf.buf);
- if (type != OBJ_BLOB)
- die("Not a blob (actually a %s): %s",
- typename(type), command_buf.buf);
+ die("%s not found: %s",
+ S_ISDIR(mode) ? "Tree" : "Blob",
+ command_buf.buf);
+ if (type != expected)
+ die("Not a %s (actually a %s): %s",
+ typename(expected), typename(type),
+ command_buf.buf);
}
+ if (!*p) {
+ tree_content_replace(&b->branch_tree, sha1, mode, NULL);
+ return;
+ }
tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
}
@@ -2253,6 +2334,13 @@ static void file_change_cr(struct branch *b, int rename)
tree_content_get(&b->branch_tree, s, &leaf);
if (!leaf.versions[1].mode)
die("Path %s not in branch", s);
+ if (!*d) { /* C "path/to/subdir" "" */
+ tree_content_replace(&b->branch_tree,
+ leaf.versions[1].sha1,
+ leaf.versions[1].mode,
+ leaf.tree);
+ return;
+ }
tree_content_set(&b->branch_tree, d,
leaf.versions[1].sha1,
leaf.versions[1].mode,
@@ -2518,6 +2606,8 @@ static void parse_new_commit(void)
note_change_n(b, prev_fanout);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ parse_ls(b);
else {
unread_command_buf = 1;
break;
@@ -2666,14 +2756,242 @@ static void parse_reset_branch(void)
unread_command_buf = 1;
}
-static void parse_checkpoint(void)
+static void cat_blob_write(const char *buf, unsigned long size)
+{
+ if (write_in_full(cat_blob_fd, buf, size) != size)
+ die_errno("Write to frontend failed");
+}
+
+static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
+{
+ struct strbuf line = STRBUF_INIT;
+ unsigned long size;
+ enum object_type type = 0;
+ char *buf;
+
+ if (!oe || oe->pack_id == MAX_PACK_ID) {
+ buf = read_sha1_file(sha1, &type, &size);
+ } else {
+ type = oe->type;
+ buf = gfi_unpack_entry(oe, &size);
+ }
+
+ /*
+ * Output based on batch_one_object() from cat-file.c.
+ */
+ if (type <= 0) {
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s missing\n", sha1_to_hex(sha1));
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ free(buf);
+ return;
+ }
+ if (!buf)
+ die("Can't read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_BLOB)
+ die("Object %s is a %s but a blob was expected.",
+ sha1_to_hex(sha1), typename(type));
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s %s %lu\n", sha1_to_hex(sha1),
+ typename(type), size);
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ cat_blob_write(buf, size);
+ cat_blob_write("\n", 1);
+ free(buf);
+}
+
+static void parse_cat_blob(void)
+{
+ const char *p;
+ struct object_entry *oe = oe;
+ unsigned char sha1[20];
+
+ /* cat-blob SP <object> LF */
+ p = command_buf.buf + strlen("cat-blob ");
+ if (*p == ':') {
+ char *x;
+ oe = find_mark(strtoumax(p + 1, &x, 10));
+ if (x == p + 1)
+ die("Invalid mark: %s", command_buf.buf);
+ if (!oe)
+ die("Unknown mark: %s", command_buf.buf);
+ if (*x)
+ die("Garbage after mark: %s", command_buf.buf);
+ hashcpy(sha1, oe->idx.sha1);
+ } else {
+ if (get_sha1_hex(p, sha1))
+ die("Invalid SHA1: %s", command_buf.buf);
+ if (p[40])
+ die("Garbage after SHA1: %s", command_buf.buf);
+ oe = find_object(sha1);
+ }
+
+ cat_blob(oe, sha1);
+}
+
+static struct object_entry *dereference(struct object_entry *oe,
+ unsigned char sha1[20])
+{
+ unsigned long size;
+ char *buf = NULL;
+ if (!oe) {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+ /* cache it! */
+ oe = insert_object(sha1);
+ oe->type = type;
+ oe->pack_id = MAX_PACK_ID;
+ oe->idx.offset = 1;
+ }
+ switch (oe->type) {
+ case OBJ_TREE: /* easy case. */
+ return oe;
+ case OBJ_COMMIT:
+ case OBJ_TAG:
+ break;
+ default:
+ die("Not a treeish: %s", command_buf.buf);
+ }
+
+ if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
+ buf = gfi_unpack_entry(oe, &size);
+ } else {
+ enum object_type unused;
+ buf = read_sha1_file(sha1, &unused, &size);
+ }
+ if (!buf)
+ die("Can't load object %s", sha1_to_hex(sha1));
+
+ /* Peel one layer. */
+ switch (oe->type) {
+ case OBJ_TAG:
+ if (size < 40 + strlen("object ") ||
+ get_sha1_hex(buf + strlen("object "), sha1))
+ die("Invalid SHA1 in tag: %s", command_buf.buf);
+ break;
+ case OBJ_COMMIT:
+ if (size < 40 + strlen("tree ") ||
+ get_sha1_hex(buf + strlen("tree "), sha1))
+ die("Invalid SHA1 in commit: %s", command_buf.buf);
+ }
+
+ free(buf);
+ return find_object(sha1);
+}
+
+static struct object_entry *parse_treeish_dataref(const char **p)
+{
+ unsigned char sha1[20];
+ struct object_entry *e;
+
+ if (**p == ':') { /* <mark> */
+ char *endptr;
+ e = find_mark(strtoumax(*p + 1, &endptr, 10));
+ if (endptr == *p + 1)
+ die("Invalid mark: %s", command_buf.buf);
+ if (!e)
+ die("Unknown mark: %s", command_buf.buf);
+ *p = endptr;
+ hashcpy(sha1, e->idx.sha1);
+ } else { /* <sha1> */
+ if (get_sha1_hex(*p, sha1))
+ die("Invalid SHA1: %s", command_buf.buf);
+ e = find_object(sha1);
+ *p += 40;
+ }
+
+ while (!e || e->type != OBJ_TREE)
+ e = dereference(e, sha1);
+ return e;
+}
+
+static void print_ls(int mode, const unsigned char *sha1, const char *path)
+{
+ static struct strbuf line = STRBUF_INIT;
+
+ /* See show_tree(). */
+ const char *type =
+ S_ISGITLINK(mode) ? commit_type :
+ S_ISDIR(mode) ? tree_type :
+ blob_type;
+
+ if (!mode) {
+ /* missing SP path LF */
+ strbuf_reset(&line);
+ strbuf_addstr(&line, "missing ");
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ } else {
+ /* mode SP type SP object_name TAB path LF */
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%06o %s %s\t",
+ mode, type, sha1_to_hex(sha1));
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ }
+ cat_blob_write(line.buf, line.len);
+}
+
+static void parse_ls(struct branch *b)
+{
+ const char *p;
+ struct tree_entry *root = NULL;
+ struct tree_entry leaf = {NULL};
+
+ /* ls SP (<treeish> SP)? <path> */
+ p = command_buf.buf + strlen("ls ");
+ if (*p == '"') {
+ if (!b)
+ die("Not in a commit: %s", command_buf.buf);
+ root = &b->branch_tree;
+ } else {
+ struct object_entry *e = parse_treeish_dataref(&p);
+ root = new_tree_entry();
+ hashcpy(root->versions[1].sha1, e->idx.sha1);
+ load_tree(root);
+ if (*p++ != ' ')
+ die("Missing space after tree-ish: %s", command_buf.buf);
+ }
+ if (*p == '"') {
+ static struct strbuf uq = STRBUF_INIT;
+ const char *endp;
+ strbuf_reset(&uq);
+ if (unquote_c_style(&uq, p, &endp))
+ die("Invalid path: %s", command_buf.buf);
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ p = uq.buf;
+ }
+ tree_content_get(root, p, &leaf);
+ /*
+ * A directory in preparation would have a sha1 of zero
+ * until it is saved. Save, for simplicity.
+ */
+ if (S_ISDIR(leaf.versions[1].mode))
+ store_tree(&leaf);
+
+ print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
+ if (!b || root != &b->branch_tree)
+ release_tree_entry(root);
+}
+
+static void checkpoint(void)
{
+ checkpoint_requested = 0;
if (object_count) {
cycle_packfile();
dump_branches();
dump_tags();
dump_marks();
}
+}
+
+static void parse_checkpoint(void)
+{
+ checkpoint_requested = 1;
skip_optional_lf();
}
@@ -2695,7 +3013,8 @@ static char* make_fast_import_path(const char *path)
return strbuf_detach(&abs_path, NULL);
}
-static void option_import_marks(const char *marks, int from_stream)
+static void option_import_marks(const char *marks,
+ int from_stream, int ignore_missing)
{
if (import_marks_file) {
if (from_stream)
@@ -2709,6 +3028,7 @@ static void option_import_marks(const char *marks, int from_stream)
import_marks_file = make_fast_import_path(marks);
safe_create_leading_directories_const(import_marks_file);
import_marks_file_from_stream = from_stream;
+ import_marks_file_ignore_missing = ignore_missing;
}
static void option_date_format(const char *fmt)
@@ -2723,16 +3043,25 @@ static void option_date_format(const char *fmt)
die("unknown --date-format argument %s", fmt);
}
+static unsigned long ulong_arg(const char *option, const char *arg)
+{
+ char *endptr;
+ unsigned long rv = strtoul(arg, &endptr, 0);
+ if (strchr(arg, '-') || endptr == arg || *endptr)
+ die("%s: argument must be a non-negative integer", option);
+ return rv;
+}
+
static void option_depth(const char *depth)
{
- max_depth = strtoul(depth, NULL, 0);
+ max_depth = ulong_arg("--depth", depth);
if (max_depth > MAX_DEPTH)
die("--depth cannot exceed %u", MAX_DEPTH);
}
static void option_active_branches(const char *branches)
{
- max_active_branches = strtoul(branches, NULL, 0);
+ max_active_branches = ulong_arg("--active-branches", branches);
}
static void option_export_marks(const char *marks)
@@ -2741,6 +3070,14 @@ static void option_export_marks(const char *marks)
safe_create_leading_directories_const(export_marks_file);
}
+static void option_cat_blob_fd(const char *fd)
+{
+ unsigned long n = ulong_arg("--cat-blob-fd", fd);
+ if (n > (unsigned long) INT_MAX)
+ die("--cat-blob-fd cannot exceed %d", INT_MAX);
+ cat_blob_fd = (int) n;
+}
+
static void option_export_pack_edges(const char *edges)
{
if (pack_edges)
@@ -2791,15 +3128,22 @@ static int parse_one_feature(const char *feature, int from_stream)
if (!prefixcmp(feature, "date-format=")) {
option_date_format(feature + 12);
} else if (!prefixcmp(feature, "import-marks=")) {
- option_import_marks(feature + 13, from_stream);
+ option_import_marks(feature + 13, from_stream, 0);
+ } else if (!prefixcmp(feature, "import-marks-if-exists=")) {
+ option_import_marks(feature + strlen("import-marks-if-exists="),
+ from_stream, 1);
} else if (!prefixcmp(feature, "export-marks=")) {
option_export_marks(feature + 13);
- } else if (!prefixcmp(feature, "relative-marks")) {
+ } else if (!strcmp(feature, "cat-blob")) {
+ ; /* Don't die - this feature is supported */
+ } else if (!strcmp(feature, "relative-marks")) {
relative_marks_paths = 1;
- } else if (!prefixcmp(feature, "no-relative-marks")) {
+ } else if (!strcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
- } else if (!prefixcmp(feature, "force")) {
+ } else if (!strcmp(feature, "force")) {
force_update = 1;
+ } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
+ ; /* do nothing; we have the feature */
} else {
return 0;
}
@@ -2852,25 +3196,21 @@ static int git_pack_config(const char *k, const char *v, void *cb)
return 0;
}
if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
+ pack_idx_opts.version = git_config_int(k, v);
+ if (pack_idx_opts.version > 2)
die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
+ pack_idx_opts.version);
return 0;
}
if (!strcmp(k, "pack.packsizelimit")) {
max_packsize = git_config_ulong(k, v);
return 0;
}
- if (!strcmp(k, "core.bigfilethreshold")) {
- long n = git_config_int(k, v);
- big_file_threshold = 0 < n ? n : 0;
- }
return git_default_config(k, v, cb);
}
static const char fast_import_usage[] =
-"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+"git fast-import [--date-format=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]";
static void parse_argv(void)
{
@@ -2888,6 +3228,11 @@ static void parse_argv(void)
if (parse_one_feature(a + 2, 0))
continue;
+ if (!prefixcmp(a + 2, "cat-blob-fd=")) {
+ option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
+ continue;
+ }
+
die("unknown option %s", a);
}
if (i != global_argc)
@@ -2908,6 +3253,7 @@ int main(int argc, const char **argv)
usage(fast_import_usage);
setup_git_directory();
+ reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
pack_compression_level = core_compression_level;
@@ -2930,9 +3276,12 @@ int main(int argc, const char **argv)
prepare_packed_git();
start_packfile();
set_die_routine(die_nicely);
+ set_checkpoint_signal();
while (read_next_command() != EOF) {
if (!strcmp("blob", command_buf.buf))
parse_new_blob();
+ else if (!prefixcmp(command_buf.buf, "ls "))
+ parse_ls(NULL);
else if (!prefixcmp(command_buf.buf, "commit "))
parse_new_commit();
else if (!prefixcmp(command_buf.buf, "tag "))
@@ -2951,6 +3300,9 @@ int main(int argc, const char **argv)
/* ignore non-git options*/;
else
die("Unsupported command: %s", command_buf.buf);
+
+ if (checkpoint_requested)
+ checkpoint();
}
/* argv hasn't been parsed yet, do so */
diff --git a/fetch-pack.h b/fetch-pack.h
index fbe85ac..0608eda 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -1,8 +1,7 @@
#ifndef FETCH_PACK_H
#define FETCH_PACK_H
-struct fetch_pack_args
-{
+struct fetch_pack_args {
const char *uploadpack;
int unpacklimit;
int depth;
diff --git a/fsck.c b/fsck.c
index 3d05d4a..60bd4bb 100644
--- a/fsck.c
+++ b/fsck.c
@@ -347,26 +347,14 @@ int fsck_object(struct object *obj, int strict, fsck_error error_func)
int fsck_error_function(struct object *obj, int type, const char *fmt, ...)
{
va_list ap;
- int len;
struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "object %s:", obj->sha1?sha1_to_hex(obj->sha1):"(null)");
+ strbuf_addf(&sb, "object %s:", sha1_to_hex(obj->sha1));
va_start(ap, fmt);
- len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap);
+ strbuf_vaddf(&sb, fmt, ap);
va_end(ap);
- if (len < 0)
- len = 0;
- if (len >= strbuf_avail(&sb)) {
- strbuf_grow(&sb, len + 2);
- va_start(ap, fmt);
- len = vsnprintf(sb.buf + sb.len, strbuf_avail(&sb), fmt, ap);
- va_end(ap);
- if (len >= strbuf_avail(&sb))
- die("this should not happen, your snprintf is broken");
- }
-
error("%s", sb.buf);
strbuf_release(&sb);
return 1;
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 75c68d9..3ef4861 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -1,8 +1,7 @@
#!/bin/sh
echo "/* Automatically generated by $0 */
-struct cmdname_help
-{
+struct cmdname_help {
char name[16];
char help[80];
};
diff --git a/gettext.c b/gettext.c
new file mode 100644
index 0000000..ae5394a
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+ */
+
+#include "git-compat-util.h"
+#include "gettext.h"
+
+int use_gettext_poison(void)
+{
+ static int poison_requested = -1;
+ if (poison_requested == -1)
+ poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
+ return poison_requested;
+}
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..24d9182
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2011 Ævar Arnfjörð Bjarmason
+ *
+ * This is a skeleton no-op implementation of gettext for Git.
+ * You can replace it with something that uses libintl.h and wraps
+ * gettext() to try out the translations.
+ */
+
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+#if defined(_) || defined(Q_)
+#error "namespace conflict: '_' or 'Q_' is pre-defined?"
+#endif
+
+#define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
+
+#ifdef GETTEXT_POISON
+extern int use_gettext_poison(void);
+#else
+#define use_gettext_poison() 0
+#endif
+
+static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
+{
+ return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
+}
+
+static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
+const char *Q_(const char *msgid, const char *plu, unsigned long n)
+{
+ if (use_gettext_poison())
+ return "# GETTEXT POISON #";
+ return n == 1 ? msgid : plu;
+}
+
+/* Mark msgid for translation but do not translate it. */
+#define N_(msgid) msgid
+
+#endif
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 27fc793..8f0839d 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1,6 +1,8 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
+use 5.008;
use strict;
+use warnings;
use Git;
binmode(STDOUT, ":raw");
@@ -43,6 +45,9 @@ my ($diff_new_color) =
my $normal_color = $repo->get_color("", "reset");
my $use_readkey = 0;
+my $use_termcap = 0;
+my %term_escapes;
+
sub ReadMode;
sub ReadKey;
if ($repo->config_bool("interactive.singlekey")) {
@@ -51,6 +56,14 @@ if ($repo->config_bool("interactive.singlekey")) {
Term::ReadKey->import;
$use_readkey = 1;
};
+ eval {
+ require Term::Cap;
+ my $termcap = Term::Cap->Tgetent;
+ foreach (values %$termcap) {
+ $term_escapes{$_} = 1 if /^\e/;
+ }
+ $use_termcap = 1;
+ };
}
sub colored {
@@ -87,6 +100,7 @@ my %patch_modes = (
TARGET => '',
PARTICIPLE => 'staging',
FILTER => 'file-only',
+ IS_REVERSE => 0,
},
'stash' => {
DIFF => 'diff-index -p HEAD',
@@ -96,6 +110,7 @@ my %patch_modes = (
TARGET => '',
PARTICIPLE => 'stashing',
FILTER => undef,
+ IS_REVERSE => 0,
},
'reset_head' => {
DIFF => 'diff-index -p --cached',
@@ -105,6 +120,7 @@ my %patch_modes = (
TARGET => '',
PARTICIPLE => 'unstaging',
FILTER => 'index-only',
+ IS_REVERSE => 1,
},
'reset_nothead' => {
DIFF => 'diff-index -R -p --cached',
@@ -114,6 +130,7 @@ my %patch_modes = (
TARGET => ' to index',
PARTICIPLE => 'applying',
FILTER => 'index-only',
+ IS_REVERSE => 0,
},
'checkout_index' => {
DIFF => 'diff-files -p',
@@ -123,6 +140,7 @@ my %patch_modes = (
TARGET => ' from worktree',
PARTICIPLE => 'discarding',
FILTER => 'file-only',
+ IS_REVERSE => 1,
},
'checkout_head' => {
DIFF => 'diff-index -p',
@@ -132,6 +150,7 @@ my %patch_modes = (
TARGET => ' from index and worktree',
PARTICIPLE => 'discarding',
FILTER => undef,
+ IS_REVERSE => 1,
},
'checkout_nothead' => {
DIFF => 'diff-index -R -p',
@@ -141,6 +160,7 @@ my %patch_modes = (
TARGET => ' to index and worktree',
PARTICIPLE => 'applying',
FILTER => undef,
+ IS_REVERSE => 0,
},
);
@@ -696,7 +716,7 @@ sub add_untracked_cmd {
sub run_git_apply {
my $cmd = shift;
my $fh;
- open $fh, '| git ' . $cmd;
+ open $fh, '| git ' . $cmd . " --recount --allow-overlap";
print $fh @_;
return close $fh;
}
@@ -999,10 +1019,12 @@ sub edit_hunk_manually {
print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
print $fh @$oldtext;
my $participle = $patch_mode_flavour{PARTICIPLE};
+ my $is_reverse = $patch_mode_flavour{IS_REVERSE};
+ my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
print $fh <<EOF;
# ---
-# To remove '-' lines, make them ' ' lines (context).
-# To remove '+' lines, delete them.
+# To remove '$remove_minus' lines, make them ' ' lines (context).
+# To remove '$remove_plus' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
@@ -1039,7 +1061,7 @@ EOF
sub diff_applies {
my $fh;
- return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',
+ return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
map { @{$_->{TEXT}} } @_);
}
@@ -1056,6 +1078,14 @@ sub prompt_single_character {
ReadMode 'cbreak';
my $key = ReadKey 0;
ReadMode 'restore';
+ if ($use_termcap and $key eq "\e") {
+ while (!defined $term_escapes{$key}) {
+ my $next = ReadKey 0.5;
+ last if (!defined $next);
+ $key .= $next;
+ }
+ $key =~ s/\e/^[/;
+ }
print "$key" if defined $key;
print "\n";
return $key;
@@ -1128,7 +1158,7 @@ EOF
sub apply_patch {
my $cmd = shift;
- my $ret = run_git_apply $cmd . ' --recount', @_;
+ my $ret = run_git_apply $cmd, @_;
if (!$ret) {
print STDERR @_;
}
@@ -1137,17 +1167,17 @@ sub apply_patch {
sub apply_patch_for_checkout_commit {
my $reverse = shift;
- my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;
- my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;
+ my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
+ my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
if ($applies_worktree && $applies_index) {
- run_git_apply 'apply '.$reverse.' --cached --recount', @_;
- run_git_apply 'apply '.$reverse.' --recount', @_;
+ run_git_apply 'apply '.$reverse.' --cached', @_;
+ run_git_apply 'apply '.$reverse, @_;
return 1;
} elsif (!$applies_index) {
print colored $error_color, "The selected hunks do not apply to the index!\n";
if (prompt_yesno "Apply them to the worktree anyway? ") {
- return run_git_apply 'apply '.$reverse.' --recount', @_;
+ return run_git_apply 'apply '.$reverse, @_;
} else {
print colored $error_color, "Nothing was applied.\n";
return 0;
@@ -1355,14 +1385,13 @@ sub patch_update_file {
next;
}
elsif ($line =~ /^q/i) {
- while ($ix < $num) {
- if (!defined $hunk[$ix]{USE}) {
- $hunk[$ix]{USE} = 0;
+ for ($i = 0; $i < $num; $i++) {
+ if (!defined $hunk[$i]{USE}) {
+ $hunk[$i]{USE} = 0;
}
- $ix++;
}
$quit = 1;
- next;
+ last;
}
elsif ($line =~ m|^/(.*)|) {
my $regex = $1;
diff --git a/git-am.sh b/git-am.sh
index e7f008c..463c741 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -5,7 +5,7 @@
SUBDIRECTORY_OK=Yes
OPTIONS_KEEPDASHDASH=
OPTIONS_SPEC="\
-git am [options] [<mbox>|<Maildir>...]
+git am [options] [(<mbox>|<Maildir>)...]
git am [options] (--resolved | --skip | --abort)
--
i,interactive run interactively
@@ -37,13 +37,14 @@ rerere-autoupdate update the index with reused conflict resolution if possible
rebasing* (internal use for git-rebase)"
. git-sh-setup
+. git-sh-i18n
prefix=$(git rev-parse --show-prefix)
set_reflog_action am
require_work_tree
cd_to_toplevel
git var GIT_COMMITTER_IDENT >/dev/null ||
- die "You need to set your committer info first"
+ die "$(gettext "You need to set your committer info first")"
if git rev-parse --verify -q HEAD >/dev/null
then
@@ -68,17 +69,42 @@ sq () {
stop_here () {
echo "$1" >"$dotest/next"
+ git rev-parse --verify -q HEAD >"$dotest/abort-safety"
exit 1
}
+safe_to_abort () {
+ if test -f "$dotest/dirtyindex"
+ then
+ return 1
+ fi
+
+ if ! test -s "$dotest/abort-safety"
+ then
+ return 0
+ fi
+
+ abort_safety=$(cat "$dotest/abort-safety")
+ if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety"
+ then
+ return 0
+ fi
+ (
+ gettext "You seem to have moved HEAD since the last 'am' failure.
+Not rewinding to ORIG_HEAD" &&
+ echo
+ ) >&2
+ return 1
+}
+
stop_here_user_resolve () {
if [ -n "$resolvemsg" ]; then
printf '%s\n' "$resolvemsg"
stop_here $1
fi
- echo "When you have resolved this problem run \"$cmdline --resolved\"."
- echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
- echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+ eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\".
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
stop_here $1
}
@@ -92,7 +118,7 @@ go_next () {
cannot_fallback () {
echo "$1"
- echo "Cannot fall back to three-way merge."
+ gettext "Cannot fall back to three-way merge."; echo
exit 1
}
@@ -107,7 +133,7 @@ fall_back_3way () {
"$dotest/patch" &&
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
git write-tree >"$dotest/patch-merge-base+" ||
- cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
+ cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")"
say Using index info to reconstruct a base tree...
if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
@@ -116,8 +142,8 @@ fall_back_3way () {
mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
else
- cannot_fallback "Did you hand edit your patch?
-It does not apply to blobs recorded in its index."
+ cannot_fallback "$(gettext "Did you hand edit your patch?
+It does not apply to blobs recorded in its index.")"
fi
test -f "$dotest/patch-merge-index" &&
@@ -125,7 +151,7 @@ It does not apply to blobs recorded in its index."
orig_tree=$(cat "$dotest/patch-merge-base") &&
rm -fr "$dotest"/patch-merge-* || exit 1
- say Falling back to patching base and 3-way merge...
+ say "$(gettext "Falling back to patching base and 3-way merge...")"
# This is not so wrong. Depending on which base we picked,
# orig_tree may be wildly different from ours, but his_tree
@@ -137,7 +163,7 @@ It does not apply to blobs recorded in its index."
export GITHEAD_$his_tree
if test -n "$GIT_QUIET"
then
- export GIT_MERGE_VERBOSITY=0
+ GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
fi
git-merge-recursive $orig_tree -- HEAD $his_tree || {
git rerere $allow_rerere_autoupdate
@@ -232,7 +258,7 @@ split_patches () {
stgit-series)
if test $# -ne 1
then
- clean_abort "Only one StGIT patch series can be applied at once"
+ clean_abort "$(gettext "Only one StGIT patch series can be applied at once")"
fi
series_dir=`dirname "$1"`
series_file="$1"
@@ -284,9 +310,9 @@ split_patches () {
;;
*)
if test -n "$parse_patch" ; then
- clean_abort "Patch format $patch_format is not supported."
+ clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")"
else
- clean_abort "Patch format detection failed."
+ clean_abort "$(gettext "Patch format detection failed.")"
fi
;;
esac
@@ -336,7 +362,7 @@ do
--rebasing)
rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
-d|--dotest)
- die "-d option is no longer supported. Do not use."
+ die "$(gettext "-d option is no longer supported. Do not use.")"
;;
--resolvemsg)
shift; resolvemsg=$1 ;;
@@ -399,12 +425,12 @@ then
false
;;
esac ||
- die "previous rebase directory $dotest still exists but mbox given."
+ die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")"
resume=yes
case "$skip,$abort" in
t,t)
- die "Please make up your mind. --skip or --abort?"
+ die "$(gettext "Please make up your mind. --skip or --abort?")"
;;
t,)
git rerere clear
@@ -419,10 +445,11 @@ then
exec git rebase --abort
fi
git rerere clear
- test -f "$dotest/dirtyindex" || {
+ if safe_to_abort
+ then
git read-tree --reset -u HEAD ORIG_HEAD
git reset ORIG_HEAD
- }
+ fi
rm -fr "$dotest"
exit ;;
esac
@@ -430,7 +457,7 @@ then
else
# Make sure we are not given --skip, --resolved, nor --abort
test "$skip$resolved$abort" = "" ||
- die "Resolve operation not in progress, we are not resuming."
+ die "$(gettext "Resolve operation not in progress, we are not resuming.")"
# Start afresh.
mkdir -p "$dotest" || exit
@@ -444,12 +471,12 @@ else
set x
first=
}
- case "$arg" in
- /*)
- set "$@" "$arg" ;;
- *)
- set "$@" "$prefix$arg" ;;
- esac
+ if is_absolute_path "$arg"
+ then
+ set "$@" "$arg"
+ else
+ set "$@" "$prefix$arg"
+ fi
done
shift
fi
@@ -495,7 +522,7 @@ case "$resolved" in
if test "$files"
then
test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
- die "Dirty index: cannot apply patches (dirty: $files)"
+ die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")"
fi
esac
@@ -554,13 +581,6 @@ then
resume=
fi
-if test "$this" -gt "$last"
-then
- say Nothing to do.
- rm -fr "$dotest"
- exit
-fi
-
while test "$this" -le "$last"
do
msgnum=`printf "%0${prec}d" $this`
@@ -591,9 +611,9 @@ do
go_next && continue
test -s "$dotest/patch" || {
- echo "Patch is empty. Was it split wrong?"
- echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
- echo "To restore the original branch and stop patching run \"$cmdline --abort\"."
+ eval_gettext "Patch is empty. Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo
stop_here $this
}
rm -f "$dotest/original-commit" "$dotest/author-script"
@@ -628,7 +648,7 @@ do
if test -z "$GIT_AUTHOR_EMAIL"
then
- echo "Patch does not have a valid e-mail address."
+ gettext "Patch does not have a valid e-mail address."; echo
stop_here $this
fi
@@ -675,15 +695,18 @@ do
if test "$interactive" = t
then
test -t 0 ||
- die "cannot be interactive without stdin connected to a terminal."
+ die "$(gettext "cannot be interactive without stdin connected to a terminal.")"
action=again
while test "$action" = again
do
- echo "Commit Body is:"
+ gettext "Commit Body is:"; echo
echo "--------------------------"
cat "$dotest/final-commit"
echo "--------------------------"
- printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+ # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ # in your translation. The program will only accept English
+ # input at this point.
+ gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
read reply
case "$reply" in
[yY]*) action=yes ;;
@@ -719,7 +742,7 @@ do
stop_here $this
fi
- say "Applying: $FIRSTLINE"
+ say "$(eval_gettext "Applying: \$FIRSTLINE")"
case "$resolved" in
'')
@@ -740,16 +763,16 @@ do
# working tree.
resolved=
git diff-index --quiet --cached HEAD -- && {
- echo "No changes - did you forget to use 'git add'?"
- echo "If there is nothing left to stage, chances are that something else"
- echo "already introduced the same changes; you might want to skip this patch."
+ gettext "No changes - did you forget to use 'git add'?
+If there is nothing left to stage, chances are that something else
+already introduced the same changes; you might want to skip this patch."; echo
stop_here_user_resolve $this
}
unmerged=$(git ls-files -u)
if test -n "$unmerged"
then
- echo "You still have unmerged paths in your index"
- echo "did you forget to use 'git add'?"
+ gettext "You still have unmerged paths in your index
+did you forget to use 'git add'?"; echo
stop_here_user_resolve $this
fi
apply_status=0
@@ -764,7 +787,7 @@ do
# Applying the patch to an earlier tree and merging the
# result may have produced the same tree as ours.
git diff-index --quiet --cached HEAD -- && {
- say No changes -- Patch already applied.
+ say "$(gettext "No changes -- Patch already applied.")"
go_next
continue
}
@@ -774,7 +797,7 @@ do
fi
if test $apply_status != 0
then
- printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
+ eval_gettext 'Patch failed at $msgnum $FIRSTLINE'; echo
stop_here_user_resolve $this
fi
@@ -790,7 +813,7 @@ do
GIT_AUTHOR_DATE=
fi
parent=$(git rev-parse --verify -q HEAD) ||
- say >&2 "applying to an empty history"
+ say >&2 "$(gettext "applying to an empty history")"
if test -n "$committer_date_is_author_date"
then
diff --git a/git-archimport.perl b/git-archimport.perl
index 98f3ede..bc32f18 100755
--- a/git-archimport.perl
+++ b/git-archimport.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
# This tool is copyright (c) 2005, Martin Langhoff.
# It is released under the Gnu Public License, version 2.
@@ -54,6 +54,7 @@ and can contain multiple, unrelated branches.
=cut
+use 5.008;
use strict;
use warnings;
use Getopt::Std;
diff --git a/git-bisect.sh b/git-bisect.sh
index 6e2acb8..b2186a8 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -28,6 +28,7 @@ Please use "git help bisect" to get the full man page.'
OPTIONS_SPEC=
. git-sh-setup
+. git-sh-i18n
require_work_tree
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -35,10 +36,16 @@ _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
bisect_autostart() {
test -s "$GIT_DIR/BISECT_START" || {
- echo >&2 'You need to start by "git bisect start"'
+ (
+ gettext "You need to start by \"git bisect start\"" &&
+ echo
+ ) >&2
if test -t 0
then
- echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+ # TRANSLATORS: Make sure to include [Y] and [n] in your
+ # translation. The program will only accept English input
+ # at this point.
+ gettext "Do you want me to do it for you [Y/n]? " >&2
read yesno
case "$yesno" in
[Nn]*)
@@ -57,7 +64,7 @@ bisect_start() {
#
head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
- die "Bad HEAD - I need a HEAD"
+ die "$(gettext "Bad HEAD - I need a HEAD")"
#
# Check if we are bisecting.
@@ -76,11 +83,11 @@ bisect_start() {
# cogito usage, and cogito users should understand
# it relates to cg-seek.
[ -s "$GIT_DIR/head-name" ] &&
- die "won't bisect on seeked tree"
+ die "$(gettext "won't bisect on seeked tree")"
start_head="${head#refs/heads/}"
;;
*)
- die "Bad HEAD - strange symbolic ref"
+ die "$(gettext "Bad HEAD - strange symbolic ref")"
;;
esac
fi
@@ -110,7 +117,7 @@ bisect_start() {
*)
rev=$(git rev-parse -q --verify "$arg^{commit}") || {
test $has_double_dash -eq 1 &&
- die "'$arg' does not appear to be a valid revision"
+ die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
break
}
case $bad_seen in
@@ -155,7 +162,7 @@ bisect_write() {
case "$state" in
bad) tag="$state" ;;
good|skip) tag="$state"-"$rev" ;;
- *) die "Bad bisect_write argument: $state" ;;
+ *) die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
esac
git update-ref "refs/bisect/$tag" "$rev" || exit
echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
@@ -183,7 +190,7 @@ bisect_skip() {
do
case "$arg" in
*..*)
- revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
+ revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
*)
revs=$(git rev-parse --sq-quote "$arg") ;;
esac
@@ -197,10 +204,10 @@ bisect_state() {
state=$1
case "$#,$state" in
0,*)
- die "Please call 'bisect_state' with at least one argument." ;;
+ die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
1,bad|1,good|1,skip)
rev=$(git rev-parse --verify HEAD) ||
- die "Bad rev input: HEAD"
+ die "$(gettext "Bad rev input: HEAD")"
bisect_write "$state" "$rev"
check_expected_revs "$rev" ;;
2,bad|*,good|*,skip)
@@ -209,13 +216,13 @@ bisect_state() {
for rev in "$@"
do
sha=$(git rev-parse --verify "$rev^{commit}") ||
- die "Bad rev input: $rev"
+ die "$(eval_gettext "Bad rev input: \$rev")"
eval="$eval bisect_write '$state' '$sha'; "
done
eval "$eval"
check_expected_revs "$@" ;;
*,bad)
- die "'git bisect bad' can take only one argument." ;;
+ die "$(gettext "'git bisect bad' can take only one argument.")" ;;
*)
usage ;;
esac
@@ -238,25 +245,38 @@ bisect_next_check() {
t,,good)
# have bad but not good. we could bisect although
# this is less optimum.
- echo >&2 'Warning: bisecting only with a bad commit.'
+ (
+ gettext "Warning: bisecting only with a bad commit." &&
+ echo
+ ) >&2
if test -t 0
then
- printf >&2 'Are you sure [Y/n]? '
+ # TRANSLATORS: Make sure to include [Y] and [n] in your
+ # translation. The program will only accept English input
+ # at this point.
+ gettext "Are you sure [Y/n]? " >&2
read yesno
case "$yesno" in [Nn]*) exit 1 ;; esac
fi
: bisect without good...
;;
*)
- THEN=''
- test -s "$GIT_DIR/BISECT_START" || {
- echo >&2 'You need to start by "git bisect start".'
- THEN='then '
- }
- echo >&2 'You '$THEN'need to give me at least one good' \
- 'and one bad revisions.'
- echo >&2 '(You can use "git bisect bad" and' \
- '"git bisect good" for that.)'
+
+ if test -s "$GIT_DIR/BISECT_START"
+ then
+ (
+ gettext "You need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
+ echo
+ ) >&2
+ else
+ (
+ gettext "You need to start by \"git bisect start\".
+You then need to give me at least one good and one bad revisions.
+(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
+ echo
+ ) >&2
+ fi
exit 1 ;;
esac
}
@@ -288,10 +308,12 @@ bisect_visualize() {
if test $# = 0
then
- case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
- '') set git log ;;
- set*) set gitk ;;
- esac
+ if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
+ type gitk >/dev/null 2>&1; then
+ set gitk
+ else
+ set git log
+ fi
else
case "$1" in
git*|tig) ;;
@@ -305,18 +327,25 @@ bisect_visualize() {
bisect_reset() {
test -s "$GIT_DIR/BISECT_START" || {
- echo "We are not bisecting."
+ gettext "We are not bisecting."; echo
return
}
case "$#" in
0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
- 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
- die "'$1' is not a valid commit"
+ 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
+ invalid="$1"
+ die "$(eval_gettext "'\$invalid' is not a valid commit")"
+ }
branch="$1" ;;
*)
usage ;;
esac
- git checkout "$branch" -- && bisect_clean_state
+ if git checkout "$branch" -- ; then
+ bisect_clean_state
+ else
+ die "$(eval_gettext "Could not check out original HEAD '\$branch'.
+Try 'git bisect reset <commit>'.")"
+ fi
}
bisect_clean_state() {
@@ -338,7 +367,9 @@ bisect_clean_state() {
}
bisect_replay () {
- test -r "$1" || die "cannot read $1 for replaying"
+ file="$1"
+ test "$#" -eq 1 || die "$(gettext "No logfile given")"
+ test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
bisect_reset
while read git bisect command rev
do
@@ -354,9 +385,9 @@ bisect_replay () {
good|bad|skip)
bisect_write "$command" "$rev" ;;
*)
- die "?? what are you talking about?" ;;
+ die "$(gettext "?? what are you talking about?")" ;;
esac
- done <"$1"
+ done <"$file"
bisect_auto_next
}
@@ -365,14 +396,18 @@ bisect_run () {
while true
do
- echo "running $@"
+ command="$@"
+ eval_gettext "running \$command"; echo
"$@"
res=$?
# Check for really bad run error.
if [ $res -lt 0 -o $res -ge 128 ]; then
- echo >&2 "bisect run failed:"
- echo >&2 "exit code $res from '$@' is < 0 or >= 128"
+ (
+ eval_gettext "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" &&
+ echo
+ ) >&2
exit $res
fi
@@ -394,24 +429,34 @@ bisect_run () {
if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
> /dev/null; then
- echo >&2 "bisect run cannot continue any more"
+ (
+ gettext "bisect run cannot continue any more" &&
+ echo
+ ) >&2
exit $res
fi
if [ $res -ne 0 ]; then
- echo >&2 "bisect run failed:"
- echo >&2 "'bisect_state $state' exited with error code $res"
+ (
+ eval_gettext "bisect run failed:
+'bisect_state \$state' exited with error code \$res" &&
+ echo
+ ) >&2
exit $res
fi
if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
- echo "bisect run success"
+ gettext "bisect run success"; echo
exit 0;
fi
done
}
+bisect_log () {
+ test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
+ cat "$GIT_DIR/BISECT_LOG"
+}
case "$#" in
0)
@@ -438,7 +483,7 @@ case "$#" in
replay)
bisect_replay "$@" ;;
log)
- cat "$GIT_DIR/BISECT_LOG" ;;
+ bisect_log ;;
run)
bisect_run "$@" ;;
*)
diff --git a/git-compat-util.h b/git-compat-util.h
index 02a73ee..ddfbf77 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -28,6 +28,24 @@
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
#define bitsizeof(x) (CHAR_BIT * sizeof(x))
+#define maximum_signed_value_of_type(a) \
+ (INTMAX_MAX >> (bitsizeof(intmax_t) - bitsizeof(a)))
+
+#define maximum_unsigned_value_of_type(a) \
+ (UINTMAX_MAX >> (bitsizeof(uintmax_t) - bitsizeof(a)))
+
+/*
+ * Signed integer overflow is undefined in C, so here's a helper macro
+ * to detect if the sum of two integers will overflow.
+ *
+ * Requires: a >= 0, typeof(a) equals typeof(b)
+ */
+#define signed_add_overflows(a, b) \
+ ((b) > maximum_signed_value_of_type(a) - (a))
+
+#define unsigned_add_overflows(a, b) \
+ ((b) > maximum_unsigned_value_of_type(a) - (a))
+
#ifdef __GNUC__
#define TYPEOF(x) (__typeof__(x))
#else
@@ -92,9 +110,15 @@
#include <assert.h>
#include <regex.h>
#include <utime.h>
+#include <syslog.h>
+#ifndef NO_SYS_POLL_H
+#include <sys/poll.h>
+#else
+#include <poll.h>
+#endif
#ifndef __MINGW32__
#include <sys/wait.h>
-#include <sys/poll.h>
+#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <termios.h>
@@ -106,7 +130,11 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
+#ifndef NO_INTTYPES_H
#include <inttypes.h>
+#else
+#include <stdint.h>
+#endif
#if defined(__CYGWIN__)
#undef _XOPEN_SOURCE
#include <grp.h>
@@ -160,6 +188,10 @@ extern char *gitbasename(char *);
#define PRIx32 "x"
#endif
+#ifndef PRIo32
+#define PRIo32 "o"
+#endif
+
#ifndef PATH_SEP
#define PATH_SEP ':'
#endif
@@ -183,7 +215,14 @@ extern char *gitbasename(char *);
#define is_dir_sep(c) ((c) == '/')
#endif
-#ifdef __GNUC__
+#ifndef find_last_dir_sep
+#define find_last_dir_sep(path) strrchr(path, '/')
+#endif
+
+#if __HP_cc >= 61000
+#define NORETURN __attribute__((noreturn))
+#define NORETURN_PTR
+#elif defined(__GNUC__) && !defined(NO_NORETURN)
#define NORETURN __attribute__((__noreturn__))
#define NORETURN_PTR __attribute__((__noreturn__))
#elif defined(_MSC_VER)
@@ -312,6 +351,11 @@ extern size_t gitstrlcpy(char *, const char *, size_t);
extern uintmax_t gitstrtoumax(const char *, char **, int);
#endif
+#ifdef NO_STRTOK_R
+#define strtok_r gitstrtok_r
+extern char *gitstrtok_r(char *s, const char *delim, char **save_ptr);
+#endif
+
#ifdef NO_HSTRERROR
#define hstrerror githstrerror
extern const char *githstrerror(int herror);
@@ -365,6 +409,14 @@ static inline void *gitmempcpy(void *dest, const void *src, size_t n)
}
#endif
+#ifdef NO_INET_PTON
+int inet_pton(int af, const char *src, void *dst);
+#endif
+
+#ifdef NO_INET_NTOP
+const char *inet_ntop(int af, const void *src, char *dst, size_t size);
+#endif
+
extern void release_pack_memory(size_t, int);
typedef void (*try_to_free_t)(size_t);
@@ -383,11 +435,14 @@ extern ssize_t xwrite(int fd, const void *buf, size_t len);
extern int xdup(int fd);
extern FILE *xfdopen(int fd, const char *mode);
extern int xmkstemp(char *template);
+extern int xmkstemp_mode(char *template, int mode);
extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
static inline size_t xsize_t(off_t len)
{
+ if (len > (size_t) len)
+ die("Cannot handle files this big");
return (size_t)len;
}
@@ -412,6 +467,7 @@ extern unsigned char sane_ctype[256];
#define GIT_ALPHA 0x04
#define GIT_GLOB_SPECIAL 0x08
#define GIT_REGEX_SPECIAL 0x10
+#define GIT_PATHSPEC_MAGIC 0x20
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
#define isascii(x) (((x) & ~0x7f) == 0)
#define isspace(x) sane_istest(x,GIT_SPACE)
@@ -422,6 +478,7 @@ extern unsigned char sane_ctype[256];
#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
#define tolower(x) sane_case((unsigned char)(x), 0x20)
#define toupper(x) sane_case((unsigned char)(x), 0)
+#define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC)
static inline int sane_case(int x, int high)
{
@@ -488,6 +545,19 @@ void git_qsort(void *base, size_t nmemb, size_t size,
#define fstat_is_reliable() 1
#endif
+#ifndef va_copy
+/*
+ * Since an obvious implementation of va_list would be to make it a
+ * pointer into the stack frame, a simple assignment will work on
+ * many systems. But let's try to be more portable.
+ */
+#ifdef __va_copy
+#define va_copy(dst, src) __va_copy(dst, src)
+#else
+#define va_copy(dst, src) ((dst) = (src))
+#endif
+#endif
+
/*
* Preserves errno, prints a message, but gives no warning for ENOENT.
* Always returns the return value of unlink(2).
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 59b6722..39a426e 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -1,6 +1,8 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
+use 5.008;
use strict;
+use warnings;
use Getopt::Std;
use File::Temp qw(tempdir);
use Data::Dumper;
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 9e03eee..8d41610 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
# This tool is copyright (c) 2005, Matthias Urlichs.
# It is released under the Gnu Public License, version 2.
@@ -13,6 +13,7 @@
# The head revision is on branch "origin" by default.
# You can change that with the '-o' option.
+use 5.008;
use strict;
use warnings;
use Getopt::Long;
@@ -89,23 +90,40 @@ sub write_author_info($) {
}
# convert getopts specs for use by git config
+my %longmap = (
+ 'A:' => 'authors-file',
+ 'M:' => 'merge-regex',
+ 'P:' => undef,
+ 'R' => 'track-revisions',
+ 'S:' => 'ignore-paths',
+);
+
sub read_repo_config {
- # Split the string between characters, unless there is a ':'
- # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
+ # Split the string between characters, unless there is a ':'
+ # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
my @opts = split(/ *(?!:)/, shift);
foreach my $o (@opts) {
my $key = $o;
$key =~ s/://g;
my $arg = 'git config';
$arg .= ' --bool' if ($o !~ /:$/);
-
- chomp(my $tmp = `$arg --get cvsimport.$key`);
+ my $ckey = $key;
+
+ if (exists $longmap{$o}) {
+ # An uppercase option like -R cannot be
+ # expressed in the configuration, as the
+ # variable names are downcased.
+ $ckey = $longmap{$o};
+ next if (! defined $ckey);
+ $ckey =~ s/-//g;
+ }
+ chomp(my $tmp = `$arg --get cvsimport.$ckey`);
if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
- no strict 'refs';
- my $opt_name = "opt_" . $key;
- if (!$$opt_name) {
- $$opt_name = $tmp;
- }
+ no strict 'refs';
+ my $opt_name = "opt_" . $key;
+ if (!$$opt_name) {
+ $$opt_name = $tmp;
+ }
}
}
}
@@ -209,6 +227,31 @@ sub new {
return $self;
}
+sub find_password_entry {
+ my ($cvspass, @cvsroot) = @_;
+ my ($file, $delim) = @$cvspass;
+ my $pass;
+ local ($_);
+
+ if (open(my $fh, $file)) {
+ # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+ CVSPASSFILE:
+ while (<$fh>) {
+ chomp;
+ s/^\/\d+\s+//;
+ my ($w, $p) = split($delim,$_,2);
+ for my $cvsroot (@cvsroot) {
+ if ($w eq $cvsroot) {
+ $pass = $p;
+ last CVSPASSFILE;
+ }
+ }
+ }
+ close($fh);
+ }
+ return $pass;
+}
+
sub conn {
my $self = shift;
my $repo = $self->{'fullrep'};
@@ -241,19 +284,23 @@ sub conn {
if ($pass) {
$pass = $self->_scramble($pass);
} else {
- open(H,$ENV{'HOME'}."/.cvspass") and do {
- # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
- while (<H>) {
- chomp;
- s/^\/\d+\s+//;
- my ($w,$p) = split(/\s/,$_,2);
- if ($w eq $rr or $w eq $rr2) {
- $pass = $p;
- last;
- }
+ my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/],
+ [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]);
+ my @loc = ();
+ foreach my $cvspass (@cvspass) {
+ my $p = find_password_entry($cvspass, $rr, $rr2);
+ if ($p) {
+ push @loc, $cvspass->[0];
+ $pass = $p;
}
- };
- $pass = "A" unless $pass;
+ }
+
+ if (1 < @loc) {
+ die("Multiple cvs password files have ".
+ "entries for CVSROOT $opt_d: @loc");
+ } elsif (!$pass) {
+ $pass = "A";
+ }
}
my ($s, $rep);
@@ -348,7 +395,9 @@ sub conn {
$self->{'socketo'}->write("valid-requests\n");
$self->{'socketo'}->flush();
- chomp(my $rep=$self->readline());
+ my $rep=$self->readline();
+ die "Failed to read from server" unless defined $rep;
+ chomp($rep);
if ($rep !~ s/^Valid-requests\s*//) {
$rep="<unknown>" unless $rep;
die "Expected Valid-requests from server, but got: $rep\n";
@@ -611,7 +660,7 @@ my %index; # holds filenames of one index per branch
unless (-d $git_dir) {
system(qw(git init));
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system(qw(git read-tree));
+ system(qw(git read-tree --empty));
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index e9f3037..1b8bff2 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -8,13 +8,14 @@
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
#### Released under the GNU Public License, version 2.
####
####
+use 5.008;
use strict;
use warnings;
use bytes;
@@ -2680,7 +2681,7 @@ package GITCVS::log;
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
@@ -2847,7 +2848,7 @@ package GITCVS::updater;
#### Copyright The Open University UK - 2006.
####
#### Authors: Martyn Smith <martyn@catalyst.net.nz>
-#### Martin Langhoff <martin@catalyst.net.nz>
+#### Martin Langhoff <martin@laptop.org>
####
####
diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh
index 524f5ea..0594bf7 100755
--- a/git-difftool--helper.sh
+++ b/git-difftool--helper.sh
@@ -49,6 +49,7 @@ launch_merge_tool () {
fi
if use_ext_cmd; then
+ export BASE
eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
else
run_merge_tool "$merge_tool"
diff --git a/git-difftool.perl b/git-difftool.perl
index adc42de..ced1615 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -10,6 +10,7 @@
#
# Any arguments that are unknown to this script are forwarded to 'git diff'.
+use 5.008;
use strict;
use warnings;
use Cwd qw(abs_path);
@@ -51,6 +52,7 @@ sub generate_command
my @command = (exe('git'), 'diff');
my $skip_next = 0;
my $idx = -1;
+ my $prompt = '';
for my $arg (@ARGV) {
$idx++;
if ($skip_next) {
@@ -88,13 +90,11 @@ sub generate_command
next;
}
if ($arg eq '-y' || $arg eq '--no-prompt') {
- $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
- delete $ENV{GIT_DIFFTOOL_PROMPT};
+ $prompt = 'no';
next;
}
if ($arg eq '--prompt') {
- $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
- delete $ENV{GIT_DIFFTOOL_NO_PROMPT};
+ $prompt = 'yes';
next;
}
if ($arg eq '-h' || $arg eq '--help') {
@@ -102,6 +102,11 @@ sub generate_command
}
push @command, $arg;
}
+ if ($prompt eq 'yes') {
+ $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+ } elsif ($prompt eq 'no') {
+ $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+ }
return @command
}
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 88fb0f0..962a93b 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -139,6 +139,7 @@ do
continue
;;
--remap-to-ancestor)
+ # deprecated ($remap_to_ancestor is set now automatically)
shift
remap_to_ancestor=t
continue
@@ -265,7 +266,14 @@ mkdir ../map || die "Could not create map/ directory"
# we need "--" only if there are no path arguments in $@
nonrevs=$(git rev-parse --no-revs "$@") || exit
-test -z "$nonrevs" && dashdash=-- || dashdash=
+if test -z "$nonrevs"
+then
+ dashdash=--
+else
+ dashdash=
+ remap_to_ancestor=t
+fi
+
rev_args=$(git rev-parse --revs-only "$@")
case "$filter_subdir" in
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN
index b3f937e..1fb4d9b 100755
--- a/git-gui/GIT-VERSION-GEN
+++ b/git-gui/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=0.12.GITGUI
+DEF_VER=0.13.GITGUI
LF='
'
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 197b55e..e22ba5c 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -215,6 +215,7 @@ endif
$(GITGUI_MAIN): git-gui.sh GIT-VERSION-FILE GIT-GUI-VARS
$(QUIET_GEN)rm -f $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+ -e 's|@@SHELL_PATH@@|$(SHELL_PATH_SQ)|' \
-e '1,30s|^ argv0=$$0| argv0=$(GITGUI_SCRIPT)|' \
-e '1,30s|^ exec wish | exec '\''$(TCLTK_PATH_SED)'\'' |' \
-e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
diff --git a/git-gui/git-gui--askpass b/git-gui/git-gui--askpass
index 12e117e..4277f30 100755
--- a/git-gui/git-gui--askpass
+++ b/git-gui/git-gui--askpass
@@ -5,6 +5,8 @@ exec wish "$0" -- "$@"
# This is a trivial implementation of an SSH_ASKPASS handler.
# Git-gui uses this script if none are already configured.
+package require Tk
+
set answer {}
set yesno 0
set rc 255
@@ -30,16 +32,20 @@ if {!$yesno} {
frame .b
button .b.ok -text OK -command finish
-button .b.cancel -text Cancel -command {destroy .}
+button .b.cancel -text Cancel -command cancel
pack .b.ok -side left -expand 1
pack .b.cancel -side right -expand 1
pack .b -side bottom -fill x -padx 10 -pady 10
bind . <Visibility> {focus -force .e}
-bind . <Key-Return> finish
-bind . <Key-Escape> {destroy .}
-bind . <Destroy> {exit $rc}
+bind . <Key-Return> [list .b.ok invoke]
+bind . <Key-Escape> [list .b.cancel invoke]
+bind . <Destroy> {set rc $rc}
+
+proc cancel {} {
+ set ::rc 255
+}
proc finish {} {
if {$::yesno} {
@@ -50,10 +56,11 @@ proc finish {} {
}
}
- set ::rc 0
puts $::answer
- destroy .
+ set ::rc 0
}
wm title . "OpenSSH"
tk::PlaceWindow .
+vwait rc
+exit $rc
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 7d54511..fd6a43d 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -10,8 +10,8 @@
exec wish "$argv0" -- "$@"
set appvers {@@GITGUI_VERSION@@}
-set copyright [encoding convertfrom utf-8 {
-Copyright © 2006, 2007 Shawn Pearce, et. al.
+set copyright [string map [list (c) \u00a9] {
+Copyright (c) 2006-2010 Shawn Pearce, et. al.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err]
tk_messageBox \
-icon error \
-type ok \
- -title [mc "git-gui: fatal error"] \
+ -title "git-gui: fatal error" \
-message $err
exit 1
}
@@ -83,6 +83,7 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
puts stderr "source $name"
uplevel 1 real__source $name
}
+ if {[tk windowingsystem] eq "win32"} { console show }
}
######################################################################
@@ -92,6 +93,25 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
package require msgcat
+# Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4)
+if {[tk windowingsystem] eq "win32"
+ && [package vcompare [package provide msgcat] 1.4.4] < 0
+} then {
+ proc _mc_update_locale {} {
+ set key {HKEY_CURRENT_USER\Control Panel\Desktop}
+ if {![catch {
+ package require registry
+ set uilocale [registry get $key "PreferredUILanguages"]
+ msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]]
+ } uilocale]} {
+ if {[string length $uilocale] > 0} {
+ msgcat::mclocale $uilocale
+ }
+ }
+ }
+ _mc_update_locale
+}
+
proc _mc_trim {fmt} {
set cmk [string first @@ $fmt]
if {$cmk > 0} {
@@ -128,6 +148,7 @@ set _githtmldir {}
set _reponame {}
set _iscygwin {}
set _search_path {}
+set _shellpath {@@SHELL_PATH@@}
set _trace [lsearch -exact $argv --trace]
if {$_trace >= 0} {
@@ -137,6 +158,22 @@ if {$_trace >= 0} {
set _trace 0
}
+# variable for the last merged branch (useful for a default when deleting
+# branches).
+set _last_merged_branch {}
+
+proc shellpath {} {
+ global _shellpath env
+ if {[string match @@* $_shellpath]} {
+ if {[info exists env(SHELL)]} {
+ return $env(SHELL)
+ } else {
+ return /bin/sh
+ }
+ }
+ return $_shellpath
+}
+
proc appname {} {
global _appname
return $_appname
@@ -269,6 +306,17 @@ proc is_config_true {name} {
}
}
+proc is_config_false {name} {
+ global repo_config
+ if {[catch {set v $repo_config($name)}]} {
+ return 0
+ } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
+ return 1
+ } else {
+ return 0
+ }
+}
+
proc get_config {name} {
global repo_config
if {[catch {set v $repo_config($name)}]} {
@@ -323,6 +371,8 @@ proc _trace_exec {cmd} {
puts stderr $d
}
+#'" fix poor old emacs font-lock mode
+
proc _git_cmd {name} {
global _git_cmd_path
@@ -416,6 +466,11 @@ proc _lappend_nice {cmd_var} {
if {![info exists _nice]} {
set _nice [_which nice]
+ if {[catch {exec $_nice git version}]} {
+ set _nice {}
+ } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
+ set _nice {}
+ }
}
if {$_nice ne {}} {
lappend cmd $_nice
@@ -634,6 +689,7 @@ proc rmsel_tag {text} {
return $text
}
+wm withdraw .
set root_exists 0
bind . <Visibility> {
bind . <Visibility> {}
@@ -643,6 +699,7 @@ bind . <Visibility> {
if {[is_Windows]} {
wm iconbitmap . -default $oguilib/git-gui.ico
set ::tk::AlwaysShowSelection 1
+ bind . <Control-F2> {console show}
# Spoof an X11 display for SSH
if {![info exists env(DISPLAY)]} {
@@ -782,6 +839,7 @@ set default_config(user.email) {}
set default_config(gui.encoding) [encoding system]
set default_config(gui.matchtrackingbranch) false
+set default_config(gui.textconv) true
set default_config(gui.pruneduringfetch) false
set default_config(gui.trustmtime) false
set default_config(gui.fastcopyblame) false
@@ -843,12 +901,19 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
exit 1
}
+proc get_trimmed_version {s} {
+ set r {}
+ foreach x [split $s -._] {
+ if {[string is integer -strict $x]} {
+ lappend r $x
+ } else {
+ break
+ }
+ }
+ return [join $r .]
+}
set _real_git_version $_git_version
-regsub -- {[\-\.]dirty$} $_git_version {} _git_version
-regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
-regsub {\.GIT$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
+set _git_version [get_trimmed_version $_git_version]
if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
catch {wm withdraw .}
@@ -1152,10 +1217,22 @@ if {![file isdirectory $_gitdir]} {
# _gitdir exists, so try loading the config
load_config 0
apply_config
-# try to set work tree from environment, falling back to core.worktree
-if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
- set _gitworktree [get_config core.worktree]
+
+# v1.7.0 introduced --show-toplevel to return the canonical work-tree
+if {[package vsatisfies $_git_version 1.7.0]} {
+ set _gitworktree [git rev-parse --show-toplevel]
+} else {
+ # try to set work tree from environment, core.worktree or use
+ # cdup to obtain a relative path to the top of the worktree. If
+ # run from the top, the ./ prefix ensures normalize expands pwd.
+ if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+ set _gitworktree [get_config core.worktree]
+ if {$_gitworktree eq ""} {
+ set _gitworktree [file normalize ./[git rev-parse --show-cdup]]
+ }
+ }
}
+
if {$_prefix ne {}} {
if {$_gitworktree eq {}} {
regsub -all {[^/]+/} $_prefix ../ cdup
@@ -1394,13 +1471,17 @@ proc rescan_stage2 {fd after} {
close $fd
}
- set ls_others [list --exclude-per-directory=.gitignore]
- if {[have_info_exclude]} {
- lappend ls_others "--exclude-from=[gitdir info exclude]"
- }
- set user_exclude [get_config core.excludesfile]
- if {$user_exclude ne {} && [file readable $user_exclude]} {
- lappend ls_others "--exclude-from=$user_exclude"
+ if {[package vsatisfies $::_git_version 1.6.3]} {
+ set ls_others [list --exclude-standard]
+ } else {
+ set ls_others [list --exclude-per-directory=.gitignore]
+ if {[have_info_exclude]} {
+ lappend ls_others "--exclude-from=[gitdir info exclude]"
+ }
+ set user_exclude [get_config core.excludesfile]
+ if {$user_exclude ne {} && [file readable $user_exclude]} {
+ lappend ls_others "--exclude-from=[file normalize $user_exclude]"
+ }
}
set buf_rdi {}
@@ -1904,8 +1985,8 @@ static unsigned char file_merge_bits[] = {
} -maskdata $filemask
image create bitmap file_statechange -background white -foreground green -data {
-#define file_merge_width 14
-#define file_merge_height 15
+#define file_statechange_width 14
+#define file_statechange_height 15
static unsigned char file_statechange_bits[] = {
0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
@@ -1939,7 +2020,11 @@ foreach i {
{MD {mc "Staged for commit, missing"}}
{_T {mc "File type changed, not staged"}}
+ {MT {mc "File type changed, old type staged for commit"}}
+ {AT {mc "File type changed, old type staged for commit"}}
{T_ {mc "File type changed, staged"}}
+ {TM {mc "File type change staged, modification not staged"}}
+ {TD {mc "File type change staged, file missing"}}
{_O {mc "Untracked, not staged"}}
{A_ {mc "Staged for commit"}}
@@ -2098,7 +2183,7 @@ proc do_explore {} {
# freedesktop.org-conforming system is our best shot
set explorer "xdg-open"
}
- eval exec $explorer $_gitworktree &
+ eval exec $explorer [list [file nativename $_gitworktree]] &
}
set is_quitting 0
@@ -2824,7 +2909,14 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
set subcommand_args {}
proc usage {} {
- puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
+ set s "usage: $::argv0 $::subcommand $::subcommand_args"
+ if {[tk windowingsystem] eq "win32"} {
+ wm withdraw .
+ tk_messageBox -icon info -message $s \
+ -title [mc "Usage"]
+ } else {
+ puts stderr $s
+ }
exit 1
}
@@ -2894,13 +2986,18 @@ blame {
if {[catch {
set head [git rev-parse --verify $head]
} err]} {
- puts stderr $err
+ if {[tk windowingsystem] eq "win32"} {
+ tk_messageBox -icon error -title [mc Error] -message $err
+ } else {
+ puts stderr $err
+ }
exit 1
}
}
set current_branch $head
}
+ wm deiconify .
switch -- $subcommand {
browser {
if {$jump_spec ne {}} usage
@@ -2916,7 +3013,12 @@ blame {
}
blame {
if {$head eq {} && ![file exists $path]} {
- puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [mc "git-gui: fatal error"] \
+ -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
exit 1
}
blame::new $head $path $jump_spec
@@ -2927,18 +3029,19 @@ blame {
citool -
gui {
if {[llength $argv] != 0} {
- puts -nonewline stderr "usage: $argv0"
- if {$subcommand ne {gui}
- && [file tail $argv0] ne "git-$subcommand"} {
- puts -nonewline stderr " $subcommand"
- }
- puts stderr {}
- exit 1
+ usage
}
# fall through to setup UI for commits
}
default {
- puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
+ set err "usage: $argv0 \[{blame|browser|citool}\]"
+ if {[tk windowingsystem] eq "win32"} {
+ wm withdraw .
+ tk_messageBox -icon error -message $err \
+ -title [mc "Usage"]
+ } else {
+ puts stderr $err
+ }
exit 1
}
}
@@ -3240,6 +3343,7 @@ text $ui_diff -background white -foreground black \
-xscrollcommand {.vpane.lower.diff.body.sbx set} \
-yscrollcommand {.vpane.lower.diff.body.sby set} \
-state disabled
+catch {$ui_diff configure -tabstyle wordprocessor}
${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
-command [list $ui_diff xview]
${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \
@@ -3250,8 +3354,18 @@ pack $ui_diff -side left -fill both -expand 1
pack .vpane.lower.diff.header -side top -fill x
pack .vpane.lower.diff.body -side bottom -fill both -expand 1
+foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} {
+ $ui_diff tag configure clr4$n -background $c
+ $ui_diff tag configure clri4$n -foreground $c
+ $ui_diff tag configure clr3$n -foreground $c
+ $ui_diff tag configure clri3$n -background $c
+}
+$ui_diff tag configure clr1 -font font_diffbold
+
+$ui_diff tag conf d_info -foreground blue -font font_diffbold
+
$ui_diff tag conf d_cr -elide true
-$ui_diff tag conf d_@ -foreground blue -font font_diffbold
+$ui_diff tag conf d_@ -font font_diffbold
$ui_diff tag conf d_+ -foreground {#00a000}
$ui_diff tag conf d_- -foreground red
@@ -3270,13 +3384,13 @@ $ui_diff tag conf d_s- \
-foreground red \
-background ivory1
-$ui_diff tag conf d<<<<<<< \
+$ui_diff tag conf d< \
-foreground orange \
-font font_diffbold
-$ui_diff tag conf d======= \
+$ui_diff tag conf d= \
-foreground orange \
-font font_diffbold
-$ui_diff tag conf d>>>>>>> \
+$ui_diff tag conf d> \
-foreground orange \
-font font_diffbold
@@ -3405,6 +3519,19 @@ lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state]
$ctxmsm add separator
create_common_diff_popup $ctxmsm
+proc has_textconv {path} {
+ if {[is_config_false gui.textconv]} {
+ return 0
+ }
+ set filter [gitattr $path diff set]
+ set textconv [get_config [join [list diff $filter textconv] .]]
+ if {$filter ne {set} && $textconv ne {}} {
+ return 1
+ } else {
+ return 0
+ }
+}
+
proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
global current_diff_path file_states
set ::cursorX $x
@@ -3439,8 +3566,9 @@ proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} {
|| $current_diff_path eq {}
|| {__} eq $state
|| {_O} eq $state
- || {_T} eq $state
- || {T_} eq $state} {
+ || [string match {?T} $state]
+ || [string match {T?} $state]
+ || [has_textconv $current_diff_path]} {
set s disabled
} else {
set s normal
@@ -3460,29 +3588,44 @@ $main_status show [mc "Initializing..."]
# -- Load geometry
#
-catch {
-set gm $repo_config(gui.geometry)
-wm geometry . [lindex $gm 0]
-if {$use_ttk} {
- .vpane sashpos 0 [lindex $gm 1]
- .vpane.files sashpos 0 [lindex $gm 2]
-} else {
- .vpane sash place 0 \
- [lindex $gm 1] \
- [lindex [.vpane sash coord 0] 1]
- .vpane.files sash place 0 \
- [lindex [.vpane.files sash coord 0] 0] \
- [lindex $gm 2]
+proc on_ttk_pane_mapped {w pane pos} {
+ bind $w <Map> {}
+ after 0 [list after idle [list $w sashpos $pane $pos]]
+}
+proc on_tk_pane_mapped {w pane x y} {
+ bind $w <Map> {}
+ after 0 [list after idle [list $w sash place $pane $x $y]]
+}
+proc on_application_mapped {} {
+ global repo_config use_ttk
+ bind . <Map> {}
+ set gm $repo_config(gui.geometry)
+ if {$use_ttk} {
+ bind .vpane <Map> \
+ [list on_ttk_pane_mapped %W 0 [lindex $gm 1]]
+ bind .vpane.files <Map> \
+ [list on_ttk_pane_mapped %W 0 [lindex $gm 2]]
+ } else {
+ bind .vpane <Map> \
+ [list on_tk_pane_mapped %W 0 \
+ [lindex $gm 1] \
+ [lindex [.vpane sash coord 0] 1]]
+ bind .vpane.files <Map> \
+ [list on_tk_pane_mapped %W 0 \
+ [lindex [.vpane.files sash coord 0] 0] \
+ [lindex $gm 2]]
+ }
+ wm geometry . [lindex $gm 0]
}
-unset gm
+if {[info exists repo_config(gui.geometry)]} {
+ bind . <Map> [list on_application_mapped]
+ wm geometry . [lindex $repo_config(gui.geometry) 0]
}
# -- Load window state
#
-catch {
-set gws $repo_config(gui.wmstate)
-wm state . $gws
-unset gws
+if {[info exists repo_config(gui.wmstate)]} {
+ catch {wm state . $repo_config(gui.wmstate)}
}
# -- Key Bindings
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index 786b50b..61e358f 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -449,11 +449,35 @@ method _load {jump} {
$status show [mc "Reading %s..." "$commit:[escape_path $path]"]
$w_path conf -text [escape_path $path]
+
+ set do_textconv 0
+ if {![is_config_false gui.textconv] && [git-version >= 1.7.2]} {
+ set filter [gitattr $path diff set]
+ set textconv [get_config [join [list diff $filter textconv] .]]
+ if {$filter ne {set} && $textconv ne {}} {
+ set do_textconv 1
+ }
+ }
if {$commit eq {}} {
- set fd [open $path r]
+ if {$do_textconv ne 0} {
+ # Run textconv with sh -c "..." to allow it to
+ # contain command + arguments. On windows, just
+ # call the filter command.
+ if {![file executable [shellpath]]} {
+ set fd [open |[linsert $textconv end $path] r]
+ } else {
+ set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r]
+ }
+ } else {
+ set fd [open $path r]
+ }
fconfigure $fd -eofchar {}
} else {
- set fd [git_read cat-file blob "$commit:$path"]
+ if {$do_textconv ne 0} {
+ set fd [git_read cat-file --textconv "$commit:$path"]
+ } else {
+ set fd [git_read cat-file blob "$commit:$path"]
+ }
}
fconfigure $fd \
-blocking 0 \
diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl
index 6398877..6e510ec 100644
--- a/git-gui/lib/branch_rename.tcl
+++ b/git-gui/lib/branch_rename.tcl
@@ -53,7 +53,7 @@ constructor dialog {} {
return 1
}
- grid $w.rename.oldname_l $w.rename.oldname_m -sticky w -padx {0 5}
+ grid $w.rename.oldname_l $w.rename.oldname_m -sticky we -padx {0 5}
grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5}
grid columnconfigure $w.rename 1 -weight 1
pack $w.rename -anchor nw -fill x -pady 5 -padx 5
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
index c241572..a8c6223 100644
--- a/git-gui/lib/browser.tcl
+++ b/git-gui/lib/browser.tcl
@@ -121,7 +121,7 @@ method _parent {} {
if {$browser_stack eq {}} {
regsub {:.*$} $browser_path {:} browser_path
} else {
- regsub {/[^/]+$} $browser_path {} browser_path
+ regsub {/[^/]+/$} $browser_path {/} browser_path
}
set browser_status [mc "Loading %s..." $browser_path]
_ls $this [lindex $parent 0] [lindex $parent 1]
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
index 64f0674..657f7d5 100644
--- a/git-gui/lib/choose_repository.tcl
+++ b/git-gui/lib/choose_repository.tcl
@@ -100,12 +100,17 @@ constructor pick {} {
$opts insert end [mc "Clone Existing Repository"] link_clone
$opts insert end "\n"
if {$m_repo ne {}} {
+ if {[tk windowingsystem] eq "win32"} {
+ set key L
+ } else {
+ set key C
+ }
$m_repo add command \
-command [cb _next clone] \
- -accelerator $M1T-C \
+ -accelerator $M1T-$key \
-label [mc "Clone..."]
- bind $top <$M1B-c> [cb _next clone]
- bind $top <$M1B-C> [cb _next clone]
+ bind $top <$M1B-[string tolower $key]> [cb _next clone]
+ bind $top <$M1B-[string toupper $key]> [cb _next clone]
}
$opts tag conf link_open -foreground blue -underline 1
@@ -209,14 +214,6 @@ constructor pick {} {
}
}
-proc _home {} {
- if {[catch {set h $::env(HOME)}]
- || ![file isdirectory $h]} {
- set h .
- }
- return $h
-}
-
method _center {} {
set nx [winfo reqwidth $top]
set ny [winfo reqheight $top]
@@ -415,7 +412,7 @@ method _new_local_path {} {
if {$local_path ne {}} {
set p [file dirname $local_path]
} else {
- set p [_home]
+ set p [pwd]
}
set p [tk_chooseDirectory \
@@ -536,7 +533,7 @@ method _open_origin {} {
if {$origin_url ne {} && [file isdirectory $origin_url]} {
set p $origin_url
} else {
- set p [_home]
+ set p [pwd]
}
set p [tk_chooseDirectory \
@@ -1037,7 +1034,7 @@ method _open_local_path {} {
if {$local_path ne {}} {
set p $local_path
} else {
- set p [_home]
+ set p [pwd]
}
set p [tk_chooseDirectory \
diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
index 7f459cd..5ce4687 100644
--- a/git-gui/lib/commit.tcl
+++ b/git-gui/lib/commit.tcl
@@ -161,11 +161,12 @@ The rescan will be automatically started now.
#
set files_ready 0
foreach path [array names file_states] {
- switch -glob -- [lindex $file_states($path) 0] {
+ set s $file_states($path)
+ switch -glob -- [lindex $s 0] {
_? {continue}
A? -
D? -
- T_ -
+ T? -
M? {set files_ready 1}
_U -
U? {
@@ -452,7 +453,11 @@ A rescan will be automatically started now.
}
AM -
AD -
+ AT -
+ TM -
+ TD -
MM -
+ MT -
MD {
set file_states($path) [list \
_[string index $m 1] \
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index ec8c11e..cf8a95e 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -55,7 +55,7 @@ proc handle_empty_diff {} {
set path $current_diff_path
set s $file_states($path)
- if {[lindex $s 0] ne {_M}} return
+ if {[lindex $s 0] ne {_M} || [has_textconv $path]} return
# Prevent infinite rescan loops
incr diff_empty_count
@@ -122,22 +122,22 @@ proc show_unmerged_diff {cont_info} {
if {$merge_stages(2) eq {}} {
set is_conflict_diff 1
lappend current_diff_queue \
- [list [mc "LOCAL: deleted\nREMOTE:\n"] d======= \
+ [list [mc "LOCAL: deleted\nREMOTE:\n"] d= \
[list ":1:$current_diff_path" ":3:$current_diff_path"]]
} elseif {$merge_stages(3) eq {}} {
set is_conflict_diff 1
lappend current_diff_queue \
- [list [mc "REMOTE: deleted\nLOCAL:\n"] d======= \
+ [list [mc "REMOTE: deleted\nLOCAL:\n"] d= \
[list ":1:$current_diff_path" ":2:$current_diff_path"]]
} elseif {[lindex $merge_stages(1) 0] eq {120000}
|| [lindex $merge_stages(2) 0] eq {120000}
|| [lindex $merge_stages(3) 0] eq {120000}} {
set is_conflict_diff 1
lappend current_diff_queue \
- [list [mc "LOCAL:\n"] d======= \
+ [list [mc "LOCAL:\n"] d= \
[list ":1:$current_diff_path" ":2:$current_diff_path"]]
lappend current_diff_queue \
- [list [mc "REMOTE:\n"] d======= \
+ [list [mc "REMOTE:\n"] d= \
[list ":1:$current_diff_path" ":3:$current_diff_path"]]
} else {
start_show_diff $cont_info
@@ -208,32 +208,32 @@ proc show_other_diff {path w m cont_info} {
$ui_diff insert end [append \
"* " \
[mc "Git Repository (subproject)"] \
- "\n"] d_@
+ "\n"] d_info
} elseif {![catch {set type [exec file $path]}]} {
set n [string length $path]
if {[string equal -length $n $path $type]} {
set type [string range $type $n end]
regsub {^:?\s*} $type {} type
}
- $ui_diff insert end "* $type\n" d_@
+ $ui_diff insert end "* $type\n" d_info
}
if {[string first "\0" $content] != -1} {
$ui_diff insert end \
[mc "* Binary file (not showing content)."] \
- d_@
+ d_info
} else {
if {$sz > $max_sz} {
$ui_diff insert end [mc \
"* Untracked file is %d bytes.
* Showing only first %d bytes.
-" $sz $max_sz] d_@
+" $sz $max_sz] d_info
}
$ui_diff insert end $content
if {$sz > $max_sz} {
$ui_diff insert end [mc "
* Untracked file clipped here by %s.
* To see the entire file, use an external editor.
-" [appname]] d_@
+" [appname]] d_info
}
}
$ui_diff conf -state disabled
@@ -253,6 +253,19 @@ proc show_other_diff {path w m cont_info} {
}
}
+proc get_conflict_marker_size {path} {
+ set size 7
+ catch {
+ set fd_rc [eval [list git_read check-attr "conflict-marker-size" -- $path]]
+ set ret [gets $fd_rc line]
+ close $fd_rc
+ if {$ret > 0} {
+ regexp {.*: conflict-marker-size: (\d+)$} $line line size
+ }
+ }
+ return $size
+}
+
proc start_show_diff {cont_info {add_opts {}}} {
global file_states file_lists
global is_3way_diff is_submodule_diff diff_active repo_config
@@ -268,6 +281,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
set is_submodule_diff 0
set diff_active 1
set current_diff_header {}
+ set conflict_size [get_conflict_marker_size $path]
set cmd [list]
if {$w eq $ui_index} {
@@ -280,6 +294,9 @@ proc start_show_diff {cont_info {add_opts {}}} {
lappend cmd diff-files
}
}
+ if {![is_config_false gui.textconv] && [git-version >= 1.6.1]} {
+ lappend cmd --textconv
+ }
if {[string match {160000 *} [lindex $s 2]]
|| [string match {160000 *} [lindex $s 3]]} {
@@ -291,7 +308,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
}
lappend cmd -p
- lappend cmd --no-color
+ lappend cmd --color
if {$repo_config(gui.diffcontext) >= 1} {
lappend cmd "-U$repo_config(gui.diffcontext)"
}
@@ -326,10 +343,35 @@ proc start_show_diff {cont_info {add_opts {}}} {
-blocking 0 \
-encoding [get_path_encoding $path] \
-translation lf
- fileevent $fd readable [list read_diff $fd $cont_info]
+ fileevent $fd readable [list read_diff $fd $conflict_size $cont_info]
}
-proc read_diff {fd cont_info} {
+proc parse_color_line {line} {
+ set start 0
+ set result ""
+ set markup [list]
+ set regexp {\033\[((?:\d+;)*\d+)?m}
+ set need_reset 0
+ while {[regexp -indices -start $start $regexp $line match code]} {
+ foreach {begin end} $match break
+ append result [string range $line $start [expr {$begin - 1}]]
+ set pos [string length $result]
+ set col [eval [linsert $code 0 string range $line]]
+ set start [incr end]
+ if {$col eq "0" || $col eq ""} {
+ if {!$need_reset} continue
+ set need_reset 0
+ } else {
+ set need_reset 1
+ }
+ lappend markup $pos $col
+ }
+ append result [string range $line $start end]
+ if {[llength $markup] < 4} {set markup {}}
+ return [list $result $markup]
+}
+
+proc read_diff {fd conflict_size cont_info} {
global ui_diff diff_active is_submodule_diff
global is_3way_diff is_conflict_diff current_diff_header
global current_diff_queue
@@ -337,37 +379,53 @@ proc read_diff {fd cont_info} {
$ui_diff conf -state normal
while {[gets $fd line] >= 0} {
- # -- Cleanup uninteresting diff header lines.
+ foreach {line markup} [parse_color_line $line] break
+ set line [string map {\033 ^} $line]
+
+ set tags {}
+
+ # -- Check for start of diff header.
+ if { [string match {diff --git *} $line]
+ || [string match {diff --cc *} $line]
+ || [string match {diff --combined *} $line]} {
+ set ::current_diff_inheader 1
+ }
+
+ # -- Check for end of diff header (any hunk line will do this).
+ #
+ if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0}
+
+ # -- Automatically detect if this is a 3 way diff.
#
+ if {[string match {@@@ *} $line]} {set is_3way_diff 1}
+
if {$::current_diff_inheader} {
+
+ # -- These two lines stop a diff header and shouldn't be in there
+ if { [string match {Binary files * and * differ} $line]
+ || [regexp {^\* Unmerged path } $line]} {
+ set ::current_diff_inheader 0
+ } else {
+ append current_diff_header $line "\n"
+ }
+
+ # -- Cleanup uninteresting diff header lines.
+ #
if { [string match {diff --git *} $line]
|| [string match {diff --cc *} $line]
|| [string match {diff --combined *} $line]
|| [string match {--- *} $line]
- || [string match {+++ *} $line]} {
- append current_diff_header $line "\n"
+ || [string match {+++ *} $line]
+ || [string match {index *} $line]} {
continue
}
- }
- if {[string match {index *} $line]} continue
- if {$line eq {deleted file mode 120000}} {
- set line "deleted symlink"
- }
- set ::current_diff_inheader 0
- # -- Automatically detect if this is a 3 way diff.
- #
- if {[string match {@@@ *} $line]} {set is_3way_diff 1}
+ # -- Name it symlink, not 120000
+ # Note, that the original line is in $current_diff_header
+ regsub {^(deleted|new) file mode 120000} $line {\1 symlink} line
- if {[string match {mode *} $line]
- || [string match {new file *} $line]
- || [regexp {^(old|new) mode *} $line]
- || [string match {deleted file *} $line]
- || [string match {deleted symlink} $line]
- || [string match {Binary files * and * differ} $line]
- || $line eq {\ No newline at end of file}
- || [regexp {^\* Unmerged path } $line]} {
- set tags {}
+ } elseif { $line eq {\ No newline at end of file}} {
+ # -- Handle some special lines
} elseif {$is_3way_diff} {
set op [string range $line 0 1]
switch -- $op {
@@ -379,7 +437,9 @@ proc read_diff {fd cont_info} {
{- } {set tags d_-s}
{--} {set tags d_--}
{++} {
- if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
+ set regexp [string map [list %conflict_size $conflict_size]\
+ {^\+\+([<>=]){%conflict_size}(?: |$)}]
+ if {[regexp $regexp $line _g op]} {
set is_conflict_diff 1
set line [string replace $line 0 1 { }]
set tags d$op
@@ -395,10 +455,10 @@ proc read_diff {fd cont_info} {
} elseif {$is_submodule_diff} {
if {$line == ""} continue
if {[regexp {^Submodule } $line]} {
- set tags d_@
+ set tags d_info
} elseif {[regexp {^\* } $line]} {
set line [string replace $line 0 1 {Submodule }]
- set tags d_@
+ set tags d_info
} else {
set op [string range $line 0 2]
switch -- $op {
@@ -418,7 +478,9 @@ proc read_diff {fd cont_info} {
{@} {set tags d_@}
{-} {set tags d_-}
{+} {
- if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
+ set regexp [string map [list %conflict_size $conflict_size]\
+ {^\+([<>=]){%conflict_size}(?: |$)}]
+ if {[regexp $regexp $line _g op]} {
set is_conflict_diff 1
set tags d$op
} else {
@@ -431,11 +493,23 @@ proc read_diff {fd cont_info} {
}
}
}
+ set mark [$ui_diff index "end - 1 line linestart"]
$ui_diff insert end $line $tags
if {[string index $line end] eq "\r"} {
$ui_diff tag add d_cr {end - 2c}
}
$ui_diff insert end "\n" $tags
+
+ foreach {posbegin colbegin posend colend} $markup {
+ set prefix clr
+ foreach style [split $colbegin ";"] {
+ if {$style eq "7"} {append prefix i; continue}
+ if {$style < 30 || $style > 47} {continue}
+ set a "$mark linestart + $posbegin chars"
+ set b "$mark linestart + $posend chars"
+ catch {$ui_diff tag add $prefix$style $a $b}
+ }
+ }
}
$ui_diff conf -state disabled
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index e9db0c4..5d7bbf2 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -103,8 +103,11 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} {
set s $file_states($path)
switch -glob -- [lindex $s 0] {
A? {set new _O}
- M? {set new _M}
+ MT -
+ TM -
T_ {set new _T}
+ M? {set new _M}
+ TD -
D_ {set new _D}
D? {set new _?}
?? {continue}
@@ -167,7 +170,10 @@ proc write_update_index {fd pathList totalCnt batch after} {
AD {set new __}
?D {set new D_}
_O -
+ AT -
AM {set new A_}
+ TM -
+ MT -
_T {set new T_}
_U -
U? {
@@ -261,7 +267,7 @@ proc unstage_helper {txt paths} {
switch -glob -- [lindex $file_states($path) 0] {
A? -
M? -
- T_ -
+ T? -
D? {
lappend pathList $path
if {$path eq $current_diff_path} {
diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
index 5cded23..460d32f 100644
--- a/git-gui/lib/merge.tcl
+++ b/git-gui/lib/merge.tcl
@@ -83,6 +83,7 @@ method _visualize {} {
method _start {} {
global HEAD current_branch remote_url
+ global _last_merged_branch
set name [_rev $this]
if {$name eq {}} {
@@ -109,6 +110,7 @@ method _start {} {
regsub ^refs/heads/ $branch {} branch
puts $fh "$cmit\t\tbranch '$branch' of $remote"
close $fh
+ set _last_merged_branch $branch
set cmd [list git]
lappend cmd merge
diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
index 3fe90e6..3c8e73b 100644
--- a/git-gui/lib/mergetool.tcl
+++ b/git-gui/lib/mergetool.tcl
@@ -175,43 +175,56 @@ proc merge_resolve_tool2 {} {
# Build the command line
switch -- $tool {
- kdiff3 {
+ araxis {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
- --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \
+ -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \
+ -title3:"'$MERGED (Remote)'" \
+ "$BASE" "$LOCAL" "$REMOTE" "$MERGED"]
} else {
- set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
- --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" -wait -2 \
+ -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \
+ "$LOCAL" "$REMOTE" "$MERGED"]
}
}
- tkdiff {
+ bc3 {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -mergeoutput="$MERGED"]
} else {
- set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -mergeoutput="$MERGED"]
}
}
- meld {
- set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
+ ecmerge {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
+ } else {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
+ }
+ }
+ emerge {
+ if {$base_stage ne {}} {
+ set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
+ "$LOCAL" "$REMOTE" "$BASE" "$basename"]
+ } else {
+ set cmdline [list "$merge_tool_path" -f emerge-files-command \
+ "$LOCAL" "$REMOTE" "$basename"]
+ }
}
gvimdiff {
set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"]
}
- xxdiff {
+ kdiff3 {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" -X --show-merged-pane \
- -R {Accel.SaveAsMerged: "Ctrl-S"} \
- -R {Accel.Search: "Ctrl+F"} \
- -R {Accel.SearchForward: "Ctrl-G"} \
- --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
+ --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
} else {
- set cmdline [list "$merge_tool_path" -X --show-merged-pane \
- -R {Accel.SaveAsMerged: "Ctrl-S"} \
- -R {Accel.Search: "Ctrl+F"} \
- -R {Accel.SearchForward: "Ctrl-G"} \
- --merged-file "$MERGED" "$LOCAL" "$REMOTE"]
+ set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
+ --L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
}
}
+ meld {
+ set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
+ }
opendiff {
if {$base_stage ne {}} {
set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"]
@@ -219,22 +232,20 @@ proc merge_resolve_tool2 {} {
set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"]
}
}
- ecmerge {
- if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
- } else {
- set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
- }
+ p4merge {
+ set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"]
}
- emerge {
+ tkdiff {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
- "$LOCAL" "$REMOTE" "$BASE" "$basename"]
+ set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
} else {
- set cmdline [list "$merge_tool_path" -f emerge-files-command \
- "$LOCAL" "$REMOTE" "$basename"]
+ set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
}
}
+ vimdiff {
+ error_popup [mc "Not a GUI merge tool: '%s'" $tool]
+ return
+ }
winmerge {
if {$base_stage ne {}} {
# This tool does not support 3-way merges.
@@ -245,25 +256,21 @@ proc merge_resolve_tool2 {} {
-dl "Theirs File" -dr "Mine File" "$REMOTE" "$LOCAL" "$MERGED"]
}
}
- araxis {
+ xxdiff {
if {$base_stage ne {}} {
- set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \
- -title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \
- -title3:"'$MERGED (Remote)'" \
- "$BASE" "$LOCAL" "$REMOTE" "$MERGED"]
+ set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+ -R {Accel.SaveAsMerged: "Ctrl-S"} \
+ -R {Accel.Search: "Ctrl+F"} \
+ -R {Accel.SearchForward: "Ctrl-G"} \
+ --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
} else {
- set cmdline [list "$merge_tool_path" -wait -2 \
- -title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \
- "$LOCAL" "$REMOTE" "$MERGED"]
+ set cmdline [list "$merge_tool_path" -X --show-merged-pane \
+ -R {Accel.SaveAsMerged: "Ctrl-S"} \
+ -R {Accel.Search: "Ctrl+F"} \
+ -R {Accel.SearchForward: "Ctrl-G"} \
+ --merged-file "$MERGED" "$LOCAL" "$REMOTE"]
}
}
- p4merge {
- set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"]
- }
- vimdiff {
- error_popup [mc "Not a GUI merge tool: '%s'" $tool]
- return
- }
default {
error_popup [mc "Unsupported merge tool '%s'" $tool]
return
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
index d4c5e45..3807c8d 100644
--- a/git-gui/lib/option.tcl
+++ b/git-gui/lib/option.tcl
@@ -148,6 +148,7 @@ proc do_options {} {
{b gui.trustmtime {mc "Trust File Modification Timestamps"}}
{b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
{b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
+ {b gui.textconv {mc "Use Textconv For Diffs and Blames"}}
{b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}}
{i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
{i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
index b92b429..5e4e7f4 100644
--- a/git-gui/lib/remote.tcl
+++ b/git-gui/lib/remote.tcl
@@ -157,22 +157,7 @@ proc add_fetch_entry {r} {
}
if {$enable} {
- if {![winfo exists $fetch_m]} {
- menu $remove_m
- $remote_m insert 0 cascade \
- -label [mc "Remove Remote"] \
- -menu $remove_m
-
- menu $prune_m
- $remote_m insert 0 cascade \
- -label [mc "Prune from"] \
- -menu $prune_m
-
- menu $fetch_m
- $remote_m insert 0 cascade \
- -label [mc "Fetch from"] \
- -menu $fetch_m
- }
+ make_sure_remote_submenues_exist $remote_m
$fetch_m add command \
-label $r \
@@ -222,6 +207,70 @@ proc add_push_entry {r} {
}
}
+proc make_sure_remote_submenues_exist {remote_m} {
+ set fetch_m $remote_m.fetch
+ set prune_m $remote_m.prune
+ set remove_m $remote_m.remove
+
+ if {![winfo exists $fetch_m]} {
+ menu $remove_m
+ $remote_m insert 0 cascade \
+ -label [mc "Remove Remote"] \
+ -menu $remove_m
+
+ menu $prune_m
+ $remote_m insert 0 cascade \
+ -label [mc "Prune from"] \
+ -menu $prune_m
+
+ menu $fetch_m
+ $remote_m insert 0 cascade \
+ -label [mc "Fetch from"] \
+ -menu $fetch_m
+ }
+}
+
+proc update_all_remotes_menu_entry {} {
+ global all_remotes
+
+ if {[git-version < 1.6.6]} { return }
+
+ set have_remote 0
+ foreach r $all_remotes {
+ incr have_remote
+ }
+
+ set remote_m .mbar.remote
+ set fetch_m $remote_m.fetch
+ set prune_m $remote_m.prune
+ if {$have_remote > 1} {
+ make_sure_remote_submenues_exist $remote_m
+ if {[$fetch_m entrycget end -label] ne "All"} {
+
+ $fetch_m insert end separator
+ $fetch_m insert end command \
+ -label "All" \
+ -command fetch_from_all
+
+ $prune_m insert end separator
+ $prune_m insert end command \
+ -label "All" \
+ -command prune_from_all
+ }
+ } else {
+ if {[winfo exists $fetch_m]} {
+ if {[$fetch_m entrycget end -label] eq "All"} {
+
+ delete_from_menu $fetch_m end
+ delete_from_menu $fetch_m end
+
+ delete_from_menu $prune_m end
+ delete_from_menu $prune_m end
+ }
+ }
+ }
+}
+
proc populate_remotes_menu {} {
global all_remotes
@@ -229,6 +278,8 @@ proc populate_remotes_menu {} {
add_fetch_entry $r
add_push_entry $r
}
+
+ update_all_remotes_menu_entry
}
proc add_single_remote {name location} {
@@ -244,6 +295,8 @@ proc add_single_remote {name location} {
add_fetch_entry $name
add_push_entry $name
+
+ update_all_remotes_menu_entry
}
proc delete_from_menu {menu name} {
@@ -264,8 +317,8 @@ proc remove_remote {name} {
unset repo_config(remote.$name.push)
}
- set i [lsearch -exact all_remotes $name]
- lreplace all_remotes $i $i
+ set i [lsearch -exact $all_remotes $name]
+ set all_remotes [lreplace $all_remotes $i $i]
set remote_m .mbar.remote
delete_from_menu $remote_m.fetch $name
@@ -273,4 +326,6 @@ proc remove_remote {name} {
delete_from_menu $remote_m.remove $name
# Not all remotes are in the push menu
catch { delete_from_menu $remote_m.push $name }
+
+ update_all_remotes_menu_entry
}
diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl
index f872a3d..fcc06d0 100644
--- a/git-gui/lib/remote_branch_delete.tcl
+++ b/git-gui/lib/remote_branch_delete.tcl
@@ -251,7 +251,7 @@ method _write_url {args} { set urltype url }
method _write_check_head {args} { set checktype head }
method _write_head_list {args} {
- global current_branch
+ global current_branch _last_merged_branch
$head_m delete 0 end
foreach abr $head_list {
@@ -267,6 +267,13 @@ method _write_head_list {args} {
set check_head $current_branch
}
}
+ set lmb [lsearch -exact -sorted $head_list $_last_merged_branch]
+ if {$lmb >= 0} {
+ $w.heads.l conf -state normal
+ $w.heads.l select set $lmb
+ $w.heads.l yview $lmb
+ $w.heads.l conf -state disabled
+ }
}
method _write_urltype {args} {
diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
index 79c1888..78878ef 100644
--- a/git-gui/lib/shortcut.tcl
+++ b/git-gui/lib/shortcut.tcl
@@ -16,7 +16,7 @@ proc do_windows_shortcut {} {
[info nameofexecutable] \
[file normalize $::argv0] \
] \
- [file normalize [$_gitworktree]]
+ [file normalize $_gitworktree]
} err]} {
error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
@@ -57,7 +57,7 @@ proc do_cygwin_shortcut {} {
$sh -c \
"CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
] \
- [file normalize [$_gitworktree]]
+ [file normalize $_gitworktree]
} err]} {
error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl
index 5fe3aad..95cb449 100644
--- a/git-gui/lib/status_bar.tcl
+++ b/git-gui/lib/status_bar.tcl
@@ -39,6 +39,7 @@ method _oneline_pack {} {
}
constructor two_line {path} {
+ global NS
set w $path
set w_l $w.l
set w_c $w.c
diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl
index 60e3a64..7fad9b7 100644
--- a/git-gui/lib/transport.tcl
+++ b/git-gui/lib/transport.tcl
@@ -20,6 +20,35 @@ proc prune_from {remote} {
console::exec $w [list git remote prune $remote]
}
+proc fetch_from_all {} {
+ set w [console::new \
+ [mc "fetch all remotes"] \
+ [mc "Fetching new changes from all remotes"]]
+
+ set cmd [list git fetch --all]
+ if {[is_config_true gui.pruneduringfetch]} {
+ lappend cmd --prune
+ }
+
+ console::exec $w $cmd
+}
+
+proc prune_from_all {} {
+ global all_remotes
+
+ set w [console::new \
+ [mc "remote prune all remotes"] \
+ [mc "Pruning tracking branches deleted from all remotes"]]
+
+ set cmd [list git remote prune]
+
+ foreach r $all_remotes {
+ lappend cmd $r
+ }
+
+ console::exec $w $cmd
+}
+
proc push_to {remote} {
set w [console::new \
[mc "push %s" $remote] \
@@ -123,6 +152,7 @@ proc do_push_anywhere {} {
$w.source.l insert end $h
if {$h eq $current_branch} {
$w.source.l select set end
+ $w.source.l yview end
}
}
pack $w.source.l -side left -fill both -expand 1
@@ -135,7 +165,9 @@ proc do_push_anywhere {} {
-value remote \
-variable push_urltype
if {$use_ttk} {
- ttk::combobox $w.dest.remote_m -textvariable push_remote \
+ ttk::combobox $w.dest.remote_m -state readonly \
+ -exportselection false \
+ -textvariable push_remote \
-values $all_remotes
} else {
eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl
index d7f93d0..db91ab8 100644
--- a/git-gui/lib/win32.tcl
+++ b/git-gui/lib/win32.tcl
@@ -18,9 +18,9 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
eval [list exec wscript.exe \
/E:jscript \
/nologo \
- [file join $oguilib win32_shortcut.js] \
+ [file nativename [file join $oguilib win32_shortcut.js]] \
$lnk_path \
- [file join $oguilib git-gui.ico] \
+ [file nativename [file join $oguilib git-gui.ico]] \
$lnk_dir \
$lnk_exec] $lnk_args
}
diff --git a/git-gui/po/glossary/pt_br.po b/git-gui/po/glossary/pt_br.po
new file mode 100644
index 0000000..eb039b2
--- /dev/null
+++ b/git-gui/po/glossary/pt_br.po
@@ -0,0 +1,169 @@
+# Translation of git-gui to Brazilian Portuguese
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git-gui package.
+#
+# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010.
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-09-18 11:09-0300\n"
+"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n"
+"Language-Team: Brazilian Portuguese <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
+msgid ""
+"English Term (Dear translator: This file will never be visible to the user!)"
+msgstr ""
+
+#. ""
+msgid "amend"
+msgstr "corrigir"
+
+#. ""
+msgid "annotate"
+msgstr "anotar"
+
+#. "A 'branch' is an active line of development."
+msgid "branch [noun]"
+msgstr "ramo"
+
+#. ""
+msgid "branch [verb]"
+msgstr "ramificar"
+
+#. ""
+msgid "checkout [noun]"
+msgstr "checkout"
+
+#. "The action of updating the working tree to a revision which was stored in the object database."
+msgid "checkout [verb]"
+msgstr "efetuar checkout"
+
+#. ""
+msgid "clone [verb]"
+msgstr "clonar"
+
+#. "A single point in the git history."
+msgid "commit [noun]"
+msgstr "revisão"
+
+#. "The action of storing a new snapshot of the project's state in the git history."
+msgid "commit [verb]"
+msgstr "salvar revisão"
+
+#. ""
+msgid "diff [noun]"
+msgstr "diff"
+
+#. ""
+msgid "diff [verb]"
+msgstr "comparar"
+
+#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
+msgid "fast forward merge"
+msgstr "mesclagem rápida"
+
+#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
+msgid "fetch"
+msgstr "receber"
+
+#. "One context of consecutive lines in a whole patch, which consists of many such hunks"
+msgid "hunk"
+msgstr "trecho"
+
+#. "A collection of files. The index is a stored version of your working tree."
+msgid "index (in git-gui: staging area)"
+msgstr "índice"
+
+#. "A successful merge results in the creation of a new commit representing the result of the merge."
+msgid "merge [noun]"
+msgstr "mesclagem"
+
+#. "To bring the contents of another branch into the current branch."
+msgid "merge [verb]"
+msgstr "mesclar"
+
+#. ""
+msgid "message"
+msgstr "descrição da revisão"
+
+#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
+msgid "prune"
+msgstr "limpar"
+
+#. "Pulling a branch means to fetch it and merge it."
+msgid "pull"
+msgstr "receber e mesclar"
+
+#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
+msgid "push"
+msgstr "enviar"
+
+#. ""
+msgid "redo"
+msgstr "refazer"
+
+#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote"
+msgstr "repositório remoto"
+
+#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
+msgid "repository"
+msgstr "repositório"
+
+#. ""
+msgid "reset"
+msgstr "descartar, redefinir"
+
+#. ""
+msgid "revert"
+msgstr "reverter"
+
+#. "A particular state of files and directories which was stored in the object database."
+msgid "revision"
+msgstr "revisão"
+
+#. ""
+msgid "sign off"
+msgstr "assinar embaixo"
+
+#. ""
+msgid "staging area"
+msgstr "???"
+
+#. ""
+msgid "status"
+msgstr "status"
+
+#. "A ref pointing to a tag or commit object"
+msgid "tag [noun]"
+msgstr "etiqueta"
+
+#. ""
+msgid "tag [verb]"
+msgstr "marcar etiqueta"
+
+#. "A regular git branch that is used to follow changes from another repository."
+msgid "tracking branch"
+msgstr "ramo de rastreamento"
+
+#. ""
+msgid "undo"
+msgstr "desfazer"
+
+#. ""
+msgid "update"
+msgstr "atualizar"
+
+#. ""
+msgid "verify"
+msgstr "verificar"
+
+#. "The tree of actual checked out files."
+msgid "working copy, working tree"
+msgstr "cópia de trabalho, árvore de trabalho"
diff --git a/git-gui/po/pt_br.po b/git-gui/po/pt_br.po
new file mode 100644
index 0000000..b175b97
--- /dev/null
+++ b/git-gui/po/pt_br.po
@@ -0,0 +1,2568 @@
+# Translation of git-gui to Brazilian Portuguese
+# Copyright (C) 2007 Shawn Pearce, et al.
+# This file is distributed under the same license as the git-gui package.
+#
+# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010.
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-09-18 11:09-0300\n"
+"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n"
+"Language-Team: Brazilian Portuguese <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
+msgid "git-gui: fatal error"
+msgstr "git-gui: erro fatal"
+
+#: git-gui.sh:743
+#, tcl-format
+msgid "Invalid font specified in %s:"
+msgstr "Fonte inválida indicada em %s:"
+
+#: git-gui.sh:779
+msgid "Main Font"
+msgstr "Fonte principal"
+
+#: git-gui.sh:780
+msgid "Diff/Console Font"
+msgstr "Fonte para o diff/console"
+
+#: git-gui.sh:794
+msgid "Cannot find git in PATH."
+msgstr "Impossível encontrar o git no \"PATH\""
+
+#: git-gui.sh:821
+msgid "Cannot parse Git version string:"
+msgstr "Impossível interpretar a versão do git:"
+
+#: git-gui.sh:839
+#, tcl-format
+msgid ""
+"Git version cannot be determined.\n"
+"\n"
+"%s claims it is version '%s'.\n"
+"\n"
+"%s requires at least Git 1.5.0 or later.\n"
+"\n"
+"Assume '%s' is version 1.5.0?\n"
+msgstr ""
+"Não foi possível determinar a versão do git:\n"
+"\n"
+"%s afirmar que sua versão é \"%s\".\n"
+"\n"
+"%s exige o Git 1.5.0 ou posterior.\n"
+"\n"
+"Assumir que '%s' é a versão 1.5.0?\n"
+
+#: git-gui.sh:1128
+msgid "Git directory not found:"
+msgstr "Diretório do Git não encontrado:"
+
+#: git-gui.sh:1146
+msgid "Cannot move to top of working directory:"
+msgstr "Impossível mover para o início do diretório de trabalho:"
+
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "Impossível usar repositório puro:"
+
+#: git-gui.sh:1162
+msgid "No working directory"
+msgstr "Sem diretório de trabalho"
+
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
+msgid "Refreshing file status..."
+msgstr "Atualizando estado dos arquivos..."
+
+#: git-gui.sh:1390
+msgid "Scanning for modified files ..."
+msgstr "Procurando por arquivos modificados ..."
+
+#: git-gui.sh:1454
+msgid "Calling prepare-commit-msg hook..."
+msgstr "Executando hook \"prepare-commit-msg\"..."
+
+#: git-gui.sh:1471
+msgid "Commit declined by prepare-commit-msg hook."
+msgstr "O script \"prepare-commit-msg\" negou a criação de uma nova revisão"
+
+#: git-gui.sh:1629 lib/browser.tcl:246
+msgid "Ready."
+msgstr "Pronto."
+
+#: git-gui.sh:1787
+#, tcl-format
+msgid "Displaying only %s of %s files."
+msgstr "Exibindo apenas %s de %s arquivos."
+
+#: git-gui.sh:1913
+msgid "Unmodified"
+msgstr "Não modificado"
+
+#: git-gui.sh:1915
+msgid "Modified, not staged"
+msgstr "Modificado, não marcado"
+
+#: git-gui.sh:1916 git-gui.sh:1924
+msgid "Staged for commit"
+msgstr "Marcado para uma nova revisão"
+
+#: git-gui.sh:1917 git-gui.sh:1925
+msgid "Portions staged for commit"
+msgstr "Trechos marcados para revisão"
+
+#: git-gui.sh:1918 git-gui.sh:1926
+msgid "Staged for commit, missing"
+msgstr "Marcado para revisão, faltando"
+
+#: git-gui.sh:1920
+msgid "File type changed, not staged"
+msgstr "Tipo do arquivo modificado, não marcado"
+
+#: git-gui.sh:1921
+msgid "File type changed, staged"
+msgstr "Tipo do arquivo modificado, marcado"
+
+#: git-gui.sh:1923
+msgid "Untracked, not staged"
+msgstr "Não monitorado, não marcado"
+
+#: git-gui.sh:1928
+msgid "Missing"
+msgstr "Faltando"
+
+#: git-gui.sh:1929
+msgid "Staged for removal"
+msgstr "Marcado para remoção"
+
+#: git-gui.sh:1930
+msgid "Staged for removal, still present"
+msgstr "Marcado para remoção, ainda presente"
+
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
+msgid "Requires merge resolution"
+msgstr "Requer resolução de conflitos"
+
+#: git-gui.sh:1972
+msgid "Starting gitk... please wait..."
+msgstr "Iniciando gitk... Aguarde..."
+
+#: git-gui.sh:1984
+msgid "Couldn't find gitk in PATH"
+msgstr "Impossível encontrar o gitk no PATH"
+
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "Impossível encontrar o \"git gui\" no PATH"
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
+msgid "Repository"
+msgstr "Repositório"
+
+#: git-gui.sh:2456
+msgid "Edit"
+msgstr "Editar"
+
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
+msgid "Branch"
+msgstr "Ramo"
+
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
+msgid "Commit@@noun"
+msgstr "Revisão"
+
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+msgid "Merge"
+msgstr "Mesclar"
+
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
+msgid "Remote"
+msgstr "Remoto"
+
+#: git-gui.sh:2468
+msgid "Tools"
+msgstr "Ferramentas"
+
+#: git-gui.sh:2477
+msgid "Explore Working Copy"
+msgstr "Explorar cópia de trabalho"
+
+#: git-gui.sh:2483
+msgid "Browse Current Branch's Files"
+msgstr "Explorar arquivos do ramo atual"
+
+#: git-gui.sh:2487
+msgid "Browse Branch Files..."
+msgstr "Explorar arquivos do ramo..."
+
+#: git-gui.sh:2492
+msgid "Visualize Current Branch's History"
+msgstr "Visualizar histórico do ramo atual"
+
+#: git-gui.sh:2496
+msgid "Visualize All Branch History"
+msgstr "Visualizar histórico de todos os ramos"
+
+#: git-gui.sh:2503
+#, tcl-format
+msgid "Browse %s's Files"
+msgstr "Explorar arquivos de %s"
+
+#: git-gui.sh:2505
+#, tcl-format
+msgid "Visualize %s's History"
+msgstr "Visualizar histórico de %s"
+
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
+msgid "Database Statistics"
+msgstr "Estatísticas do banco de dados"
+
+#: git-gui.sh:2513 lib/database.tcl:34
+msgid "Compress Database"
+msgstr "Compactar banco de dados"
+
+#: git-gui.sh:2516
+msgid "Verify Database"
+msgstr "Verificar banco de dados"
+
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
+msgid "Create Desktop Icon"
+msgstr "Criar ícone na área de trabalho"
+
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+msgid "Quit"
+msgstr "Sair"
+
+#: git-gui.sh:2547
+msgid "Undo"
+msgstr "Desfazer"
+
+#: git-gui.sh:2550
+msgid "Redo"
+msgstr "Refazer"
+
+#: git-gui.sh:2554 git-gui.sh:3109
+msgid "Cut"
+msgstr "Recortar"
+
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
+#: lib/console.tcl:69
+msgid "Copy"
+msgstr "Copiar"
+
+#: git-gui.sh:2560 git-gui.sh:3115
+msgid "Paste"
+msgstr "Colar"
+
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
+#: lib/remote_branch_delete.tcl:38
+msgid "Delete"
+msgstr "Apagar"
+
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
+msgid "Select All"
+msgstr "Selecionar tudo"
+
+#: git-gui.sh:2576
+msgid "Create..."
+msgstr "Criar..."
+
+#: git-gui.sh:2582
+msgid "Checkout..."
+msgstr "Checkout..."
+
+#: git-gui.sh:2588
+msgid "Rename..."
+msgstr "Renomear..."
+
+#: git-gui.sh:2593
+msgid "Delete..."
+msgstr "Apagar..."
+
+#: git-gui.sh:2598
+msgid "Reset..."
+msgstr "Redefinir..."
+
+#: git-gui.sh:2608
+msgid "Done"
+msgstr "Pronto"
+
+#: git-gui.sh:2610
+msgid "Commit@@verb"
+msgstr "Salvar revisão"
+
+#: git-gui.sh:2619 git-gui.sh:3050
+msgid "New Commit"
+msgstr "Nova revisão"
+
+#: git-gui.sh:2627 git-gui.sh:3057
+msgid "Amend Last Commit"
+msgstr "Corrigir última revisão"
+
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
+msgid "Rescan"
+msgstr "Atualizar"
+
+#: git-gui.sh:2643
+msgid "Stage To Commit"
+msgstr "Marcar para revisão"
+
+#: git-gui.sh:2649
+msgid "Stage Changed Files To Commit"
+msgstr "Marcar arquivos modificados"
+
+#: git-gui.sh:2655
+msgid "Unstage From Commit"
+msgstr "Desmarcar"
+
+#: git-gui.sh:2661 lib/index.tcl:412
+msgid "Revert Changes"
+msgstr "Reverter mudanças"
+
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
+msgid "Show Less Context"
+msgstr "Mostrar menos contexto"
+
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
+msgid "Show More Context"
+msgstr "Mostrar mais contexto"
+
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
+msgid "Sign Off"
+msgstr "Assinar embaixo"
+
+#: git-gui.sh:2696
+msgid "Local Merge..."
+msgstr "Mesclar localmente..."
+
+#: git-gui.sh:2701
+msgid "Abort Merge..."
+msgstr "Abortar mesclagem..."
+
+#: git-gui.sh:2713 git-gui.sh:2741
+msgid "Add..."
+msgstr "Adicionar..."
+
+#: git-gui.sh:2717
+msgid "Push..."
+msgstr "Enviar..."
+
+#: git-gui.sh:2721
+msgid "Delete Branch..."
+msgstr "Apagar ramo..."
+
+#: git-gui.sh:2731 git-gui.sh:3292
+msgid "Options..."
+msgstr "Opções..."
+
+#: git-gui.sh:2742
+msgid "Remove..."
+msgstr "Remover..."
+
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
+msgid "Help"
+msgstr "Ajuda"
+
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "Sobre o %s"
+
+#: git-gui.sh:2783
+msgid "Online Documentation"
+msgstr "Ajuda online"
+
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+msgid "Show SSH Key"
+msgstr "Mostrar chave SSH"
+
+#: git-gui.sh:2893
+#, tcl-format
+msgid "fatal: cannot stat path %s: No such file or directory"
+msgstr ""
+"erro fatal: impossível executar \"stat\" em %s: Arquivo ou diretório não "
+"encontrado"
+
+#: git-gui.sh:2926
+msgid "Current Branch:"
+msgstr "Ramo atual:"
+
+#: git-gui.sh:2947
+msgid "Staged Changes (Will Commit)"
+msgstr "Mudanças marcadas"
+
+#: git-gui.sh:2967
+msgid "Unstaged Changes"
+msgstr "Mudanças não marcadas"
+
+#: git-gui.sh:3017
+msgid "Stage Changed"
+msgstr "Marcar alterados"
+
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
+msgid "Push"
+msgstr "Enviar"
+
+#: git-gui.sh:3071
+msgid "Initial Commit Message:"
+msgstr "Descrição da revisão inicial:"
+
+#: git-gui.sh:3072
+msgid "Amended Commit Message:"
+msgstr "Descrição da revisão corrigida:"
+
+#: git-gui.sh:3073
+msgid "Amended Initial Commit Message:"
+msgstr "Descrição da revisão inicial corrigida:"
+
+#: git-gui.sh:3074
+msgid "Amended Merge Commit Message:"
+msgstr "Descrição da revisão de mescla corrigida:"
+
+#: git-gui.sh:3075
+msgid "Merge Commit Message:"
+msgstr "Descrição da revisão de mescla:"
+
+#: git-gui.sh:3076
+msgid "Commit Message:"
+msgstr "Descrição da revisão:"
+
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
+msgid "Copy All"
+msgstr "Copiar todos"
+
+#: git-gui.sh:3149 lib/blame.tcl:104
+msgid "File:"
+msgstr "Arquivo:"
+
+#: git-gui.sh:3255
+msgid "Refresh"
+msgstr "Atualizar"
+
+#: git-gui.sh:3276
+msgid "Decrease Font Size"
+msgstr "Reduzir tamanho da fonte"
+
+#: git-gui.sh:3280
+msgid "Increase Font Size"
+msgstr "Aumentar tamanho da fonte"
+
+#: git-gui.sh:3288 lib/blame.tcl:281
+msgid "Encoding"
+msgstr "Codificação"
+
+#: git-gui.sh:3299
+msgid "Apply/Reverse Hunk"
+msgstr "Aplicar/reverter trecho"
+
+#: git-gui.sh:3304
+msgid "Apply/Reverse Line"
+msgstr "Aplicar/reverter linha"
+
+#: git-gui.sh:3323
+msgid "Run Merge Tool"
+msgstr "Executar ferramenta de mescla"
+
+#: git-gui.sh:3328
+msgid "Use Remote Version"
+msgstr "Usar versão remota"
+
+#: git-gui.sh:3332
+msgid "Use Local Version"
+msgstr "Usar versão local"
+
+#: git-gui.sh:3336
+msgid "Revert To Base"
+msgstr "Reverter para a versão-base"
+
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr "Visualizar estas mudanças no sub-módulo"
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Visualizar histórico do ramo atual no sub-módulo"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Visualizar histórico de todos os camos no sub-módulo"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr "Iniciar \"git gui\" no sub-módulo"
+
+#: git-gui.sh:3389
+msgid "Unstage Hunk From Commit"
+msgstr "Desmarcar trecho para revisão"
+
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "Desmarcar linhas para revisão"
+
+#: git-gui.sh:3393
+msgid "Unstage Line From Commit"
+msgstr "Desmarcar linha para revisão"
+
+#: git-gui.sh:3396
+msgid "Stage Hunk For Commit"
+msgstr "Marcar trecho para revisão"
+
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "Marcar linhas para revisão"
+
+#: git-gui.sh:3400
+msgid "Stage Line For Commit"
+msgstr "Marcar linha para revisão"
+
+#: git-gui.sh:3424
+msgid "Initializing..."
+msgstr "Iniciando..."
+
+#: git-gui.sh:3541
+#, tcl-format
+msgid ""
+"Possible environment issues exist.\n"
+"\n"
+"The following environment variables are probably\n"
+"going to be ignored by any Git subprocess run\n"
+"by %s:\n"
+"\n"
+msgstr ""
+"Possíveis problemas com as variáveis de ambiente.\n"
+"\n"
+"As seguintes variáveis de ambiente provavelmente serão\n"
+"ignoradas por qualquer sub-processo do Git executado por\n"
+"%s:\n"
+
+#: git-gui.sh:3570
+msgid ""
+"\n"
+"This is due to a known issue with the\n"
+"Tcl binary distributed by Cygwin."
+msgstr ""
+"\n"
+"Isto se deve a um problema conhecido com os binários da Tcl \n"
+"distribuídos com o Cygwin"
+
+#: git-gui.sh:3575
+#, tcl-format
+msgid ""
+"\n"
+"\n"
+"A good replacement for %s\n"
+"is placing values for the user.name and\n"
+"user.email settings into your personal\n"
+"~/.gitconfig file.\n"
+msgstr ""
+"\n"
+"\n"
+"Uma boa alternativa para %s\n"
+"é colocar os valores para o nome de usuário e e-mail\n"
+"no seu arquivo \"~/.gitconfig\"\n"
+
+#: lib/about.tcl:26
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - uma interface gráfica para o Git"
+
+#: lib/blame.tcl:72
+msgid "File Viewer"
+msgstr "Visualizador de arquivos"
+
+#: lib/blame.tcl:78
+msgid "Commit:"
+msgstr "Revisão:"
+
+#: lib/blame.tcl:271
+msgid "Copy Commit"
+msgstr "Copiar revisão"
+
+#: lib/blame.tcl:275
+msgid "Find Text..."
+msgstr "Procurar texto..."
+
+#: lib/blame.tcl:284
+msgid "Do Full Copy Detection"
+msgstr "Executar detecção completa de cópias"
+
+#: lib/blame.tcl:288
+msgid "Show History Context"
+msgstr "Mostrar contexto do histórico"
+
+#: lib/blame.tcl:291
+msgid "Blame Parent Commit"
+msgstr "Anotar revisão anterior"
+
+#: lib/blame.tcl:450
+#, tcl-format
+msgid "Reading %s..."
+msgstr "Lendo %s..."
+
+#: lib/blame.tcl:557
+msgid "Loading copy/move tracking annotations..."
+msgstr "Carregando anotações de cópia/movimentação..."
+
+#: lib/blame.tcl:577
+msgid "lines annotated"
+msgstr "linhas anotadas"
+
+#: lib/blame.tcl:769
+msgid "Loading original location annotations..."
+msgstr "Carregando anotações originais..."
+
+#: lib/blame.tcl:772
+msgid "Annotation complete."
+msgstr "Anotação completa."
+
+#: lib/blame.tcl:802
+msgid "Busy"
+msgstr "Ocupado"
+
+#: lib/blame.tcl:803
+msgid "Annotation process is already running."
+msgstr "O processo de anotação já está em execução"
+
+#: lib/blame.tcl:842
+msgid "Running thorough copy detection..."
+msgstr "Executando detecção de cópia..."
+
+#: lib/blame.tcl:910
+msgid "Loading annotation..."
+msgstr "Carregando anotações..."
+
+#: lib/blame.tcl:963
+msgid "Author:"
+msgstr "Autor:"
+
+#: lib/blame.tcl:967
+msgid "Committer:"
+msgstr "Revisor:"
+
+#: lib/blame.tcl:972
+msgid "Original File:"
+msgstr "Arquivo original:"
+
+#: lib/blame.tcl:1020
+msgid "Cannot find HEAD commit:"
+msgstr "Impossível encontrar revisão HEAD:"
+
+#: lib/blame.tcl:1075
+msgid "Cannot find parent commit:"
+msgstr "Impossível encontrar revisão anterior:"
+
+#: lib/blame.tcl:1090
+msgid "Unable to display parent"
+msgstr "Impossível exibir revisão anterior"
+
+#: lib/blame.tcl:1091 lib/diff.tcl:320
+msgid "Error loading diff:"
+msgstr "Erro ao carregar as diferenças:"
+
+#: lib/blame.tcl:1231
+msgid "Originally By:"
+msgstr "Originalmente por:"
+
+#: lib/blame.tcl:1237
+msgid "In File:"
+msgstr "No arquivo:"
+
+#: lib/blame.tcl:1242
+msgid "Copied Or Moved Here By:"
+msgstr "Copiado ou movido para cá por:"
+
+#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+msgid "Checkout Branch"
+msgstr "Efetuar checkout do ramo"
+
+#: lib/branch_checkout.tcl:23
+msgid "Checkout"
+msgstr "Checkout"
+
+#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
+#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
+#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
+#: lib/transport.tcl:108
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
+msgid "Revision"
+msgstr "Revisão"
+
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
+msgid "Options"
+msgstr "Opções"
+
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Obter ramo de rastreamento"
+
+#: lib/branch_checkout.tcl:44
+msgid "Detach From Local Branch"
+msgstr "Separar do ramo local"
+
+#: lib/branch_create.tcl:22
+msgid "Create Branch"
+msgstr "Criar ramo"
+
+#: lib/branch_create.tcl:27
+msgid "Create New Branch"
+msgstr "Criar novo ramo"
+
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
+msgid "Create"
+msgstr "Criar"
+
+#: lib/branch_create.tcl:40
+msgid "Branch Name"
+msgstr "Nome do ramo"
+
+#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
+msgid "Name:"
+msgstr "Nome:"
+
+#: lib/branch_create.tcl:58
+msgid "Match Tracking Branch Name"
+msgstr "Coincidir nome do ramo de rastreamento"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Revisão inicial"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Atualizar ramo existente:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Não"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Somente se for um avanço rápido"
+
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
+msgid "Reset"
+msgstr "Redefinir"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Efetuar checkout após a criação"
+
+#: lib/branch_create.tcl:131
+msgid "Please select a tracking branch."
+msgstr "Selecione um ramo de rastreamento."
+
+#: lib/branch_create.tcl:140
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "O ramo de rastreamento %s não é um ramo do repositório remoto."
+
+#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+msgid "Please supply a branch name."
+msgstr "Indique um nome para o ramo."
+
+#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "\"%s\" não é um nome de ramo válido"
+
+#: lib/branch_delete.tcl:15
+msgid "Delete Branch"
+msgstr "Apagar ramo"
+
+#: lib/branch_delete.tcl:20
+msgid "Delete Local Branch"
+msgstr "Apagar ramo local"
+
+#: lib/branch_delete.tcl:37
+msgid "Local Branches"
+msgstr "Ramos locais"
+
+#: lib/branch_delete.tcl:52
+msgid "Delete Only If Merged Into"
+msgstr "Apagar somente se mesclado em"
+
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
+msgstr "Forçar exclusão (não verificar se o ramo foi mesclado)"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Os ramos seguintes não foram completamente mesclados em %s:"
+
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Recuperar ramos apagados é difícil.\n"
+"\n"
+"Apagar os ramos selecionados?"
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Erro ao apagar ramos:\n"
+"%s"
+
+#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+msgid "Rename Branch"
+msgstr "Renomear ramo"
+
+#: lib/branch_rename.tcl:26
+msgid "Rename"
+msgstr "Renomear"
+
+#: lib/branch_rename.tcl:36
+msgid "Branch:"
+msgstr "Ramo:"
+
+#: lib/branch_rename.tcl:39
+msgid "New Name:"
+msgstr "Novo nome:"
+
+#: lib/branch_rename.tcl:75
+msgid "Please select a branch to rename."
+msgstr "Selecione um ramo para renomear."
+
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
+#, tcl-format
+msgid "Branch '%s' already exists."
+msgstr "O ramo \"%s\" já existe."
+
+#: lib/branch_rename.tcl:117
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Erro ao renomear \"%s\"."
+
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Inciando..."
+
+#: lib/browser.tcl:26
+msgid "File Browser"
+msgstr "Navegador de arquivos"
+
+#: lib/browser.tcl:126 lib/browser.tcl:143
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Carregando %s..."
+
+#: lib/browser.tcl:187
+msgid "[Up To Parent]"
+msgstr "[Subir]"
+
+#: lib/browser.tcl:267 lib/browser.tcl:273
+msgid "Browse Branch Files"
+msgstr "Explorar arquivos do ramo"
+
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
+msgid "Browse"
+msgstr "Explorar"
+
+#: lib/checkout_op.tcl:85
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Obtendo %s de %s"
+
+#: lib/checkout_op.tcl:133
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "Erro fatal: impossível resolver %s"
+
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
+#: lib/sshkey.tcl:53
+msgid "Close"
+msgstr "Fechar"
+
+#: lib/checkout_op.tcl:175
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "O ramo \"%s\" não existe."
+
+#: lib/checkout_op.tcl:194
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
+msgstr "Erro ao configurar git-pull simplificado para \"%s\"."
+
+#: lib/checkout_op.tcl:229
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
+msgstr ""
+"O ramo \"%s\" já existe.\n"
+"\n"
+"Não é possível avançá-lo para %s.\n"
+"É preciso mesclar."
+
+#: lib/checkout_op.tcl:243
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Estratégia de mesclagem \"%s\" não suportada."
+
+#: lib/checkout_op.tcl:262
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Erro ao atualizar \"%s\"."
+
+#: lib/checkout_op.tcl:274
+msgid "Staging area (index) is already locked."
+msgstr "A área de marcação (staging area, index) já está bloqueada."
+
+#: lib/checkout_op.tcl:289
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan. A "
+"rescan must be performed before the current branch can be changed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"O último estado lido não confere com o estado atual.\n"
+"\n"
+"Outro programa do Git modificou o repositório desde a última leitura. Uma "
+"atualização deve ser executada antes de alterar o ramo atual.\n"
+"\n"
+"A atualização começará automaticamente agora.\n"
+
+#: lib/checkout_op.tcl:345
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Atualizando diretório de trabalho para \"%s\"..."
+
+#: lib/checkout_op.tcl:346
+msgid "files checked out"
+msgstr "arquivos retirados"
+
+#: lib/checkout_op.tcl:376
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
+msgstr "Checkout de \"%s\" abortado (é preciso mesclar alguns arquivos)"
+
+#: lib/checkout_op.tcl:377
+msgid "File level merge required."
+msgstr "Mesclagem de arquivos necessária."
+
+#: lib/checkout_op.tcl:381
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Permanecendo no ramo \"%s\"."
+
+#: lib/checkout_op.tcl:452
+msgid ""
+"You are no longer on a local branch.\n"
+"\n"
+"If you wanted to be on a branch, create one now starting from 'This Detached "
+"Checkout'."
+msgstr ""
+"Você não está mais em um ramo local\n"
+"\n"
+"Se você deseja um ramo, crie um agora a partir deste checkout."
+
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "Checkout de \"%s\" concluído."
+
+#: lib/checkout_op.tcl:535
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr "Redefinir \"%s\" para \"%s\" provocará a perda das seguintes revisões:"
+
+#: lib/checkout_op.tcl:557
+msgid "Recovering lost commits may not be easy."
+msgstr "Recuperar revisões perdidas pode não ser fácil."
+
+#: lib/checkout_op.tcl:562
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Redefinir \"%s\"?"
+
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+msgid "Visualize"
+msgstr "Visualizar"
+
+#: lib/checkout_op.tcl:635
+#, tcl-format
+msgid ""
+"Failed to set current branch.\n"
+"\n"
+"This working directory is only partially switched. We successfully updated "
+"your files, but failed to update an internal Git file.\n"
+"\n"
+"This should not have occurred. %s will now close and give up."
+msgstr ""
+"Erro ao definir o ramo atual.\n"
+"\n"
+"Este diretório de trabalho está incompleto. Foi possível atualizar seus "
+"arquivos, mas houve uma falha ao atualizar os arquivos internos do Git.\n"
+"\n"
+"Isto não deveria ter acontecido, %s terminará agora."
+
+#: lib/choose_font.tcl:39
+msgid "Select"
+msgstr "Selecionar"
+
+#: lib/choose_font.tcl:53
+msgid "Font Family"
+msgstr "Tipo da fonte"
+
+#: lib/choose_font.tcl:74
+msgid "Font Size"
+msgstr "Tamanho da fonte"
+
+#: lib/choose_font.tcl:91
+msgid "Font Example"
+msgstr "Exemplo"
+
+#: lib/choose_font.tcl:103
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
+msgstr ""
+"Este é um texto de exemplo.\n"
+"Se você gostar deste texto, esta pode ser sua fonte."
+
+#: lib/choose_repository.tcl:28
+msgid "Git Gui"
+msgstr "Git Gui"
+
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
+msgid "Create New Repository"
+msgstr "Criar novo repositório"
+
+#: lib/choose_repository.tcl:93
+msgid "New..."
+msgstr "Novo..."
+
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
+msgid "Clone Existing Repository"
+msgstr "Clonar repositório existente"
+
+#: lib/choose_repository.tcl:106
+msgid "Clone..."
+msgstr "Clonar..."
+
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
+msgid "Open Existing Repository"
+msgstr "Abrir repositório existente"
+
+#: lib/choose_repository.tcl:119
+msgid "Open..."
+msgstr "Abrir..."
+
+#: lib/choose_repository.tcl:132
+msgid "Recent Repositories"
+msgstr "Repositórios recentes"
+
+#: lib/choose_repository.tcl:138
+msgid "Open Recent Repository:"
+msgstr "Abrir repositório recente:"
+
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Erro ao criar repositório %s:"
+
+#: lib/choose_repository.tcl:391
+msgid "Directory:"
+msgstr "Diretório:"
+
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
+msgid "Git Repository"
+msgstr "Repositório Git"
+
+#: lib/choose_repository.tcl:448
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "O diretório %s já existe."
+
+#: lib/choose_repository.tcl:452
+#, tcl-format
+msgid "File %s already exists."
+msgstr "O arquivo %s já existe."
+
+#: lib/choose_repository.tcl:466
+msgid "Clone"
+msgstr "Clonar"
+
+#: lib/choose_repository.tcl:479
+msgid "Source Location:"
+msgstr "Origem:"
+
+#: lib/choose_repository.tcl:490
+msgid "Target Directory:"
+msgstr "Diretório de destino:"
+
+#: lib/choose_repository.tcl:502
+msgid "Clone Type:"
+msgstr "Tipo de clonagem:"
+
+#: lib/choose_repository.tcl:508
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Padrão (rápida, semi-redundante, com hardlinks)"
+
+#: lib/choose_repository.tcl:514
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Cópia completa (mais lenta, backup redundante)"
+
+#: lib/choose_repository.tcl:520
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Compartilhada (A mais rápida, não recomendada, sem backup)"
+
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Este não é um repositório do Git: %s"
+
+#: lib/choose_repository.tcl:592
+msgid "Standard only available for local repository."
+msgstr "Clonagens padrões só são possíveis em repositórios locais."
+
+#: lib/choose_repository.tcl:596
+msgid "Shared only available for local repository."
+msgstr "Clonagens parciais só são possíveis em repositórios locais."
+
+#: lib/choose_repository.tcl:617
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "O local %s já existe."
+
+#: lib/choose_repository.tcl:628
+msgid "Failed to configure origin"
+msgstr "Erro ao configurar origem"
+
+#: lib/choose_repository.tcl:640
+msgid "Counting objects"
+msgstr "Contando objetos"
+
+#: lib/choose_repository.tcl:641
+msgid "buckets"
+msgstr "buckets"
+
+#: lib/choose_repository.tcl:665
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Erro ao copiar objetos ou informações adicionais: %s"
+
+#: lib/choose_repository.tcl:701
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Não há nada para clonar em %s."
+
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
+msgid "The 'master' branch has not been initialized."
+msgstr "O ramo \"master\" não foi inicializado."
+
+#: lib/choose_repository.tcl:716
+msgid "Hardlinks are unavailable. Falling back to copying."
+msgstr "Não foi possível criar hardlinks, usando cópias convencionais."
+
+#: lib/choose_repository.tcl:728
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Clonando de %s"
+
+#: lib/choose_repository.tcl:759
+msgid "Copying objects"
+msgstr "Copiando objetos"
+
+#: lib/choose_repository.tcl:760
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:784
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Não foi possível copiar o objeto: %s"
+
+#: lib/choose_repository.tcl:794
+msgid "Linking objects"
+msgstr "Ligando objetos"
+
+#: lib/choose_repository.tcl:795
+msgid "objects"
+msgstr "objetos"
+
+#: lib/choose_repository.tcl:803
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Não foi possível ligar o objeto: %s"
+
+#: lib/choose_repository.tcl:858
+msgid "Cannot fetch branches and objects. See console output for details."
+msgstr ""
+"Não foi possível receber ramos ou objetos. Veja a saída do console para "
+"detalhes."
+
+#: lib/choose_repository.tcl:869
+msgid "Cannot fetch tags. See console output for details."
+msgstr ""
+"Não foi possível receber as etiquetas. Veja a saída do console para detalhes."
+
+#: lib/choose_repository.tcl:893
+msgid "Cannot determine HEAD. See console output for details."
+msgstr ""
+"Não foi possível determinar a etiqueta HEAD. Veja a saída do console para "
+"detalhes."
+
+#: lib/choose_repository.tcl:902
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "Não foi possível limpar %s"
+
+#: lib/choose_repository.tcl:908
+msgid "Clone failed."
+msgstr "A clonagem falhou."
+
+#: lib/choose_repository.tcl:915
+msgid "No default branch obtained."
+msgstr "O ramo padrão não foi recebido."
+
+#: lib/choose_repository.tcl:926
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Não foi possível resolver %s como uma revisão."
+
+#: lib/choose_repository.tcl:938
+msgid "Creating working directory"
+msgstr "Criando diretório de trabalho."
+
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
+msgid "files"
+msgstr "arquivos"
+
+#: lib/choose_repository.tcl:968
+msgid "Initial file checkout failed."
+msgstr "Erro ao efetuar checkout inicial."
+
+#: lib/choose_repository.tcl:1011
+msgid "Open"
+msgstr "Abrir"
+
+#: lib/choose_repository.tcl:1021
+msgid "Repository:"
+msgstr "Repositório:"
+
+#: lib/choose_repository.tcl:1072
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Erro ao abrir o repositório %s:"
+
+#: lib/choose_rev.tcl:53
+msgid "This Detached Checkout"
+msgstr "Este checkout"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Expressão de revisão:"
+
+#: lib/choose_rev.tcl:74
+msgid "Local Branch"
+msgstr "Ramo local"
+
+#: lib/choose_rev.tcl:79
+msgid "Tracking Branch"
+msgstr "Ramo de rastreamento"
+
+#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: lib/choose_rev.tcl:317
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Revisão inválida: %s"
+
+#: lib/choose_rev.tcl:338
+msgid "No revision selected."
+msgstr "Nenhuma revisão selecionada."
+
+#: lib/choose_rev.tcl:346
+msgid "Revision expression is empty."
+msgstr "A expressão de revisão está vazia."
+
+#: lib/choose_rev.tcl:531
+msgid "Updated"
+msgstr "Atualizado"
+
+#: lib/choose_rev.tcl:559
+msgid "URL"
+msgstr "URL"
+
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit. There is no commit before this "
+"to amend.\n"
+msgstr ""
+"Não há nada para corrigir.\n"
+"\n"
+"Você está prestes a criar uma revisão inicial. Não há revisão anterior para "
+"corrigir.\n"
+
+#: lib/commit.tcl:18
+msgid ""
+"Cannot amend while merging.\n"
+"\n"
+"You are currently in the middle of a merge that has not been fully "
+"completed. You cannot amend the prior commit unless you first abort the "
+"current merge activity.\n"
+msgstr ""
+"Não é possível corrigir durante uma mesclagem.\n"
+"\n"
+"Você está em meio a uma operação de mesclagem que não foi completada. Não é "
+"possível corrigir a revisão anterior a menos que você aborte a mescla atual "
+"antes.\n"
+
+#: lib/commit.tcl:48
+msgid "Error loading commit data for amend:"
+msgstr "Erro ao carregar dados da revisão para corrigir:"
+
+#: lib/commit.tcl:75
+msgid "Unable to obtain your identity:"
+msgstr "Não foi possível obter a sua identidade:"
+
+#: lib/commit.tcl:80
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Variável \"GIT_COMMITTER_IDENT\" inválida:"
+
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "aviso: O Tcl não suporta a codificação \"%s\"."
+
+#: lib/commit.tcl:149
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan. A "
+"rescan must be performed before another commit can be created.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"O último estado lido não confere com o estado atual.\n"
+"\n"
+"Outro programa do Git modificou o repositório desde a última leitura. Uma "
+"atualização deve ser executada antes de criar outra revisão.\n"
+"\n"
+"A atualização começará automaticamente agora.\n"
+
+#: lib/commit.tcl:172
+#, tcl-format
+msgid ""
+"Unmerged files cannot be committed.\n"
+"\n"
+"File %s has merge conflicts. You must resolve them and stage the file "
+"before committing.\n"
+msgstr ""
+"Não é possível salvar revisões para arquivos não mesclados.\n"
+"\n"
+"O arquivo %s possui conflitos de mesclagem. Você deve resolvê-los e marcar o "
+"arquivo antes de salvar a revisão.\n"
+
+#: lib/commit.tcl:180
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Estado desconhecido detectado para o arquivo %s.\n"
+"\n"
+"Este programa não pode salvar uma revisão para o arquivo %s.\n"
+
+#: lib/commit.tcl:188
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Não há mudanças para salvar.\n"
+"\n"
+"Você deve marcar ao menos um arquivo antes de salvar a revisão.\n"
+
+#: lib/commit.tcl:203
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Por favor, indique uma descrição para a revisão.\n"
+"\n"
+"Uma boa descrição tem o seguinte formato:\n"
+"\n"
+"- Primeira linha: descreve, em uma única frase, o que você fez.\n"
+"- Segunda linha: em branco.\n"
+"- Demais linhas: Descreve detalhadamente a revisão.\n"
+
+#: lib/commit.tcl:234
+msgid "Calling pre-commit hook..."
+msgstr "Executando script \"pre-commit\"..."
+
+#: lib/commit.tcl:249
+msgid "Commit declined by pre-commit hook."
+msgstr "A revisão foi bloqueada pelo script \"pre-commit\"."
+
+#: lib/commit.tcl:272
+msgid "Calling commit-msg hook..."
+msgstr "Executando script \"commit-msg\"..."
+
+#: lib/commit.tcl:287
+msgid "Commit declined by commit-msg hook."
+msgstr "Revisão bloqueada pelo script \"commit-msg\"."
+
+#: lib/commit.tcl:300
+msgid "Committing changes..."
+msgstr "Salvando revisão..."
+
+#: lib/commit.tcl:316
+msgid "write-tree failed:"
+msgstr "write-tree falhou:"
+
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
+msgid "Commit failed."
+msgstr "A revisão falhou."
+
+#: lib/commit.tcl:334
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "A revisão %s parece estar corrompida."
+
+#: lib/commit.tcl:339
+msgid ""
+"No changes to commit.\n"
+"\n"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
+msgstr ""
+"Não há alterações para salvar.\n"
+"\n"
+"Nenhum arquivo foi modificado e esta não é uma revisão de mesclagem.\n"
+"\n"
+"Uma atualização será executada automaticamente agora.\n"
+
+#: lib/commit.tcl:346
+msgid "No changes to commit."
+msgstr "Não há alterações para salvar."
+
+#: lib/commit.tcl:360
+msgid "commit-tree failed:"
+msgstr "commit-tree falhou:"
+
+#: lib/commit.tcl:381
+msgid "update-ref failed:"
+msgstr "update-ref falhou:"
+
+#: lib/commit.tcl:469
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Revisão %s criada: %s"
+
+#: lib/console.tcl:59
+msgid "Working... please wait..."
+msgstr "Trabalhando... aguarde..."
+
+#: lib/console.tcl:186
+msgid "Success"
+msgstr "Sucesso"
+
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
+msgstr "Erro: o comando falhou"
+
+#: lib/database.tcl:43
+msgid "Number of loose objects"
+msgstr "Número de objetos soltos"
+
+#: lib/database.tcl:44
+msgid "Disk space used by loose objects"
+msgstr "Espaço ocupado pelos objetos soltos"
+
+#: lib/database.tcl:45
+msgid "Number of packed objects"
+msgstr "Número de objetos compactados"
+
+#: lib/database.tcl:46
+msgid "Number of packs"
+msgstr "Número de pacotes"
+
+#: lib/database.tcl:47
+msgid "Disk space used by packed objects"
+msgstr "Espaço ocupado pelos objetos compactados"
+
+#: lib/database.tcl:48
+msgid "Packed objects waiting for pruning"
+msgstr "Objetos compactados aguardando eliminação"
+
+#: lib/database.tcl:49
+msgid "Garbage files"
+msgstr "Arquivos de lixo"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Compactando banco de dados de objetos"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Verificando banco de dados de objetos com fsck-objects"
+
+#: lib/database.tcl:107
+#, tcl-format
+msgid ""
+"This repository currently has approximately %i loose objects.\n"
+"\n"
+"To maintain optimal performance it is strongly recommended that you compress "
+"the database.\n"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Este repositório possui aproximadamente %i objetos soltos.\n"
+"\n"
+"Para manter o desempenho ótimo é altamente recomendado que você compacte o "
+"banco de dados.\n"
+"\n"
+"Compactar o banco de dados agora?"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Data inválida recebida do Git: %s"
+
+#: lib/diff.tcl:64
+#, tcl-format
+msgid ""
+"No differences detected.\n"
+"\n"
+"%s has no changes.\n"
+"\n"
+"The modification date of this file was updated by another application, but "
+"the content within the file was not changed.\n"
+"\n"
+"A rescan will be automatically started to find other files which may have "
+"the same state."
+msgstr ""
+"Nenhuma diferença foi detectada.\n"
+"\n"
+"%s não possui mudanças.\n"
+"\n"
+"A data de modificação deste arquivo foi atualizada por outro aplicativo, mas "
+"o conteúdo do arquivo não foi alterado.\n"
+"\n"
+"Uma atualização ser executada para encontrar outros arquivos que possam ter "
+"o mesmo estado."
+
+#: lib/diff.tcl:104
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Carregando diferenças de %s..."
+
+#: lib/diff.tcl:125
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
+msgstr ""
+"Local: apagado\n"
+"Remoto:\n"
+
+#: lib/diff.tcl:130
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
+msgstr ""
+"Remoto: apagado\n"
+"Local:\n"
+
+#: lib/diff.tcl:137
+msgid "LOCAL:\n"
+msgstr "Local:\n"
+
+#: lib/diff.tcl:140
+msgid "REMOTE:\n"
+msgstr "Remoto:\n"
+
+#: lib/diff.tcl:202 lib/diff.tcl:319
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Impossível exibir %s"
+
+#: lib/diff.tcl:203
+msgid "Error loading file:"
+msgstr "Erro ao carregar o arquivo:"
+
+#: lib/diff.tcl:210
+msgid "Git Repository (subproject)"
+msgstr "Repositório Git (sub-projeto)"
+
+#: lib/diff.tcl:222
+msgid "* Binary file (not showing content)."
+msgstr "* Arquivo binário (conteúdo não exibido)."
+
+#: lib/diff.tcl:227
+#, tcl-format
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
+msgstr ""
+"* O arquivo não rastreado possui %d bytes.\n"
+"* Exibindo apenas os primeiros %d bytes.\n"
+
+#: lib/diff.tcl:233
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+"\n"
+"* O arquivo não rastreado foi cortado aqui por %s.\n"
+"* Para ver o arquivo completo, use um editor externo.\n"
+
+#: lib/diff.tcl:482
+msgid "Failed to unstage selected hunk."
+msgstr "Erro ao desmarcar o trecho selecionado."
+
+#: lib/diff.tcl:489
+msgid "Failed to stage selected hunk."
+msgstr "Erro ao marcar o trecho selecionado."
+
+#: lib/diff.tcl:568
+msgid "Failed to unstage selected line."
+msgstr "Erro ao desmarcar a linha selecionada."
+
+#: lib/diff.tcl:576
+msgid "Failed to stage selected line."
+msgstr "Erro ao marcar a linha selecionada."
+
+#: lib/encoding.tcl:443
+msgid "Default"
+msgstr "Padrão"
+
+#: lib/encoding.tcl:448
+#, tcl-format
+msgid "System (%s)"
+msgstr "Sistema (%s)"
+
+#: lib/encoding.tcl:459 lib/encoding.tcl:465
+msgid "Other"
+msgstr "Outro"
+
+#: lib/error.tcl:20 lib/error.tcl:114
+msgid "error"
+msgstr "Erro"
+
+#: lib/error.tcl:36
+msgid "warning"
+msgstr "aviso"
+
+#: lib/error.tcl:94
+msgid "You must correct the above errors before committing."
+msgstr "Você precisa corrigir os erros acima antes de salvar a revisão."
+
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Impossível desbloquear o índice."
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "Erro no índice"
+
+#: lib/index.tcl:17
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"A atualização do índice do Git falhou. Uma atualização será executada "
+"automaticamente para ressincronizar o Git GUI"
+
+#: lib/index.tcl:28
+msgid "Continue"
+msgstr "Continuar"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "Desbloquear índice"
+
+#: lib/index.tcl:289
+#, tcl-format
+msgid "Unstaging %s from commit"
+msgstr "Desmarcando %s para revisão"
+
+#: lib/index.tcl:328
+msgid "Ready to commit."
+msgstr "Pronto para salvar a revisão."
+
+#: lib/index.tcl:341
+#, tcl-format
+msgid "Adding %s"
+msgstr "Adicionando %s"
+
+#: lib/index.tcl:398
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Reverter as alterações no arquivo %s?"
+
+#: lib/index.tcl:400
+#, tcl-format
+msgid "Revert changes in these %i files?"
+msgstr "Reverter as alterações nestes %i arquivos?"
+
+#: lib/index.tcl:408
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Qualquer alteração não marcada será permanentemente perdida na reversão."
+
+#: lib/index.tcl:411
+msgid "Do Nothing"
+msgstr "Não fazer nada"
+
+#: lib/index.tcl:429
+msgid "Reverting selected files"
+msgstr "Revertendo os arquivos selecionados"
+
+#: lib/index.tcl:433
+#, tcl-format
+msgid "Reverting %s"
+msgstr "Revertendo %s"
+
+#: lib/merge.tcl:13
+msgid ""
+"Cannot merge while amending.\n"
+"\n"
+"You must finish amending this commit before starting any type of merge.\n"
+msgstr ""
+"Não é possível mesclar durante uma correção.\n"
+"\n"
+"Você deve concluir a correção antes de começar qualquer mesclagem.\n"
+
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan. A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"O último estado lido não confere com o estado atual.\n"
+"\n"
+"Outro programa do Git modificou o repositório desde a última leitura. Uma "
+"atualização deve ser executada antes de efetuar uma mesclagem.\n"
+"\n"
+"A atualização começará automaticamente agora.\n"
+
+#: lib/merge.tcl:45
+#, tcl-format
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge. Only then can you begin another merge.\n"
+msgstr ""
+"Há uma mesclagem com conflitos em progresso.\n"
+"\n"
+"O arquivo %s possui conflitos de mesclagem.\n"
+"\n"
+"Você deve resolvê-los, marcar o arquivo e salvar a revisão para completar a "
+"mesclagem atual. Só então você poderá começar outra.\n"
+
+#: lib/merge.tcl:55
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge. Doing so "
+"will help you abort a failed merge, should the need arise.\n"
+msgstr ""
+"Você está em meio a uma mudança.\n"
+"\n"
+"O arquivo %s foi modificado.\n"
+"\n"
+"Você deve completar e salvar a revisão atual antes de começar uma mesclagem. "
+"Ao fazê-lo, você poderá abortar a mesclagem caso haja algum erro.\n"
+
+#: lib/merge.tcl:107
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s de %s"
+
+#: lib/merge.tcl:120
+#, tcl-format
+msgid "Merging %s and %s..."
+msgstr "Mesclando %s e %s..."
+
+#: lib/merge.tcl:131
+msgid "Merge completed successfully."
+msgstr "Mesclagem completada com sucesso."
+
+#: lib/merge.tcl:133
+msgid "Merge failed. Conflict resolution is required."
+msgstr "A mesclagem falhou. É necessário resolver conflitos."
+
+#: lib/merge.tcl:158
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Mesclar em %s"
+
+#: lib/merge.tcl:177
+msgid "Revision To Merge"
+msgstr "Revisão para mesclar"
+
+#: lib/merge.tcl:212
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Não é possível abortar durante uma correção.\n"
+"\n"
+"Você precisa finalizar a correção desta revisão.\n"
+
+#: lib/merge.tcl:222
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
+msgstr ""
+"Abortar mesclagem?\n"
+"\n"
+"Abortar a mesclagem atual implicará na perda de *TODAS* as mudanças não "
+"salvas.\n"
+"\n"
+"Abortar a mesclagem atual?"
+
+#: lib/merge.tcl:228
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
+msgstr ""
+"Descartar as mudanças?\n"
+"\n"
+"Ao fazê-lo, *TODAS* as alterações não salvas serão perdidas.\n"
+"\n"
+"Continuar e descartar as mudanças atuais?"
+
+#: lib/merge.tcl:239
+msgid "Aborting"
+msgstr "Abortando"
+
+#: lib/merge.tcl:239
+msgid "files reset"
+msgstr "arquivos redefindos"
+
+#: lib/merge.tcl:267
+msgid "Abort failed."
+msgstr "A tentativa de abortar a operação falhou"
+
+#: lib/merge.tcl:269
+msgid "Abort completed. Ready."
+msgstr "Operação abortada com sucesso. Pronto."
+
+#: lib/mergetool.tcl:8
+msgid "Force resolution to the base version?"
+msgstr "Forçar a resolução para a versão base?"
+
+#: lib/mergetool.tcl:9
+msgid "Force resolution to this branch?"
+msgstr "Forçar resolução para este ramo?"
+
+#: lib/mergetool.tcl:10
+msgid "Force resolution to the other branch?"
+msgstr "Forçar resolução para o outro ramo?"
+
+#: lib/mergetool.tcl:14
+#, tcl-format
+msgid ""
+"Note that the diff shows only conflicting changes.\n"
+"\n"
+"%s will be overwritten.\n"
+"\n"
+"This operation can be undone only by restarting the merge."
+msgstr ""
+"Note que o diff mostra apenas as mudanças conflitantes.\n"
+"\n"
+"%s será sobrescrito.\n"
+"\n"
+"Caso necessário, será preciso reiniciar a mesclagem para desfazer esta "
+"operação."
+
+#: lib/mergetool.tcl:45
+#, tcl-format
+msgid "File %s seems to have unresolved conflicts, still stage?"
+msgstr "O arquivo %s parece ter conflitos não resolvidos. Marcar mesmo assim?"
+
+#: lib/mergetool.tcl:60
+#, tcl-format
+msgid "Adding resolution for %s"
+msgstr "Adicionando resolução para %s"
+
+#: lib/mergetool.tcl:141
+msgid "Cannot resolve deletion or link conflicts using a tool"
+msgstr ""
+"Impossível resolver conflitos envolvendo exclusão ou links de arquivos com "
+"esta ferramenta."
+
+#: lib/mergetool.tcl:146
+msgid "Conflict file does not exist"
+msgstr "O arquivo conflitante não existe"
+
+#: lib/mergetool.tcl:264
+#, tcl-format
+msgid "Not a GUI merge tool: '%s'"
+msgstr "Não é uma ferramenta de mesclagem gráfica: \"%s\""
+
+#: lib/mergetool.tcl:268
+#, tcl-format
+msgid "Unsupported merge tool '%s'"
+msgstr "Ferramenta de mesclagem não suportada \"%s\""
+
+#: lib/mergetool.tcl:303
+msgid "Merge tool is already running, terminate it?"
+msgstr "A ferramenta de mesclagem já está em execução. Finalizar?"
+
+#: lib/mergetool.tcl:323
+#, tcl-format
+msgid ""
+"Error retrieving versions:\n"
+"%s"
+msgstr ""
+"Erro ao obter as versões:\n"
+"%s"
+
+#: lib/mergetool.tcl:343
+#, tcl-format
+msgid ""
+"Could not start the merge tool:\n"
+"\n"
+"%s"
+msgstr ""
+"Não foi possível iniciar a ferramenta de mesclagem:\n"
+"\n"
+"%s"
+
+#: lib/mergetool.tcl:347
+msgid "Running merge tool..."
+msgstr "Executando ferramenta de mesclagem..."
+
+#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
+msgid "Merge tool failed."
+msgstr "Ferramenta de mesclagem falhou."
+
+#: lib/option.tcl:11
+#, tcl-format
+msgid "Invalid global encoding '%s'"
+msgstr "Codificação global inválida \"%s\""
+
+#: lib/option.tcl:19
+#, tcl-format
+msgid "Invalid repo encoding '%s'"
+msgstr "Codificação do repositório inválida \"%s\""
+
+#: lib/option.tcl:117
+msgid "Restore Defaults"
+msgstr "Restaurar padrões"
+
+#: lib/option.tcl:121
+msgid "Save"
+msgstr "Salvar"
+
+#: lib/option.tcl:131
+#, tcl-format
+msgid "%s Repository"
+msgstr "Repositório %s"
+
+#: lib/option.tcl:132
+msgid "Global (All Repositories)"
+msgstr "Global (todos os repositórios)"
+
+#: lib/option.tcl:138
+msgid "User Name"
+msgstr "Nome do usuário"
+
+#: lib/option.tcl:139
+msgid "Email Address"
+msgstr "Endereço de e-mail"
+
+#: lib/option.tcl:141
+msgid "Summarize Merge Commits"
+msgstr "Exibir sumário das revisões de mesclagem"
+
+#: lib/option.tcl:142
+msgid "Merge Verbosity"
+msgstr "Nível de detalhamento da mesclagem"
+
+#: lib/option.tcl:143
+msgid "Show Diffstat After Merge"
+msgstr "Exibir estatísticas após mesclagens"
+
+#: lib/option.tcl:144
+msgid "Use Merge Tool"
+msgstr "Usar ferramenta de mesclagem"
+
+#: lib/option.tcl:146
+msgid "Trust File Modification Timestamps"
+msgstr "Confiar nas datas de modificação dos arquivos"
+
+#: lib/option.tcl:147
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Eliminar ramos de rastreamento ao receber"
+
+#: lib/option.tcl:148
+msgid "Match Tracking Branches"
+msgstr "Coincidir ramos de rastreamento"
+
+#: lib/option.tcl:149
+msgid "Blame Copy Only On Changed Files"
+msgstr "Detectar cópias somente em arquivos modificados"
+
+#: lib/option.tcl:150
+msgid "Minimum Letters To Blame Copy On"
+msgstr "Número mínimo de letras para detectar cópias"
+
+#: lib/option.tcl:151
+msgid "Blame History Context Radius (days)"
+msgstr "Extensão do contexto de detecção (em dias)"
+
+#: lib/option.tcl:152
+msgid "Number of Diff Context Lines"
+msgstr "Número de linhas para o diff contextual"
+
+#: lib/option.tcl:153
+msgid "Commit Message Text Width"
+msgstr "Largura do texto da descrição da revisão"
+
+#: lib/option.tcl:154
+msgid "New Branch Name Template"
+msgstr "Modelo de nome para novos ramos"
+
+#: lib/option.tcl:155
+msgid "Default File Contents Encoding"
+msgstr "Codificação padrão dos arquivos"
+
+#: lib/option.tcl:203
+msgid "Change"
+msgstr "Alterar"
+
+#: lib/option.tcl:230
+msgid "Spelling Dictionary:"
+msgstr "Dicionário para o verificador ortográfico:"
+
+#: lib/option.tcl:254
+msgid "Change Font"
+msgstr "Mudar fonte"
+
+#: lib/option.tcl:258
+#, tcl-format
+msgid "Choose %s"
+msgstr "Escolher %s"
+
+#: lib/option.tcl:264
+msgid "pt."
+msgstr "pt."
+
+#: lib/option.tcl:278
+msgid "Preferences"
+msgstr "Preferências"
+
+#: lib/option.tcl:314
+msgid "Failed to completely save options:"
+msgstr "Houve um erro ao salvar as opções:"
+
+#: lib/remote.tcl:163
+msgid "Remove Remote"
+msgstr "Excluir"
+
+#: lib/remote.tcl:168
+msgid "Prune from"
+msgstr "Limpar de"
+
+#: lib/remote.tcl:173
+msgid "Fetch from"
+msgstr "Receber de"
+
+#: lib/remote.tcl:215
+msgid "Push to"
+msgstr "Enviar para"
+
+#: lib/remote_add.tcl:19
+msgid "Add Remote"
+msgstr "Adicionar repositório remoto"
+
+#: lib/remote_add.tcl:24
+msgid "Add New Remote"
+msgstr "Adicionar novo repositório remoto"
+
+#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
+msgid "Add"
+msgstr "Adicionar"
+
+#: lib/remote_add.tcl:37
+msgid "Remote Details"
+msgstr "Detalhes do repositório remoto"
+
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Local:"
+
+#: lib/remote_add.tcl:62
+msgid "Further Action"
+msgstr "Ações adicionais"
+
+#: lib/remote_add.tcl:65
+msgid "Fetch Immediately"
+msgstr "Receber imediatamente"
+
+#: lib/remote_add.tcl:71
+msgid "Initialize Remote Repository and Push"
+msgstr "Inicializar repositório remoto e enviar"
+
+#: lib/remote_add.tcl:77
+msgid "Do Nothing Else Now"
+msgstr "Não fazer nada agora"
+
+#: lib/remote_add.tcl:101
+msgid "Please supply a remote name."
+msgstr "Por favor, indique um nome para o repositório remoto."
+
+#: lib/remote_add.tcl:114
+#, tcl-format
+msgid "'%s' is not an acceptable remote name."
+msgstr "\"%s\" não é um nome válido para um repositório remoto."
+
+#: lib/remote_add.tcl:125
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr "Erro ao adicionar repositório remoto \"%s\" do local \"%s\":"
+
+#: lib/remote_add.tcl:133 lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "receber %s"
+
+#: lib/remote_add.tcl:134
+#, tcl-format
+msgid "Fetching the %s"
+msgstr "Recebendo o %s"
+
+#: lib/remote_add.tcl:157
+#, tcl-format
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr "Não sabe como inicializar o repositório remoto em \"%s\"."
+
+#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/transport.tcl:81
+#, tcl-format
+msgid "push %s"
+msgstr "enviar %s"
+
+#: lib/remote_add.tcl:164
+#, tcl-format
+msgid "Setting up the %s (at %s)"
+msgstr "Configurando %s (em %s)"
+
+#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+msgid "Delete Branch Remotely"
+msgstr "Apagar ramo remoto"
+
+#: lib/remote_branch_delete.tcl:47
+msgid "From Repository"
+msgstr "Do repositório"
+
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
+msgid "Remote:"
+msgstr "Remoto:"
+
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
+msgid "Arbitrary Location:"
+msgstr "Outro local:"
+
+#: lib/remote_branch_delete.tcl:84
+msgid "Branches"
+msgstr "Ramos"
+
+#: lib/remote_branch_delete.tcl:109
+msgid "Delete Only If"
+msgstr "Apagar somente se"
+
+#: lib/remote_branch_delete.tcl:111
+msgid "Merged Into:"
+msgstr "Mesclado em:"
+
+#: lib/remote_branch_delete.tcl:152
+msgid "A branch is required for 'Merged Into'."
+msgstr "É preciso indicar um ramo para \"Mesclado em\"."
+
+#: lib/remote_branch_delete.tcl:184
+#, tcl-format
+msgid ""
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
+msgstr ""
+"Os seguintes ramos não estão inteiramente mesclados em %s:\n"
+"\n"
+" - %s"
+
+#: lib/remote_branch_delete.tcl:189
+#, tcl-format
+msgid ""
+"One or more of the merge tests failed because you have not fetched the "
+"necessary commits. Try fetching from %s first."
+msgstr ""
+"Um ou mais testes de mesclagem falharam porque você não possui as revisões "
+"necessárias. Tente receber revisões de %s primeiro."
+
+#: lib/remote_branch_delete.tcl:207
+msgid "Please select one or more branches to delete."
+msgstr "Por favor selecione um ou mais ramos para apagar."
+
+#: lib/remote_branch_delete.tcl:226
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Apagar ramos de %s"
+
+#: lib/remote_branch_delete.tcl:292
+msgid "No repository selected."
+msgstr "Nenhum repositório foi selecionado."
+
+#: lib/remote_branch_delete.tcl:297
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Atualizando %s..."
+
+#: lib/search.tcl:21
+msgid "Find:"
+msgstr "Encontrar:"
+
+#: lib/search.tcl:23
+msgid "Next"
+msgstr "Próximo"
+
+#: lib/search.tcl:24
+msgid "Prev"
+msgstr "Anterior"
+
+#: lib/search.tcl:25
+msgid "Case-Sensitive"
+msgstr "Sensível a maiúsculas/minúsculas"
+
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
+msgid "Cannot write shortcut:"
+msgstr "Não foi possível gravar o atalho:"
+
+#: lib/shortcut.tcl:137
+msgid "Cannot write icon:"
+msgstr "Não foi possível gravar o ícone:"
+
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
+msgstr "Verificador ortográfico não suportado"
+
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
+msgstr "Verificação ortográfica indisponível"
+
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
+msgstr "Configuração do verificador ortográfico inválida"
+
+#: lib/spellcheck.tcl:70
+#, tcl-format
+msgid "Reverting dictionary to %s."
+msgstr "Revertendo dicionário para %s."
+
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
+msgstr "O verificador ortográfico falhou sem relatar nenhum erro"
+
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
+msgstr "Verificador ortográfico não reconhecido"
+
+#: lib/spellcheck.tcl:186
+msgid "No Suggestions"
+msgstr "Sem sugestões"
+
+#: lib/spellcheck.tcl:388
+msgid "Unexpected EOF from spell checker"
+msgstr "Final de arquivo inesperado recebido do verificador ortográfico"
+
+#: lib/spellcheck.tcl:392
+msgid "Spell Checker Failed"
+msgstr "A verificação ortográfica falhou"
+
+#: lib/sshkey.tcl:31
+msgid "No keys found."
+msgstr "Nenhuma chave encontrada"
+
+#: lib/sshkey.tcl:34
+#, tcl-format
+msgid "Found a public key in: %s"
+msgstr "Chave pública encontrada em: %s"
+
+#: lib/sshkey.tcl:40
+msgid "Generate Key"
+msgstr "Gerar chave"
+
+#: lib/sshkey.tcl:56
+msgid "Copy To Clipboard"
+msgstr "Copiar para a área de transferência"
+
+#: lib/sshkey.tcl:70
+msgid "Your OpenSSH Public Key"
+msgstr "Sua chave pública OpenSSH"
+
+#: lib/sshkey.tcl:78
+msgid "Generating..."
+msgstr "Gerando..."
+
+#: lib/sshkey.tcl:84
+#, tcl-format
+msgid ""
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
+msgstr ""
+"Impossível iniciar ssh-keygen:\n"
+"\n"
+"%s"
+
+#: lib/sshkey.tcl:111
+msgid "Generation failed."
+msgstr "A geração da chave falhou."
+
+#: lib/sshkey.tcl:118
+msgid "Generation succeded, but no keys found."
+msgstr "A geração da chave foi bem-sucedida, mas nenhuma chave foi encontrada."
+
+#: lib/sshkey.tcl:121
+#, tcl-format
+msgid "Your key is in: %s"
+msgstr "Sua chave em: %s"
+
+#: lib/status_bar.tcl:83
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i de %*i %s (%3i%%)"
+
+#: lib/tools.tcl:75
+#, tcl-format
+msgid "Running %s requires a selected file."
+msgstr "É preciso selecionar um arquivo para executar %s."
+
+#: lib/tools.tcl:90
+#, tcl-format
+msgid "Are you sure you want to run %s?"
+msgstr "Você tem certeza que deseja executar %s?"
+
+#: lib/tools.tcl:110
+#, tcl-format
+msgid "Tool: %s"
+msgstr "Ferramenta: %s"
+
+#: lib/tools.tcl:111
+#, tcl-format
+msgid "Running: %s"
+msgstr "Executando: %s"
+
+#: lib/tools.tcl:149
+#, tcl-format
+msgid "Tool completed successfully: %s"
+msgstr "Execução completada com sucesso: %s"
+
+#: lib/tools.tcl:151
+#, tcl-format
+msgid "Tool failed: %s"
+msgstr "Ferramenta falhou: %s"
+
+#: lib/tools_dlg.tcl:22
+msgid "Add Tool"
+msgstr "Adicionar ferramenta"
+
+#: lib/tools_dlg.tcl:28
+msgid "Add New Tool Command"
+msgstr "Adicionar novo comando de ferramenta"
+
+#: lib/tools_dlg.tcl:33
+msgid "Add globally"
+msgstr "Adicionar globalmente"
+
+#: lib/tools_dlg.tcl:45
+msgid "Tool Details"
+msgstr "Detalhes da ferramenta"
+
+#: lib/tools_dlg.tcl:48
+msgid "Use '/' separators to create a submenu tree:"
+msgstr "Use o separador \"/\" para criar uma árvore de sub-menus:"
+
+#: lib/tools_dlg.tcl:61
+msgid "Command:"
+msgstr "Comando:"
+
+#: lib/tools_dlg.tcl:74
+msgid "Show a dialog before running"
+msgstr "Exibir uma caixa de diálogo antes de executar"
+
+#: lib/tools_dlg.tcl:80
+msgid "Ask the user to select a revision (sets $REVISION)"
+msgstr "Solicitar a seleção de uma revisão (a variável $REVISION)"
+
+#: lib/tools_dlg.tcl:85
+msgid "Ask the user for additional arguments (sets $ARGS)"
+msgstr "Solicitar argumentos adicionais (define a variável $ARGS)"
+
+#: lib/tools_dlg.tcl:92
+msgid "Don't show the command output window"
+msgstr "Não exibir a janela de saída do comando"
+
+#: lib/tools_dlg.tcl:97
+msgid "Run only if a diff is selected ($FILENAME not empty)"
+msgstr "Executar apenas se houver um diff selecionado ($FILENAME não-vazio)"
+
+#: lib/tools_dlg.tcl:121
+msgid "Please supply a name for the tool."
+msgstr "Por favor, indique um nome para a ferramenta."
+
+#: lib/tools_dlg.tcl:129
+#, tcl-format
+msgid "Tool '%s' already exists."
+msgstr "A ferramenta \"%s\" já existe."
+
+#: lib/tools_dlg.tcl:151
+#, tcl-format
+msgid ""
+"Could not add tool:\n"
+"%s"
+msgstr ""
+"Não foi possível adicionar a ferramenta:\n"
+"%s"
+
+#: lib/tools_dlg.tcl:190
+msgid "Remove Tool"
+msgstr "Excluir ferramenta"
+
+#: lib/tools_dlg.tcl:196
+msgid "Remove Tool Commands"
+msgstr "Excluir comando de ferramenta"
+
+#: lib/tools_dlg.tcl:200
+msgid "Remove"
+msgstr "Excluir"
+
+#: lib/tools_dlg.tcl:236
+msgid "(Blue denotes repository-local tools)"
+msgstr "(Azul indica ferramentas do repositório local)"
+
+#: lib/tools_dlg.tcl:297
+#, tcl-format
+msgid "Run Command: %s"
+msgstr "Executar comando: %s"
+
+#: lib/tools_dlg.tcl:311
+msgid "Arguments"
+msgstr "Argumentos"
+
+#: lib/tools_dlg.tcl:348
+msgid "OK"
+msgstr "OK"
+
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Recebendo novas mudanças de %s"
+
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "Limpar %s"
+
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Limpando ramos excluídos de %s"
+
+#: lib/transport.tcl:26
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Enviando mudanças para %s"
+
+#: lib/transport.tcl:64
+#, tcl-format
+msgid "Mirroring to %s"
+msgstr "Duplicando para %s"
+
+#: lib/transport.tcl:82
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "Enviando %s %s para %s"
+
+#: lib/transport.tcl:100
+msgid "Push Branches"
+msgstr "Enviar ramos"
+
+#: lib/transport.tcl:114
+msgid "Source Branches"
+msgstr "Ramos de origem"
+
+#: lib/transport.tcl:131
+msgid "Destination Repository"
+msgstr "Repositório de destino"
+
+#: lib/transport.tcl:169
+msgid "Transfer Options"
+msgstr "Opções de transferência"
+
+#: lib/transport.tcl:171
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr "Sobrescrever ramos existentes (pode descartar mudanças)"
+
+#: lib/transport.tcl:175
+msgid "Use thin pack (for slow network connections)"
+msgstr "Usar compactação minimalista (para redes lentas)"
+
+#: lib/transport.tcl:179
+msgid "Include tags"
+msgstr "Incluir etiquetas"
diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po
index 364c074..30f4b77 100644
--- a/git-gui/po/ru.po
+++ b/git-gui/po/ru.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: git-gui\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-12-08 08:31-0800\n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
"PO-Revision-Date: 2007-10-22 22:30-0200\n"
"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n"
"Language-Team: Russian Translation <git@vger.kernel.org>\n"
@@ -15,33 +15,33 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
-#: git-gui.sh:866
+#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
+#: git-gui.sh:922
msgid "git-gui: fatal error"
msgstr "git-gui: критическая ошибка"
-#: git-gui.sh:689
+#: git-gui.sh:743
#, tcl-format
msgid "Invalid font specified in %s:"
msgstr "В %s установлен неверный шрифт:"
-#: git-gui.sh:723
+#: git-gui.sh:779
msgid "Main Font"
msgstr "Шрифт интерфейса"
-#: git-gui.sh:724
+#: git-gui.sh:780
msgid "Diff/Console Font"
msgstr "Шрифт консоли и изменений (diff)"
-#: git-gui.sh:738
+#: git-gui.sh:794
msgid "Cannot find git in PATH."
msgstr "git не найден в PATH."
-#: git-gui.sh:765
+#: git-gui.sh:821
msgid "Cannot parse Git version string:"
msgstr "Невозможно распознать строку версии Git: "
-#: git-gui.sh:783
+#: git-gui.sh:839
#, tcl-format
msgid ""
"Git version cannot be determined.\n"
@@ -60,450 +60,474 @@ msgstr ""
"\n"
"Принять '%s' как версию 1.5.0?\n"
-#: git-gui.sh:1062
+#: git-gui.sh:1128
msgid "Git directory not found:"
msgstr "Каталог Git не найден:"
-#: git-gui.sh:1069
+#: git-gui.sh:1146
msgid "Cannot move to top of working directory:"
msgstr "Невозможно перейти к корню рабочего каталога репозитория: "
-#: git-gui.sh:1076
-msgid "Cannot use funny .git directory:"
-msgstr "Каталог .git испорчен: "
+#: git-gui.sh:1154
+msgid "Cannot use bare repository:"
+msgstr "Невозможно использование репозитория без рабочего каталога:"
-#: git-gui.sh:1081
+#: git-gui.sh:1162
msgid "No working directory"
msgstr "Отсутствует рабочий каталог"
-#: git-gui.sh:1247 lib/checkout_op.tcl:305
+#: git-gui.sh:1334 lib/checkout_op.tcl:306
msgid "Refreshing file status..."
msgstr "Обновление информации о состоянии файлов..."
-#: git-gui.sh:1303
+#: git-gui.sh:1390
msgid "Scanning for modified files ..."
msgstr "Поиск измененных файлов..."
-#: git-gui.sh:1367
+#: git-gui.sh:1454
msgid "Calling prepare-commit-msg hook..."
msgstr "Вызов программы поддержки репозитория prepare-commit-msg..."
-#: git-gui.sh:1384
+#: git-gui.sh:1471
msgid "Commit declined by prepare-commit-msg hook."
msgstr ""
"Сохранение прервано программой поддержки репозитория prepare-commit-msg"
-#: git-gui.sh:1542 lib/browser.tcl:246
+#: git-gui.sh:1629 lib/browser.tcl:246
msgid "Ready."
msgstr "Готово."
-#: git-gui.sh:1726
+#: git-gui.sh:1787
#, tcl-format
msgid "Displaying only %s of %s files."
msgstr "Показано %s из %s файлов."
-#: git-gui.sh:1819
+#: git-gui.sh:1913
msgid "Unmodified"
msgstr "Не изменено"
-#: git-gui.sh:1821
+#: git-gui.sh:1915
msgid "Modified, not staged"
msgstr "Изменено, не подготовлено"
-#: git-gui.sh:1822 git-gui.sh:1830
+#: git-gui.sh:1916 git-gui.sh:1924
msgid "Staged for commit"
msgstr "Подготовлено для сохранения"
-#: git-gui.sh:1823 git-gui.sh:1831
+#: git-gui.sh:1917 git-gui.sh:1925
msgid "Portions staged for commit"
msgstr "Части, подготовленные для сохранения"
-#: git-gui.sh:1824 git-gui.sh:1832
+#: git-gui.sh:1918 git-gui.sh:1926
msgid "Staged for commit, missing"
msgstr "Подготовлено для сохранения, отсутствует"
-#: git-gui.sh:1826
+#: git-gui.sh:1920
msgid "File type changed, not staged"
msgstr "Тип файла изменён, не подготовлено"
-#: git-gui.sh:1827
+#: git-gui.sh:1921
msgid "File type changed, staged"
msgstr "Тип файла изменён, подготовлено"
-#: git-gui.sh:1829
+#: git-gui.sh:1923
msgid "Untracked, not staged"
msgstr "Не отслеживается, не подготовлено"
-#: git-gui.sh:1834
+#: git-gui.sh:1928
msgid "Missing"
msgstr "Отсутствует"
-#: git-gui.sh:1835
+#: git-gui.sh:1929
msgid "Staged for removal"
msgstr "Подготовлено для удаления"
-#: git-gui.sh:1836
+#: git-gui.sh:1930
msgid "Staged for removal, still present"
msgstr "Подготовлено для удаления, еще не удалено"
-#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
-#: git-gui.sh:1842 git-gui.sh:1843
+#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
+#: git-gui.sh:1936 git-gui.sh:1937
msgid "Requires merge resolution"
msgstr "Требуется разрешение конфликта при слиянии"
-#: git-gui.sh:1878
+#: git-gui.sh:1972
msgid "Starting gitk... please wait..."
msgstr "Запускается gitk... Подождите, пожалуйста..."
-#: git-gui.sh:1887
+#: git-gui.sh:1984
msgid "Couldn't find gitk in PATH"
msgstr "gitk не найден в PATH."
-#: git-gui.sh:2280 lib/choose_repository.tcl:36
+#: git-gui.sh:2043
+msgid "Couldn't find git gui in PATH"
+msgstr "git gui не найден в PATH."
+
+#: git-gui.sh:2455 lib/choose_repository.tcl:36
msgid "Repository"
msgstr "Репозиторий"
-#: git-gui.sh:2281
+#: git-gui.sh:2456
msgid "Edit"
msgstr "Редактировать"
-#: git-gui.sh:2283 lib/choose_rev.tcl:561
+#: git-gui.sh:2458 lib/choose_rev.tcl:561
msgid "Branch"
msgstr "Ветвь"
-#: git-gui.sh:2286 lib/choose_rev.tcl:548
+#: git-gui.sh:2461 lib/choose_rev.tcl:548
msgid "Commit@@noun"
msgstr "Состояние"
-#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
msgid "Merge"
msgstr "Слияние"
-#: git-gui.sh:2290 lib/choose_rev.tcl:557
+#: git-gui.sh:2465 lib/choose_rev.tcl:557
msgid "Remote"
msgstr "Внешние репозитории"
-#: git-gui.sh:2293
+#: git-gui.sh:2468
msgid "Tools"
msgstr "Вспомогательные операции"
-#: git-gui.sh:2302
+#: git-gui.sh:2477
msgid "Explore Working Copy"
msgstr "Просмотр рабочего каталога"
-#: git-gui.sh:2307
+#: git-gui.sh:2483
msgid "Browse Current Branch's Files"
msgstr "Просмотреть файлы текущей ветви"
-#: git-gui.sh:2311
+#: git-gui.sh:2487
msgid "Browse Branch Files..."
msgstr "Показать файлы ветви..."
-#: git-gui.sh:2316
+#: git-gui.sh:2492
msgid "Visualize Current Branch's History"
msgstr "Показать историю текущей ветви"
-#: git-gui.sh:2320
+#: git-gui.sh:2496
msgid "Visualize All Branch History"
msgstr "Показать историю всех ветвей"
-#: git-gui.sh:2327
+#: git-gui.sh:2503
#, tcl-format
msgid "Browse %s's Files"
msgstr "Показать файлы ветви %s"
-#: git-gui.sh:2329
+#: git-gui.sh:2505
#, tcl-format
msgid "Visualize %s's History"
msgstr "Показать историю ветви %s"
-#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
msgid "Database Statistics"
msgstr "Статистика базы данных"
-#: git-gui.sh:2337 lib/database.tcl:34
+#: git-gui.sh:2513 lib/database.tcl:34
msgid "Compress Database"
msgstr "Сжать базу данных"
-#: git-gui.sh:2340
+#: git-gui.sh:2516
msgid "Verify Database"
msgstr "Проверить базу данных"
-#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
-#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
+#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
msgid "Create Desktop Icon"
msgstr "Создать ярлык на рабочем столе"
-#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
msgid "Quit"
msgstr "Выход"
-#: git-gui.sh:2371
+#: git-gui.sh:2547
msgid "Undo"
msgstr "Отменить"
-#: git-gui.sh:2374
+#: git-gui.sh:2550
msgid "Redo"
msgstr "Повторить"
-#: git-gui.sh:2378 git-gui.sh:2937
+#: git-gui.sh:2554 git-gui.sh:3109
msgid "Cut"
msgstr "Вырезать"
-#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096
+#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
#: lib/console.tcl:69
msgid "Copy"
msgstr "Копировать"
-#: git-gui.sh:2384 git-gui.sh:2943
+#: git-gui.sh:2560 git-gui.sh:3115
msgid "Paste"
msgstr "Вставить"
-#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26
+#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
#: lib/remote_branch_delete.tcl:38
msgid "Delete"
msgstr "Удалить"
-#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71
+#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
msgid "Select All"
msgstr "Выделить все"
-#: git-gui.sh:2400
+#: git-gui.sh:2576
msgid "Create..."
msgstr "Создать..."
-#: git-gui.sh:2406
+#: git-gui.sh:2582
msgid "Checkout..."
msgstr "Перейти..."
-#: git-gui.sh:2412
+#: git-gui.sh:2588
msgid "Rename..."
msgstr "Переименовать..."
-#: git-gui.sh:2417
+#: git-gui.sh:2593
msgid "Delete..."
msgstr "Удалить..."
-#: git-gui.sh:2422
+#: git-gui.sh:2598
msgid "Reset..."
msgstr "Сбросить..."
-#: git-gui.sh:2432
+#: git-gui.sh:2608
msgid "Done"
msgstr "Завершено"
-#: git-gui.sh:2434
+#: git-gui.sh:2610
msgid "Commit@@verb"
msgstr "Сохранить"
-#: git-gui.sh:2443 git-gui.sh:2878
+#: git-gui.sh:2619 git-gui.sh:3050
msgid "New Commit"
msgstr "Новое состояние"
-#: git-gui.sh:2451 git-gui.sh:2885
+#: git-gui.sh:2627 git-gui.sh:3057
msgid "Amend Last Commit"
msgstr "Исправить последнее состояние"
-#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
msgid "Rescan"
msgstr "Перечитать"
-#: git-gui.sh:2467
+#: git-gui.sh:2643
msgid "Stage To Commit"
msgstr "Подготовить для сохранения"
-#: git-gui.sh:2473
+#: git-gui.sh:2649
msgid "Stage Changed Files To Commit"
msgstr "Подготовить измененные файлы для сохранения"
-#: git-gui.sh:2479
+#: git-gui.sh:2655
msgid "Unstage From Commit"
msgstr "Убрать из подготовленного"
-#: git-gui.sh:2484 lib/index.tcl:410
+#: git-gui.sh:2661 lib/index.tcl:412
msgid "Revert Changes"
msgstr "Отменить изменения"
-#: git-gui.sh:2491 git-gui.sh:3083
+#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
msgid "Show Less Context"
msgstr "Меньше контекста"
-#: git-gui.sh:2495 git-gui.sh:3087
+#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
msgid "Show More Context"
msgstr "Больше контекста"
-#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961
+#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
msgid "Sign Off"
msgstr "Вставить Signed-off-by"
-#: git-gui.sh:2518
+#: git-gui.sh:2696
msgid "Local Merge..."
msgstr "Локальное слияние..."
-#: git-gui.sh:2523
+#: git-gui.sh:2701
msgid "Abort Merge..."
msgstr "Прервать слияние..."
-#: git-gui.sh:2535 git-gui.sh:2575
+#: git-gui.sh:2713 git-gui.sh:2741
msgid "Add..."
msgstr "Добавить..."
-#: git-gui.sh:2539
+#: git-gui.sh:2717
msgid "Push..."
msgstr "Отправить..."
-#: git-gui.sh:2543
+#: git-gui.sh:2721
msgid "Delete Branch..."
msgstr "Удалить ветвь..."
-#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
-#, tcl-format
-msgid "About %s"
-msgstr "О %s"
-
-#: git-gui.sh:2557
-msgid "Preferences..."
-msgstr "Настройки..."
-
-#: git-gui.sh:2565 git-gui.sh:3129
+#: git-gui.sh:2731 git-gui.sh:3292
msgid "Options..."
msgstr "Настройки..."
-#: git-gui.sh:2576
+#: git-gui.sh:2742
msgid "Remove..."
msgstr "Удалить..."
-#: git-gui.sh:2585 lib/choose_repository.tcl:50
+#: git-gui.sh:2751 lib/choose_repository.tcl:50
msgid "Help"
msgstr "Помощь"
-#: git-gui.sh:2611
+#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#, tcl-format
+msgid "About %s"
+msgstr "О %s"
+
+#: git-gui.sh:2783
msgid "Online Documentation"
msgstr "Документация в интернете"
-#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
msgid "Show SSH Key"
msgstr "Показать ключ SSH"
-#: git-gui.sh:2721
+#: git-gui.sh:2893
#, tcl-format
msgid "fatal: cannot stat path %s: No such file or directory"
msgstr "критическая ошибка: %s: нет такого файла или каталога"
-#: git-gui.sh:2754
+#: git-gui.sh:2926
msgid "Current Branch:"
msgstr "Текущая ветвь:"
-#: git-gui.sh:2775
+#: git-gui.sh:2947
msgid "Staged Changes (Will Commit)"
msgstr "Подготовлено (будет сохранено)"
-#: git-gui.sh:2795
+#: git-gui.sh:2967
msgid "Unstaged Changes"
msgstr "Изменено (не будет сохранено)"
-#: git-gui.sh:2845
+#: git-gui.sh:3017
msgid "Stage Changed"
msgstr "Подготовить все"
-#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
msgid "Push"
msgstr "Отправить"
-#: git-gui.sh:2899
+#: git-gui.sh:3071
msgid "Initial Commit Message:"
msgstr "Комментарий к первому состоянию:"
-#: git-gui.sh:2900
+#: git-gui.sh:3072
msgid "Amended Commit Message:"
msgstr "Комментарий к исправленному состоянию:"
-#: git-gui.sh:2901
+#: git-gui.sh:3073
msgid "Amended Initial Commit Message:"
msgstr "Комментарий к исправленному первоначальному состоянию:"
-#: git-gui.sh:2902
+#: git-gui.sh:3074
msgid "Amended Merge Commit Message:"
msgstr "Комментарий к исправленному слиянию:"
-#: git-gui.sh:2903
+#: git-gui.sh:3075
msgid "Merge Commit Message:"
msgstr "Комментарий к слиянию:"
-#: git-gui.sh:2904
+#: git-gui.sh:3076
msgid "Commit Message:"
msgstr "Комментарий к состоянию:"
-#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73
+#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
msgid "Copy All"
msgstr "Копировать все"
-#: git-gui.sh:2977 lib/blame.tcl:104
+#: git-gui.sh:3149 lib/blame.tcl:104
msgid "File:"
msgstr "Файл:"
-#: git-gui.sh:3092
+#: git-gui.sh:3255
msgid "Refresh"
msgstr "Обновить"
-#: git-gui.sh:3113
+#: git-gui.sh:3276
msgid "Decrease Font Size"
msgstr "Уменьшить размер шрифта"
-#: git-gui.sh:3117
+#: git-gui.sh:3280
msgid "Increase Font Size"
msgstr "Увеличить размер шрифта"
-#: git-gui.sh:3125 lib/blame.tcl:281
+#: git-gui.sh:3288 lib/blame.tcl:281
msgid "Encoding"
msgstr "Кодировка"
-#: git-gui.sh:3136
+#: git-gui.sh:3299
msgid "Apply/Reverse Hunk"
msgstr "Применить/Убрать изменение"
-#: git-gui.sh:3141
+#: git-gui.sh:3304
msgid "Apply/Reverse Line"
msgstr "Применить/Убрать строку"
-#: git-gui.sh:3151
+#: git-gui.sh:3323
msgid "Run Merge Tool"
msgstr "Запустить программу слияния"
-#: git-gui.sh:3156
+#: git-gui.sh:3328
msgid "Use Remote Version"
msgstr "Взять внешнюю версию"
-#: git-gui.sh:3160
+#: git-gui.sh:3332
msgid "Use Local Version"
msgstr "Взять локальную версию"
-#: git-gui.sh:3164
+#: git-gui.sh:3336
msgid "Revert To Base"
msgstr "Отменить изменения"
-#: git-gui.sh:3183
+#: git-gui.sh:3354
+msgid "Visualize These Changes In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3358
+msgid "Visualize Current Branch History In The Submodule"
+msgstr "Показать историю текущей ветви подмодуля"
+
+#: git-gui.sh:3362
+msgid "Visualize All Branch History In The Submodule"
+msgstr "Показать историю всех ветвей подмодуля"
+
+#: git-gui.sh:3367
+msgid "Start git gui In The Submodule"
+msgstr ""
+
+#: git-gui.sh:3389
msgid "Unstage Hunk From Commit"
msgstr "Не сохранять часть"
-#: git-gui.sh:3184
+#: git-gui.sh:3391
+msgid "Unstage Lines From Commit"
+msgstr "Убрать строки из подготовленного"
+
+#: git-gui.sh:3393
msgid "Unstage Line From Commit"
msgstr "Убрать строку из подготовленного"
-#: git-gui.sh:3186
+#: git-gui.sh:3396
msgid "Stage Hunk For Commit"
msgstr "Подготовить часть для сохранения"
-#: git-gui.sh:3187
+#: git-gui.sh:3398
+msgid "Stage Lines For Commit"
+msgstr "Подготовить строки для сохранения"
+
+#: git-gui.sh:3400
msgid "Stage Line For Commit"
msgstr "Подготовить строку для сохранения"
-#: git-gui.sh:3210
+#: git-gui.sh:3424
msgid "Initializing..."
msgstr "Инициализация..."
-#: git-gui.sh:3315
+#: git-gui.sh:3541
#, tcl-format
msgid ""
"Possible environment issues exist.\n"
@@ -520,7 +544,7 @@ msgstr ""
"запущенными из %s\n"
"\n"
-#: git-gui.sh:3345
+#: git-gui.sh:3570
msgid ""
"\n"
"This is due to a known issue with the\n"
@@ -530,7 +554,7 @@ msgstr ""
"Это известная проблема с Tcl,\n"
"распространяемым Cygwin."
-#: git-gui.sh:3350
+#: git-gui.sh:3575
#, tcl-format
msgid ""
"\n"
@@ -640,7 +664,7 @@ msgstr "Невозможно найти состояние предка:"
msgid "Unable to display parent"
msgstr "Не могу показать предка"
-#: lib/blame.tcl:1091 lib/diff.tcl:297
+#: lib/blame.tcl:1091 lib/diff.tcl:320
msgid "Error loading diff:"
msgstr "Ошибка загрузки изменений:"
@@ -666,7 +690,7 @@ msgstr "Перейти"
#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
#: lib/transport.tcl:108
@@ -697,7 +721,7 @@ msgstr "Создание ветви"
msgid "Create New Branch"
msgstr "Создать новую ветвь"
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
msgid "Create"
msgstr "Создать"
@@ -729,7 +753,7 @@ msgstr "Нет"
msgid "Fast Forward Only"
msgstr "Только Fast Forward"
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
msgid "Reset"
msgstr "Сброс"
@@ -771,8 +795,8 @@ msgstr "Локальные ветви"
msgid "Delete Only If Merged Into"
msgstr "Удалить только в случае, если было слияние с"
-#: lib/branch_delete.tcl:54
-msgid "Always (Do not perform merge test.)"
+#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+msgid "Always (Do not perform merge checks)"
msgstr "Всегда (не выполнять проверку на слияние)"
#: lib/branch_delete.tcl:103
@@ -780,6 +804,16 @@ msgstr "Всегда (не выполнять проверку на слияни
msgid "The following branches are not completely merged into %s:"
msgstr "Ветви, которые не полностью сливаются с %s:"
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Восстановить удаленные ветви сложно.\n"
+"\n"
+"Продолжить?"
+
#: lib/branch_delete.tcl:141
#, tcl-format
msgid ""
@@ -809,7 +843,7 @@ msgstr "Новое название:"
msgid "Please select a branch to rename."
msgstr "Укажите ветвь для переименования."
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
#, tcl-format
msgid "Branch '%s' already exists."
msgstr "Ветвь '%s' уже существует."
@@ -840,38 +874,38 @@ msgstr "[На уровень выше]"
msgid "Browse Branch Files"
msgstr "Показать файлы ветви"
-#: lib/browser.tcl:278 lib/choose_repository.tcl:394
-#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
-#: lib/choose_repository.tcl:995
+#: lib/browser.tcl:278 lib/choose_repository.tcl:398
+#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
+#: lib/choose_repository.tcl:1028
msgid "Browse"
msgstr "Показать"
-#: lib/checkout_op.tcl:84
+#: lib/checkout_op.tcl:85
#, tcl-format
msgid "Fetching %s from %s"
msgstr "Получение %s из %s "
-#: lib/checkout_op.tcl:132
+#: lib/checkout_op.tcl:133
#, tcl-format
msgid "fatal: Cannot resolve %s"
msgstr "критическая ошибка: невозможно разрешить %s"
-#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
#: lib/sshkey.tcl:53
msgid "Close"
msgstr "Закрыть"
-#: lib/checkout_op.tcl:174
+#: lib/checkout_op.tcl:175
#, tcl-format
msgid "Branch '%s' does not exist."
msgstr "Ветвь '%s' не существует "
-#: lib/checkout_op.tcl:193
+#: lib/checkout_op.tcl:194
#, tcl-format
msgid "Failed to configure simplified git-pull for '%s'."
msgstr "Ошибка создания упрощённой конфигурации git pull для '%s'."
-#: lib/checkout_op.tcl:228
+#: lib/checkout_op.tcl:229
#, tcl-format
msgid ""
"Branch '%s' already exists.\n"
@@ -884,21 +918,21 @@ msgstr ""
"Она не может быть прокручена(fast-forward) к %s.\n"
"Требуется слияние."
-#: lib/checkout_op.tcl:242
+#: lib/checkout_op.tcl:243
#, tcl-format
msgid "Merge strategy '%s' not supported."
msgstr "Неизвестная стратегия слияния: '%s'."
-#: lib/checkout_op.tcl:261
+#: lib/checkout_op.tcl:262
#, tcl-format
msgid "Failed to update '%s'."
msgstr "Не удалось обновить '%s'."
-#: lib/checkout_op.tcl:273
+#: lib/checkout_op.tcl:274
msgid "Staging area (index) is already locked."
msgstr "Рабочая область заблокирована другим процессом."
-#: lib/checkout_op.tcl:288
+#: lib/checkout_op.tcl:289
msgid ""
"Last scanned state does not match repository state.\n"
"\n"
@@ -914,30 +948,30 @@ msgstr ""
"\n"
"Это будет сделано сейчас автоматически.\n"
-#: lib/checkout_op.tcl:344
+#: lib/checkout_op.tcl:345
#, tcl-format
msgid "Updating working directory to '%s'..."
msgstr "Обновление рабочего каталога из '%s'..."
-#: lib/checkout_op.tcl:345
+#: lib/checkout_op.tcl:346
msgid "files checked out"
msgstr "файлы извлечены"
-#: lib/checkout_op.tcl:375
+#: lib/checkout_op.tcl:376
#, tcl-format
msgid "Aborted checkout of '%s' (file level merging is required)."
msgstr "Прерван переход на '%s' (требуется слияние содержания файлов)"
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
msgid "File level merge required."
msgstr "Требуется слияние содержания файлов."
-#: lib/checkout_op.tcl:380
+#: lib/checkout_op.tcl:381
#, tcl-format
msgid "Staying on branch '%s'."
msgstr "Ветвь '%s' остается текущей."
-#: lib/checkout_op.tcl:451
+#: lib/checkout_op.tcl:452
msgid ""
"You are no longer on a local branch.\n"
"\n"
@@ -949,30 +983,30 @@ msgstr ""
"Если вы хотите снова вернуться к какой-нибудь ветви, создайте ее сейчас, "
"начиная с 'Текущего отсоединенного состояния'."
-#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
#, tcl-format
msgid "Checked out '%s'."
msgstr "Ветвь '%s' сделана текущей."
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:535
#, tcl-format
msgid "Resetting '%s' to '%s' will lose the following commits:"
msgstr "Сброс '%s' в '%s' приведет к потере следующих сохраненных состояний: "
-#: lib/checkout_op.tcl:522
+#: lib/checkout_op.tcl:557
msgid "Recovering lost commits may not be easy."
msgstr "Восстановить потерянные сохраненные состояния будет сложно."
-#: lib/checkout_op.tcl:527
+#: lib/checkout_op.tcl:562
#, tcl-format
msgid "Reset '%s'?"
msgstr "Сбросить '%s'?"
-#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
msgid "Visualize"
msgstr "Наглядно"
-#: lib/checkout_op.tcl:600
+#: lib/checkout_op.tcl:635
#, tcl-format
msgid ""
"Failed to set current branch.\n"
@@ -1017,7 +1051,7 @@ msgstr ""
msgid "Git Gui"
msgstr "Git Gui"
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
msgid "Create New Repository"
msgstr "Создать новый репозиторий"
@@ -1025,7 +1059,7 @@ msgstr "Создать новый репозиторий"
msgid "New..."
msgstr "Новый..."
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
msgid "Clone Existing Repository"
msgstr "Склонировать существующий репозиторий"
@@ -1033,7 +1067,7 @@ msgstr "Склонировать существующий репозиторий
msgid "Clone..."
msgstr "Склонировать..."
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
msgid "Open Existing Repository"
msgstr "Выбрать существующий репозиторий"
@@ -1049,194 +1083,194 @@ msgstr "Недавние репозитории"
msgid "Open Recent Repository:"
msgstr "Открыть последний репозиторий"
-#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
-#: lib/choose_repository.tcl:316
+#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
+#: lib/choose_repository.tcl:320
#, tcl-format
msgid "Failed to create repository %s:"
msgstr "Не удалось создать репозиторий %s:"
-#: lib/choose_repository.tcl:387
+#: lib/choose_repository.tcl:391
msgid "Directory:"
msgstr "Каталог:"
-#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
-#: lib/choose_repository.tcl:1017
+#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1052
msgid "Git Repository"
msgstr "Репозиторий"
-#: lib/choose_repository.tcl:442
+#: lib/choose_repository.tcl:448
#, tcl-format
msgid "Directory %s already exists."
msgstr "Каталог '%s' уже существует."
-#: lib/choose_repository.tcl:446
+#: lib/choose_repository.tcl:452
#, tcl-format
msgid "File %s already exists."
msgstr "Файл '%s' уже существует."
-#: lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:466
msgid "Clone"
msgstr "Склонировать"
-#: lib/choose_repository.tcl:473
+#: lib/choose_repository.tcl:479
msgid "Source Location:"
msgstr "Исходное положение:"
-#: lib/choose_repository.tcl:484
+#: lib/choose_repository.tcl:490
msgid "Target Directory:"
msgstr "Каталог назначения:"
-#: lib/choose_repository.tcl:496
+#: lib/choose_repository.tcl:502
msgid "Clone Type:"
msgstr "Тип клона:"
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:508
msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
msgstr "Стандартный (Быстрый, полуизбыточный, \"жесткие\" ссылки)"
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:514
msgid "Full Copy (Slower, Redundant Backup)"
msgstr "Полная копия (Медленный, создает резервную копию)"
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:520
msgid "Shared (Fastest, Not Recommended, No Backup)"
msgstr "Общий (Самый быстрый, не рекомендуется, без резервной копии)"
-#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
-#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
-#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
+#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
+#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
#, tcl-format
msgid "Not a Git repository: %s"
msgstr "Каталог не является репозиторием: %s"
-#: lib/choose_repository.tcl:586
+#: lib/choose_repository.tcl:592
msgid "Standard only available for local repository."
msgstr "Стандартный клон возможен только для локального репозитория."
-#: lib/choose_repository.tcl:590
+#: lib/choose_repository.tcl:596
msgid "Shared only available for local repository."
msgstr "Общий клон возможен только для локального репозитория."
-#: lib/choose_repository.tcl:611
+#: lib/choose_repository.tcl:617
#, tcl-format
msgid "Location %s already exists."
msgstr "Путь '%s' уже существует."
-#: lib/choose_repository.tcl:622
+#: lib/choose_repository.tcl:628
msgid "Failed to configure origin"
msgstr "Не могу сконфигурировать исходный репозиторий."
-#: lib/choose_repository.tcl:634
+#: lib/choose_repository.tcl:640
msgid "Counting objects"
msgstr "Считаю объекты"
-#: lib/choose_repository.tcl:635
+#: lib/choose_repository.tcl:641
msgid "buckets"
msgstr ""
-#: lib/choose_repository.tcl:659
+#: lib/choose_repository.tcl:665
#, tcl-format
msgid "Unable to copy objects/info/alternates: %s"
msgstr "Не могу скопировать objects/info/alternates: %s"
-#: lib/choose_repository.tcl:695
+#: lib/choose_repository.tcl:701
#, tcl-format
msgid "Nothing to clone from %s."
msgstr "Нечего клонировать с %s."
-#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
-#: lib/choose_repository.tcl:923
+#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
+#: lib/choose_repository.tcl:929
msgid "The 'master' branch has not been initialized."
msgstr "Не инициализирована ветвь 'master'."
-#: lib/choose_repository.tcl:710
+#: lib/choose_repository.tcl:716
msgid "Hardlinks are unavailable. Falling back to copying."
msgstr "\"Жесткие ссылки\" недоступны. Будет использовано копирование."
-#: lib/choose_repository.tcl:722
+#: lib/choose_repository.tcl:728
#, tcl-format
msgid "Cloning from %s"
msgstr "Клонирование %s"
-#: lib/choose_repository.tcl:753
+#: lib/choose_repository.tcl:759
msgid "Copying objects"
msgstr "Копирование objects"
-#: lib/choose_repository.tcl:754
+#: lib/choose_repository.tcl:760
msgid "KiB"
msgstr "КБ"
-#: lib/choose_repository.tcl:778
+#: lib/choose_repository.tcl:784
#, tcl-format
msgid "Unable to copy object: %s"
msgstr "Не могу скопировать объект: %s"
-#: lib/choose_repository.tcl:788
+#: lib/choose_repository.tcl:794
msgid "Linking objects"
msgstr "Создание ссылок на objects"
-#: lib/choose_repository.tcl:789
+#: lib/choose_repository.tcl:795
msgid "objects"
msgstr "объекты"
-#: lib/choose_repository.tcl:797
+#: lib/choose_repository.tcl:803
#, tcl-format
msgid "Unable to hardlink object: %s"
msgstr "Не могу \"жестко связать\" объект: %s"
-#: lib/choose_repository.tcl:852
+#: lib/choose_repository.tcl:858
msgid "Cannot fetch branches and objects. See console output for details."
msgstr ""
"Не могу получить ветви и объекты. Дополнительная информация на консоли."
-#: lib/choose_repository.tcl:863
+#: lib/choose_repository.tcl:869
msgid "Cannot fetch tags. See console output for details."
msgstr "Не могу получить метки. Дополнительная информация на консоли."
-#: lib/choose_repository.tcl:887
+#: lib/choose_repository.tcl:893
msgid "Cannot determine HEAD. See console output for details."
msgstr "Не могу определить HEAD. Дополнительная информация на консоли."
-#: lib/choose_repository.tcl:896
+#: lib/choose_repository.tcl:902
#, tcl-format
msgid "Unable to cleanup %s"
msgstr "Не могу очистить %s"
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:908
msgid "Clone failed."
msgstr "Клонирование не удалось."
-#: lib/choose_repository.tcl:909
+#: lib/choose_repository.tcl:915
msgid "No default branch obtained."
msgstr "Не было получено ветви по умолчанию."
-#: lib/choose_repository.tcl:920
+#: lib/choose_repository.tcl:926
#, tcl-format
msgid "Cannot resolve %s as a commit."
msgstr "Не могу распознать %s как состояние."
-#: lib/choose_repository.tcl:932
+#: lib/choose_repository.tcl:938
msgid "Creating working directory"
msgstr "Создаю рабочий каталог"
-#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
-#: lib/index.tcl:196
+#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
+#: lib/index.tcl:198
msgid "files"
msgstr "файлов"
-#: lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:968
msgid "Initial file checkout failed."
msgstr "Не удалось получить начальное состояние файлов репозитория."
-#: lib/choose_repository.tcl:978
+#: lib/choose_repository.tcl:1011
msgid "Open"
msgstr "Открыть"
-#: lib/choose_repository.tcl:988
+#: lib/choose_repository.tcl:1021
msgid "Repository:"
msgstr "Репозиторий:"
-#: lib/choose_repository.tcl:1037
+#: lib/choose_repository.tcl:1072
#, tcl-format
msgid "Failed to open repository %s:"
msgstr "Не удалось открыть репозиторий %s:"
@@ -1318,7 +1352,12 @@ msgstr "Невозможно получить информацию об авто
msgid "Invalid GIT_COMMITTER_IDENT:"
msgstr "Неверный GIT_COMMITTER_IDENT:"
-#: lib/commit.tcl:132
+#: lib/commit.tcl:129
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "предупреждение: Tcl не поддерживает кодировку '%s'."
+
+#: lib/commit.tcl:149
msgid ""
"Last scanned state does not match repository state.\n"
"\n"
@@ -1334,7 +1373,7 @@ msgstr ""
"\n"
"Это будет сделано сейчас автоматически.\n"
-#: lib/commit.tcl:155
+#: lib/commit.tcl:172
#, tcl-format
msgid ""
"Unmerged files cannot be committed.\n"
@@ -1342,12 +1381,12 @@ msgid ""
"File %s has merge conflicts. You must resolve them and stage the file "
"before committing.\n"
msgstr ""
-"Нельзя сохранить файлы с незавершённой операцей слияния.\n"
+"Нельзя сохранить файлы с незавершённой операцией слияния.\n"
"\n"
"Для файла %s возник конфликт слияния. Разрешите конфликт и добавьте к "
"подготовленным файлам перед сохранением.\n"
-#: lib/commit.tcl:163
+#: lib/commit.tcl:180
#, tcl-format
msgid ""
"Unknown file state %s detected.\n"
@@ -1358,7 +1397,7 @@ msgstr ""
"\n"
"Файл %s не может быть сохранен данной программой.\n"
-#: lib/commit.tcl:171
+#: lib/commit.tcl:188
msgid ""
"No changes to commit.\n"
"\n"
@@ -1368,7 +1407,7 @@ msgstr ""
"\n"
"Подготовьте хотя бы один файл до создания сохраненного состояния.\n"
-#: lib/commit.tcl:186
+#: lib/commit.tcl:203
msgid ""
"Please supply a commit message.\n"
"\n"
@@ -1386,45 +1425,40 @@ msgstr ""
"- вторая строка пустая\n"
"- оставшиеся строки: опишите, что дают ваши изменения.\n"
-#: lib/commit.tcl:210
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "предупреждение: Tcl не поддерживает кодировку '%s'."
-
-#: lib/commit.tcl:226
+#: lib/commit.tcl:234
msgid "Calling pre-commit hook..."
msgstr "Вызов программы поддержки репозитория pre-commit..."
-#: lib/commit.tcl:241
+#: lib/commit.tcl:249
msgid "Commit declined by pre-commit hook."
msgstr "Сохранение прервано программой поддержки репозитория pre-commit"
-#: lib/commit.tcl:264
+#: lib/commit.tcl:272
msgid "Calling commit-msg hook..."
msgstr "Вызов программы поддержки репозитория commit-msg..."
-#: lib/commit.tcl:279
+#: lib/commit.tcl:287
msgid "Commit declined by commit-msg hook."
msgstr "Сохранение прервано программой поддержки репозитория commit-msg"
-#: lib/commit.tcl:292
+#: lib/commit.tcl:300
msgid "Committing changes..."
msgstr "Сохранение изменений..."
-#: lib/commit.tcl:308
+#: lib/commit.tcl:316
msgid "write-tree failed:"
msgstr "Программа write-tree завершилась с ошибкой:"
-#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373
+#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
msgid "Commit failed."
msgstr "Сохранить состояние не удалось."
-#: lib/commit.tcl:326
+#: lib/commit.tcl:334
#, tcl-format
msgid "Commit %s appears to be corrupt"
msgstr "Состояние %s выглядит поврежденным"
-#: lib/commit.tcl:331
+#: lib/commit.tcl:339
msgid ""
"No changes to commit.\n"
"\n"
@@ -1438,19 +1472,19 @@ msgstr ""
"\n"
"Сейчас автоматически запустится перечитывание репозитория.\n"
-#: lib/commit.tcl:338
+#: lib/commit.tcl:346
msgid "No changes to commit."
-msgstr "Отуствуют измения для сохранения."
+msgstr "Отсутствуют изменения для сохранения."
-#: lib/commit.tcl:352
+#: lib/commit.tcl:360
msgid "commit-tree failed:"
msgstr "Программа commit-tree завершилась с ошибкой:"
-#: lib/commit.tcl:372
+#: lib/commit.tcl:381
msgid "update-ref failed:"
msgstr "Программа update-ref завершилась с ошибкой:"
-#: lib/commit.tcl:460
+#: lib/commit.tcl:469
#, tcl-format
msgid "Created commit %s: %s"
msgstr "Создано состояние %s: %s "
@@ -1503,20 +1537,19 @@ msgstr "Сжатие базы объектов"
msgid "Verifying the object database with fsck-objects"
msgstr "Проверка базы объектов при помощи fsck"
-#: lib/database.tcl:108
+#: lib/database.tcl:107
#, tcl-format
msgid ""
"This repository currently has approximately %i loose objects.\n"
"\n"
"To maintain optimal performance it is strongly recommended that you compress "
-"the database when more than %i loose objects exist.\n"
+"the database.\n"
"\n"
"Compress the database now?"
msgstr ""
"Этот репозиторий сейчас содержит примерно %i свободных объектов\n"
"\n"
-"Для лучшей производительности рекомендуется сжать базу данных, когда есть "
-"более %i несвязанных объектов.\n"
+"Для лучшей производительности рекомендуется сжать базу данных.\n"
"\n"
"Сжать базу данных сейчас?"
@@ -1525,7 +1558,7 @@ msgstr ""
msgid "Invalid date from Git: %s"
msgstr "Неправильная дата в репозитории: %s"
-#: lib/diff.tcl:59
+#: lib/diff.tcl:64
#, tcl-format
msgid ""
"No differences detected.\n"
@@ -1540,19 +1573,19 @@ msgid ""
msgstr ""
"Изменений не обнаружено.\n"
"\n"
-"в %s отутствуют изменения.\n"
+"в %s отсутствуют изменения.\n"
"\n"
"Дата изменения файла была обновлена другой программой, но содержимое файла "
"осталось прежним.\n"
"\n"
"Сейчас будет запущено перечитывание репозитория, чтобы найти подобные файлы."
-#: lib/diff.tcl:99
+#: lib/diff.tcl:104
#, tcl-format
msgid "Loading diff of %s..."
msgstr "Загрузка изменений в %s..."
-#: lib/diff.tcl:120
+#: lib/diff.tcl:125
msgid ""
"LOCAL: deleted\n"
"REMOTE:\n"
@@ -1560,7 +1593,7 @@ msgstr ""
"ЛОКАЛЬНО: удалён\n"
"ВНЕШНИЙ:\n"
-#: lib/diff.tcl:125
+#: lib/diff.tcl:130
msgid ""
"REMOTE: deleted\n"
"LOCAL:\n"
@@ -1568,41 +1601,41 @@ msgstr ""
"ВНЕШНИЙ: удалён\n"
"ЛОКАЛЬНО:\n"
-#: lib/diff.tcl:132
+#: lib/diff.tcl:137
msgid "LOCAL:\n"
msgstr "ЛОКАЛЬНО:\n"
-#: lib/diff.tcl:135
+#: lib/diff.tcl:140
msgid "REMOTE:\n"
msgstr "ВНЕШНИЙ:\n"
-#: lib/diff.tcl:197 lib/diff.tcl:296
+#: lib/diff.tcl:202 lib/diff.tcl:319
#, tcl-format
msgid "Unable to display %s"
msgstr "Не могу показать %s"
-#: lib/diff.tcl:198
+#: lib/diff.tcl:203
msgid "Error loading file:"
msgstr "Ошибка загрузки файла:"
-#: lib/diff.tcl:205
+#: lib/diff.tcl:210
msgid "Git Repository (subproject)"
msgstr "Репозиторий Git (подпроект)"
-#: lib/diff.tcl:217
+#: lib/diff.tcl:222
msgid "* Binary file (not showing content)."
msgstr "* Двоичный файл (содержимое не показано)"
-#: lib/diff.tcl:222
+#: lib/diff.tcl:227
#, tcl-format
msgid ""
"* Untracked file is %d bytes.\n"
"* Showing only first %d bytes.\n"
msgstr ""
-"* Размер неподготовленого файла %d байт.\n"
+"* Размер неподготовленного файла %d байт.\n"
"* Показано первых %d байт.\n"
-#: lib/diff.tcl:228
+#: lib/diff.tcl:233
#, tcl-format
msgid ""
"\n"
@@ -1610,22 +1643,22 @@ msgid ""
"* To see the entire file, use an external editor.\n"
msgstr ""
"\n"
-"* Неподготовленый файл обрезан: %s.\n"
+"* Неподготовленный файл обрезан: %s.\n"
"* Чтобы увидеть весь файл, используйте программу-редактор.\n"
-#: lib/diff.tcl:436
+#: lib/diff.tcl:482
msgid "Failed to unstage selected hunk."
msgstr "Не удалось исключить выбранную часть."
-#: lib/diff.tcl:443
+#: lib/diff.tcl:489
msgid "Failed to stage selected hunk."
msgstr "Не удалось подготовить к сохранению выбранную часть."
-#: lib/diff.tcl:509
+#: lib/diff.tcl:568
msgid "Failed to unstage selected line."
msgstr "Не удалось исключить выбранную строку."
-#: lib/diff.tcl:517
+#: lib/diff.tcl:576
msgid "Failed to stage selected line."
msgstr "Не удалось подготовить к сохранению выбранную строку."
@@ -1662,15 +1695,15 @@ msgstr "Не удалось разблокировать индекс"
msgid "Index Error"
msgstr "Ошибка в индексе"
-#: lib/index.tcl:21
+#: lib/index.tcl:17
msgid ""
"Updating the Git index failed. A rescan will be automatically started to "
"resynchronize git-gui."
msgstr ""
-"Не удалось обновить индекс Git. Состояние репозитория будетперечитано "
+"Не удалось обновить индекс Git. Состояние репозитория будет перечитано "
"автоматически."
-#: lib/index.tcl:27
+#: lib/index.tcl:28
msgid "Continue"
msgstr "Продолжить"
@@ -1678,45 +1711,45 @@ msgstr "Продолжить"
msgid "Unlock Index"
msgstr "Разблокировать индекс"
-#: lib/index.tcl:287
+#: lib/index.tcl:289
#, tcl-format
msgid "Unstaging %s from commit"
msgstr "Удаление %s из подготовленного"
-#: lib/index.tcl:326
+#: lib/index.tcl:328
msgid "Ready to commit."
msgstr "Подготовлено для сохранения"
-#: lib/index.tcl:339
+#: lib/index.tcl:341
#, tcl-format
msgid "Adding %s"
msgstr "Добавление %s..."
-#: lib/index.tcl:396
+#: lib/index.tcl:398
#, tcl-format
msgid "Revert changes in file %s?"
msgstr "Отменить изменения в файле %s?"
-#: lib/index.tcl:398
+#: lib/index.tcl:400
#, tcl-format
msgid "Revert changes in these %i files?"
msgstr "Отменить изменения в %i файле(-ах)?"
-#: lib/index.tcl:406
+#: lib/index.tcl:408
msgid "Any unstaged changes will be permanently lost by the revert."
msgstr ""
"Любые изменения, не подготовленные к сохранению, будут потеряны при данной "
"операции."
-#: lib/index.tcl:409
+#: lib/index.tcl:411
msgid "Do Nothing"
msgstr "Ничего не делать"
-#: lib/index.tcl:427
+#: lib/index.tcl:429
msgid "Reverting selected files"
-msgstr "Удаление изменений в выбраных файлах"
+msgstr "Удаление изменений в выбранных файлах"
-#: lib/index.tcl:431
+#: lib/index.tcl:433
#, tcl-format
msgid "Reverting %s"
msgstr "Отмена изменений в %s"
@@ -1778,7 +1811,7 @@ msgstr ""
"\n"
"Файл %s изменен.\n"
"\n"
-"Подготовьте и сохраните измения перед началом слияния. В случае "
+"Подготовьте и сохраните изменения перед началом слияния. В случае "
"необходимости это позволит прервать операцию слияния.\n"
#: lib/merge.tcl:107
@@ -1893,7 +1926,7 @@ msgstr ""
#, tcl-format
msgid "File %s seems to have unresolved conflicts, still stage?"
msgstr ""
-"Файл %s кажется содержит необработаные конфликты. Продолжить подготовку к "
+"Файл %s, похоже, содержит необработанные конфликты. Продолжить подготовку к "
"сохранению?"
#: lib/mergetool.tcl:60
@@ -2152,7 +2185,7 @@ msgstr "Получение %s"
#: lib/remote_add.tcl:157
#, tcl-format
msgid "Do not know how to initialize repository at location '%s'."
-msgstr "Невозможно инициалировать репозиторий в '%s'."
+msgstr "Невозможно инициализировать репозиторий в '%s'."
#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
#: lib/transport.tcl:81
@@ -2179,7 +2212,7 @@ msgstr "внешний:"
#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
msgid "Arbitrary Location:"
-msgstr "Указаное положение:"
+msgstr "Указанное положение:"
#: lib/remote_branch_delete.tcl:84
msgid "Branches"
@@ -2193,10 +2226,6 @@ msgstr "Удалить только в случае, если"
msgid "Merged Into:"
msgstr "Слияние с:"
-#: lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Всегда (не выполнять проверку на слияние)"
-
#: lib/remote_branch_delete.tcl:152
msgid "A branch is required for 'Merged Into'."
msgstr "Для опции 'Слияние с' требуется указать ветвь."
@@ -2225,26 +2254,16 @@ msgstr ""
msgid "Please select one or more branches to delete."
msgstr "Укажите одну или несколько ветвей для удаления."
-#: lib/remote_branch_delete.tcl:216
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Восстановить удаленные ветви сложно.\n"
-"\n"
-"Продолжить?"
-
#: lib/remote_branch_delete.tcl:226
#, tcl-format
msgid "Deleting branches from %s"
msgstr "Удаление ветвей из %s"
-#: lib/remote_branch_delete.tcl:286
+#: lib/remote_branch_delete.tcl:292
msgid "No repository selected."
msgstr "Не указан репозиторий."
-#: lib/remote_branch_delete.tcl:291
+#: lib/remote_branch_delete.tcl:297
#, tcl-format
msgid "Scanning %s..."
msgstr "Перечитывание %s... "
@@ -2265,11 +2284,11 @@ msgstr "Обратно"
msgid "Case-Sensitive"
msgstr "Игн. большие/маленькие"
-#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
msgid "Cannot write shortcut:"
msgstr "Невозможно записать ссылку:"
-#: lib/shortcut.tcl:136
+#: lib/shortcut.tcl:137
msgid "Cannot write icon:"
msgstr "Невозможно записать значок:"
@@ -2292,11 +2311,11 @@ msgstr "Словарь вернут к %s."
#: lib/spellcheck.tcl:73
msgid "Spell checker silently failed on startup"
-msgstr "Программа проверки правописания не смогла запустится"
+msgstr "Программа проверки правописания не смогла запуститься"
#: lib/spellcheck.tcl:80
msgid "Unrecognized spell checker"
-msgstr "Нераспознаная программа проверки правописания"
+msgstr "Нераспознанная программа проверки правописания"
#: lib/spellcheck.tcl:186
msgid "No Suggestions"
@@ -2412,7 +2431,7 @@ msgstr "Описание вспомогательной операции"
#: lib/tools_dlg.tcl:48
msgid "Use '/' separators to create a submenu tree:"
-msgstr "Испольуйте '/' для создания подменю"
+msgstr "Используйте '/' для создания подменю"
#: lib/tools_dlg.tcl:61
msgid "Command:"
diff --git a/git-gui/po/sv.po b/git-gui/po/sv.po
index d8d73ac..8bd3c5d 100644
--- a/git-gui/po/sv.po
+++ b/git-gui/po/sv.po
@@ -8,41 +8,41 @@ msgid ""
msgstr ""
"Project-Id-Version: sv\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-26 15:47-0800\n"
-"PO-Revision-Date: 2010-01-28 13:57+0100\n"
+"POT-Creation-Date: 2010-09-12 21:11+0100\n"
+"PO-Revision-Date: 2010-09-12 21:12+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit"
-#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
-#: git-gui.sh:922
-msgid "git-gui: fatal error"
-msgstr "git-gui: ödesdigert fel"
-
-#: git-gui.sh:743
+#: git-gui.sh:781
#, tcl-format
msgid "Invalid font specified in %s:"
msgstr "Ogiltigt teckensnitt angivet i %s:"
-#: git-gui.sh:779
+#: git-gui.sh:831
msgid "Main Font"
msgstr "Huvudteckensnitt"
-#: git-gui.sh:780
+#: git-gui.sh:832
msgid "Diff/Console Font"
msgstr "Diff/konsolteckensnitt"
-#: git-gui.sh:794
+#: git-gui.sh:845 git-gui.sh:859 git-gui.sh:872 git-gui.sh:955 git-gui.sh:974
+#: git-gui.sh:2964
+msgid "git-gui: fatal error"
+msgstr "git-gui: ödesdigert fel"
+
+#: git-gui.sh:846
msgid "Cannot find git in PATH."
msgstr "Hittar inte git i PATH."
-#: git-gui.sh:821
+#: git-gui.sh:873
msgid "Cannot parse Git version string:"
msgstr "Kan inte tolka versionssträng från Git:"
-#: git-gui.sh:839
+#: git-gui.sh:891
#, tcl-format
msgid ""
"Git version cannot be determined.\n"
@@ -61,478 +61,478 @@ msgstr ""
"\n"
"Anta att \"%s\" är version 1.5.0?\n"
-#: git-gui.sh:1128
+#: git-gui.sh:1180
msgid "Git directory not found:"
msgstr "Git-katalogen hittades inte:"
-#: git-gui.sh:1146
+#: git-gui.sh:1201
msgid "Cannot move to top of working directory:"
msgstr "Kan inte gå till början på arbetskatalogen:"
-#: git-gui.sh:1154
+#: git-gui.sh:1209
msgid "Cannot use bare repository:"
msgstr "Kan inte använda naket arkiv:"
-#: git-gui.sh:1162
+#: git-gui.sh:1217
msgid "No working directory"
msgstr "Ingen arbetskatalog"
-#: git-gui.sh:1334 lib/checkout_op.tcl:306
+#: git-gui.sh:1389 lib/checkout_op.tcl:306
msgid "Refreshing file status..."
msgstr "Uppdaterar filstatus..."
-#: git-gui.sh:1390
+#: git-gui.sh:1445
msgid "Scanning for modified files ..."
msgstr "Söker efter ändrade filer..."
-#: git-gui.sh:1454
+#: git-gui.sh:1509
msgid "Calling prepare-commit-msg hook..."
msgstr ""
"Anropar kroken för förberedelse av incheckningsmeddelande (prepare-commit-"
"msg)..."
-#: git-gui.sh:1471
+#: git-gui.sh:1526
msgid "Commit declined by prepare-commit-msg hook."
msgstr ""
"Incheckningen avvisades av kroken för förberedelse av incheckningsmeddelande "
"(prepare-commit-msg)."
-#: git-gui.sh:1629 lib/browser.tcl:246
+#: git-gui.sh:1684 lib/browser.tcl:246
msgid "Ready."
msgstr "Klar."
-#: git-gui.sh:1787
+#: git-gui.sh:1842
#, tcl-format
msgid "Displaying only %s of %s files."
msgstr "Visar endast %s av %s filer."
-#: git-gui.sh:1913
+#: git-gui.sh:1968
msgid "Unmodified"
msgstr "Oförändrade"
-#: git-gui.sh:1915
+#: git-gui.sh:1970
msgid "Modified, not staged"
msgstr "Förändrade, ej köade"
-#: git-gui.sh:1916 git-gui.sh:1924
+#: git-gui.sh:1971 git-gui.sh:1979
msgid "Staged for commit"
msgstr "Köade för incheckning"
-#: git-gui.sh:1917 git-gui.sh:1925
+#: git-gui.sh:1972 git-gui.sh:1980
msgid "Portions staged for commit"
msgstr "Delar köade för incheckning"
-#: git-gui.sh:1918 git-gui.sh:1926
+#: git-gui.sh:1973 git-gui.sh:1981
msgid "Staged for commit, missing"
msgstr "Köade för incheckning, saknade"
-#: git-gui.sh:1920
+#: git-gui.sh:1975
msgid "File type changed, not staged"
msgstr "Filtyp ändrad, ej köade"
-#: git-gui.sh:1921
+#: git-gui.sh:1976
msgid "File type changed, staged"
msgstr "Filtyp ändrad, köade"
-#: git-gui.sh:1923
+#: git-gui.sh:1978
msgid "Untracked, not staged"
msgstr "Ej spårade, ej köade"
-#: git-gui.sh:1928
+#: git-gui.sh:1983
msgid "Missing"
msgstr "Saknade"
-#: git-gui.sh:1929
+#: git-gui.sh:1984
msgid "Staged for removal"
msgstr "Köade för borttagning"
-#: git-gui.sh:1930
+#: git-gui.sh:1985
msgid "Staged for removal, still present"
msgstr "Köade för borttagning, fortfarande närvarande"
-#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
-#: git-gui.sh:1936 git-gui.sh:1937
+#: git-gui.sh:1987 git-gui.sh:1988 git-gui.sh:1989 git-gui.sh:1990
+#: git-gui.sh:1991 git-gui.sh:1992
msgid "Requires merge resolution"
msgstr "Kräver konflikthantering efter sammanslagning"
-#: git-gui.sh:1972
+#: git-gui.sh:2027
msgid "Starting gitk... please wait..."
msgstr "Startar gitk... vänta..."
-#: git-gui.sh:1984
+#: git-gui.sh:2039
msgid "Couldn't find gitk in PATH"
msgstr "Hittade inte gitk i PATH."
-#: git-gui.sh:2043
+#: git-gui.sh:2098
msgid "Couldn't find git gui in PATH"
msgstr "Hittade inte git gui i PATH."
-#: git-gui.sh:2455 lib/choose_repository.tcl:36
+#: git-gui.sh:2515 lib/choose_repository.tcl:36
msgid "Repository"
msgstr "Arkiv"
-#: git-gui.sh:2456
+#: git-gui.sh:2516
msgid "Edit"
msgstr "Redigera"
-#: git-gui.sh:2458 lib/choose_rev.tcl:561
+#: git-gui.sh:2518 lib/choose_rev.tcl:566
msgid "Branch"
msgstr "Gren"
-#: git-gui.sh:2461 lib/choose_rev.tcl:548
+#: git-gui.sh:2521 lib/choose_rev.tcl:553
msgid "Commit@@noun"
msgstr "Incheckning"
-#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2524 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
msgid "Merge"
msgstr "Slå ihop"
-#: git-gui.sh:2465 lib/choose_rev.tcl:557
+#: git-gui.sh:2525 lib/choose_rev.tcl:562
msgid "Remote"
msgstr "Fjärrarkiv"
-#: git-gui.sh:2468
+#: git-gui.sh:2528
msgid "Tools"
msgstr "Verktyg"
-#: git-gui.sh:2477
+#: git-gui.sh:2537
msgid "Explore Working Copy"
msgstr "Utforska arbetskopia"
-#: git-gui.sh:2483
+#: git-gui.sh:2543
msgid "Browse Current Branch's Files"
msgstr "Bläddra i grenens filer"
-#: git-gui.sh:2487
+#: git-gui.sh:2547
msgid "Browse Branch Files..."
msgstr "Bläddra filer på gren..."
-#: git-gui.sh:2492
+#: git-gui.sh:2552
msgid "Visualize Current Branch's History"
msgstr "Visualisera grenens historik"
-#: git-gui.sh:2496
+#: git-gui.sh:2556
msgid "Visualize All Branch History"
msgstr "Visualisera alla grenars historik"
-#: git-gui.sh:2503
+#: git-gui.sh:2563
#, tcl-format
msgid "Browse %s's Files"
msgstr "Bläddra i filer för %s"
-#: git-gui.sh:2505
+#: git-gui.sh:2565
#, tcl-format
msgid "Visualize %s's History"
msgstr "Visualisera historik för %s"
-#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2570 lib/database.tcl:40 lib/database.tcl:66
msgid "Database Statistics"
msgstr "Databasstatistik"
-#: git-gui.sh:2513 lib/database.tcl:34
+#: git-gui.sh:2573 lib/database.tcl:33
msgid "Compress Database"
msgstr "Komprimera databas"
-#: git-gui.sh:2516
+#: git-gui.sh:2576
msgid "Verify Database"
msgstr "Verifiera databas"
-#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
+#: git-gui.sh:2583 git-gui.sh:2587 git-gui.sh:2591 lib/shortcut.tcl:8
#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
msgid "Create Desktop Icon"
msgstr "Skapa skrivbordsikon"
-#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2599 lib/choose_repository.tcl:188 lib/choose_repository.tcl:196
msgid "Quit"
msgstr "Avsluta"
-#: git-gui.sh:2547
+#: git-gui.sh:2607
msgid "Undo"
msgstr "Ångra"
-#: git-gui.sh:2550
+#: git-gui.sh:2610
msgid "Redo"
msgstr "Gör om"
-#: git-gui.sh:2554 git-gui.sh:3109
+#: git-gui.sh:2614 git-gui.sh:3190
msgid "Cut"
msgstr "Klipp ut"
-#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
+#: git-gui.sh:2617 git-gui.sh:3193 git-gui.sh:3267 git-gui.sh:3340
#: lib/console.tcl:69
msgid "Copy"
msgstr "Kopiera"
-#: git-gui.sh:2560 git-gui.sh:3115
+#: git-gui.sh:2620 git-gui.sh:3196
msgid "Paste"
msgstr "Klistra in"
-#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
-#: lib/remote_branch_delete.tcl:38
+#: git-gui.sh:2623 git-gui.sh:3199 lib/branch_delete.tcl:28
+#: lib/remote_branch_delete.tcl:39
msgid "Delete"
msgstr "Ta bort"
-#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
+#: git-gui.sh:2627 git-gui.sh:3203 git-gui.sh:3344 lib/console.tcl:71
msgid "Select All"
msgstr "Markera alla"
-#: git-gui.sh:2576
+#: git-gui.sh:2636
msgid "Create..."
msgstr "Skapa..."
-#: git-gui.sh:2582
+#: git-gui.sh:2642
msgid "Checkout..."
msgstr "Checka ut..."
-#: git-gui.sh:2588
+#: git-gui.sh:2648
msgid "Rename..."
msgstr "Byt namn..."
-#: git-gui.sh:2593
+#: git-gui.sh:2653
msgid "Delete..."
msgstr "Ta bort..."
-#: git-gui.sh:2598
+#: git-gui.sh:2658
msgid "Reset..."
msgstr "Återställ..."
-#: git-gui.sh:2608
+#: git-gui.sh:2668
msgid "Done"
msgstr "Färdig"
-#: git-gui.sh:2610
+#: git-gui.sh:2670
msgid "Commit@@verb"
msgstr "Checka in"
-#: git-gui.sh:2619 git-gui.sh:3050
+#: git-gui.sh:2679 git-gui.sh:3131
msgid "New Commit"
msgstr "Ny incheckning"
-#: git-gui.sh:2627 git-gui.sh:3057
+#: git-gui.sh:2687 git-gui.sh:3138
msgid "Amend Last Commit"
msgstr "Lägg till föregående incheckning"
-#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2697 git-gui.sh:3092 lib/remote_branch_delete.tcl:101
msgid "Rescan"
msgstr "Sök på nytt"
-#: git-gui.sh:2643
+#: git-gui.sh:2703
msgid "Stage To Commit"
msgstr "Köa för incheckning"
-#: git-gui.sh:2649
+#: git-gui.sh:2709
msgid "Stage Changed Files To Commit"
msgstr "Köa ändrade filer för incheckning"
-#: git-gui.sh:2655
+#: git-gui.sh:2715
msgid "Unstage From Commit"
msgstr "Ta bort från incheckningskö"
-#: git-gui.sh:2661 lib/index.tcl:412
+#: git-gui.sh:2721 lib/index.tcl:415
msgid "Revert Changes"
msgstr "Återställ ändringar"
-#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
+#: git-gui.sh:2729 git-gui.sh:3391 git-gui.sh:3422
msgid "Show Less Context"
msgstr "Visa mindre sammanhang"
-#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
+#: git-gui.sh:2733 git-gui.sh:3395 git-gui.sh:3426
msgid "Show More Context"
msgstr "Visa mer sammanhang"
-#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
+#: git-gui.sh:2740 git-gui.sh:3105 git-gui.sh:3214
msgid "Sign Off"
msgstr "Skriv under"
-#: git-gui.sh:2696
+#: git-gui.sh:2756
msgid "Local Merge..."
msgstr "Lokal sammanslagning..."
-#: git-gui.sh:2701
+#: git-gui.sh:2761
msgid "Abort Merge..."
msgstr "Avbryt sammanslagning..."
-#: git-gui.sh:2713 git-gui.sh:2741
+#: git-gui.sh:2773 git-gui.sh:2801
msgid "Add..."
msgstr "Lägg till..."
-#: git-gui.sh:2717
+#: git-gui.sh:2777
msgid "Push..."
msgstr "Sänd..."
-#: git-gui.sh:2721
+#: git-gui.sh:2781
msgid "Delete Branch..."
msgstr "Ta bort gren..."
-#: git-gui.sh:2731 git-gui.sh:3292
+#: git-gui.sh:2791 git-gui.sh:3373
msgid "Options..."
msgstr "Alternativ..."
-#: git-gui.sh:2742
+#: git-gui.sh:2802
msgid "Remove..."
msgstr "Ta bort..."
-#: git-gui.sh:2751 lib/choose_repository.tcl:50
+#: git-gui.sh:2811 lib/choose_repository.tcl:50
msgid "Help"
msgstr "Hjälp"
-#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
+#: git-gui.sh:2815 git-gui.sh:2819 lib/about.tcl:14
#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
#, tcl-format
msgid "About %s"
msgstr "Om %s"
-#: git-gui.sh:2783
+#: git-gui.sh:2843
msgid "Online Documentation"
msgstr "Webbdokumentation"
-#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:2846 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
msgid "Show SSH Key"
msgstr "Visa SSH-nyckel"
-#: git-gui.sh:2893
+#: git-gui.sh:2965
#, tcl-format
msgid "fatal: cannot stat path %s: No such file or directory"
msgstr ""
"ödesdigert: kunde inte ta status på sökvägen %s: Fil eller katalog saknas"
-#: git-gui.sh:2926
+#: git-gui.sh:2997
msgid "Current Branch:"
msgstr "Aktuell gren:"
-#: git-gui.sh:2947
+#: git-gui.sh:3023
msgid "Staged Changes (Will Commit)"
msgstr "Köade ändringar (kommer att checkas in)"
-#: git-gui.sh:2967
+#: git-gui.sh:3043
msgid "Unstaged Changes"
msgstr "Oköade ändringar"
-#: git-gui.sh:3017
+#: git-gui.sh:3098
msgid "Stage Changed"
msgstr "Köa ändrade"
-#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3117 lib/transport.tcl:107 lib/transport.tcl:196
msgid "Push"
msgstr "Sänd"
-#: git-gui.sh:3071
+#: git-gui.sh:3152
msgid "Initial Commit Message:"
msgstr "Inledande incheckningsmeddelande:"
-#: git-gui.sh:3072
+#: git-gui.sh:3153
msgid "Amended Commit Message:"
msgstr "Utökat incheckningsmeddelande:"
-#: git-gui.sh:3073
+#: git-gui.sh:3154
msgid "Amended Initial Commit Message:"
msgstr "Utökat inledande incheckningsmeddelande:"
-#: git-gui.sh:3074
+#: git-gui.sh:3155
msgid "Amended Merge Commit Message:"
msgstr "Utökat incheckningsmeddelande för sammanslagning:"
-#: git-gui.sh:3075
+#: git-gui.sh:3156
msgid "Merge Commit Message:"
msgstr "Incheckningsmeddelande för sammanslagning:"
-#: git-gui.sh:3076
+#: git-gui.sh:3157
msgid "Commit Message:"
msgstr "Incheckningsmeddelande:"
-#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
+#: git-gui.sh:3206 git-gui.sh:3348 lib/console.tcl:73
msgid "Copy All"
msgstr "Kopiera alla"
-#: git-gui.sh:3149 lib/blame.tcl:104
+#: git-gui.sh:3230 lib/blame.tcl:104
msgid "File:"
msgstr "Fil:"
-#: git-gui.sh:3255
+#: git-gui.sh:3336
msgid "Refresh"
msgstr "Uppdatera"
-#: git-gui.sh:3276
+#: git-gui.sh:3357
msgid "Decrease Font Size"
msgstr "Minska teckensnittsstorlek"
-#: git-gui.sh:3280
+#: git-gui.sh:3361
msgid "Increase Font Size"
msgstr "Öka teckensnittsstorlek"
-#: git-gui.sh:3288 lib/blame.tcl:281
+#: git-gui.sh:3369 lib/blame.tcl:281
msgid "Encoding"
msgstr "Teckenkodning"
-#: git-gui.sh:3299
+#: git-gui.sh:3380
msgid "Apply/Reverse Hunk"
msgstr "Använd/återställ del"
-#: git-gui.sh:3304
+#: git-gui.sh:3385
msgid "Apply/Reverse Line"
msgstr "Använd/återställ rad"
-#: git-gui.sh:3323
+#: git-gui.sh:3404
msgid "Run Merge Tool"
msgstr "Starta verktyg för sammanslagning"
-#: git-gui.sh:3328
+#: git-gui.sh:3409
msgid "Use Remote Version"
msgstr "Använd versionen från fjärrarkivet"
-#: git-gui.sh:3332
+#: git-gui.sh:3413
msgid "Use Local Version"
msgstr "Använd lokala versionen"
-#: git-gui.sh:3336
+#: git-gui.sh:3417
msgid "Revert To Base"
msgstr "Återställ till basversionen"
-#: git-gui.sh:3354
+#: git-gui.sh:3435
msgid "Visualize These Changes In The Submodule"
msgstr "Visualisera ändringarna i undermodulen"
-#: git-gui.sh:3358
+#: git-gui.sh:3439
msgid "Visualize Current Branch History In The Submodule"
msgstr "Visualisera grenens historik i undermodulen"
-#: git-gui.sh:3362
+#: git-gui.sh:3443
msgid "Visualize All Branch History In The Submodule"
msgstr "Visualisera alla grenars historik i undermodulen"
-#: git-gui.sh:3367
+#: git-gui.sh:3448
msgid "Start git gui In The Submodule"
msgstr "Starta git gui i undermodulen"
-#: git-gui.sh:3389
+#: git-gui.sh:3483
msgid "Unstage Hunk From Commit"
msgstr "Ta bort del ur incheckningskö"
-#: git-gui.sh:3391
+#: git-gui.sh:3485
msgid "Unstage Lines From Commit"
msgstr "Ta bort rader ur incheckningskö"
-#: git-gui.sh:3393
+#: git-gui.sh:3487
msgid "Unstage Line From Commit"
msgstr "Ta bort rad ur incheckningskö"
-#: git-gui.sh:3396
+#: git-gui.sh:3490
msgid "Stage Hunk For Commit"
msgstr "Ställ del i incheckningskö"
-#: git-gui.sh:3398
+#: git-gui.sh:3492
msgid "Stage Lines For Commit"
msgstr "Ställ rader i incheckningskö"
-#: git-gui.sh:3400
+#: git-gui.sh:3494
msgid "Stage Line For Commit"
msgstr "Ställ rad i incheckningskö"
-#: git-gui.sh:3424
+#: git-gui.sh:3519
msgid "Initializing..."
msgstr "Initierar..."
-#: git-gui.sh:3541
+#: git-gui.sh:3658
#, tcl-format
msgid ""
"Possible environment issues exist.\n"
@@ -549,7 +549,7 @@ msgstr ""
"av %s:\n"
"\n"
-#: git-gui.sh:3570
+#: git-gui.sh:3687
msgid ""
"\n"
"This is due to a known issue with the\n"
@@ -559,7 +559,7 @@ msgstr ""
"Detta beror på ett känt problem med\n"
"Tcl-binären som följer med Cygwin."
-#: git-gui.sh:3575
+#: git-gui.sh:3692
#, tcl-format
msgid ""
"\n"
@@ -613,132 +613,132 @@ msgstr "Klandra föräldraincheckning"
msgid "Reading %s..."
msgstr "Läser %s..."
-#: lib/blame.tcl:557
+#: lib/blame.tcl:581
msgid "Loading copy/move tracking annotations..."
msgstr "Läser annoteringar för kopiering/flyttning..."
-#: lib/blame.tcl:577
+#: lib/blame.tcl:601
msgid "lines annotated"
msgstr "rader annoterade"
-#: lib/blame.tcl:769
+#: lib/blame.tcl:793
msgid "Loading original location annotations..."
msgstr "Läser in annotering av originalplacering..."
-#: lib/blame.tcl:772
+#: lib/blame.tcl:796
msgid "Annotation complete."
msgstr "Annotering fullbordad."
-#: lib/blame.tcl:802
+#: lib/blame.tcl:826
msgid "Busy"
msgstr "Upptagen"
-#: lib/blame.tcl:803
+#: lib/blame.tcl:827
msgid "Annotation process is already running."
msgstr "Annoteringsprocess körs redan."
-#: lib/blame.tcl:842
+#: lib/blame.tcl:866
msgid "Running thorough copy detection..."
msgstr "Kör grundlig kopieringsigenkänning..."
-#: lib/blame.tcl:910
+#: lib/blame.tcl:934
msgid "Loading annotation..."
msgstr "Läser in annotering..."
-#: lib/blame.tcl:963
+#: lib/blame.tcl:987
msgid "Author:"
msgstr "Författare:"
-#: lib/blame.tcl:967
+#: lib/blame.tcl:991
msgid "Committer:"
msgstr "Incheckare:"
-#: lib/blame.tcl:972
+#: lib/blame.tcl:996
msgid "Original File:"
msgstr "Ursprunglig fil:"
-#: lib/blame.tcl:1020
+#: lib/blame.tcl:1044
msgid "Cannot find HEAD commit:"
msgstr "Hittar inte incheckning för HEAD:"
-#: lib/blame.tcl:1075
+#: lib/blame.tcl:1099
msgid "Cannot find parent commit:"
msgstr "Hittar inte föräldraincheckning:"
-#: lib/blame.tcl:1090
+#: lib/blame.tcl:1114
msgid "Unable to display parent"
msgstr "Kan inte visa förälder"
-#: lib/blame.tcl:1091 lib/diff.tcl:320
+#: lib/blame.tcl:1115 lib/diff.tcl:323
msgid "Error loading diff:"
msgstr "Fel vid inläsning av differens:"
-#: lib/blame.tcl:1231
+#: lib/blame.tcl:1255
msgid "Originally By:"
msgstr "Ursprungligen av:"
-#: lib/blame.tcl:1237
+#: lib/blame.tcl:1261
msgid "In File:"
msgstr "I filen:"
-#: lib/blame.tcl:1242
+#: lib/blame.tcl:1266
msgid "Copied Or Moved Here By:"
msgstr "Kopierad eller flyttad hit av:"
-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
+#: lib/branch_checkout.tcl:16 lib/branch_checkout.tcl:21
msgid "Checkout Branch"
msgstr "Checka ut gren"
-#: lib/branch_checkout.tcl:23
+#: lib/branch_checkout.tcl:26
msgid "Checkout"
msgstr "Checka ut"
-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:108
+#: lib/branch_checkout.tcl:30 lib/branch_create.tcl:37
+#: lib/branch_delete.tcl:34 lib/branch_rename.tcl:32 lib/browser.tcl:286
+#: lib/checkout_op.tcl:579 lib/choose_font.tcl:45 lib/merge.tcl:172
+#: lib/option.tcl:127 lib/remote_add.tcl:34 lib/remote_branch_delete.tcl:43
+#: lib/tools_dlg.tcl:41 lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345
+#: lib/transport.tcl:111
msgid "Cancel"
msgstr "Avbryt"
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
+#: lib/branch_checkout.tcl:35 lib/browser.tcl:291 lib/tools_dlg.tcl:321
msgid "Revision"
msgstr "Revision"
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
+#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:69 lib/option.tcl:287
msgid "Options"
msgstr "Alternativ"
-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
+#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
msgid "Fetch Tracking Branch"
msgstr "Hämta spårande gren"
-#: lib/branch_checkout.tcl:44
+#: lib/branch_checkout.tcl:47
msgid "Detach From Local Branch"
msgstr "Koppla bort från lokal gren"
-#: lib/branch_create.tcl:22
+#: lib/branch_create.tcl:23
msgid "Create Branch"
msgstr "Skapa gren"
-#: lib/branch_create.tcl:27
+#: lib/branch_create.tcl:28
msgid "Create New Branch"
msgstr "Skapa ny gren"
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
+#: lib/branch_create.tcl:33 lib/choose_repository.tcl:389
msgid "Create"
msgstr "Skapa"
-#: lib/branch_create.tcl:40
+#: lib/branch_create.tcl:42
msgid "Branch Name"
msgstr "Namn på gren"
-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
+#: lib/branch_create.tcl:44 lib/remote_add.tcl:41 lib/tools_dlg.tcl:51
msgid "Name:"
msgstr "Namn:"
-#: lib/branch_create.tcl:58
+#: lib/branch_create.tcl:57
msgid "Match Tracking Branch Name"
msgstr "Använd namn på spårad gren"
@@ -766,41 +766,41 @@ msgstr "Återställ"
msgid "Checkout After Creation"
msgstr "Checka ut när skapad"
-#: lib/branch_create.tcl:131
+#: lib/branch_create.tcl:132
msgid "Please select a tracking branch."
msgstr "Välj en gren att spåra."
-#: lib/branch_create.tcl:140
+#: lib/branch_create.tcl:141
#, tcl-format
msgid "Tracking branch %s is not a branch in the remote repository."
msgstr "Den spårade grenen %s är inte en gren i fjärrarkivet."
-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
+#: lib/branch_create.tcl:154 lib/branch_rename.tcl:92
msgid "Please supply a branch name."
msgstr "Ange ett namn för grenen."
-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#: lib/branch_create.tcl:165 lib/branch_rename.tcl:112
#, tcl-format
msgid "'%s' is not an acceptable branch name."
msgstr "\"%s\" kan inte användas som namn på grenen."
-#: lib/branch_delete.tcl:15
+#: lib/branch_delete.tcl:16
msgid "Delete Branch"
msgstr "Ta bort gren"
-#: lib/branch_delete.tcl:20
+#: lib/branch_delete.tcl:21
msgid "Delete Local Branch"
msgstr "Ta bort lokal gren"
-#: lib/branch_delete.tcl:37
+#: lib/branch_delete.tcl:39
msgid "Local Branches"
msgstr "Lokala grenar"
-#: lib/branch_delete.tcl:52
+#: lib/branch_delete.tcl:51
msgid "Delete Only If Merged Into"
msgstr "Ta bara bort om sammanslagen med"
-#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
+#: lib/branch_delete.tcl:53 lib/remote_branch_delete.tcl:120
msgid "Always (Do not perform merge checks)"
msgstr "Alltid (utför inte sammanslagningstest)"
@@ -809,7 +809,7 @@ msgstr "Alltid (utför inte sammanslagningstest)"
msgid "The following branches are not completely merged into %s:"
msgstr "Följande grenar är inte till fullo sammanslagna med %s:"
-#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
+#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:218
msgid ""
"Recovering deleted branches is difficult.\n"
"\n"
@@ -828,32 +828,32 @@ msgstr ""
"Kunde inte ta bort grenar:\n"
"%s"
-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
+#: lib/branch_rename.tcl:15 lib/branch_rename.tcl:23
msgid "Rename Branch"
msgstr "Byt namn på gren"
-#: lib/branch_rename.tcl:26
+#: lib/branch_rename.tcl:28
msgid "Rename"
msgstr "Byt namn"
-#: lib/branch_rename.tcl:36
+#: lib/branch_rename.tcl:38
msgid "Branch:"
msgstr "Gren:"
-#: lib/branch_rename.tcl:39
+#: lib/branch_rename.tcl:46
msgid "New Name:"
msgstr "Nytt namn:"
-#: lib/branch_rename.tcl:75
+#: lib/branch_rename.tcl:81
msgid "Please select a branch to rename."
msgstr "Välj en gren att byta namn på."
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
+#: lib/branch_rename.tcl:102 lib/checkout_op.tcl:202
#, tcl-format
msgid "Branch '%s' already exists."
msgstr "Grenen \"%s\" finns redan."
-#: lib/branch_rename.tcl:117
+#: lib/branch_rename.tcl:123
#, tcl-format
msgid "Failed to rename '%s'."
msgstr "Kunde inte byta namn på \"%s\"."
@@ -862,7 +862,7 @@ msgstr "Kunde inte byta namn på \"%s\"."
msgid "Starting..."
msgstr "Startar..."
-#: lib/browser.tcl:26
+#: lib/browser.tcl:27
msgid "File Browser"
msgstr "Filbläddrare"
@@ -875,13 +875,13 @@ msgstr "Läser %s..."
msgid "[Up To Parent]"
msgstr "[Upp till förälder]"
-#: lib/browser.tcl:267 lib/browser.tcl:273
+#: lib/browser.tcl:269 lib/browser.tcl:276
msgid "Browse Branch Files"
msgstr "Bläddra filer på grenen"
-#: lib/browser.tcl:278 lib/choose_repository.tcl:398
-#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
-#: lib/choose_repository.tcl:1028
+#: lib/browser.tcl:282 lib/choose_repository.tcl:404
+#: lib/choose_repository.tcl:491 lib/choose_repository.tcl:500
+#: lib/choose_repository.tcl:1027
msgid "Browse"
msgstr "Bläddra"
@@ -895,8 +895,8 @@ msgstr "Hämtar %s från %s"
msgid "fatal: Cannot resolve %s"
msgstr "ödesdigert: Kunde inte slå upp %s"
-#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
-#: lib/sshkey.tcl:53
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:30
+#: lib/sshkey.tcl:55
msgid "Close"
msgstr "Stäng"
@@ -1008,7 +1008,7 @@ msgstr "Det kanske inte är så enkelt att återskapa förlorade incheckningar."
msgid "Reset '%s'?"
msgstr "Återställa \"%s\"?"
-#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:336
msgid "Visualize"
msgstr "Visualisera"
@@ -1029,23 +1029,23 @@ msgstr ""
"\n"
"Detta skulle inte ha hänt. %s kommer nu stängas och ge upp."
-#: lib/choose_font.tcl:39
+#: lib/choose_font.tcl:41
msgid "Select"
msgstr "Välj"
-#: lib/choose_font.tcl:53
+#: lib/choose_font.tcl:55
msgid "Font Family"
msgstr "Teckensnittsfamilj"
-#: lib/choose_font.tcl:74
+#: lib/choose_font.tcl:76
msgid "Font Size"
msgstr "Storlek"
-#: lib/choose_font.tcl:91
+#: lib/choose_font.tcl:93
msgid "Font Example"
msgstr "Exempel"
-#: lib/choose_font.tcl:103
+#: lib/choose_font.tcl:105
msgid ""
"This is example text.\n"
"If you like this text, it can be your font."
@@ -1057,7 +1057,7 @@ msgstr ""
msgid "Git Gui"
msgstr "Git Gui"
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:394
msgid "Create New Repository"
msgstr "Skapa nytt arkiv"
@@ -1065,76 +1065,76 @@ msgstr "Skapa nytt arkiv"
msgid "New..."
msgstr "Nytt..."
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:478
msgid "Clone Existing Repository"
msgstr "Klona befintligt arkiv"
-#: lib/choose_repository.tcl:106
+#: lib/choose_repository.tcl:111
msgid "Clone..."
msgstr "Klona..."
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
+#: lib/choose_repository.tcl:118 lib/choose_repository.tcl:1017
msgid "Open Existing Repository"
msgstr "Öppna befintligt arkiv"
-#: lib/choose_repository.tcl:119
+#: lib/choose_repository.tcl:124
msgid "Open..."
msgstr "Öppna..."
-#: lib/choose_repository.tcl:132
+#: lib/choose_repository.tcl:137
msgid "Recent Repositories"
msgstr "Senaste arkiven"
-#: lib/choose_repository.tcl:138
+#: lib/choose_repository.tcl:143
msgid "Open Recent Repository:"
msgstr "Öppna tidigare arkiv:"
-#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
-#: lib/choose_repository.tcl:320
+#: lib/choose_repository.tcl:313 lib/choose_repository.tcl:320
+#: lib/choose_repository.tcl:327
#, tcl-format
msgid "Failed to create repository %s:"
msgstr "Kunde inte skapa arkivet %s:"
-#: lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:399
msgid "Directory:"
msgstr "Katalog:"
-#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
-#: lib/choose_repository.tcl:1052
+#: lib/choose_repository.tcl:429 lib/choose_repository.tcl:550
+#: lib/choose_repository.tcl:1051
msgid "Git Repository"
msgstr "Gitarkiv"
-#: lib/choose_repository.tcl:448
+#: lib/choose_repository.tcl:454
#, tcl-format
msgid "Directory %s already exists."
msgstr "Katalogen %s finns redan."
-#: lib/choose_repository.tcl:452
+#: lib/choose_repository.tcl:458
#, tcl-format
msgid "File %s already exists."
msgstr "Filen %s finns redan."
-#: lib/choose_repository.tcl:466
+#: lib/choose_repository.tcl:473
msgid "Clone"
msgstr "Klona"
-#: lib/choose_repository.tcl:479
+#: lib/choose_repository.tcl:486
msgid "Source Location:"
msgstr "Plats för källkod:"
-#: lib/choose_repository.tcl:490
+#: lib/choose_repository.tcl:495
msgid "Target Directory:"
msgstr "Målkatalog:"
-#: lib/choose_repository.tcl:502
+#: lib/choose_repository.tcl:505
msgid "Clone Type:"
msgstr "Typ av klon:"
-#: lib/choose_repository.tcl:508
+#: lib/choose_repository.tcl:510
msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
msgstr "Standard (snabb, semiredundant, hårda länkar)"
-#: lib/choose_repository.tcl:514
+#: lib/choose_repository.tcl:515
msgid "Full Copy (Slower, Redundant Backup)"
msgstr "Full kopia (långsammare, redundant säkerhetskopia)"
@@ -1144,7 +1144,7 @@ msgstr "Delad (snabbast, rekommenderas ej, ingen säkerhetskopia)"
#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
-#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
+#: lib/choose_repository.tcl:1057 lib/choose_repository.tcl:1065
#, tcl-format
msgid "Not a Git repository: %s"
msgstr "Inte ett Gitarkiv: %s"
@@ -1258,8 +1258,8 @@ msgstr "Kunde inte slå upp %s till någon incheckning."
msgid "Creating working directory"
msgstr "Skapar arbetskatalog"
-#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
-#: lib/index.tcl:198
+#: lib/choose_repository.tcl:939 lib/index.tcl:70 lib/index.tcl:133
+#: lib/index.tcl:201
msgid "files"
msgstr "filer"
@@ -1267,20 +1267,20 @@ msgstr "filer"
msgid "Initial file checkout failed."
msgstr "Inledande filutcheckning misslyckades."
-#: lib/choose_repository.tcl:1011
+#: lib/choose_repository.tcl:1012
msgid "Open"
msgstr "Öppna"
-#: lib/choose_repository.tcl:1021
+#: lib/choose_repository.tcl:1022
msgid "Repository:"
msgstr "Arkiv:"
-#: lib/choose_repository.tcl:1072
+#: lib/choose_repository.tcl:1071
#, tcl-format
msgid "Failed to open repository %s:"
msgstr "Kunde inte öppna arkivet %s:"
-#: lib/choose_rev.tcl:53
+#: lib/choose_rev.tcl:52
msgid "This Detached Checkout"
msgstr "Denna frånkopplade utcheckning"
@@ -1288,36 +1288,36 @@ msgstr "Denna frånkopplade utcheckning"
msgid "Revision Expression:"
msgstr "Revisionsuttryck:"
-#: lib/choose_rev.tcl:74
+#: lib/choose_rev.tcl:72
msgid "Local Branch"
msgstr "Lokal gren"
-#: lib/choose_rev.tcl:79
+#: lib/choose_rev.tcl:77
msgid "Tracking Branch"
msgstr "Spårande gren"
-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:543
msgid "Tag"
msgstr "Tagg"
-#: lib/choose_rev.tcl:317
+#: lib/choose_rev.tcl:321
#, tcl-format
msgid "Invalid revision: %s"
msgstr "Ogiltig revision: %s"
-#: lib/choose_rev.tcl:338
+#: lib/choose_rev.tcl:342
msgid "No revision selected."
msgstr "Ingen revision vald."
-#: lib/choose_rev.tcl:346
+#: lib/choose_rev.tcl:350
msgid "Revision expression is empty."
msgstr "Revisionsuttrycket är tomt."
-#: lib/choose_rev.tcl:531
+#: lib/choose_rev.tcl:536
msgid "Updated"
msgstr "Uppdaterad"
-#: lib/choose_rev.tcl:559
+#: lib/choose_rev.tcl:564
msgid "URL"
msgstr "Webbadress"
@@ -1508,31 +1508,31 @@ msgstr "Lyckades"
msgid "Error: Command Failed"
msgstr "Fel: Kommando misslyckades"
-#: lib/database.tcl:43
+#: lib/database.tcl:42
msgid "Number of loose objects"
msgstr "Antal lösa objekt"
-#: lib/database.tcl:44
+#: lib/database.tcl:43
msgid "Disk space used by loose objects"
msgstr "Diskutrymme använt av lösa objekt"
-#: lib/database.tcl:45
+#: lib/database.tcl:44
msgid "Number of packed objects"
msgstr "Antal packade objekt"
-#: lib/database.tcl:46
+#: lib/database.tcl:45
msgid "Number of packs"
msgstr "Antal paket"
-#: lib/database.tcl:47
+#: lib/database.tcl:46
msgid "Disk space used by packed objects"
msgstr "Diskutrymme använt av packade objekt"
-#: lib/database.tcl:48
+#: lib/database.tcl:47
msgid "Packed objects waiting for pruning"
msgstr "Packade objekt som väntar på städning"
-#: lib/database.tcl:49
+#: lib/database.tcl:48
msgid "Garbage files"
msgstr "Skräpfiler"
@@ -1618,7 +1618,7 @@ msgstr "LOKAL:\n"
msgid "REMOTE:\n"
msgstr "FJÄRR:\n"
-#: lib/diff.tcl:202 lib/diff.tcl:319
+#: lib/diff.tcl:202 lib/diff.tcl:322
#, tcl-format
msgid "Unable to display %s"
msgstr "Kan inte visa %s"
@@ -1655,19 +1655,19 @@ msgstr ""
"* Den ospårade filen klipptes här av %s.\n"
"* För att se hela filen, använd ett externt redigeringsprogram.\n"
-#: lib/diff.tcl:482
+#: lib/diff.tcl:485
msgid "Failed to unstage selected hunk."
msgstr "Kunde inte ta bort den valda delen från kön."
-#: lib/diff.tcl:489
+#: lib/diff.tcl:492
msgid "Failed to stage selected hunk."
msgstr "Kunde inte lägga till den valda delen till kön."
-#: lib/diff.tcl:568
+#: lib/diff.tcl:571
msgid "Failed to unstage selected line."
msgstr "Kunde inte ta bort den valda raden från kön."
-#: lib/diff.tcl:576
+#: lib/diff.tcl:579
msgid "Failed to stage selected line."
msgstr "Kunde inte lägga till den valda raden till kön."
@@ -1684,7 +1684,7 @@ msgstr "Systemets (%s)"
msgid "Other"
msgstr "Annan"
-#: lib/error.tcl:20 lib/error.tcl:114
+#: lib/error.tcl:20 lib/error.tcl:116
msgid "error"
msgstr "fel"
@@ -1692,7 +1692,7 @@ msgstr "fel"
msgid "warning"
msgstr "varning"
-#: lib/error.tcl:94
+#: lib/error.tcl:96
msgid "You must correct the above errors before committing."
msgstr "Du måste rätta till felen ovan innan du checkar in."
@@ -1700,11 +1700,11 @@ msgstr "Du måste rätta till felen ovan innan du checkar in."
msgid "Unable to unlock the index."
msgstr "Kunde inte låsa upp indexet."
-#: lib/index.tcl:15
+#: lib/index.tcl:17
msgid "Index Error"
msgstr "Indexfel"
-#: lib/index.tcl:17
+#: lib/index.tcl:19
msgid ""
"Updating the Git index failed. A rescan will be automatically started to "
"resynchronize git-gui."
@@ -1712,52 +1712,52 @@ msgstr ""
"Misslyckades med att uppdatera Gitindexet. En omsökning kommer att startas "
"automatiskt för att synkronisera om git-gui."
-#: lib/index.tcl:28
+#: lib/index.tcl:30
msgid "Continue"
msgstr "Forstätt"
-#: lib/index.tcl:31
+#: lib/index.tcl:33
msgid "Unlock Index"
msgstr "Lås upp index"
-#: lib/index.tcl:289
+#: lib/index.tcl:292
#, tcl-format
msgid "Unstaging %s from commit"
msgstr "Tar bort %s för incheckningskön"
-#: lib/index.tcl:328
+#: lib/index.tcl:331
msgid "Ready to commit."
msgstr "Redo att checka in."
-#: lib/index.tcl:341
+#: lib/index.tcl:344
#, tcl-format
msgid "Adding %s"
msgstr "Lägger till %s"
-#: lib/index.tcl:398
+#: lib/index.tcl:401
#, tcl-format
msgid "Revert changes in file %s?"
msgstr "Återställ ändringarna i filen %s?"
-#: lib/index.tcl:400
+#: lib/index.tcl:403
#, tcl-format
msgid "Revert changes in these %i files?"
msgstr "Återställ ändringarna i dessa %i filer?"
-#: lib/index.tcl:408
+#: lib/index.tcl:411
msgid "Any unstaged changes will be permanently lost by the revert."
msgstr ""
"Alla oköade ändringar kommer permanent gå förlorade vid återställningen."
-#: lib/index.tcl:411
+#: lib/index.tcl:414
msgid "Do Nothing"
msgstr "Gör ingenting"
-#: lib/index.tcl:429
+#: lib/index.tcl:432
msgid "Reverting selected files"
msgstr "Återställer valda filer"
-#: lib/index.tcl:433
+#: lib/index.tcl:436
#, tcl-format
msgid "Reverting %s"
msgstr "Återställer %s"
@@ -2004,145 +2004,133 @@ msgstr "Den globala teckenkodningen \"%s\" är ogiltig"
msgid "Invalid repo encoding '%s'"
msgstr "Arkivets teckenkodning \"%s\" är ogiltig"
-#: lib/option.tcl:117
+#: lib/option.tcl:119
msgid "Restore Defaults"
msgstr "Återställ standardvärden"
-#: lib/option.tcl:121
+#: lib/option.tcl:123
msgid "Save"
msgstr "Spara"
-#: lib/option.tcl:131
+#: lib/option.tcl:133
#, tcl-format
msgid "%s Repository"
msgstr "Arkivet %s"
-#: lib/option.tcl:132
+#: lib/option.tcl:134
msgid "Global (All Repositories)"
msgstr "Globalt (alla arkiv)"
-#: lib/option.tcl:138
+#: lib/option.tcl:140
msgid "User Name"
msgstr "Användarnamn"
-#: lib/option.tcl:139
+#: lib/option.tcl:141
msgid "Email Address"
msgstr "E-postadress"
-#: lib/option.tcl:141
+#: lib/option.tcl:143
msgid "Summarize Merge Commits"
msgstr "Summera sammanslagningsincheckningar"
-#: lib/option.tcl:142
+#: lib/option.tcl:144
msgid "Merge Verbosity"
msgstr "Pratsamhet för sammanslagningar"
-#: lib/option.tcl:143
+#: lib/option.tcl:145
msgid "Show Diffstat After Merge"
msgstr "Visa diffstatistik efter sammanslagning"
-#: lib/option.tcl:144
+#: lib/option.tcl:146
msgid "Use Merge Tool"
msgstr "Använd verktyg för sammanslagning"
-#: lib/option.tcl:146
+#: lib/option.tcl:148
msgid "Trust File Modification Timestamps"
msgstr "Lita på filändringstidsstämplar"
-#: lib/option.tcl:147
+#: lib/option.tcl:149
msgid "Prune Tracking Branches During Fetch"
msgstr "Städa spårade grenar vid hämtning"
-#: lib/option.tcl:148
+#: lib/option.tcl:150
msgid "Match Tracking Branches"
msgstr "Matcha spårade grenar"
-#: lib/option.tcl:149
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
+msgstr "Använd Textconv för diff och klandring"
+
+#: lib/option.tcl:152
msgid "Blame Copy Only On Changed Files"
msgstr "Klandra kopiering bara i ändrade filer"
-#: lib/option.tcl:150
+#: lib/option.tcl:153
msgid "Minimum Letters To Blame Copy On"
msgstr "Minsta antal tecken att klandra kopiering för"
-#: lib/option.tcl:151
+#: lib/option.tcl:154
msgid "Blame History Context Radius (days)"
msgstr "Historikradie för klandring (dagar)"
-#: lib/option.tcl:152
+#: lib/option.tcl:155
msgid "Number of Diff Context Lines"
msgstr "Antal rader sammanhang i differenser"
-#: lib/option.tcl:153
+#: lib/option.tcl:156
msgid "Commit Message Text Width"
msgstr "Textbredd för incheckningsmeddelande"
-#: lib/option.tcl:154
+#: lib/option.tcl:157
msgid "New Branch Name Template"
msgstr "Mall för namn på nya grenar"
-#: lib/option.tcl:155
+#: lib/option.tcl:158
msgid "Default File Contents Encoding"
msgstr "Standardteckenkodning för filinnehåll"
-#: lib/option.tcl:203
+#: lib/option.tcl:204
msgid "Change"
msgstr "Ändra"
-#: lib/option.tcl:230
+#: lib/option.tcl:231
msgid "Spelling Dictionary:"
msgstr "Stavningsordlista:"
-#: lib/option.tcl:254
+#: lib/option.tcl:261
msgid "Change Font"
msgstr "Byt teckensnitt"
-#: lib/option.tcl:258
+#: lib/option.tcl:265
#, tcl-format
msgid "Choose %s"
msgstr "Välj %s"
-#: lib/option.tcl:264
+#: lib/option.tcl:271
msgid "pt."
msgstr "p."
-#: lib/option.tcl:278
+#: lib/option.tcl:285
msgid "Preferences"
msgstr "Inställningar"
-#: lib/option.tcl:314
+#: lib/option.tcl:322
msgid "Failed to completely save options:"
msgstr "Misslyckades med att helt spara alternativ:"
-#: lib/remote.tcl:163
-msgid "Remove Remote"
-msgstr "Ta bort fjärrarkiv"
-
-#: lib/remote.tcl:168
-msgid "Prune from"
-msgstr "Ta bort från"
-
-#: lib/remote.tcl:173
-msgid "Fetch from"
-msgstr "Hämta från"
-
-#: lib/remote.tcl:215
-msgid "Push to"
-msgstr "Sänd till"
-
-#: lib/remote_add.tcl:19
+#: lib/remote_add.tcl:20
msgid "Add Remote"
msgstr "Lägg till fjärrarkiv"
-#: lib/remote_add.tcl:24
+#: lib/remote_add.tcl:25
msgid "Add New Remote"
msgstr "Lägg till nytt fjärrarkiv"
-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
msgid "Add"
msgstr "Lägg till"
-#: lib/remote_add.tcl:37
+#: lib/remote_add.tcl:39
msgid "Remote Details"
msgstr "Detaljer för fjärrarkiv"
@@ -2150,58 +2138,58 @@ msgstr "Detaljer för fjärrarkiv"
msgid "Location:"
msgstr "Plats:"
-#: lib/remote_add.tcl:62
+#: lib/remote_add.tcl:60
msgid "Further Action"
msgstr "Ytterligare åtgärd"
-#: lib/remote_add.tcl:65
+#: lib/remote_add.tcl:63
msgid "Fetch Immediately"
msgstr "Hämta omedelbart"
-#: lib/remote_add.tcl:71
+#: lib/remote_add.tcl:69
msgid "Initialize Remote Repository and Push"
msgstr "Initiera fjärrarkiv och sänd till"
-#: lib/remote_add.tcl:77
+#: lib/remote_add.tcl:75
msgid "Do Nothing Else Now"
msgstr "Gör ingent mer nu"
-#: lib/remote_add.tcl:101
+#: lib/remote_add.tcl:100
msgid "Please supply a remote name."
msgstr "Ange ett namn för fjärrarkivet."
-#: lib/remote_add.tcl:114
+#: lib/remote_add.tcl:113
#, tcl-format
msgid "'%s' is not an acceptable remote name."
msgstr "\"%s\" kan inte användas som namn på fjärrarkivet."
-#: lib/remote_add.tcl:125
+#: lib/remote_add.tcl:124
#, tcl-format
msgid "Failed to add remote '%s' of location '%s'."
msgstr "Kunde inte lägga till fjärrarkivet \"%s\" på platsen \"%s\"."
-#: lib/remote_add.tcl:133 lib/transport.tcl:6
+#: lib/remote_add.tcl:132 lib/transport.tcl:6
#, tcl-format
msgid "fetch %s"
msgstr "hämta %s"
-#: lib/remote_add.tcl:134
+#: lib/remote_add.tcl:133
#, tcl-format
msgid "Fetching the %s"
msgstr "Hämtar %s"
-#: lib/remote_add.tcl:157
+#: lib/remote_add.tcl:156
#, tcl-format
msgid "Do not know how to initialize repository at location '%s'."
msgstr "Vet inte hur arkivet på platsen \"%s\" skall initieras."
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/remote_add.tcl:162 lib/transport.tcl:25 lib/transport.tcl:63
#: lib/transport.tcl:81
#, tcl-format
msgid "push %s"
msgstr "sänd %s"
-#: lib/remote_add.tcl:164
+#: lib/remote_add.tcl:163
#, tcl-format
msgid "Setting up the %s (at %s)"
msgstr "Konfigurerar %s (på %s)"
@@ -2210,35 +2198,35 @@ msgstr "Konfigurerar %s (på %s)"
msgid "Delete Branch Remotely"
msgstr "Ta bort gren från fjärrarkiv"
-#: lib/remote_branch_delete.tcl:47
+#: lib/remote_branch_delete.tcl:48
msgid "From Repository"
msgstr "Från arkiv"
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
+#: lib/remote_branch_delete.tcl:51 lib/transport.tcl:134
msgid "Remote:"
msgstr "Fjärrarkiv:"
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
+#: lib/remote_branch_delete.tcl:72 lib/transport.tcl:154
msgid "Arbitrary Location:"
msgstr "Godtycklig plats:"
-#: lib/remote_branch_delete.tcl:84
+#: lib/remote_branch_delete.tcl:88
msgid "Branches"
msgstr "Grenar"
-#: lib/remote_branch_delete.tcl:109
+#: lib/remote_branch_delete.tcl:110
msgid "Delete Only If"
msgstr "Ta endast bort om"
-#: lib/remote_branch_delete.tcl:111
+#: lib/remote_branch_delete.tcl:112
msgid "Merged Into:"
msgstr "Sammanslagen i:"
-#: lib/remote_branch_delete.tcl:152
+#: lib/remote_branch_delete.tcl:153
msgid "A branch is required for 'Merged Into'."
msgstr "En gren krävs för \"Sammanslagen i\"."
-#: lib/remote_branch_delete.tcl:184
+#: lib/remote_branch_delete.tcl:185
#, tcl-format
msgid ""
"The following branches are not completely merged into %s:\n"
@@ -2249,7 +2237,7 @@ msgstr ""
"\n"
" - %s"
-#: lib/remote_branch_delete.tcl:189
+#: lib/remote_branch_delete.tcl:190
#, tcl-format
msgid ""
"One or more of the merge tests failed because you have not fetched the "
@@ -2258,37 +2246,53 @@ msgstr ""
"En eller flera av sammanslagningstesterna misslyckades eftersom du inte har "
"hämtat de nödvändiga incheckningarna. Försök hämta från %s först."
-#: lib/remote_branch_delete.tcl:207
+#: lib/remote_branch_delete.tcl:208
msgid "Please select one or more branches to delete."
msgstr "Välj en eller flera grenar att ta bort."
-#: lib/remote_branch_delete.tcl:226
+#: lib/remote_branch_delete.tcl:227
#, tcl-format
msgid "Deleting branches from %s"
msgstr "Tar bort grenar från %s"
-#: lib/remote_branch_delete.tcl:292
+#: lib/remote_branch_delete.tcl:293
msgid "No repository selected."
msgstr "Inget arkiv markerat."
-#: lib/remote_branch_delete.tcl:297
+#: lib/remote_branch_delete.tcl:298
#, tcl-format
msgid "Scanning %s..."
msgstr "Söker %s..."
-#: lib/search.tcl:21
+#: lib/remote.tcl:163
+msgid "Remove Remote"
+msgstr "Ta bort fjärrarkiv"
+
+#: lib/remote.tcl:168
+msgid "Prune from"
+msgstr "Ta bort från"
+
+#: lib/remote.tcl:173
+msgid "Fetch from"
+msgstr "Hämta från"
+
+#: lib/remote.tcl:215
+msgid "Push to"
+msgstr "Sänd till"
+
+#: lib/search.tcl:22
msgid "Find:"
msgstr "Sök:"
-#: lib/search.tcl:23
+#: lib/search.tcl:24
msgid "Next"
msgstr "Nästa"
-#: lib/search.tcl:24
+#: lib/search.tcl:25
msgid "Prev"
msgstr "Föreg"
-#: lib/search.tcl:25
+#: lib/search.tcl:26
msgid "Case-Sensitive"
msgstr "Skilj på VERSALER/gemener"
@@ -2350,19 +2354,19 @@ msgstr "Hittade öppen nyckel i: %s"
msgid "Generate Key"
msgstr "Skapa nyckel"
-#: lib/sshkey.tcl:56
+#: lib/sshkey.tcl:58
msgid "Copy To Clipboard"
msgstr "Kopiera till Urklipp"
-#: lib/sshkey.tcl:70
+#: lib/sshkey.tcl:72
msgid "Your OpenSSH Public Key"
msgstr "Din öppna OpenSSH-nyckel"
-#: lib/sshkey.tcl:78
+#: lib/sshkey.tcl:80
msgid "Generating..."
msgstr "Skapar..."
-#: lib/sshkey.tcl:84
+#: lib/sshkey.tcl:86
#, tcl-format
msgid ""
"Could not start ssh-keygen:\n"
@@ -2373,54 +2377,24 @@ msgstr ""
"\n"
"%s"
-#: lib/sshkey.tcl:111
+#: lib/sshkey.tcl:113
msgid "Generation failed."
msgstr "Misslyckades med att skapa."
-#: lib/sshkey.tcl:118
+#: lib/sshkey.tcl:120
msgid "Generation succeded, but no keys found."
msgstr "Lyckades skapa nyckeln, men hittar inte någon nyckel."
-#: lib/sshkey.tcl:121
+#: lib/sshkey.tcl:123
#, tcl-format
msgid "Your key is in: %s"
msgstr "Din nyckel finns i: %s"
-#: lib/status_bar.tcl:83
+#: lib/status_bar.tcl:86
#, tcl-format
msgid "%s ... %*i of %*i %s (%3i%%)"
msgstr "%s... %*i av %*i %s (%3i%%)"
-#: lib/tools.tcl:75
-#, tcl-format
-msgid "Running %s requires a selected file."
-msgstr "För att starta %s måste du välja en fil."
-
-#: lib/tools.tcl:90
-#, tcl-format
-msgid "Are you sure you want to run %s?"
-msgstr "Är du säker på att du vill starta %s?"
-
-#: lib/tools.tcl:110
-#, tcl-format
-msgid "Tool: %s"
-msgstr "Verktyg: %s"
-
-#: lib/tools.tcl:111
-#, tcl-format
-msgid "Running: %s"
-msgstr "Exekverar: %s"
-
-#: lib/tools.tcl:149
-#, tcl-format
-msgid "Tool completed successfully: %s"
-msgstr "Verktyget avslutades framgångsrikt: %s"
-
-#: lib/tools.tcl:151
-#, tcl-format
-msgid "Tool failed: %s"
-msgstr "Verktyget misslyckades: %s"
-
#: lib/tools_dlg.tcl:22
msgid "Add Tool"
msgstr "Lägg till verktyg"
@@ -2429,52 +2403,52 @@ msgstr "Lägg till verktyg"
msgid "Add New Tool Command"
msgstr "Lägg till nytt verktygskommando"
-#: lib/tools_dlg.tcl:33
+#: lib/tools_dlg.tcl:34
msgid "Add globally"
msgstr "Lägg till globalt"
-#: lib/tools_dlg.tcl:45
+#: lib/tools_dlg.tcl:46
msgid "Tool Details"
msgstr "Detaljer för verktyg"
-#: lib/tools_dlg.tcl:48
+#: lib/tools_dlg.tcl:49
msgid "Use '/' separators to create a submenu tree:"
msgstr "Använd \"/\"-avdelare för att skapa ett undermenyträd:"
-#: lib/tools_dlg.tcl:61
+#: lib/tools_dlg.tcl:60
msgid "Command:"
msgstr "Kommando:"
-#: lib/tools_dlg.tcl:74
+#: lib/tools_dlg.tcl:71
msgid "Show a dialog before running"
msgstr "Visa dialog innan programmet startas"
-#: lib/tools_dlg.tcl:80
+#: lib/tools_dlg.tcl:77
msgid "Ask the user to select a revision (sets $REVISION)"
msgstr "Be användaren välja en version (sätter $REVISION)"
-#: lib/tools_dlg.tcl:85
+#: lib/tools_dlg.tcl:82
msgid "Ask the user for additional arguments (sets $ARGS)"
msgstr "Be användaren om ytterligare parametrar (sätter $ARGS)"
-#: lib/tools_dlg.tcl:92
+#: lib/tools_dlg.tcl:89
msgid "Don't show the command output window"
msgstr "Visa inte kommandots utdatafönster"
-#: lib/tools_dlg.tcl:97
+#: lib/tools_dlg.tcl:94
msgid "Run only if a diff is selected ($FILENAME not empty)"
msgstr "Kör endast om en diff har markerats ($FILENAME är inte tomt)"
-#: lib/tools_dlg.tcl:121
+#: lib/tools_dlg.tcl:118
msgid "Please supply a name for the tool."
msgstr "Ange ett namn för verktyget."
-#: lib/tools_dlg.tcl:129
+#: lib/tools_dlg.tcl:126
#, tcl-format
msgid "Tool '%s' already exists."
msgstr "Verktyget \"%s\" finns redan."
-#: lib/tools_dlg.tcl:151
+#: lib/tools_dlg.tcl:148
#, tcl-format
msgid ""
"Could not add tool:\n"
@@ -2483,35 +2457,65 @@ msgstr ""
"Kunde inte lägga till verktyget:\n"
"%s"
-#: lib/tools_dlg.tcl:190
+#: lib/tools_dlg.tcl:187
msgid "Remove Tool"
msgstr "Ta bort verktyg"
-#: lib/tools_dlg.tcl:196
+#: lib/tools_dlg.tcl:193
msgid "Remove Tool Commands"
msgstr "Ta bort verktygskommandon"
-#: lib/tools_dlg.tcl:200
+#: lib/tools_dlg.tcl:198
msgid "Remove"
msgstr "Ta bort"
-#: lib/tools_dlg.tcl:236
+#: lib/tools_dlg.tcl:231
msgid "(Blue denotes repository-local tools)"
msgstr "(Blått anger verktyg lokala för arkivet)"
-#: lib/tools_dlg.tcl:297
+#: lib/tools_dlg.tcl:292
#, tcl-format
msgid "Run Command: %s"
msgstr "Kör kommandot: %s"
-#: lib/tools_dlg.tcl:311
+#: lib/tools_dlg.tcl:306
msgid "Arguments"
msgstr "Argument"
-#: lib/tools_dlg.tcl:348
+#: lib/tools_dlg.tcl:341
msgid "OK"
msgstr "OK"
+#: lib/tools.tcl:75
+#, tcl-format
+msgid "Running %s requires a selected file."
+msgstr "För att starta %s måste du välja en fil."
+
+#: lib/tools.tcl:90
+#, tcl-format
+msgid "Are you sure you want to run %s?"
+msgstr "Är du säker på att du vill starta %s?"
+
+#: lib/tools.tcl:110
+#, tcl-format
+msgid "Tool: %s"
+msgstr "Verktyg: %s"
+
+#: lib/tools.tcl:111
+#, tcl-format
+msgid "Running: %s"
+msgstr "Exekverar: %s"
+
+#: lib/tools.tcl:149
+#, tcl-format
+msgid "Tool completed successfully: %s"
+msgstr "Verktyget avslutades framgångsrikt: %s"
+
+#: lib/tools.tcl:151
+#, tcl-format
+msgid "Tool failed: %s"
+msgstr "Verktyget misslyckades: %s"
+
#: lib/transport.tcl:7
#, tcl-format
msgid "Fetching new changes from %s"
@@ -2542,11 +2546,11 @@ msgstr "Speglar till %s"
msgid "Pushing %s %s to %s"
msgstr "Sänder %s %s till %s"
-#: lib/transport.tcl:100
+#: lib/transport.tcl:102
msgid "Push Branches"
msgstr "Sänd grenar"
-#: lib/transport.tcl:114
+#: lib/transport.tcl:117
msgid "Source Branches"
msgstr "Källgrenar"
@@ -2554,19 +2558,19 @@ msgstr "Källgrenar"
msgid "Destination Repository"
msgstr "Destinationsarkiv"
-#: lib/transport.tcl:169
+#: lib/transport.tcl:172
msgid "Transfer Options"
msgstr "Överföringsalternativ"
-#: lib/transport.tcl:171
+#: lib/transport.tcl:174
msgid "Force overwrite existing branch (may discard changes)"
msgstr "Tvinga överskrivning av befintlig gren (kan kasta bort ändringar)"
-#: lib/transport.tcl:175
+#: lib/transport.tcl:178
msgid "Use thin pack (for slow network connections)"
msgstr "Använd tunt paket (för långsamma nätverksanslutningar)"
-#: lib/transport.tcl:179
+#: lib/transport.tcl:182
msgid "Include tags"
msgstr "Ta med taggar"
diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh
index 66bbb2f..b1845c5 100644
--- a/git-gui/windows/git-gui.sh
+++ b/git-gui/windows/git-gui.sh
@@ -13,10 +13,11 @@ if { $argc >=2 && [lindex $argv 0] == "--working-dir" } {
incr argc -2
}
-set bindir [file dirname \
+set basedir [file dirname \
[file dirname \
[file dirname [info script]]]]
-set bindir [file join $bindir bin]
+set bindir [file join $basedir bin]
+set bindir "$bindir;[file join $basedir mingw bin]"
regsub -all ";" $bindir "\\;" bindir
set env(PATH) "$bindir;$env(PATH)"
unset bindir
diff --git a/git-instaweb.sh b/git-instaweb.sh
index 6635fbe..01a1b05 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -27,6 +27,7 @@ httpd="$(git config --get instaweb.httpd)"
root="$(git config --get instaweb.gitwebdir)"
port=$(git config --get instaweb.port)
module_path="$(git config --get instaweb.modulepath)"
+action="browse"
conf="$GIT_DIR/gitweb/httpd.conf"
@@ -43,7 +44,8 @@ test -z "$port" && port=1234
resolve_full_httpd () {
case "$httpd" in
- *apache2*|*lighttpd*)
+ *apache2*|*lighttpd*|*httpd*)
+ # yes, *httpd* covers *lighttpd* above, but it is there for clarity
# ensure that the apache2/lighttpd command ends with "-f"
if ! echo "$httpd" | sane_grep -- '-f *$' >/dev/null 2>&1
then
@@ -56,6 +58,13 @@ resolve_full_httpd () {
httpd_only="${httpd%% *}" # cut on first space
return
;;
+ *webrick*)
+ # server is started by running via generated webrick.rb in
+ # $fqgitdir/gitweb
+ full_httpd="$fqgitdir/gitweb/webrick.rb"
+ httpd_only="${httpd%% *}" # cut on first space
+ return
+ ;;
esac
httpd_only="$(echo $httpd | cut -f1 -d' ')"
@@ -90,12 +99,18 @@ start_httpd () {
# here $httpd should have a meaningful value
resolve_full_httpd
+ mkdir -p "$fqgitdir/gitweb/$httpd_only"
+ conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+ # generate correct config file if it doesn't exist
+ test -f "$conf" || configure_httpd
+ test -f "$fqgitdir/gitweb/gitweb_config.perl" || gitweb_conf
# don't quote $full_httpd, there can be arguments to it (-f)
case "$httpd" in
*mongoose*|*plackup*)
#These servers don't have a daemon mode so we'll have to fork it
- $full_httpd "$fqgitdir/gitweb/httpd.conf" &
+ $full_httpd "$conf" &
#Save the pid before doing anything else (we'll print it later)
pid=$!
@@ -109,7 +124,7 @@ $pid
EOF
;;
*)
- $full_httpd "$fqgitdir/gitweb/httpd.conf"
+ $full_httpd "$conf"
if test $? != 0; then
echo "Could not execute http daemon $httpd."
exit 1
@@ -140,17 +155,13 @@ while test $# != 0
do
case "$1" in
--stop|stop)
- stop_httpd
- exit 0
+ action="stop"
;;
--start|start)
- start_httpd
- exit 0
+ action="start"
;;
--restart|restart)
- stop_httpd
- start_httpd
- exit 0
+ action="restart"
;;
-l|--local)
local=true
@@ -187,40 +198,53 @@ GITWEB_CONFIG="$fqgitdir/gitweb/gitweb_config.perl"
export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
webrick_conf () {
+ # webrick seems to have no way of passing arbitrary environment
+ # variables to the underlying CGI executable, so we wrap the
+ # actual gitweb.cgi using a shell script to force it
+ wrapper="$fqgitdir/gitweb/$httpd/wrapper.sh"
+ cat > "$wrapper" <<EOF
+#!/bin/sh
+# we use this shell script wrapper around the real gitweb.cgi since
+# there appears to be no other way to pass arbitrary environment variables
+# into the CGI process
+GIT_EXEC_PATH=$GIT_EXEC_PATH GIT_DIR=$GIT_DIR GITWEB_CONFIG=$GITWEB_CONFIG
+export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
+exec $root/gitweb.cgi
+EOF
+ chmod +x "$wrapper"
+
+ # This assumes _ruby_ is in the user's $PATH. that's _one_
+ # portable way to run ruby, which could be installed anywhere, really.
# generate a standalone server script in $fqgitdir/gitweb.
cat >"$fqgitdir/gitweb/$httpd.rb" <<EOF
+#!/usr/bin/env ruby
require 'webrick'
-require 'yaml'
-options = YAML::load_file(ARGV[0])
-options[:StartCallback] = proc do
- File.open(options[:PidFile],"w") do |f|
- f.puts Process.pid
- end
-end
-options[:ServerType] = WEBrick::Daemon
+require 'logger'
+options = {
+ :Port => $port,
+ :DocumentRoot => "$root",
+ :Logger => Logger.new('$fqgitdir/gitweb/error.log'),
+ :AccessLog => [
+ [ Logger.new('$fqgitdir/gitweb/access.log'),
+ WEBrick::AccessLog::COMBINED_LOG_FORMAT ]
+ ],
+ :DirectoryIndex => ["gitweb.cgi"],
+ :CGIInterpreter => "$wrapper",
+ :StartCallback => lambda do
+ File.open("$fqgitdir/pid", "w") { |f| f.puts Process.pid }
+ end,
+ :ServerType => WEBrick::Daemon,
+}
+options[:BindAddress] = '127.0.0.1' if "$local" == "true"
server = WEBrick::HTTPServer.new(options)
['INT', 'TERM'].each do |signal|
trap(signal) {server.shutdown}
end
server.start
EOF
- # generate a shell script to invoke the above ruby script,
- # which assumes _ruby_ is in the user's $PATH. that's _one_
- # portable way to run ruby, which could be installed anywhere,
- # really.
- cat >"$fqgitdir/gitweb/$httpd" <<EOF
-#!/bin/sh
-exec ruby "$fqgitdir/gitweb/$httpd.rb" \$*
-EOF
- chmod +x "$fqgitdir/gitweb/$httpd"
-
- cat >"$conf" <<EOF
-:Port: $port
-:DocumentRoot: "$root"
-:DirectoryIndex: ["gitweb.cgi"]
-:PidFile: "$fqgitdir/pid"
-EOF
- test "$local" = true && echo ':BindAddress: "127.0.0.1"' >> "$conf"
+ chmod +x "$fqgitdir/gitweb/$httpd.rb"
+ # configuration is embedded in server script file, webrick.rb
+ rm -f "$conf"
}
lighttpd_conf () {
@@ -300,7 +324,13 @@ EOF
}
apache2_conf () {
- test -z "$module_path" && module_path=/usr/lib/apache2/modules
+ if test -z "$module_path"
+ then
+ test -d "/usr/lib/httpd/modules" &&
+ module_path="/usr/lib/httpd/modules"
+ test -d "/usr/lib/apache2/modules" &&
+ module_path="/usr/lib/apache2/modules"
+ fi
bind=
test x"$local" = xtrue && bind='127.0.0.1:'
echo 'text/css css' > "$fqgitdir/mime.types"
@@ -314,8 +344,10 @@ PidFile "$fqgitdir/pid"
Listen $bind$port
EOF
- for mod in mime dir; do
- if test -e $module_path/mod_${mod}.so; then
+ for mod in mime dir env log_config
+ do
+ if test -e $module_path/mod_${mod}.so
+ then
echo "LoadModule ${mod}_module " \
"$module_path/mod_${mod}.so" >> "$conf"
fi
@@ -334,7 +366,7 @@ EOF
cat >> "$conf" <<EOF
LoadModule perl_module $module_path/mod_perl.so
PerlPassEnv GIT_DIR
-PerlPassEnv GIT_EXEC_DIR
+PerlPassEnv GIT_EXEC_PATH
PerlPassEnv GITWEB_CONFIG
<Location /gitweb.cgi>
SetHandler perl-script
@@ -364,6 +396,9 @@ EOF
echo "ScriptSock logs/gitweb.sock" >> "$conf"
fi
cat >> "$conf" <<EOF
+PassEnv GIT_DIR
+PassEnv GIT_EXEC_PATH
+PassEnv GITWEB_CONFIG
AddHandler cgi-script .cgi
<Location /gitweb.cgi>
Options +ExecCGI
@@ -526,12 +561,14 @@ my \$app = builder {
# make it runnable as standalone app,
# like it would be run via 'plackup' utility
-if (__FILE__ eq \$0) {
+if (caller) {
+ return \$app;
+} else {
require Plack::Runner;
my \$runner = Plack::Runner->new();
\$runner->parse_options(qw(--env deployment --port $port),
- "$local" ? qw(--host 127.0.0.1) : ());
+ "$local" ? qw(--host 127.0.0.1) : ());
\$runner->run(\$app);
}
__END__
@@ -548,36 +585,59 @@ gitweb_conf() {
our \$projectroot = "$(dirname "$fqgitdir")";
our \$git_temp = "$fqgitdir/gitweb/tmp";
our \$projects_list = \$projectroot;
+
+\$feature{'remote_heads'}{'default'} = [1];
EOF
}
-gitweb_conf
-
-resolve_full_httpd
-mkdir -p "$fqgitdir/gitweb/$httpd_only"
+configure_httpd() {
+ case "$httpd" in
+ *lighttpd*)
+ lighttpd_conf
+ ;;
+ *apache2*|*httpd*)
+ apache2_conf
+ ;;
+ webrick)
+ webrick_conf
+ ;;
+ *mongoose*)
+ mongoose_conf
+ ;;
+ *plackup*)
+ plackup_conf
+ ;;
+ *)
+ echo "Unknown httpd specified: $httpd"
+ exit 1
+ ;;
+ esac
+}
-case "$httpd" in
-*lighttpd*)
- lighttpd_conf
- ;;
-*apache2*)
- apache2_conf
+case "$action" in
+stop)
+ stop_httpd
+ exit 0
;;
-webrick)
- webrick_conf
+start)
+ start_httpd
+ exit 0
;;
-*mongoose*)
- mongoose_conf
- ;;
-*plackup*)
- plackup_conf
- ;;
-*)
- echo "Unknown httpd specified: $httpd"
- exit 1
+restart)
+ stop_httpd
+ start_httpd
+ exit 0
;;
esac
+gitweb_conf
+
+resolve_full_httpd
+mkdir -p "$fqgitdir/gitweb/$httpd_only"
+conf="$fqgitdir/gitweb/$httpd_only.conf"
+
+configure_httpd
+
start_httpd
url=http://127.0.0.1:$port
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index 615753c..8643f74 100755
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
@@ -61,6 +61,11 @@ do
esac
eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
+ if test "$SHA1" = "$pretty_name"
+ then
+ SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)"
+ eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name}
+ fi
common=$(git merge-base --all $SHA1 $MRC) ||
die "Unable to find common commit with $pretty_name"
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index b86402a..7aeb969 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -22,6 +22,11 @@ LONG_USAGE="Usage: git merge-one-file $USAGE
Blob ids and modes should be empty for missing files."
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+cd_to_toplevel
+require_work_tree
+
if ! test "$#" -eq 7
then
echo "$LONG_USAGE"
@@ -132,7 +137,7 @@ case "${1:-.}${2:-.}${3:-.}" in
# Create the working tree file, using "our tree" version from the
# index, and then store the result of the merge.
- git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
+ git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1
rm -f -- "$orig" "$src1" "$src2"
if [ "$6" != "$7" ]; then
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index 51dd0d6..91f90ac 100644
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
@@ -10,17 +10,20 @@ merge_mode() {
translate_merge_tool_path () {
case "$1" in
- vimdiff)
- echo vim
+ araxis)
+ echo compare
;;
- gvimdiff)
- echo gvim
+ bc3)
+ echo bcompare
;;
emerge)
echo emacs
;;
- araxis)
- echo compare
+ gvimdiff|gvimdiff2)
+ echo gvim
+ ;;
+ vimdiff|vimdiff2)
+ echo vim
;;
*)
echo "$1"
@@ -35,7 +38,7 @@ check_unchanged () {
while true; do
echo "$MERGED seems unchanged."
printf "Was the merge successful? [y/n] "
- read answer < /dev/tty
+ read answer
case "$answer" in
y*|Y*) status=0; break ;;
n*|N*) status=1; break ;;
@@ -46,16 +49,16 @@ check_unchanged () {
valid_tool () {
case "$1" in
- kdiff3 | tkdiff | xxdiff | meld | opendiff | \
- emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
+ araxis | bc3 | diffuse | ecmerge | emerge | gvimdiff | gvimdiff2 | \
+ kdiff3 | meld | opendiff | p4merge | tkdiff | vimdiff | vimdiff2 | xxdiff)
;; # happy
- tortoisemerge)
- if ! merge_mode; then
+ kompare)
+ if ! diff_mode; then
return 1
fi
;;
- kompare)
- if ! diff_mode; then
+ tortoisemerge)
+ if ! merge_mode; then
return 1
fi
;;
@@ -83,138 +86,165 @@ get_merge_tool_cmd () {
}
run_merge_tool () {
+ # If GIT_PREFIX is empty then we cannot use it in tools
+ # that expect to be able to chdir() to its value.
+ GIT_PREFIX=${GIT_PREFIX:-.}
+ export GIT_PREFIX
+
merge_tool_path="$(get_merge_tool_path "$1")" || exit
base_present="$2"
status=0
case "$1" in
- kdiff3)
+ araxis)
if merge_mode; then
+ touch "$BACKUP"
if $base_present; then
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (Base)" \
- --L2 "$MERGED (Local)" \
- --L3 "$MERGED (Remote)" \
- -o "$MERGED" \
- "$BASE" "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
+ "$merge_tool_path" -wait -merge -3 -a1 \
+ "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
+ >/dev/null 2>&1
else
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (Local)" \
- --L2 "$MERGED (Remote)" \
- -o "$MERGED" \
- "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
+ "$merge_tool_path" -wait -2 \
+ "$LOCAL" "$REMOTE" "$MERGED" \
+ >/dev/null 2>&1
fi
- status=$?
+ check_unchanged
else
- ("$merge_tool_path" --auto \
- --L1 "$MERGED (A)" \
- --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
- > /dev/null 2>&1)
+ "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
+ >/dev/null 2>&1
fi
;;
- kompare)
- "$merge_tool_path" "$LOCAL" "$REMOTE"
- ;;
- tkdiff)
+ bc3)
if merge_mode; then
+ touch "$BACKUP"
if $base_present; then
- "$merge_tool_path" -a "$BASE" \
- -o "$MERGED" "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" \
+ -mergeoutput="$MERGED"
else
- "$merge_tool_path" \
- -o "$MERGED" "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ -mergeoutput="$MERGED"
fi
- status=$?
+ check_unchanged
else
"$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
- p4merge)
+ diffuse)
if merge_mode; then
- touch "$BACKUP"
+ touch "$BACKUP"
if $base_present; then
- "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+ "$merge_tool_path" \
+ "$LOCAL" "$MERGED" "$REMOTE" \
+ "$BASE" | cat
else
- "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
+ "$merge_tool_path" \
+ "$LOCAL" "$MERGED" "$REMOTE" | cat
fi
check_unchanged
else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
fi
;;
- meld)
+ ecmerge)
if merge_mode; then
touch "$BACKUP"
- "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+ if $base_present; then
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
+ --default --mode=merge3 --to="$MERGED"
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ --default --mode=merge2 --to="$MERGED"
+ fi
check_unchanged
else
- "$merge_tool_path" "$LOCAL" "$REMOTE"
+ "$merge_tool_path" --default --mode=diff2 \
+ "$LOCAL" "$REMOTE"
fi
;;
- diffuse)
+ emerge)
if merge_mode; then
- touch "$BACKUP"
if $base_present; then
"$merge_tool_path" \
- "$LOCAL" "$MERGED" "$REMOTE" \
- "$BASE" | cat
+ -f emerge-files-with-ancestor-command \
+ "$LOCAL" "$REMOTE" "$BASE" \
+ "$(basename "$MERGED")"
else
"$merge_tool_path" \
- "$LOCAL" "$MERGED" "$REMOTE" | cat
+ -f emerge-files-command \
+ "$LOCAL" "$REMOTE" \
+ "$(basename "$MERGED")"
fi
- check_unchanged
+ status=$?
else
- "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+ "$merge_tool_path" -f emerge-files-command \
+ "$LOCAL" "$REMOTE"
fi
;;
- vimdiff)
+ gvimdiff|vimdiff)
if merge_mode; then
touch "$BACKUP"
- "$merge_tool_path" -d -c "wincmd l" \
- "$LOCAL" "$MERGED" "$REMOTE"
+ if $base_present; then
+ "$merge_tool_path" -f -d -c "wincmd J" \
+ "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
+ else
+ "$merge_tool_path" -f -d -c "wincmd l" \
+ "$LOCAL" "$MERGED" "$REMOTE"
+ fi
check_unchanged
else
- "$merge_tool_path" -d -c "wincmd l" \
+ "$merge_tool_path" -R -f -d -c "wincmd l" \
+ -c 'cd $GIT_PREFIX' \
"$LOCAL" "$REMOTE"
fi
;;
- gvimdiff)
+ gvimdiff2|vimdiff2)
if merge_mode; then
touch "$BACKUP"
- "$merge_tool_path" -d -c "wincmd l" -f \
+ "$merge_tool_path" -f -d -c "wincmd l" \
"$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
- "$merge_tool_path" -d -c "wincmd l" -f \
+ "$merge_tool_path" -R -f -d -c "wincmd l" \
+ -c 'cd $GIT_PREFIX' \
"$LOCAL" "$REMOTE"
fi
;;
- xxdiff)
+ kdiff3)
if merge_mode; then
- touch "$BACKUP"
if $base_present; then
- "$merge_tool_path" -X --show-merged-pane \
- -R 'Accel.SaveAsMerged: "Ctrl-S"' \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- --merged-file "$MERGED" \
- "$LOCAL" "$BASE" "$REMOTE"
+ ("$merge_tool_path" --auto \
+ --L1 "$MERGED (Base)" \
+ --L2 "$MERGED (Local)" \
+ --L3 "$MERGED (Remote)" \
+ -o "$MERGED" \
+ "$BASE" "$LOCAL" "$REMOTE" \
+ > /dev/null 2>&1)
else
- "$merge_tool_path" -X $extra \
- -R 'Accel.SaveAsMerged: "Ctrl-S"' \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- --merged-file "$MERGED" \
- "$LOCAL" "$REMOTE"
+ ("$merge_tool_path" --auto \
+ --L1 "$MERGED (Local)" \
+ --L2 "$MERGED (Remote)" \
+ -o "$MERGED" \
+ "$LOCAL" "$REMOTE" \
+ > /dev/null 2>&1)
fi
+ status=$?
+ else
+ ("$merge_tool_path" --auto \
+ --L1 "$MERGED (A)" \
+ --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
+ > /dev/null 2>&1)
+ fi
+ ;;
+ kompare)
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+ ;;
+ meld)
+ if merge_mode; then
+ touch "$BACKUP"
+ "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
- "$merge_tool_path" \
- -R 'Accel.Search: "Ctrl+F"' \
- -R 'Accel.SearchForward: "Ctrl-G"' \
- "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
opendiff)
@@ -233,39 +263,28 @@ run_merge_tool () {
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
fi
;;
- ecmerge)
+ p4merge)
if merge_mode; then
touch "$BACKUP"
- if $base_present; then
- "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
- --default --mode=merge3 --to="$MERGED"
- else
- "$merge_tool_path" "$LOCAL" "$REMOTE" \
- --default --mode=merge2 --to="$MERGED"
- fi
+ $base_present || >"$BASE"
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
check_unchanged
else
- "$merge_tool_path" --default --mode=diff2 \
- "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
- emerge)
+ tkdiff)
if merge_mode; then
if $base_present; then
- "$merge_tool_path" \
- -f emerge-files-with-ancestor-command \
- "$LOCAL" "$REMOTE" "$BASE" \
- "$(basename "$MERGED")"
+ "$merge_tool_path" -a "$BASE" \
+ -o "$MERGED" "$LOCAL" "$REMOTE"
else
"$merge_tool_path" \
- -f emerge-files-command \
- "$LOCAL" "$REMOTE" \
- "$(basename "$MERGED")"
+ -o "$MERGED" "$LOCAL" "$REMOTE"
fi
status=$?
else
- "$merge_tool_path" -f emerge-files-command \
- "$LOCAL" "$REMOTE"
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
tortoisemerge)
@@ -280,22 +299,30 @@ run_merge_tool () {
status=1
fi
;;
- araxis)
+ xxdiff)
if merge_mode; then
touch "$BACKUP"
if $base_present; then
- "$merge_tool_path" -wait -merge -3 -a1 \
- "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \
- >/dev/null 2>&1
+ "$merge_tool_path" -X --show-merged-pane \
+ -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ --merged-file "$MERGED" \
+ "$LOCAL" "$BASE" "$REMOTE"
else
- "$merge_tool_path" -wait -2 \
- "$LOCAL" "$REMOTE" "$MERGED" \
- >/dev/null 2>&1
+ "$merge_tool_path" -X $extra \
+ -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ --merged-file "$MERGED" \
+ "$LOCAL" "$REMOTE"
fi
check_unchanged
else
- "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \
- >/dev/null 2>&1
+ "$merge_tool_path" \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ "$LOCAL" "$REMOTE"
fi
;;
*)
@@ -337,7 +364,7 @@ guess_merge_tool () {
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
fi
- tools="$tools gvimdiff diffuse ecmerge p4merge araxis"
+ tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3"
fi
case "${VISUAL:-$EDITOR}" in
*vim*)
diff --git a/git-mergetool.sh b/git-mergetool.sh
index b52a741..3c157bc 100755
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
@@ -21,6 +21,10 @@ is_symlink () {
test "$1" = 120000
}
+is_submodule () {
+ test "$1" = 160000
+}
+
local_present () {
test -n "$local_mode"
}
@@ -35,7 +39,8 @@ base_present () {
cleanup_temp_files () {
if test "$1" = --save-backup ; then
- mv -- "$BACKUP" "$MERGED.orig"
+ rm -rf -- "$MERGED.orig"
+ test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
rm -f -- "$LOCAL" "$REMOTE" "$BASE"
else
rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
@@ -52,11 +57,13 @@ describe_file () {
echo "deleted"
elif is_symlink "$mode" ; then
echo "a symbolic link -> '$(cat "$file")'"
+ elif is_submodule "$mode" ; then
+ echo "submodule commit $file"
else
if base_present; then
- echo "modified"
+ echo "modified file"
else
- echo "created"
+ echo "created file"
fi
fi
}
@@ -93,7 +100,7 @@ resolve_deleted_merge () {
else
printf "Use (c)reated or (d)eleted file, or (a)bort? "
fi
- read ans
+ read ans || return 1
case "$ans" in
[mMcC]*)
git add -- "$MERGED"
@@ -112,6 +119,67 @@ resolve_deleted_merge () {
done
}
+resolve_submodule_merge () {
+ while true; do
+ printf "Use (l)ocal or (r)emote, or (a)bort? "
+ read ans || return 1
+ case "$ans" in
+ [lL]*)
+ if ! local_present; then
+ if test -n "$(git ls-tree HEAD -- "$MERGED")"; then
+ # Local isn't present, but it's a subdirectory
+ git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $?
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git update-index --force-remove "$MERGED"
+ cleanup_temp_files --save-backup
+ fi
+ elif is_submodule "$local_mode"; then
+ stage_submodule "$MERGED" "$local_sha1"
+ else
+ git checkout-index -f --stage=2 -- "$MERGED"
+ git add -- "$MERGED"
+ fi
+ return 0
+ ;;
+ [rR]*)
+ if ! remote_present; then
+ if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then
+ # Remote isn't present, but it's a subdirectory
+ git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $?
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git update-index --force-remove "$MERGED"
+ fi
+ elif is_submodule "$remote_mode"; then
+ ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ stage_submodule "$MERGED" "$remote_sha1"
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git checkout-index -f --stage=3 -- "$MERGED"
+ git add -- "$MERGED"
+ fi
+ cleanup_temp_files --save-backup
+ return 0
+ ;;
+ [aA]*)
+ return 1
+ ;;
+ esac
+ done
+}
+
+stage_submodule () {
+ path="$1"
+ submodule_sha1="$2"
+ mkdir -p "$path" || die "fatal: unable to create directory for module at $path"
+ # Find $path relative to work tree
+ work_tree_root=$(cd_to_toplevel && pwd)
+ work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix)
+ test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree"
+ git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
+}
+
checkout_staged_file () {
tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ')
@@ -139,13 +207,23 @@ merge_file () {
REMOTE="./$MERGED.REMOTE.$ext"
BASE="./$MERGED.BASE.$ext"
- mv -- "$MERGED" "$BACKUP"
- cp -- "$BACKUP" "$MERGED"
-
base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
+ if is_submodule "$local_mode" || is_submodule "$remote_mode"; then
+ echo "Submodule merge conflict for '$MERGED':"
+ local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
+ remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
+ describe_file "$local_mode" "local" "$local_sha1"
+ describe_file "$remote_mode" "remote" "$remote_sha1"
+ resolve_submodule_merge
+ return
+ fi
+
+ mv -- "$MERGED" "$BACKUP"
+ cp -- "$BACKUP" "$MERGED"
+
base_present && checkout_staged_file 1 "$MERGED" "$BASE"
local_present && checkout_staged_file 2 "$MERGED" "$LOCAL"
remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
@@ -171,7 +249,7 @@ merge_file () {
describe_file "$remote_mode" "remote" "$REMOTE"
if "$prompt" = true; then
printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
- read ans
+ read ans || return 1
fi
if base_present; then
@@ -242,7 +320,7 @@ done
prompt_after_failed_merge() {
while true; do
printf "Continue merging other unresolved paths (y/n) ? "
- read ans
+ read ans || return 1
case "$ans" in
[yY]*)
@@ -264,24 +342,46 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa
last_status=0
rollup_status=0
+rerere=false
+
+files_to_merge() {
+ if test "$rerere" = true
+ then
+ git rerere remaining
+ else
+ git ls-files -u | sed -e 's/^[^ ]* //' | sort -u
+ fi
+}
+
if test $# -eq 0 ; then
- files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u)
+ cd_to_toplevel
+
+ if test -e "$GIT_DIR/MERGE_RR"
+ then
+ rerere=true
+ fi
+
+ files=$(files_to_merge)
if test -z "$files" ; then
echo "No files need merging"
exit 0
fi
- echo Merging the files: "$files"
- git ls-files -u |
- sed -e 's/^[^ ]* //' |
- sort -u |
+
+ # Save original stdin
+ exec 3<&0
+
+ printf "Merging:\n"
+ printf "$files\n"
+
+ files_to_merge |
while IFS= read i
do
if test $last_status -ne 0; then
- prompt_after_failed_merge < /dev/tty || exit 1
+ prompt_after_failed_merge <&3 || exit 1
fi
printf "\n"
- merge_file "$i" < /dev/tty > /dev/tty
+ merge_file "$i" <&3
last_status=$?
if test $last_status -ne 0; then
rollup_status=1
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 5f47b18..b24119d 100644
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -4,58 +4,9 @@
# this would fail in that case and would issue an error message.
GIT_DIR=$(git rev-parse -q --git-dir) || :;
-get_data_source () {
- case "$1" in
- */*)
- echo ''
- ;;
- .)
- echo self
- ;;
- *)
- if test "$(git config --get "remote.$1.url")"
- then
- echo config
- elif test -f "$GIT_DIR/remotes/$1"
- then
- echo remotes
- elif test -f "$GIT_DIR/branches/$1"
- then
- echo branches
- else
- echo ''
- fi ;;
- esac
-}
-
-get_remote_url () {
- data_source=$(get_data_source "$1")
- case "$data_source" in
- '')
- echo "$1"
- ;;
- self)
- echo "$1"
- ;;
- config)
- git config --get "remote.$1.url"
- ;;
- remotes)
- sed -ne '/^URL: */{
- s///p
- q
- }' "$GIT_DIR/remotes/$1"
- ;;
- branches)
- sed -e 's/#.*//' "$GIT_DIR/branches/$1"
- ;;
- *)
- die "internal error: get-remote-url $1" ;;
- esac
-}
-
get_default_remote () {
- curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+ curr_branch=$(git symbolic-ref -q HEAD)
+ curr_branch="${curr_branch#refs/heads/}"
origin=$(git config --get "branch.$curr_branch.remote")
echo ${origin:-origin}
}
@@ -66,7 +17,7 @@ get_remote_merge_branch () {
origin="$1"
default=$(get_default_remote)
test -z "$origin" && origin=$default
- curr_branch=$(git symbolic-ref -q HEAD)
+ curr_branch=$(git symbolic-ref -q HEAD) &&
[ "$origin" = "$default" ] &&
echo $(git for-each-ref --format='%(upstream)' $curr_branch)
;;
@@ -89,7 +40,51 @@ get_remote_merge_branch () {
refs/heads/*) remote=${remote#refs/heads/} ;;
refs/* | tags/* | remotes/* ) remote=
esac
-
- [ -n "$remote" ] && echo "refs/remotes/$repo/$remote"
+ [ -n "$remote" ] && case "$repo" in
+ .)
+ echo "refs/heads/$remote"
+ ;;
+ *)
+ echo "refs/remotes/$repo/$remote"
+ ;;
+ esac
esac
}
+
+error_on_missing_default_upstream () {
+ cmd="$1"
+ op_type="$2"
+ op_prep="$3"
+ example="$4"
+ branch_name=$(git symbolic-ref -q HEAD)
+ if test -z "$branch_name"
+ then
+ echo "You are not currently on a branch, so I cannot use any
+'branch.<branchname>.merge' in your configuration file.
+Please specify which branch you want to $op_type $op_prep on the command
+line and try again (e.g. '$example').
+See git-${cmd}(1) for details."
+ else
+ echo "You asked me to $cmd without telling me which branch you
+want to $op_type $op_prep, and 'branch.${branch_name#refs/heads/}.merge' in
+your configuration file does not tell me, either. Please
+specify which branch you want to use on the command line and
+try again (e.g. '$example').
+See git-${cmd}(1) for details.
+
+If you often $op_type $op_prep the same branch, you may want to
+use something like the following in your configuration file:
+ [branch \"${branch_name#refs/heads/}\"]
+ remote = <nickname>
+ merge = <remote-ref>"
+ test rebase = "$op_type" &&
+ echo " rebase = true"
+ echo "
+ [remote \"<nickname>\"]
+ url = <url>
+ fetch = <refspec>
+
+See git-config(1) for details."
+ fi
+ exit 1
+}
diff --git a/git-pull.sh b/git-pull.sh
index a09a44e..a10b129 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
. git-sh-setup
+. git-sh-i18n
set_reflog_action "pull $*"
require_work_tree
cd_to_toplevel
@@ -17,20 +18,20 @@ cd_to_toplevel
die_conflict () {
git diff-index --cached --name-status -r --ignore-submodules HEAD --
if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "Pull is not possible because you have unmerged files.
+ die "$(gettext "Pull is not possible because you have unmerged files.
Please, fix them up in the work tree, and then use 'git add/rm <file>'
-as appropriate to mark resolution, or use 'git commit -a'."
+as appropriate to mark resolution, or use 'git commit -a'.")"
else
- die "Pull is not possible because you have unmerged files."
+ die "$(gettext "Pull is not possible because you have unmerged files.")"
fi
}
die_merge () {
if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
- die "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge."
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge.")"
else
- die "You have not concluded your merge (MERGE_HEAD exists)."
+ die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
fi
}
@@ -38,7 +39,7 @@ test -z "$(git ls-files -u)" || die_conflict
test -f "$GIT_DIR/MERGE_HEAD" && die_merge
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
-log_arg= verbosity= progress=
+log_arg= verbosity= progress= recurse_submodules=
merge_args=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short="${curr_branch#refs/heads/}"
@@ -53,6 +54,8 @@ do
verbosity="$verbosity -v" ;;
--progress)
progress=--progress ;;
+ --no-progress)
+ progress=--no-progress ;;
-n|--no-stat|--no-summary)
diffstat=--no-stat ;;
--stat|--summary)
@@ -105,10 +108,19 @@ do
--no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
rebase=false
;;
+ --recurse-submodules)
+ recurse_submodules=--recurse-submodules
+ ;;
+ --recurse-submodules=*)
+ recurse_submodules="$1"
+ ;;
+ --no-recurse-submodules)
+ recurse_submodules=--no-recurse-submodules
+ ;;
--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
dry_run=--dry-run
;;
- -h|--h|--he|--hel|--help)
+ -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
usage
;;
*)
@@ -157,34 +169,10 @@ error_on_no_merge_candidates () {
echo "You asked to pull from the remote '$1', but did not specify"
echo "a branch. Because this is not the default configured remote"
echo "for your current branch, you must specify a branch on the command line."
- elif [ -z "$curr_branch" ]; then
- echo "You are not currently on a branch, so I cannot use any"
- echo "'branch.<branchname>.merge' in your configuration file."
- echo "Please specify which remote branch you want to use on the command"
- echo "line and try again (e.g. 'git pull <repository> <refspec>')."
- echo "See git-pull(1) for details."
- elif [ -z "$upstream" ]; then
- echo "You asked me to pull without telling me which branch you"
- echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
- echo "your configuration file does not tell me, either. Please"
- echo "specify which branch you want to use on the command line and"
- echo "try again (e.g. 'git pull <repository> <refspec>')."
- echo "See git-pull(1) for details."
- echo
- echo "If you often $op_type $op_prep the same branch, you may want to"
- echo "use something like the following in your configuration file:"
- echo
- echo " [branch \"${curr_branch}\"]"
- echo " remote = <nickname>"
- echo " merge = <remote-ref>"
- test rebase = "$op_type" &&
- echo " rebase = true"
- echo
- echo " [remote \"<nickname>\"]"
- echo " url = <url>"
- echo " fetch = <refspec>"
- echo
- echo "See git-config(1) for details."
+ elif [ -z "$curr_branch" -o -z "$upstream" ]; then
+ . git-parse-remote
+ error_on_missing_default_upstream "pull" $op_type $op_prep \
+ "git pull <repository> <refspec>"
else
echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
echo "from the remote, but no such ref was fetched."
@@ -198,13 +186,10 @@ test true = "$rebase" && {
# On an unborn branch
if test -f "$GIT_DIR/index"
then
- die "updating an unborn branch with changes added to the index"
+ die "$(gettext "updating an unborn branch with changes added to the index")"
fi
else
- git update-index --ignore-submodules --refresh &&
- git diff-files --ignore-submodules --quiet &&
- git diff-index --ignore-submodules --cached --quiet HEAD -- ||
- die "refusing to pull with rebase: your working tree is not up-to-date"
+ require_clean_work_tree "pull with rebase" "Please commit or stash them."
fi
oldremoteref= &&
. git-parse-remote &&
@@ -220,7 +205,7 @@ test true = "$rebase" && {
done
}
orig_head=$(git rev-parse -q --verify HEAD)
-git fetch $verbosity $progress $dry_run --update-head-ok "$@" || exit 1
+git fetch $verbosity $progress $dry_run $recurse_submodules --update-head-ok "$@" || exit 1
test -z "$dry_run" || exit 0
curr_head=$(git rev-parse -q --verify HEAD)
@@ -232,17 +217,20 @@ then
# $orig_head commit, but we are merging into $curr_head.
# First update the working tree to match $curr_head.
- echo >&2 "Warning: fetch updated the current branch head."
- echo >&2 "Warning: fast-forwarding your working tree from"
- echo >&2 "Warning: commit $orig_head."
+ (
+ eval_gettext "Warning: fetch updated the current branch head.
+Warning: fast-forwarding your working tree from
+Warning: commit \$orig_head." &&
+ echo
+ ) >&2
git update-index -q --refresh
git read-tree -u -m "$orig_head" "$curr_head" ||
- die 'Cannot fast-forward your working tree.
+ die "$(eval_gettext "Cannot fast-forward your working tree.
After making sure that you saved anything precious from
-$ git diff '$orig_head'
+$ git diff \$orig_head
output, run
$ git reset --hard
-to recover.'
+to recover.")"
fi
@@ -257,11 +245,11 @@ case "$merge_head" in
?*' '?*)
if test -z "$orig_head"
then
- die "Cannot merge multiple branches into empty head"
+ die "$(gettext "Cannot merge multiple branches into empty head")"
fi
if test true = "$rebase"
then
- die "Cannot rebase onto multiple branches"
+ die "$(gettext "Cannot rebase onto multiple branches")"
fi
;;
esac
@@ -269,10 +257,19 @@ esac
if test -z "$orig_head"
then
git update-ref -m "initial pull" HEAD $merge_head "$curr_head" &&
- git read-tree --reset -u HEAD || exit 1
+ git read-tree -m -u HEAD || exit 1
exit
fi
+if test true = "$rebase"
+then
+ o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
+ if test "$oldremoteref" = "$o"
+ then
+ unset oldremoteref
+ fi
+fi
+
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
case "$rebase" in
true)
@@ -281,8 +278,8 @@ true)
;;
*)
eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
- eval="$eval $log_arg $strategy_args $merge_args"
- eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
+ eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
+ eval="$eval \"\$merge_name\" HEAD $merge_head"
;;
esac
eval "exec $eval"
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
new file mode 100644
index 0000000..c815a24
--- /dev/null
+++ b/git-rebase--am.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Junio C Hamano.
+#
+
+. git-sh-setup
+
+case "$action" in
+continue)
+ git am --resolved --resolvemsg="$resolvemsg" &&
+ move_to_original_branch
+ exit
+ ;;
+skip)
+ git am --skip --resolvemsg="$resolvemsg" &&
+ move_to_original_branch
+ exit
+ ;;
+esac
+
+test -n "$rebase_root" && root_flag=--root
+
+git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+ --src-prefix=a/ --dst-prefix=b/ \
+ --no-renames $root_flag "$revisions" |
+git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
+move_to_original_branch
+ret=$?
+test 0 != $ret -a -d "$state_dir" && write_basic_state
+exit $ret
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 6b86abc..c6ba7c1 100755..100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -10,47 +10,22 @@
# The original idea comes from Eric W. Biederman, in
# http://article.gmane.org/gmane.comp.version-control.git/22407
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git-rebase [-i] [options] [--] <upstream> [<branch>]
-git-rebase [-i] (--continue | --abort | --skip)
---
- Available options are
-v,verbose display a diffstat of what changed upstream
-onto= rebase onto given branch instead of upstream
-p,preserve-merges try to recreate merges instead of ignoring them
-s,strategy= use the given merge strategy
-no-ff cherry-pick all commits, even if unchanged
-m,merge always used (no-op)
-i,interactive always used (no-op)
- Actions:
-continue continue rebasing process
-abort abort rebasing process and restore original branch
-skip skip current patch and continue rebasing process
-no-verify override pre-rebase hook from stopping the operation
-root rebase all reachable commmits up to the root(s)
-autosquash move commits that begin with squash!/fixup! under -i
-"
-
. git-sh-setup
-require_work_tree
-
-DOTEST="$GIT_DIR/rebase-merge"
# The file containing rebase commands, comments, and empty lines.
# This file is created by "git rebase -i" then edited by the user. As
# the lines are processed, they are removed from the front of this
-# file and written to the tail of $DONE.
-TODO="$DOTEST"/git-rebase-todo
+# file and written to the tail of $done.
+todo="$state_dir"/git-rebase-todo
# The rebase command lines that have already been processed. A line
# is moved here when it is first handled, before any associated user
# actions.
-DONE="$DOTEST"/done
+done="$state_dir"/done
# The commit message that is planned to be used for any changes that
# need to be committed following a user interaction.
-MSG="$DOTEST"/message
+msg="$state_dir"/message
# The file into which is accumulated the suggested commit message for
# squash/fixup commands. When the first of a series of squash/fixups
@@ -60,34 +35,34 @@ MSG="$DOTEST"/message
# is appended to the file as it is processed.
#
# The first line of the file is of the form
-# # This is a combination of $COUNT commits.
-# where $COUNT is the number of commits whose messages have been
+# # This is a combination of $count commits.
+# where $count is the number of commits whose messages have been
# written to the file so far (including the initial "pick" commit).
# Each time that a commit message is processed, this line is read and
# updated. It is deleted just before the combined commit is made.
-SQUASH_MSG="$DOTEST"/message-squash
+squash_msg="$state_dir"/message-squash
# If the current series of squash/fixups has not yet included a squash
# command, then this file exists and holds the commit message of the
# original "pick" commit. (If the series ends without a "squash"
# command, then this can be used as the commit message of the combined
# commit without opening the editor.)
-FIXUP_MSG="$DOTEST"/message-fixup
+fixup_msg="$state_dir"/message-fixup
-# $REWRITTEN is the name of a directory containing files for each
-# commit that is reachable by at least one merge base of $HEAD and
-# $UPSTREAM. They are not necessarily rewritten, but their children
+# $rewritten is the name of a directory containing files for each
+# commit that is reachable by at least one merge base of $head and
+# $upstream. They are not necessarily rewritten, but their children
# might be. This ensures that commits on merged, but otherwise
# unrelated side branches are left alone. (Think "X" in the man page's
# example.)
-REWRITTEN="$DOTEST"/rewritten
+rewritten="$state_dir"/rewritten
-DROPPED="$DOTEST"/dropped
+dropped="$state_dir"/dropped
# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
# GIT_AUTHOR_DATE that will be used for the commit that is currently
# being rebased.
-AUTHOR_SCRIPT="$DOTEST"/author-script
+author_script="$state_dir"/author-script
# When an "edit" rebase command is being processed, the SHA1 of the
# commit to be edited is recorded in this file. When "git rebase
@@ -95,45 +70,20 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
# will be amended to the HEAD commit, but only provided the HEAD
# commit is still the commit to be edited. When any other rebase
# command is processed, this file is deleted.
-AMEND="$DOTEST"/amend
+amend="$state_dir"/amend
# For the post-rewrite hook, we make a list of rewritten commits and
# their new sha1s. The rewritten-pending list keeps the sha1s of
# commits that have been processed, but not committed yet,
# e.g. because they are waiting for a 'squash' command.
-REWRITTEN_LIST="$DOTEST"/rewritten-list
-REWRITTEN_PENDING="$DOTEST"/rewritten-pending
-
-PRESERVE_MERGES=
-STRATEGY=
-ONTO=
-VERBOSE=
-OK_TO_SKIP_PRE_REBASE=
-REBASE_ROOT=
-AUTOSQUASH=
-NEVER_FF=
-
-GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
-mark the corrected paths with 'git add <paths>', and
-run 'git rebase --continue'"
+rewritten_list="$state_dir"/rewritten-list
+rewritten_pending="$state_dir"/rewritten-pending
+
+GIT_CHERRY_PICK_HELP="$resolvemsg"
export GIT_CHERRY_PICK_HELP
warn () {
- echo "$*" >&2
-}
-
-output () {
- case "$VERBOSE" in
- '')
- output=$("$@" 2>&1 )
- status=$?
- test $status != 0 && printf "%s\n" "$output"
- return $status
- ;;
- *)
- "$@"
- ;;
- esac
+ printf '%s\n' "$*" >&2
}
# Output the commit message for the specified commit.
@@ -141,30 +91,10 @@ commit_message () {
git cat-file commit "$1" | sed "1,/^$/d"
}
-run_pre_rebase_hook () {
- if test -z "$OK_TO_SKIP_PRE_REBASE" &&
- test -x "$GIT_DIR/hooks/pre-rebase"
- then
- "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
- echo >&2 "The pre-rebase hook refused to rebase."
- exit 1
- }
- fi
-}
-
-require_clean_work_tree () {
- # test if working tree is dirty
- git rev-parse --verify HEAD > /dev/null &&
- git update-index --ignore-submodules --refresh &&
- git diff-files --quiet --ignore-submodules &&
- git diff-index --cached --quiet HEAD --ignore-submodules -- ||
- die "Working tree is dirty"
-}
-
-ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+orig_reflog_action="$GIT_REFLOG_ACTION"
comment_for_reflog () {
- case "$ORIG_REFLOG_ACTION" in
+ case "$orig_reflog_action" in
''|rebase*)
GIT_REFLOG_ACTION="rebase -i ($1)"
export GIT_REFLOG_ACTION
@@ -174,16 +104,16 @@ comment_for_reflog () {
last_count=
mark_action_done () {
- sed -e 1q < "$TODO" >> "$DONE"
- sed -e 1d < "$TODO" >> "$TODO".new
- mv -f "$TODO".new "$TODO"
- count=$(sane_grep -c '^[^#]' < "$DONE")
- total=$(($count+$(sane_grep -c '^[^#]' < "$TODO")))
- if test "$last_count" != "$count"
+ sed -e 1q < "$todo" >> "$done"
+ sed -e 1d < "$todo" >> "$todo".new
+ mv -f "$todo".new "$todo"
+ new_count=$(sane_grep -c '^[^#]' < "$done")
+ total=$(($new_count+$(sane_grep -c '^[^#]' < "$todo")))
+ if test "$last_count" != "$new_count"
then
- last_count=$count
- printf "Rebasing (%d/%d)\r" $count $total
- test -z "$VERBOSE" || echo
+ last_count=$new_count
+ printf "Rebasing (%d/%d)\r" $new_count $total
+ test -z "$verbose" || echo
fi
}
@@ -199,22 +129,22 @@ make_patch () {
*)
echo "Root commit"
;;
- esac > "$DOTEST"/patch
- test -f "$MSG" ||
- commit_message "$1" > "$MSG"
- test -f "$AUTHOR_SCRIPT" ||
- get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
+ esac > "$state_dir"/patch
+ test -f "$msg" ||
+ commit_message "$1" > "$msg"
+ test -f "$author_script" ||
+ get_author_ident_from_commit "$1" > "$author_script"
}
die_with_patch () {
- echo "$1" > "$DOTEST"/stopped-sha
+ echo "$1" > "$state_dir"/stopped-sha
make_patch "$1"
git rerere
die "$2"
}
die_abort () {
- rm -rf "$DOTEST"
+ rm -rf "$state_dir"
die "$1"
}
@@ -234,15 +164,10 @@ do_with_author () {
pick_one () {
ff=--ff
case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
- case "$NEVER_FF" in '') ;; ?*) ff= ;; esac
+ case "$force_rebase" in '') ;; ?*) ff= ;; esac
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
- test -d "$REWRITTEN" &&
+ test -d "$rewritten" &&
pick_one_preserving_merges "$@" && return
- if test -n "$REBASE_ROOT"
- then
- output git cherry-pick "$@"
- return
- fi
output git cherry-pick $ff "$@"
}
@@ -259,20 +184,20 @@ pick_one_preserving_merges () {
esac
sha1=$(git rev-parse $sha1)
- if test -f "$DOTEST"/current-commit
+ if test -f "$state_dir"/current-commit
then
if test "$fast_forward" = t
then
- cat "$DOTEST"/current-commit | while read current_commit
+ while read current_commit
do
- git rev-parse HEAD > "$REWRITTEN"/$current_commit
- done
- rm "$DOTEST"/current-commit ||
+ git rev-parse HEAD > "$rewritten"/$current_commit
+ done <"$state_dir"/current-commit
+ rm "$state_dir"/current-commit ||
die "Cannot write current commit's replacement sha1"
fi
fi
- echo $sha1 >> "$DOTEST"/current-commit
+ echo $sha1 >> "$state_dir"/current-commit
# rewrite parents; if none were rewritten, we can fast-forward.
new_parents=
@@ -286,9 +211,9 @@ pick_one_preserving_merges () {
p=$(expr "$pend" : ' \([^ ]*\)')
pend="${pend# $p}"
- if test -f "$REWRITTEN"/$p
+ if test -f "$rewritten"/$p
then
- new_p=$(cat "$REWRITTEN"/$p)
+ new_p=$(cat "$rewritten"/$p)
# If the todo reordered commits, and our parent is marked for
# rewriting, but hasn't been gotten to yet, assume the user meant to
@@ -307,10 +232,10 @@ pick_one_preserving_merges () {
;;
esac
else
- if test -f "$DROPPED"/$p
+ if test -f "$dropped"/$p
then
fast_forward=f
- replacement="$(cat "$DROPPED"/$p)"
+ replacement="$(cat "$dropped"/$p)"
test -z "$replacement" && replacement=root
pend=" $replacement$pend"
else
@@ -339,18 +264,19 @@ pick_one_preserving_merges () {
test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
# redo merge
- author_script=$(get_author_ident_from_commit $sha1)
- eval "$author_script"
- msg="$(commit_message $sha1)"
+ author_script_content=$(get_author_ident_from_commit $sha1)
+ eval "$author_script_content"
+ msg_content="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
if ! do_with_author output \
- git merge $STRATEGY -m "$msg" $new_parents
+ git merge --no-ff ${strategy:+-s $strategy} -m \
+ "$msg_content" $new_parents
then
- printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
+ printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
fi
- echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
+ echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
;;
*)
output git cherry-pick "$@" ||
@@ -371,46 +297,46 @@ nth_string () {
}
update_squash_messages () {
- if test -f "$SQUASH_MSG"; then
- mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
- COUNT=$(($(sed -n \
+ if test -f "$squash_msg"; then
+ mv "$squash_msg" "$squash_msg".bak || exit
+ count=$(($(sed -n \
-e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
- -e "q" < "$SQUASH_MSG".bak)+1))
+ -e "q" < "$squash_msg".bak)+1))
{
- echo "# This is a combination of $COUNT commits."
+ echo "# This is a combination of $count commits."
sed -e 1d -e '2,/^./{
/^$/d
- }' <"$SQUASH_MSG".bak
- } >"$SQUASH_MSG"
+ }' <"$squash_msg".bak
+ } >"$squash_msg"
else
- commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
- COUNT=2
+ commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg"
+ count=2
{
echo "# This is a combination of 2 commits."
echo "# The first commit's message is:"
echo
- cat "$FIXUP_MSG"
- } >"$SQUASH_MSG"
+ cat "$fixup_msg"
+ } >"$squash_msg"
fi
case $1 in
squash)
- rm -f "$FIXUP_MSG"
+ rm -f "$fixup_msg"
echo
- echo "# This is the $(nth_string $COUNT) commit message:"
+ echo "# This is the $(nth_string $count) commit message:"
echo
commit_message $2
;;
fixup)
echo
- echo "# The $(nth_string $COUNT) commit message will be skipped:"
+ echo "# The $(nth_string $count) commit message will be skipped:"
echo
commit_message $2 | sed -e 's/^/# /'
;;
- esac >>"$SQUASH_MSG"
+ esac >>"$squash_msg"
}
peek_next_command () {
- sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO"
+ sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo"
}
# A squash/fixup has failed. Prepare the long version of the squash
@@ -420,37 +346,37 @@ peek_next_command () {
# messages, effectively causing the combined commit to be used as the
# new basis for any further squash/fixups. Args: sha1 rest
die_failed_squash() {
- mv "$SQUASH_MSG" "$MSG" || exit
- rm -f "$FIXUP_MSG"
- cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+ mv "$squash_msg" "$msg" || exit
+ rm -f "$fixup_msg"
+ cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
warn
warn "Could not apply $1... $2"
die_with_patch $1 ""
}
flush_rewritten_pending() {
- test -s "$REWRITTEN_PENDING" || return
+ test -s "$rewritten_pending" || return
newsha1="$(git rev-parse HEAD^0)"
- sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
- rm -f "$REWRITTEN_PENDING"
+ sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list"
+ rm -f "$rewritten_pending"
}
record_in_rewritten() {
oldsha1="$(git rev-parse $1)"
- echo "$oldsha1" >> "$REWRITTEN_PENDING"
+ echo "$oldsha1" >> "$rewritten_pending"
case "$(peek_next_command)" in
- squash|s|fixup|f)
+ squash|s|fixup|f)
;;
- *)
+ *)
flush_rewritten_pending
;;
esac
}
do_next () {
- rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
- read command sha1 rest < "$TODO"
+ rm -f "$msg" "$author_script" "$amend" || exit
+ read -r command sha1 rest < "$todo"
case "$command" in
'#'*|''|noop)
mark_action_done
@@ -478,9 +404,9 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
- echo "$sha1" > "$DOTEST"/stopped-sha
+ echo "$sha1" > "$state_dir"/stopped-sha
make_patch $sha1
- git rev-parse --verify HEAD > "$AMEND"
+ git rev-parse --verify HEAD > "$amend"
warn "Stopped at $sha1... $rest"
warn "You can amend the commit now, with"
warn
@@ -503,80 +429,108 @@ do_next () {
esac
comment_for_reflog $squash_style
- test -f "$DONE" && has_action "$DONE" ||
+ test -f "$done" && has_action "$done" ||
die "Cannot '$squash_style' without a previous commit"
mark_action_done
update_squash_messages $squash_style $sha1
- author_script=$(get_author_ident_from_commit HEAD)
- echo "$author_script" > "$AUTHOR_SCRIPT"
- eval "$author_script"
+ author_script_content=$(get_author_ident_from_commit HEAD)
+ echo "$author_script_content" > "$author_script"
+ eval "$author_script_content"
output git reset --soft HEAD^
pick_one -n $sha1 || die_failed_squash $sha1 "$rest"
case "$(peek_next_command)" in
squash|s|fixup|f)
# This is an intermediate commit; its message will only be
# used in case of trouble. So use the long version:
- do_with_author output git commit --no-verify -F "$SQUASH_MSG" ||
+ do_with_author output git commit --no-verify -F "$squash_msg" ||
die_failed_squash $sha1 "$rest"
;;
*)
# This is the final command of this squash/fixup group
- if test -f "$FIXUP_MSG"
+ if test -f "$fixup_msg"
then
- do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
+ do_with_author git commit --no-verify -F "$fixup_msg" ||
die_failed_squash $sha1 "$rest"
else
- cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
+ cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
rm -f "$GIT_DIR"/MERGE_MSG
do_with_author git commit --no-verify -e ||
die_failed_squash $sha1 "$rest"
fi
- rm -f "$SQUASH_MSG" "$FIXUP_MSG"
+ rm -f "$squash_msg" "$fixup_msg"
;;
esac
record_in_rewritten $sha1
;;
+ x|"exec")
+ read -r command rest < "$todo"
+ mark_action_done
+ printf 'Executing: %s\n' "$rest"
+ # "exec" command doesn't take a sha1 in the todo-list.
+ # => can't just use $sha1 here.
+ git rev-parse --verify HEAD > "$state_dir"/stopped-sha
+ ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
+ status=$?
+ if test "$status" -ne 0
+ then
+ warn "Execution failed: $rest"
+ warn "You can fix the problem, and then run"
+ warn
+ warn " git rebase --continue"
+ warn
+ exit "$status"
+ fi
+ # Run in subshell because require_clean_work_tree can die.
+ if ! (require_clean_work_tree "rebase")
+ then
+ warn "Commit or stash your changes, and then run"
+ warn
+ warn " git rebase --continue"
+ warn
+ exit 1
+ fi
+ ;;
*)
warn "Unknown command: $command $sha1 $rest"
if git rev-parse --verify -q "$sha1" >/dev/null
then
- die_with_patch $sha1 "Please fix this in the file $TODO."
+ die_with_patch $sha1 "Please fix this in the file $todo."
else
- die "Please fix this in the file $TODO."
+ die "Please fix this in the file $todo."
fi
;;
esac
- test -s "$TODO" && return
+ test -s "$todo" && return
comment_for_reflog finish &&
- HEADNAME=$(cat "$DOTEST"/head-name) &&
- OLDHEAD=$(cat "$DOTEST"/head) &&
- SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
- NEWHEAD=$(git rev-parse HEAD) &&
- case $HEADNAME in
+ shortonto=$(git rev-parse --short $onto) &&
+ newhead=$(git rev-parse HEAD) &&
+ case $head_name in
refs/*)
- message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" &&
- git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
- git symbolic-ref HEAD $HEADNAME
+ message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" &&
+ git update-ref -m "$message" $head_name $newhead $orig_head &&
+ git symbolic-ref \
+ -m "$GIT_REFLOG_ACTION: returning to $head_name" \
+ HEAD $head_name
;;
esac && {
- test ! -f "$DOTEST"/verbose ||
- git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
+ test ! -f "$state_dir"/verbose ||
+ git diff-tree --stat $orig_head..HEAD
} &&
{
- test -s "$REWRITTEN_LIST" &&
- git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+ test -s "$rewritten_list" &&
+ git notes copy --for-rewrite=rebase < "$rewritten_list" ||
true # we don't care if this copying failed
} &&
if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$REWRITTEN_LIST"; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+ test -s "$rewritten_list"; then
+ "$GIT_DIR"/hooks/post-rewrite rebase < "$rewritten_list"
true # we don't care if this hook failed
fi &&
- rm -rf "$DOTEST" &&
+ rm -rf "$state_dir" &&
git gc --auto &&
- warn "Successfully rebased and updated $HEADNAME."
+ warn "Successfully rebased and updated $head_name."
exit
}
@@ -591,364 +545,253 @@ do_rest () {
# skip picking commits whose parents are unchanged
skip_unnecessary_picks () {
fd=3
- while read command sha1 rest
+ while read -r command rest
do
# fd=3 means we skip the command
- case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in
- 3,pick,"$ONTO"*|3,p,"$ONTO"*)
- # pick a commit whose parent is current $ONTO -> skip
- ONTO=$sha1
+ case "$fd,$command" in
+ 3,pick|3,p)
+ # pick a commit whose parent is current $onto -> skip
+ sha1=${rest%% *}
+ case "$(git rev-parse --verify --quiet "$sha1"^)" in
+ "$onto"*)
+ onto=$sha1
+ ;;
+ *)
+ fd=1
+ ;;
+ esac
;;
- 3,#*|3,,*)
+ 3,#*|3,)
# copy comments
;;
*)
fd=1
;;
esac
- echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
- done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
- mv -f "$TODO".new "$TODO" &&
+ printf '%s\n' "$command${rest:+ }$rest" >&$fd
+ done <"$todo" >"$todo.new" 3>>"$done" &&
+ mv -f "$todo".new "$todo" &&
case "$(peek_next_command)" in
squash|s|fixup|f)
- record_in_rewritten "$ONTO"
+ record_in_rewritten "$onto"
;;
esac ||
die "Could not skip unnecessary pick commands"
}
-# check if no other options are set
-is_standalone () {
- test $# -eq 2 -a "$2" = '--' &&
- test -z "$ONTO" &&
- test -z "$PRESERVE_MERGES" &&
- test -z "$STRATEGY" &&
- test -z "$VERBOSE"
-}
-
-get_saved_options () {
- test -d "$REWRITTEN" && PRESERVE_MERGES=t
- test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
- test -f "$DOTEST"/verbose && VERBOSE=t
- test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
-}
-
# Rearrange the todo list that has both "pick sha1 msg" and
# "pick sha1 fixup!/squash! msg" appears in it so that the latter
# comes immediately after the former, and change "pick" to
# "fixup"/"squash".
rearrange_squash () {
- sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
- -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
- "$1" >"$1.sq"
+ # extract fixup!/squash! lines and resolve any referenced sha1's
+ while read -r pick sha1 message
+ do
+ case "$message" in
+ "squash! "*|"fixup! "*)
+ action="${message%%!*}"
+ rest="${message#*! }"
+ echo "$sha1 $action $rest"
+ # if it's a single word, try to resolve to a full sha1 and
+ # emit a second copy. This allows us to match on both message
+ # and on sha1 prefix
+ if test "${rest#* }" = "$rest"; then
+ fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
+ if test -n "$fullsha"; then
+ # prefix the action to uniquely identify this line as
+ # intended for full sha1 match
+ echo "$sha1 +$action $fullsha"
+ fi
+ fi
+ esac
+ done >"$1.sq" <"$1"
test -s "$1.sq" || return
used=
- while read pick sha1 message
+ while read -r pick sha1 message
do
case " $used" in
*" $sha1 "*) continue ;;
esac
- echo "$pick $sha1 $message"
- while read squash action msg
+ printf '%s\n' "$pick $sha1 $message"
+ used="$used$sha1 "
+ while read -r squash action msg_content
do
- case "$message" in
- "$msg"*)
- echo "$action $squash $action! $msg"
- used="$used$squash "
- ;;
+ case " $used" in
+ *" $squash "*) continue ;;
+ esac
+ emit=0
+ case "$action" in
+ +*)
+ action="${action#+}"
+ # full sha1 prefix test
+ case "$msg_content" in "$sha1"*) emit=1;; esac ;;
+ *)
+ # message prefix test
+ case "$message" in "$msg_content"*) emit=1;; esac ;;
esac
+ if test $emit = 1; then
+ printf '%s\n' "$action $squash $action! $msg_content"
+ used="$used$squash "
+ fi
done <"$1.sq"
done >"$1.rearranged" <"$1"
cat "$1.rearranged" >"$1"
rm -f "$1.sq" "$1.rearranged"
}
-LF='
-'
-parse_onto () {
- case "$1" in
- *...*)
- if left=${1%...*} right=${1#*...} &&
- onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
- then
- case "$onto" in
- ?*"$LF"?* | '')
- exit 1 ;;
- esac
- echo "$onto"
- exit 0
- fi
- esac
- git rev-parse --verify "$1^0"
-}
-
-while test $# != 0
-do
- case "$1" in
- --no-verify)
- OK_TO_SKIP_PRE_REBASE=yes
- ;;
- --verify)
- ;;
- --continue)
- is_standalone "$@" || usage
- get_saved_options
- comment_for_reflog continue
-
- test -d "$DOTEST" || die "No interactive rebase running"
-
- # Sanity check
- git rev-parse --verify HEAD >/dev/null ||
- die "Cannot read HEAD"
- git update-index --ignore-submodules --refresh &&
- git diff-files --quiet --ignore-submodules ||
- die "Working tree is dirty"
-
- # do we have anything to commit?
- if git diff-index --cached --quiet --ignore-submodules HEAD --
+case "$action" in
+continue)
+ # do we have anything to commit?
+ if git diff-index --cached --quiet --ignore-submodules HEAD --
+ then
+ : Nothing to commit -- skip this
+ else
+ . "$author_script" ||
+ die "Cannot find the author identity"
+ current_head=
+ if test -f "$amend"
then
- : Nothing to commit -- skip this
- else
- . "$AUTHOR_SCRIPT" ||
- die "Cannot find the author identity"
- amend=
- if test -f "$AMEND"
- then
- amend=$(git rev-parse --verify HEAD)
- test "$amend" = $(cat "$AMEND") ||
- die "\
+ current_head=$(git rev-parse --verify HEAD)
+ test "$current_head" = $(cat "$amend") ||
+ die "\
You have uncommitted changes in your working tree. Please, commit them
first and then run 'git rebase --continue' again."
- git reset --soft HEAD^ ||
- die "Cannot rewind the HEAD"
- fi
- do_with_author git commit --no-verify -F "$MSG" -e || {
- test -n "$amend" && git reset --soft $amend
- die "Could not commit staged changes."
- }
+ git reset --soft HEAD^ ||
+ die "Cannot rewind the HEAD"
fi
+ do_with_author git commit --no-verify -F "$msg" -e || {
+ test -n "$current_head" && git reset --soft $current_head
+ die "Could not commit staged changes."
+ }
+ fi
- record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
-
- require_clean_work_tree
- do_rest
- ;;
- --abort)
- is_standalone "$@" || usage
- get_saved_options
- comment_for_reflog abort
-
- git rerere clear
- test -d "$DOTEST" || die "No interactive rebase running"
-
- HEADNAME=$(cat "$DOTEST"/head-name)
- HEAD=$(cat "$DOTEST"/head)
- case $HEADNAME in
- refs/*)
- git symbolic-ref HEAD $HEADNAME
- ;;
- esac &&
- output git reset --hard $HEAD &&
- rm -rf "$DOTEST"
- exit
- ;;
- --skip)
- is_standalone "$@" || usage
- get_saved_options
- comment_for_reflog skip
-
- git rerere clear
- test -d "$DOTEST" || die "No interactive rebase running"
-
- output git reset --hard && do_rest
- ;;
- -s)
- case "$#,$1" in
- *,*=*)
- STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
- 1,*)
- usage ;;
- *)
- STRATEGY="-s $2"
- shift ;;
- esac
- ;;
- -m)
- # we use merge anyway
- ;;
- -v)
- VERBOSE=t
- ;;
- -p)
- PRESERVE_MERGES=t
- ;;
- -i)
- # yeah, we know
- ;;
- --no-ff)
- NEVER_FF=t
- ;;
- --root)
- REBASE_ROOT=t
- ;;
- --autosquash)
- AUTOSQUASH=t
- ;;
- --onto)
- shift
- ONTO=$(parse_onto "$1") ||
- die "Does not point to a valid commit: $1"
- ;;
- --)
- shift
- test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 ||
- test ! -z "$REBASE_ROOT" -a $# -le 1 || usage
- test -d "$DOTEST" &&
- die "Interactive rebase already started"
-
- git var GIT_COMMITTER_IDENT >/dev/null ||
- die "You need to set your committer info first"
-
- if test -z "$REBASE_ROOT"
- then
- UPSTREAM_ARG="$1"
- UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
- test -z "$ONTO" && ONTO=$UPSTREAM
- shift
- else
- UPSTREAM=
- UPSTREAM_ARG=--root
- test -z "$ONTO" &&
- die "You must specify --onto when using --root"
- fi
- run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
+ record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
- comment_for_reflog start
+ require_clean_work_tree "rebase"
+ do_rest
+ ;;
+skip)
+ git rerere clear
- require_clean_work_tree
+ do_rest
+ ;;
+esac
- if test ! -z "$1"
- then
- output git checkout "$1" ||
- die "Could not checkout $1"
- fi
+git var GIT_COMMITTER_IDENT >/dev/null ||
+ die "You need to set your committer info first"
- HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
- mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
+comment_for_reflog start
- : > "$DOTEST"/interactive || die "Could not mark as interactive"
- git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
- echo "detached HEAD" > "$DOTEST"/head-name
+if test ! -z "$switch_to"
+then
+ output git checkout "$switch_to" -- ||
+ die "Could not checkout $switch_to"
+fi
- echo $HEAD > "$DOTEST"/head
- case "$REBASE_ROOT" in
- '')
- rm -f "$DOTEST"/rebase-root ;;
- *)
- : >"$DOTEST"/rebase-root ;;
- esac
- echo $ONTO > "$DOTEST"/onto
- test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
- test t = "$VERBOSE" && : > "$DOTEST"/verbose
- if test t = "$PRESERVE_MERGES"
- then
- if test -z "$REBASE_ROOT"
- then
- mkdir "$REWRITTEN" &&
- for c in $(git merge-base --all $HEAD $UPSTREAM)
- do
- echo $ONTO > "$REWRITTEN"/$c ||
- die "Could not init rewritten commits"
- done
- else
- mkdir "$REWRITTEN" &&
- echo $ONTO > "$REWRITTEN"/root ||
- die "Could not init rewritten commits"
- fi
- # No cherry-pick because our first pass is to determine
- # parents to rewrite and skipping dropped commits would
- # prematurely end our probe
- MERGES_OPTION=
- first_after_upstream="$(git rev-list --reverse --first-parent $UPSTREAM..$HEAD | head -n 1)"
- else
- MERGES_OPTION="--no-merges --cherry-pick"
- fi
+orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
+mkdir "$state_dir" || die "Could not create temporary $state_dir"
- SHORTHEAD=$(git rev-parse --short $HEAD)
- SHORTONTO=$(git rev-parse --short $ONTO)
- if test -z "$REBASE_ROOT"
- # this is now equivalent to ! -z "$UPSTREAM"
- then
- SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
- REVISIONS=$UPSTREAM...$HEAD
- SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
- else
- REVISIONS=$ONTO...$HEAD
- SHORTREVISIONS=$SHORTHEAD
- fi
- git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
- --abbrev=7 --reverse --left-right --topo-order \
- $REVISIONS | \
- sed -n "s/^>//p" | while read shortsha1 rest
+: > "$state_dir"/interactive || die "Could not mark as interactive"
+write_basic_state
+if test t = "$preserve_merges"
+then
+ if test -z "$rebase_root"
+ then
+ mkdir "$rewritten" &&
+ for c in $(git merge-base --all $orig_head $upstream)
do
- if test t != "$PRESERVE_MERGES"
- then
- echo "pick $shortsha1 $rest" >> "$TODO"
- else
- sha1=$(git rev-parse $shortsha1)
- if test -z "$REBASE_ROOT"
- then
- preserve=t
- for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
- do
- if test -f "$REWRITTEN"/$p -a \( $p != $ONTO -o $sha1 = $first_after_upstream \)
- then
- preserve=f
- fi
- done
- else
- preserve=f
- fi
- if test f = "$preserve"
- then
- touch "$REWRITTEN"/$sha1
- echo "pick $shortsha1 $rest" >> "$TODO"
- fi
- fi
+ echo $onto > "$rewritten"/$c ||
+ die "Could not init rewritten commits"
done
-
- # Watch for commits that been dropped by --cherry-pick
- if test t = "$PRESERVE_MERGES"
+ else
+ mkdir "$rewritten" &&
+ echo $onto > "$rewritten"/root ||
+ die "Could not init rewritten commits"
+ fi
+ # No cherry-pick because our first pass is to determine
+ # parents to rewrite and skipping dropped commits would
+ # prematurely end our probe
+ merges_option=
+else
+ merges_option="--no-merges --cherry-pick"
+fi
+
+shorthead=$(git rev-parse --short $orig_head)
+shortonto=$(git rev-parse --short $onto)
+if test -z "$rebase_root"
+ # this is now equivalent to ! -z "$upstream"
+then
+ shortupstream=$(git rev-parse --short $upstream)
+ revisions=$upstream...$orig_head
+ shortrevisions=$shortupstream..$shorthead
+else
+ revisions=$onto...$orig_head
+ shortrevisions=$shorthead
+fi
+git rev-list $merges_option --pretty=oneline --abbrev-commit \
+ --abbrev=7 --reverse --left-right --topo-order \
+ $revisions | \
+ sed -n "s/^>//p" |
+while read -r shortsha1 rest
+do
+ if test t != "$preserve_merges"
+ then
+ printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ else
+ sha1=$(git rev-parse $shortsha1)
+ if test -z "$rebase_root"
then
- mkdir "$DROPPED"
- # Save all non-cherry-picked changes
- git rev-list $REVISIONS --left-right --cherry-pick | \
- sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
- # Now all commits and note which ones are missing in
- # not-cherry-picks and hence being dropped
- git rev-list $REVISIONS |
- while read rev
+ preserve=t
+ for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
do
- if test -f "$REWRITTEN"/$rev -a "$(sane_grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
+ if test -f "$rewritten"/$p
then
- # Use -f2 because if rev-list is telling us this commit is
- # not worthwhile, we don't want to track its multiple heads,
- # just the history of its first-parent for others that will
- # be rebasing on top of it
- git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
- short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
- sane_grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
- rm "$REWRITTEN"/$rev
+ preserve=f
fi
done
+ else
+ preserve=f
+ fi
+ if test f = "$preserve"
+ then
+ touch "$rewritten"/$sha1
+ printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+ fi
+ fi
+done
+
+# Watch for commits that been dropped by --cherry-pick
+if test t = "$preserve_merges"
+then
+ mkdir "$dropped"
+ # Save all non-cherry-picked changes
+ git rev-list $revisions --left-right --cherry-pick | \
+ sed -n "s/^>//p" > "$state_dir"/not-cherry-picks
+ # Now all commits and note which ones are missing in
+ # not-cherry-picks and hence being dropped
+ git rev-list $revisions |
+ while read rev
+ do
+ if test -f "$rewritten"/$rev -a "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = ""
+ then
+ # Use -f2 because if rev-list is telling us this commit is
+ # not worthwhile, we don't want to track its multiple heads,
+ # just the history of its first-parent for others that will
+ # be rebasing on top of it
+ git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev
+ short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
+ sane_grep -v "^[a-z][a-z]* $short" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
+ rm "$rewritten"/$rev
fi
+ done
+fi
- test -s "$TODO" || echo noop >> "$TODO"
- test -n "$AUTOSQUASH" && rearrange_squash "$TODO"
- cat >> "$TODO" << EOF
+test -s "$todo" || echo noop >> "$todo"
+test -n "$autosquash" && rearrange_squash "$todo"
+cat >> "$todo" << EOF
-# Rebase $SHORTREVISIONS onto $SHORTONTO
+# Rebase $shortrevisions onto $shortonto
#
# Commands:
# p, pick = use commit
@@ -956,28 +799,25 @@ first and then run 'git rebase --continue' again."
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
+# x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
EOF
- has_action "$TODO" ||
- die_abort "Nothing to do"
+has_action "$todo" ||
+ die_abort "Nothing to do"
- cp "$TODO" "$TODO".backup
- git_editor "$TODO" ||
- die_abort "Could not execute editor"
+cp "$todo" "$todo".backup
+git_editor "$todo" ||
+ die_abort "Could not execute editor"
- has_action "$TODO" ||
- die_abort "Nothing to do"
+has_action "$todo" ||
+ die_abort "Nothing to do"
- test -d "$REWRITTEN" || test -n "$NEVER_FF" || skip_unnecessary_picks
+test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
- output git checkout $ONTO || die_abort "could not detach HEAD"
- git update-ref ORIG_HEAD $HEAD
- do_rest
- ;;
- esac
- shift
-done
+output git checkout $onto || die_abort "could not detach HEAD"
+git update-ref ORIG_HEAD $orig_head
+do_rest
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
new file mode 100644
index 0000000..26afc75
--- /dev/null
+++ b/git-rebase--merge.sh
@@ -0,0 +1,151 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Junio C Hamano.
+#
+
+. git-sh-setup
+
+prec=4
+
+read_state () {
+ onto_name=$(cat "$state_dir"/onto_name) &&
+ end=$(cat "$state_dir"/end) &&
+ msgnum=$(cat "$state_dir"/msgnum)
+}
+
+continue_merge () {
+ test -d "$state_dir" || die "$state_dir directory does not exist"
+
+ unmerged=$(git ls-files -u)
+ if test -n "$unmerged"
+ then
+ echo "You still have unmerged paths in your index"
+ echo "did you forget to use git add?"
+ die "$resolvemsg"
+ fi
+
+ cmt=`cat "$state_dir/current"`
+ if ! git diff-index --quiet --ignore-submodules HEAD --
+ then
+ if ! git commit --no-verify -C "$cmt"
+ then
+ echo "Commit failed, please do not call \"git commit\""
+ echo "directly, but instead do one of the following: "
+ die "$resolvemsg"
+ fi
+ if test -z "$GIT_QUIET"
+ then
+ printf "Committed: %0${prec}d " $msgnum
+ fi
+ echo "$cmt $(git rev-parse HEAD^0)" >> "$state_dir/rewritten"
+ else
+ if test -z "$GIT_QUIET"
+ then
+ printf "Already applied: %0${prec}d " $msgnum
+ fi
+ fi
+ test -z "$GIT_QUIET" &&
+ GIT_PAGER='' git log --format=%s -1 "$cmt"
+
+ # onto the next patch:
+ msgnum=$(($msgnum + 1))
+ echo "$msgnum" >"$state_dir/msgnum"
+}
+
+call_merge () {
+ cmt="$(cat "$state_dir/cmt.$1")"
+ echo "$cmt" > "$state_dir/current"
+ hd=$(git rev-parse --verify HEAD)
+ cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
+ msgnum=$(cat "$state_dir/msgnum")
+ eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
+ eval GITHEAD_$hd='$onto_name'
+ export GITHEAD_$cmt GITHEAD_$hd
+ if test -n "$GIT_QUIET"
+ then
+ GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY
+ fi
+ test -z "$strategy" && strategy=recursive
+ eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"'
+ rv=$?
+ case "$rv" in
+ 0)
+ unset GITHEAD_$cmt GITHEAD_$hd
+ return
+ ;;
+ 1)
+ git rerere $allow_rerere_autoupdate
+ die "$resolvemsg"
+ ;;
+ 2)
+ echo "Strategy: $strategy failed, try another" 1>&2
+ die "$resolvemsg"
+ ;;
+ *)
+ die "Unknown exit code ($rv) from command:" \
+ "git-merge-$strategy $cmt^ -- HEAD $cmt"
+ ;;
+ esac
+}
+
+finish_rb_merge () {
+ move_to_original_branch
+ git notes copy --for-rewrite=rebase < "$state_dir"/rewritten
+ if test -x "$GIT_DIR"/hooks/post-rewrite &&
+ test -s "$state_dir"/rewritten; then
+ "$GIT_DIR"/hooks/post-rewrite rebase < "$state_dir"/rewritten
+ fi
+ rm -r "$state_dir"
+ say All done.
+}
+
+case "$action" in
+continue)
+ read_state
+ continue_merge
+ while test "$msgnum" -le "$end"
+ do
+ call_merge "$msgnum"
+ continue_merge
+ done
+ finish_rb_merge
+ exit
+ ;;
+skip)
+ read_state
+ git rerere clear
+ msgnum=$(($msgnum + 1))
+ while test "$msgnum" -le "$end"
+ do
+ call_merge "$msgnum"
+ continue_merge
+ done
+ finish_rb_merge
+ exit
+ ;;
+esac
+
+mkdir -p "$state_dir"
+echo "$onto_name" > "$state_dir/onto_name"
+write_basic_state
+
+msgnum=0
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
+do
+ msgnum=$(($msgnum + 1))
+ echo "$cmt" > "$state_dir/cmt.$msgnum"
+done
+
+echo 1 >"$state_dir/msgnum"
+echo $msgnum >"$state_dir/end"
+
+end=$msgnum
+msgnum=1
+
+while test "$msgnum" -le "$end"
+do
+ call_merge "$msgnum"
+ continue_merge
+done
+
+finish_rb_merge
diff --git a/git-rebase.sh b/git-rebase.sh
index ab4afa7..266a4c1 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] (<upstream>|--root) [<branch>] [--quiet | -q]'
+USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -13,7 +13,7 @@ It then attempts to create a new commit for each commit from the original
It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run git rebase --continue. Another option is to bypass the commit
-that caused the merge failure with git rebase --skip. To restore the
+that caused the merge failure with git rebase --skip. To check out the
original <branch> and remove the .git/rebase-apply working files, use the
command git rebase --abort instead.
@@ -28,7 +28,39 @@ Example: git-rebase master~1 topic
'
SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
+git rebase [-i] [options] --onto <newbase> --root [<branch>]
+git-rebase [-i] --continue | --abort | --skip
+--
+ Available options are
+v,verbose! display a diffstat of what changed upstream
+q,quiet! be quiet. implies --no-stat
+onto=! rebase onto given branch instead of upstream
+p,preserve-merges! try to recreate merges instead of ignoring them
+s,strategy=! use the given merge strategy
+no-ff! cherry-pick all commits, even if unchanged
+m,merge! use merging strategies to rebase
+i,interactive! let the user edit the list of commits to rebase
+f,force-rebase! force rebase even if branch is up to date
+X,strategy-option=! pass the argument through to the merge strategy
+stat! display a diffstat of what changed upstream
+n,no-stat! do not show diffstat of what changed upstream
+verify allow pre-rebase hook to run
+rerere-autoupdate allow rerere to update index with resolved conflicts
+root! rebase all reachable commits up to the root(s)
+autosquash move commits that begin with squash!/fixup! under -i
+committer-date-is-author-date! passed to 'git am'
+ignore-date! passed to 'git am'
+whitespace=! passed to 'git apply'
+ignore-whitespace! passed to 'git apply'
+C=! passed to 'git apply'
+ Actions:
+continue! continue
+abort! abort and check out the original branch
+skip! skip current patch and continue
+"
. git-sh-setup
set_reflog_action rebase
require_work_tree
@@ -36,156 +68,109 @@ cd_to_toplevel
LF='
'
-OK_TO_SKIP_PRE_REBASE=
-RESOLVEMSG="
+ok_to_skip_pre_rebase=
+resolvemsg="
When you have resolved this problem run \"git rebase --continue\".
If you would prefer to skip this patch, instead run \"git rebase --skip\".
-To restore the original branch and stop rebasing run \"git rebase --abort\".
+To check out the original branch and stop rebasing run \"git rebase --abort\".
"
-unset newbase
-strategy=recursive
+unset onto
+strategy=
+strategy_opts=
do_merge=
-dotest="$GIT_DIR"/rebase-merge
-prec=4
+merge_dir="$GIT_DIR"/rebase-merge
+apply_dir="$GIT_DIR"/rebase-apply
verbose=
-diffstat=$(git config --bool rebase.stat)
+diffstat=
+test "$(git config --bool rebase.stat)" = true && diffstat=t
git_am_opt=
rebase_root=
force_rebase=
allow_rerere_autoupdate=
-
-continue_merge () {
- test -n "$prev_head" || die "prev_head must be defined"
- test -d "$dotest" || die "$dotest directory does not exist"
-
- unmerged=$(git ls-files -u)
- if test -n "$unmerged"
- then
- echo "You still have unmerged paths in your index"
- echo "did you forget to use git add?"
- die "$RESOLVEMSG"
- fi
-
- cmt=`cat "$dotest/current"`
- if ! git diff-index --quiet --ignore-submodules HEAD --
+# Non-empty if a rebase was in progress when 'git rebase' was invoked
+in_progress=
+# One of {am, merge, interactive}
+type=
+# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge}
+state_dir=
+# One of {'', continue, skip, abort}, as parsed from command line
+action=
+preserve_merges=
+autosquash=
+test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
+
+read_basic_state () {
+ head_name=$(cat "$state_dir"/head-name) &&
+ onto=$(cat "$state_dir"/onto) &&
+ # We always write to orig-head, but interactive rebase used to write to
+ # head. Fall back to reading from head to cover for the case that the
+ # user upgraded git with an ongoing interactive rebase.
+ if test -f "$state_dir"/orig-head
then
- if ! git commit --no-verify -C "$cmt"
- then
- echo "Commit failed, please do not call \"git commit\""
- echo "directly, but instead do one of the following: "
- die "$RESOLVEMSG"
- fi
- if test -z "$GIT_QUIET"
- then
- printf "Committed: %0${prec}d " $msgnum
- fi
- echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
+ orig_head=$(cat "$state_dir"/orig-head)
else
- if test -z "$GIT_QUIET"
- then
- printf "Already applied: %0${prec}d " $msgnum
- fi
- fi
- test -z "$GIT_QUIET" &&
- GIT_PAGER='' git log --format=%s -1 "$cmt"
-
- prev_head=`git rev-parse HEAD^0`
- # save the resulting commit so we can read-tree on it later
- echo "$prev_head" > "$dotest/prev_head"
+ orig_head=$(cat "$state_dir"/head)
+ fi &&
+ GIT_QUIET=$(cat "$state_dir"/quiet) &&
+ test -f "$state_dir"/verbose && verbose=t
+ test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)"
+ test -f "$state_dir"/strategy_opts &&
+ strategy_opts="$(cat "$state_dir"/strategy_opts)"
+ test -f "$state_dir"/allow_rerere_autoupdate &&
+ allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)"
+}
- # onto the next patch:
- msgnum=$(($msgnum + 1))
- echo "$msgnum" >"$dotest/msgnum"
+write_basic_state () {
+ echo "$head_name" > "$state_dir"/head-name &&
+ echo "$onto" > "$state_dir"/onto &&
+ echo "$orig_head" > "$state_dir"/orig-head &&
+ echo "$GIT_QUIET" > "$state_dir"/quiet &&
+ test t = "$verbose" && : > "$state_dir"/verbose
+ test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
+ test -n "$strategy_opts" && echo "$strategy_opts" > \
+ "$state_dir"/strategy_opts
+ test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
+ "$state_dir"/allow_rerere_autoupdate
}
-call_merge () {
- cmt="$(cat "$dotest/cmt.$1")"
- echo "$cmt" > "$dotest/current"
- hd=$(git rev-parse --verify HEAD)
- cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
- msgnum=$(cat "$dotest/msgnum")
- end=$(cat "$dotest/end")
- eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
- eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
- export GITHEAD_$cmt GITHEAD_$hd
- if test -n "$GIT_QUIET"
- then
- export GIT_MERGE_VERBOSITY=1
- fi
- git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
- rv=$?
- case "$rv" in
- 0)
- unset GITHEAD_$cmt GITHEAD_$hd
- return
- ;;
- 1)
- git rerere $allow_rerere_autoupdate
- die "$RESOLVEMSG"
- ;;
- 2)
- echo "Strategy: $rv $strategy failed, try another" 1>&2
- die "$RESOLVEMSG"
+output () {
+ case "$verbose" in
+ '')
+ output=$("$@" 2>&1 )
+ status=$?
+ test $status != 0 && printf "%s\n" "$output"
+ return $status
;;
*)
- die "Unknown exit code ($rv) from command:" \
- "git-merge-$strategy $cmt^ -- HEAD $cmt"
+ "$@"
;;
esac
}
move_to_original_branch () {
- test -z "$head_name" &&
- head_name="$(cat "$dotest"/head-name)" &&
- onto="$(cat "$dotest"/onto)" &&
- orig_head="$(cat "$dotest"/orig-head)"
case "$head_name" in
refs/*)
message="rebase finished: $head_name onto $onto"
git update-ref -m "$message" \
$head_name $(git rev-parse HEAD) $orig_head &&
- git symbolic-ref HEAD $head_name ||
+ git symbolic-ref \
+ -m "rebase finished: returning to $head_name" \
+ HEAD $head_name ||
die "Could not move back to $head_name"
;;
esac
}
-finish_rb_merge () {
- move_to_original_branch
- git notes copy --for-rewrite=rebase < "$dotest"/rewritten
- if test -x "$GIT_DIR"/hooks/post-rewrite &&
- test -s "$dotest"/rewritten; then
- "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
- fi
- rm -r "$dotest"
- say All done.
-}
-
-is_interactive () {
- while test $# != 0
- do
- case "$1" in
- -i|--interactive)
- interactive_rebase=explicit
- break
- ;;
- -p|--preserve-merges)
- interactive_rebase=implied
- ;;
- esac
- shift
- done
-
+run_specific_rebase () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
export GIT_EDITOR
fi
-
- test -n "$interactive_rebase" || test -f "$dotest"/interactive
+ . git-rebase--$type
}
run_pre_rebase_hook () {
- if test -z "$OK_TO_SKIP_PRE_REBASE" &&
+ if test -z "$ok_to_skip_pre_rebase" &&
test -x "$GIT_DIR/hooks/pre-rebase"
then
"$GIT_DIR/hooks/pre-rebase" ${1+"$@"} ||
@@ -193,141 +178,94 @@ run_pre_rebase_hook () {
fi
}
-test -f "$GIT_DIR"/rebase-apply/applying &&
+test -f "$apply_dir"/applying &&
die 'It looks like git-am is in progress. Cannot rebase.'
-is_interactive "$@" && exec git-rebase--interactive "$@"
+if test -d "$apply_dir"
+then
+ type=am
+ state_dir="$apply_dir"
+elif test -d "$merge_dir"
+then
+ if test -f "$merge_dir"/interactive
+ then
+ type=interactive
+ interactive_rebase=explicit
+ else
+ type=merge
+ fi
+ state_dir="$merge_dir"
+fi
+test -n "$type" && in_progress=t
+total_argc=$#
while test $# != 0
do
case "$1" in
--no-verify)
- OK_TO_SKIP_PRE_REBASE=yes
- ;;
- --continue)
- test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
- die "No rebase in progress?"
-
- git diff-files --quiet --ignore-submodules || {
- echo "You must edit all merge conflicts and then"
- echo "mark them as resolved using git add"
- exit 1
- }
- if test -d "$dotest"
- then
- prev_head=$(cat "$dotest/prev_head")
- end=$(cat "$dotest/end")
- msgnum=$(cat "$dotest/msgnum")
- onto=$(cat "$dotest/onto")
- GIT_QUIET=$(cat "$dotest/quiet")
- continue_merge
- while test "$msgnum" -le "$end"
- do
- call_merge "$msgnum"
- continue_merge
- done
- finish_rb_merge
- exit
- fi
- head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
- onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
- orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
- GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
- git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
- move_to_original_branch
- exit
+ ok_to_skip_pre_rebase=yes
;;
- --skip)
- test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
- die "No rebase in progress?"
-
- git reset --hard HEAD || exit $?
- if test -d "$dotest"
- then
- git rerere clear
- prev_head=$(cat "$dotest/prev_head")
- end=$(cat "$dotest/end")
- msgnum=$(cat "$dotest/msgnum")
- msgnum=$(($msgnum + 1))
- onto=$(cat "$dotest/onto")
- GIT_QUIET=$(cat "$dotest/quiet")
- while test "$msgnum" -le "$end"
- do
- call_merge "$msgnum"
- continue_merge
- done
- finish_rb_merge
- exit
- fi
- head_name=$(cat "$GIT_DIR"/rebase-apply/head-name) &&
- onto=$(cat "$GIT_DIR"/rebase-apply/onto) &&
- orig_head=$(cat "$GIT_DIR"/rebase-apply/orig-head) &&
- GIT_QUIET=$(cat "$GIT_DIR"/rebase-apply/quiet)
- git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
- move_to_original_branch
- exit
+ --verify)
+ ok_to_skip_pre_rebase=
;;
- --abort)
- test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
- die "No rebase in progress?"
-
- git rerere clear
- if test -d "$dotest"
- then
- GIT_QUIET=$(cat "$dotest/quiet")
- move_to_original_branch
- else
- dotest="$GIT_DIR"/rebase-apply
- GIT_QUIET=$(cat "$dotest/quiet")
- move_to_original_branch
- fi
- git reset --hard $(cat "$dotest/orig-head")
- rm -r "$dotest"
- exit
+ --continue|--skip|--abort)
+ test $total_argc -eq 2 || usage
+ action=${1##--}
;;
--onto)
test 2 -le "$#" || usage
- newbase="$2"
+ onto="$2"
shift
;;
- -M|-m|--m|--me|--mer|--merg|--merge)
+ -i)
+ interactive_rebase=explicit
+ ;;
+ -p)
+ preserve_merges=t
+ test -z "$interactive_rebase" && interactive_rebase=implied
+ ;;
+ --autosquash)
+ autosquash=t
+ ;;
+ --no-autosquash)
+ autosquash=
+ ;;
+ -M|-m)
do_merge=t
;;
- -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
- --strateg=*|--strategy=*|\
- -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
- case "$#,$1" in
- *,*=*)
- strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
- 1,*)
- usage ;;
- *)
- strategy="$2"
- shift ;;
- esac
+ -X)
+ shift
+ strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")"
+ do_merge=t
+ test -z "$strategy" && strategy=recursive
+ ;;
+ -s)
+ shift
+ strategy="$1"
do_merge=t
;;
- -n|--no-stat)
+ -n)
diffstat=
;;
--stat)
diffstat=t
;;
- -v|--verbose)
+ -v)
verbose=t
diffstat=t
GIT_QUIET=
;;
- -q|--quiet)
+ -q)
GIT_QUIET=t
git_am_opt="$git_am_opt -q"
verbose=
diffstat=
;;
- --whitespace=*)
- git_am_opt="$git_am_opt $1"
+ --whitespace)
+ shift
+ git_am_opt="$git_am_opt --whitespace=$1"
case "$1" in
- --whitespace=fix|--whitespace=strip)
+ fix|strip)
force_rebase=t
;;
esac
@@ -339,22 +277,21 @@ do
git_am_opt="$git_am_opt $1"
force_rebase=t
;;
- -C*)
- git_am_opt="$git_am_opt $1"
+ -C)
+ shift
+ git_am_opt="$git_am_opt -C$1"
;;
--root)
rebase_root=t
;;
- -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase|--no-ff)
+ -f|--no-ff)
force_rebase=t
;;
--rerere-autoupdate|--no-rerere-autoupdate)
allow_rerere_autoupdate="$1"
;;
- -*)
- usage
- ;;
- *)
+ --)
+ shift
break
;;
esac
@@ -362,70 +299,106 @@ do
done
test $# -gt 2 && usage
-if test $# -eq 0 && test -z "$rebase_root"
+if test -n "$action"
then
- test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply || usage
- test -d "$dotest" -o -f "$GIT_DIR"/rebase-apply/rebasing &&
- die 'A rebase is in progress, try --continue, --skip or --abort.'
+ test -z "$in_progress" && die "No rebase in progress?"
+ # Only interactive rebase uses detailed reflog messages
+ if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase
+ then
+ GIT_REFLOG_ACTION="rebase -i ($action)"
+ export GIT_REFLOG_ACTION
+ fi
fi
-# Make sure we do not have $GIT_DIR/rebase-apply
-if test -z "$do_merge"
+case "$action" in
+continue)
+ # Sanity check
+ git rev-parse --verify HEAD >/dev/null ||
+ die "Cannot read HEAD"
+ git update-index --ignore-submodules --refresh &&
+ git diff-files --quiet --ignore-submodules || {
+ echo "You must edit all merge conflicts and then"
+ echo "mark them as resolved using git add"
+ exit 1
+ }
+ read_basic_state
+ run_specific_rebase
+ ;;
+skip)
+ output git reset --hard HEAD || exit $?
+ read_basic_state
+ run_specific_rebase
+ ;;
+abort)
+ git rerere clear
+ read_basic_state
+ case "$head_name" in
+ refs/*)
+ git symbolic-ref -m "rebase: aborting" HEAD $head_name ||
+ die "Could not move back to $head_name"
+ ;;
+ esac
+ output git reset --hard $orig_head
+ rm -r "$state_dir"
+ exit
+ ;;
+esac
+
+# Make sure no rebase is in progress
+if test -n "$in_progress"
then
- if mkdir "$GIT_DIR"/rebase-apply 2>/dev/null
- then
- rmdir "$GIT_DIR"/rebase-apply
- else
- echo >&2 '
-It seems that I cannot create a rebase-apply directory, and
-I wonder if you are in the middle of patch application or another
-rebase. If that is not the case, please
- rm -fr '"$GIT_DIR"'/rebase-apply
+ die '
+It seems that there is already a '"${state_dir##*/}"' directory, and
+I wonder if you are in the middle of another rebase. If that is the
+case, please try
+ git rebase (--continue | --abort | --skip)
+If that is not the case, please
+ rm -fr '"$state_dir"'
and run me again. I am stopping in case you still have something
valuable there.'
- exit 1
- fi
-else
- if test -d "$dotest"
- then
- die "previous rebase directory $dotest still exists." \
- 'Try git rebase (--continue | --abort | --skip)'
- fi
fi
-# The tree must be really really clean.
-if ! git update-index --ignore-submodules --refresh > /dev/null; then
- echo >&2 "cannot rebase: you have unstaged changes"
- git diff-files --name-status -r --ignore-submodules -- >&2
- exit 1
+if test -n "$interactive_rebase"
+then
+ type=interactive
+ state_dir="$merge_dir"
+elif test -n "$do_merge"
+then
+ type=merge
+ state_dir="$merge_dir"
+else
+ type=am
+ state_dir="$apply_dir"
fi
-diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
-case "$diff" in
-?*) echo >&2 "cannot rebase: your index contains uncommitted changes"
- echo >&2 "$diff"
- exit 1
- ;;
-esac
if test -z "$rebase_root"
then
- # The upstream head must be given. Make sure it is valid.
- upstream_name="$1"
- shift
+ case "$#" in
+ 0)
+ if ! upstream_name=$(git rev-parse --symbolic-full-name \
+ --verify -q @{upstream} 2>/dev/null)
+ then
+ . git-parse-remote
+ error_on_missing_default_upstream "rebase" "rebase" \
+ "against" "git rebase <upstream branch>"
+ fi
+ ;;
+ *) upstream_name="$1"
+ shift
+ ;;
+ esac
upstream=`git rev-parse --verify "${upstream_name}^0"` ||
die "invalid upstream $upstream_name"
- unset root_flag
upstream_arg="$upstream_name"
else
- test -z "$newbase" && die "--root must be used with --onto"
+ test -z "$onto" && die "You must specify --onto when using --root"
unset upstream_name
unset upstream
- root_flag="--root"
- upstream_arg="$root_flag"
+ upstream_arg=--root
fi
# Make sure the branch to rebase onto is valid.
-onto_name=${newbase-"$upstream_name"}
+onto_name=${onto-"$upstream_name"}
case "$onto_name" in
*...*)
if left=${onto_name%...*} right=${onto_name#*...} &&
@@ -444,13 +417,11 @@ case "$onto_name" in
fi
;;
*)
- onto=$(git rev-parse --verify "${onto_name}^0") || exit
+ onto=$(git rev-parse --verify "${onto_name}^0") ||
+ die "Does not point to a valid commit: $1"
;;
esac
-# If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook "$upstream_arg" "$@"
-
# If the branch to rebase is given, that is the branch we will rebase
# $branch_name -- branch being rebased, or HEAD (already detached)
# $orig_head -- commit object name of tip of the branch before rebasing
@@ -463,14 +434,14 @@ case "$#" in
switch_to="$1"
if git show-ref --verify --quiet -- "refs/heads/$1" &&
- branch=$(git rev-parse -q --verify "refs/heads/$1")
+ orig_head=$(git rev-parse -q --verify "refs/heads/$1")
then
head_name="refs/heads/$1"
- elif branch=$(git rev-parse -q --verify "$1")
+ elif orig_head=$(git rev-parse -q --verify "$1")
then
head_name="detached HEAD"
else
- usage
+ die "fatal: no such branch: $1"
fi
;;
*)
@@ -483,25 +454,28 @@ case "$#" in
head_name="detached HEAD"
branch_name=HEAD ;# detached
fi
- branch=$(git rev-parse --verify "${branch_name}^0") || exit
+ orig_head=$(git rev-parse --verify "${branch_name}^0") || exit
;;
esac
-orig_head=$branch
-# Now we are rebasing commits $upstream..$branch (or with --root,
-# everything leading up to $branch) on top of $onto
+require_clean_work_tree "rebase" "Please commit or stash them."
+
+# Now we are rebasing commits $upstream..$orig_head (or with --root,
+# everything leading up to $orig_head) on top of $onto
# Check if we are already based on $onto with linear history,
-# but this should be done only when upstream and onto are the same.
-mb=$(git merge-base "$onto" "$branch")
-if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
+# but this should be done only when upstream and onto are the same
+# and if this is not an interactive rebase.
+mb=$(git merge-base "$onto" "$orig_head")
+if test "$type" != interactive && test "$upstream" = "$onto" &&
+ test "$mb" = "$onto" &&
# linear history?
- ! (git rev-list --parents "$onto".."$branch" | sane_grep " .* ") > /dev/null
+ ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null
then
if test -z "$force_rebase"
then
# Lazily switch to the target branch if needed...
- test -z "$switch_to" || git checkout "$switch_to"
+ test -z "$switch_to" || git checkout "$switch_to" --
say "Current branch $branch_name is up to date."
exit 0
else
@@ -509,10 +483,8 @@ then
fi
fi
-# Detach HEAD and reset the tree
-say "First, rewinding head to replay your work on top of it..."
-git checkout -q "$onto^0" || die "could not detach HEAD"
-git update-ref ORIG_HEAD $branch
+# If a hook exists, give it a chance to interrupt
+run_pre_rebase_hook "$upstream_arg" "$@"
if test -n "$diffstat"
then
@@ -524,9 +496,16 @@ then
GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
+test "$type" = interactive && run_specific_rebase
+
+# Detach HEAD and reset the tree
+say "First, rewinding head to replay your work on top of it..."
+git checkout -q "$onto^0" || die "could not detach HEAD"
+git update-ref ORIG_HEAD $orig_head
+
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast-forwarded.
-if test "$mb" = "$branch"
+if test "$mb" = "$orig_head"
then
say "Fast-forwarded $branch_name to $onto_name."
move_to_original_branch
@@ -540,50 +519,4 @@ else
revisions="$upstream..$orig_head"
fi
-if test -z "$do_merge"
-then
- git format-patch -k --stdout --full-index --ignore-if-in-upstream \
- $root_flag "$revisions" |
- git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
- move_to_original_branch
- ret=$?
- test 0 != $ret -a -d "$GIT_DIR"/rebase-apply &&
- echo $head_name > "$GIT_DIR"/rebase-apply/head-name &&
- echo $onto > "$GIT_DIR"/rebase-apply/onto &&
- echo $orig_head > "$GIT_DIR"/rebase-apply/orig-head &&
- echo "$GIT_QUIET" > "$GIT_DIR"/rebase-apply/quiet
- exit $ret
-fi
-
-# start doing a rebase with git-merge
-# this is rename-aware if the recursive (default) strategy is used
-
-mkdir -p "$dotest"
-echo "$onto" > "$dotest/onto"
-echo "$onto_name" > "$dotest/onto_name"
-prev_head=$orig_head
-echo "$prev_head" > "$dotest/prev_head"
-echo "$orig_head" > "$dotest/orig-head"
-echo "$head_name" > "$dotest/head-name"
-echo "$GIT_QUIET" > "$dotest/quiet"
-
-msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$revisions"`
-do
- msgnum=$(($msgnum + 1))
- echo "$cmt" > "$dotest/cmt.$msgnum"
-done
-
-echo 1 >"$dotest/msgnum"
-echo $msgnum >"$dotest/end"
-
-end=$msgnum
-msgnum=1
-
-while test "$msgnum" -le "$end"
-do
- call_merge "$msgnum"
- continue_merge
-done
-
-finish_rb_merge
+run_specific_rebase
diff --git a/git-relink.perl b/git-relink.perl
index 937c69a..e136732 100755
--- a/git-relink.perl
+++ b/git-relink.perl
@@ -6,7 +6,7 @@
#
# Scan two git object-trees, and hardlink any common objects between them.
-use 5.006;
+use 5.008;
use strict;
use warnings;
use Getopt::Long;
@@ -163,7 +163,7 @@ sub link_two_files($$) {
sub usage() {
- print("Usage: git relink [--safe] <dir> [<dir> ...] <master_dir> \n");
+ print("Usage: git relink [--safe] <dir>... <master_dir> \n");
print("All directories should contain a .git/objects/ subdirectory.\n");
print("Options\n");
print("\t--safe\t" .
diff --git a/git-repack.sh b/git-repack.sh
index 1eb3bca..624feec 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -10,7 +10,8 @@ git repack [options]
a pack everything in a single pack
A same as -a, and turn unreachable objects loose
d remove redundant packs, and run git-prune-packed
-f pass --no-reuse-object to git-pack-objects
+f pass --no-reuse-delta to git-pack-objects
+F pass --no-reuse-object to git-pack-objects
n do not run git-update-server-info
q,quiet be quiet
l pass --local to git-pack-objects
@@ -34,7 +35,8 @@ do
unpack_unreachable=--unpack-unreachable ;;
-d) remove_redundant=t ;;
-q) GIT_QUIET=t ;;
- -f) no_reuse=--no-reuse-object ;;
+ -f) no_reuse=--no-reuse-delta ;;
+ -F) no_reuse=--no-reuse-object ;;
-l) local=--local ;;
--max-pack-size|--window|--window-memory|--depth)
extra="$extra $1=$2"; shift ;;
@@ -50,7 +52,7 @@ true)
esac
PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
-PACKTMP="$GIT_OBJECT_DIRECTORY/.tmp-$$-pack"
+PACKTMP="$PACKDIR/.tmp-$$-pack"
rm -f "$PACKTMP"-*
trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15
@@ -80,6 +82,8 @@ case ",$all_into_one," in
;;
esac
+mkdir -p "$PACKDIR" || exit
+
args="$args $local ${GIT_QUIET:+-q} $no_reuse$extra"
names=$(git pack-objects --keep-true-parents --honor-pack-keep --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
exit 1
@@ -88,7 +92,6 @@ if [ -z "$names" ]; then
fi
# Ok we have prepared all new packfiles.
-mkdir -p "$PACKDIR" || exit
# First see if there are packs of the same name and if so
# if we can move them out of the way (this can happen if we
diff --git a/git-request-pull.sh b/git-request-pull.sh
index 6fdea39..fc080cc 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -15,7 +15,6 @@ p show patch text as well
'
. git-sh-setup
-. git-parse-remote
GIT_PAGER=
export GIT_PAGER
@@ -55,7 +54,7 @@ branch=$(git ls-remote "$url" \
p
q
}")
-url=$(get_remote_url "$url")
+url=$(git ls-remote --get-url "$url")
if [ -z "$branch" ]; then
echo "warn: No branch of $url is at:" >&2
git log --max-count=1 --pretty='tformat:warn: %h: %s' $headrev >&2
diff --git a/git-send-email.perl b/git-send-email.perl
index 6dab3bf..98ab33a 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
# Copyright 2005 Ryan Anderson <ryan@michonline.com>
@@ -16,6 +16,7 @@
# and second line is the subject of the message.
#
+use 5.008;
use strict;
use warnings;
use Term::ReadLine;
@@ -24,6 +25,7 @@ use Text::ParseWords;
use Data::Dumper;
use Term::ANSIColor;
use File::Temp qw/ tempdir tempfile /;
+use File::Spec::Functions qw(catfile);
use Error qw(:try);
use Git;
@@ -60,6 +62,7 @@ git send-email [options] <file | directory | rev-list options >
--envelope-sender <str> * Email envelope sender.
--smtp-server <str:int> * Outgoing SMTP server to use. The port
is optional. Default 'localhost'.
+ --smtp-server-option <str> * Outgoing SMTP server option to use.
--smtp-server-port <int> * Outgoing SMTP server port.
--smtp-user <str> * Username for SMTP-AUTH.
--smtp-pass <str> * Password for SMTP-AUTH; not necessary.
@@ -70,6 +73,7 @@ git send-email [options] <file | directory | rev-list options >
Automating:
--identity <str> * Use the sendemail.<id> options.
+ --to-cmd <str> * Email To: via `<str> \$patch_path`
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
--[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
@@ -85,6 +89,7 @@ git send-email [options] <file | directory | rev-list options >
--[no-]validate * Perform patch sanity checks. Default on.
--[no-]format-patch * understand any non optional arguments as
`git format-patch` ones.
+ --force * Send even if safety checks would prevent it.
EOT
exit(1);
@@ -134,11 +139,8 @@ my $have_mail_address = eval { require Mail::Address; 1 };
my $smtp;
my $auth;
-sub unique_email_list(@);
-sub cleanup_compose_files();
-
# Variables we fill in automatically, or via prompting:
-my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
+my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
$initial_reply_to,$initial_subject,@files,
$author,$sender,$smtp_authpass,$annotate,$compose,$time);
@@ -162,6 +164,7 @@ if ($@) {
my ($quiet, $dry_run) = (0, 0);
my $format_patch;
my $compose_filename;
+my $force = 0;
# Handle interactive edition of files.
my $multiedit;
@@ -187,9 +190,11 @@ sub do_edit {
}
# Variables with corresponding config settings
-my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
-my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
-my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain);
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc);
+my ($to_cmd, $cc_cmd);
+my ($smtp_server, $smtp_server_port, @smtp_server_options);
+my ($smtp_authuser, $smtp_encryption);
+my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
my ($validate, $confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
@@ -210,10 +215,12 @@ my %config_bool_settings = (
my %config_settings = (
"smtpserver" => \$smtp_server,
"smtpserverport" => \$smtp_server_port,
+ "smtpserveroption" => \@smtp_server_options,
"smtpuser" => \$smtp_authuser,
"smtppass" => \$smtp_authpass,
- "smtpdomain" => \$smtp_domain,
- "to" => \@to,
+ "smtpdomain" => \$smtp_domain,
+ "to" => \@initial_to,
+ "tocmd" => \$to_cmd,
"cc" => \@initial_cc,
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
@@ -271,7 +278,8 @@ $SIG{INT} = \&signal_handler;
my $rc = GetOptions("sender|from=s" => \$sender,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
- "to=s" => \@to,
+ "to=s" => \@initial_to,
+ "to-cmd=s" => \$to_cmd,
"no-to" => \$no_to,
"cc=s" => \@initial_cc,
"no-cc" => \$no_cc,
@@ -279,6 +287,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"no-bcc" => \$no_bcc,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
+ "smtp-server-option=s" => \@smtp_server_options,
"smtp-server-port=s" => \$smtp_server_port,
"smtp-user=s" => \$smtp_authuser,
"smtp-pass:s" => \$smtp_authpass,
@@ -301,6 +310,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
"validate!" => \$validate,
"format-patch!" => \$format_patch,
"8bit-encoding=s" => \$auto_8bit_encoding,
+ "force" => \$force,
);
unless ($rc) {
@@ -364,7 +374,7 @@ my(%suppress_cc);
if (@suppress_cc) {
foreach my $entry (@suppress_cc) {
die "Unknown --suppress-cc field: '$entry'\n"
- unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
+ unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
$suppress_cc{$entry} = 1;
}
}
@@ -409,7 +419,7 @@ my ($repoauthor, $repocommitter);
# Verify the user input
-foreach my $entry (@to) {
+foreach my $entry (@initial_to) {
die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
}
@@ -508,12 +518,12 @@ while (defined(my $f = shift @ARGV)) {
push @rev_list_opts, "--", @ARGV;
@ARGV = ();
} elsif (-d $f and !check_file_rev_conflict($f)) {
- opendir(DH,$f)
+ opendir my $dh, $f
or die "Failed to opendir $f: $!";
- push @files, grep { -f $_ } map { +$f . "/" . $_ }
- sort readdir(DH);
- closedir(DH);
+ push @files, grep { -f $_ } map { catfile($f, $_) }
+ sort readdir $dh;
+ closedir $dh;
} elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
push @files, $f;
} else {
@@ -545,7 +555,7 @@ if (@files) {
usage();
}
-sub get_patch_subject($) {
+sub get_patch_subject {
my $fn = shift;
open (my $fh, '<', $fn);
while (my $line = <$fh>) {
@@ -563,7 +573,7 @@ if ($compose) {
$compose_filename = ($repo ?
tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
- open(C,">",$compose_filename)
+ open my $c, ">", $compose_filename
or die "Failed to open for writing $compose_filename: $!";
@@ -571,7 +581,7 @@ if ($compose) {
my $tpl_subject = $initial_subject || '';
my $tpl_reply_to = $initial_reply_to || '';
- print C <<EOT;
+ print $c <<EOT;
From $tpl_sender # This line is ignored.
GIT: Lines beginning in "GIT:" will be removed.
GIT: Consider including an overall diffstat or table of contents
@@ -584,9 +594,9 @@ In-Reply-To: $tpl_reply_to
EOT
for my $f (@files) {
- print C get_patch_subject($f);
+ print $c get_patch_subject($f);
}
- close(C);
+ close $c;
if ($annotate) {
do_edit($compose_filename, @files);
@@ -594,23 +604,23 @@ EOT
do_edit($compose_filename);
}
- open(C2,">",$compose_filename . ".final")
+ open my $c2, ">", $compose_filename . ".final"
or die "Failed to open $compose_filename.final : " . $!;
- open(C,"<",$compose_filename)
+ open $c, "<", $compose_filename
or die "Failed to open $compose_filename : " . $!;
my $need_8bit_cte = file_has_nonascii($compose_filename);
my $in_body = 0;
my $summary_empty = 1;
- while(<C>) {
+ while(<$c>) {
next if m/^GIT:/;
if ($in_body) {
$summary_empty = 0 unless (/^\n$/);
} elsif (/^\n$/) {
$in_body = 1;
if ($need_8bit_cte) {
- print C2 "MIME-Version: 1.0\n",
+ print $c2 "MIME-Version: 1.0\n",
"Content-Type: text/plain; ",
"charset=UTF-8\n",
"Content-Transfer-Encoding: 8bit\n";
@@ -635,10 +645,10 @@ EOT
print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
next;
}
- print C2 $_;
+ print $c2 $_;
}
- close(C);
- close(C2);
+ close $c;
+ close $c2;
if ($summary_empty) {
print "Summary email is empty, skipping it\n";
@@ -675,7 +685,7 @@ sub ask {
my %broken_encoding;
-sub file_declares_8bit_cte($) {
+sub file_declares_8bit_cte {
my $fn = shift;
open (my $fh, '<', $fn);
while (my $line = <$fh>) {
@@ -702,6 +712,16 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) {
default => "UTF-8");
}
+if (!$force) {
+ for my $f (@files) {
+ if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) {
+ die "Refusing to send because the patch\n\t$f\n"
+ . "has the template subject '*** SUBJECT HERE ***'. "
+ . "Pass --force if you really want to send.\n";
+ }
+ }
+}
+
my $prompting = 0;
if (!defined $sender) {
$sender = $repoauthor || $repocommitter || '';
@@ -711,9 +731,9 @@ if (!defined $sender) {
$prompting++;
}
-if (!@to) {
+if (!@initial_to && !defined $to_cmd) {
my $to = ask("Who should the emails be sent to? ");
- push @to, parse_address_line($to) if defined $to; # sanitized/validated later
+ push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
$prompting++;
}
@@ -731,8 +751,8 @@ sub expand_one_alias {
return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
}
-@to = expand_aliases(@to);
-@to = (map { sanitize_address($_) } @to);
+@initial_to = expand_aliases(@initial_to);
+@initial_to = (map { sanitize_address($_) } @initial_to);
@initial_cc = expand_aliases(@initial_cc);
@bcclist = expand_aliases(@bcclist);
@@ -766,8 +786,8 @@ our ($message_id, %mail, $subject, $reply_to, $references, $message,
sub extract_valid_address {
my $address = shift;
- my $local_part_regexp = '[^<>"\s@]+';
- my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
+ my $local_part_regexp = qr/[^<>"\s@]+/;
+ my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/;
# check for a local address:
return $address if ($address =~ /^($local_part_regexp)$/);
@@ -808,7 +828,7 @@ sub make_message_id {
last if (defined $du_part and $du_part ne '');
}
if (not defined $du_part or $du_part eq '') {
- use Sys::Hostname qw();
+ require Sys::Hostname;
$du_part = 'user@' . Sys::Hostname::hostname();
}
my $message_id_template = "<%s-git-send-email-%s>";
@@ -841,8 +861,8 @@ sub quote_rfc2047 {
sub is_rfc2047_quoted {
my $s = shift;
- my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+';
- my $encoded_text = '[!->@-~]+';
+ my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/;
+ my $encoded_text = qr/[!->@-~]+/;
length($s) <= 75 &&
$s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
}
@@ -853,7 +873,7 @@ sub sanitize_address {
my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
if (not $recipient_name) {
- return "$recipient";
+ return $recipient;
}
# if recipient_name is already quoted, do nothing
@@ -870,7 +890,7 @@ sub sanitize_address {
# double quotes are needed if specials or CTLs are included
elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
$recipient_name =~ s/(["\\\r])/\\$1/g;
- $recipient_name = "\"$recipient_name\"";
+ $recipient_name = qq["$recipient_name"];
}
return "$recipient_name $recipient_addr";
@@ -895,7 +915,7 @@ sub sanitize_address {
sub valid_fqdn {
my $domain = shift;
- return !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./;
+ return defined $domain && !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./;
}
sub maildomain_net {
@@ -940,7 +960,7 @@ sub maildomain {
sub send_message {
my @recipients = unique_email_list(@to);
@cc = (grep { my $cc = extract_valid_address($_);
- not grep { $cc eq $_ } @recipients
+ not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients
}
map { sanitize_address($_) }
@cc);
@@ -1015,6 +1035,8 @@ X-Mailer: git-send-email $gitversion
}
}
+ unshift (@sendmail_parameters, @smtp_server_options);
+
if ($dry_run) {
# We don't want to send the email.
} elsif ($smtp_server =~ m#^/#) {
@@ -1024,7 +1046,7 @@ X-Mailer: git-send-email $gitversion
exec($smtp_server, @sendmail_parameters) or die $!;
}
print $sm "$header\n$message";
- close $sm or die $?;
+ close $sm or die $!;
} else {
if (!defined $smtp_server) {
@@ -1069,7 +1091,7 @@ X-Mailer: git-send-email $gitversion
"VALUES: server=$smtp_server ",
"encryption=$smtp_encryption ",
"hello=$smtp_domain",
- defined $smtp_server_port ? "port=$smtp_server_port" : "";
+ defined $smtp_server_port ? " port=$smtp_server_port" : "";
}
if (defined $smtp_authuser) {
@@ -1130,12 +1152,13 @@ $subject = $initial_subject;
$message_num = 0;
foreach my $t (@files) {
- open(F,"<",$t) or die "can't open file $t";
+ open my $fh, "<", $t or die "can't open file $t";
my $author = undef;
my $author_encoding;
my $has_content_type;
my $body_encoding;
+ @to = ();
@cc = ();
@xh = ();
my $input_format = undef;
@@ -1143,7 +1166,7 @@ foreach my $t (@files) {
$message = "";
$message_num++;
# First unfold multiline header fields
- while(<F>) {
+ while(<$fh>) {
last if /^\s*$/;
if (/^\s+\S/ and @header) {
chomp($header[$#header]);
@@ -1176,6 +1199,13 @@ foreach my $t (@files) {
$1, $_) unless $quiet;
push @cc, $1;
}
+ elsif (/^To:\s+(.*)$/) {
+ foreach my $addr (parse_address_line($1)) {
+ printf("(mbox) Adding to: %s from line '%s'\n",
+ $addr, $_) unless $quiet;
+ push @to, sanitize_address($addr);
+ }
+ }
elsif (/^Cc:\s+(.*)$/) {
foreach my $addr (parse_address_line($1)) {
if (unquote_rfc2047($addr) eq $sender) {
@@ -1219,7 +1249,7 @@ foreach my $t (@files) {
}
}
# Now parse the message body
- while(<F>) {
+ while(<$fh>) {
$message .= $_;
if (/^(Signed-off-by|Cc): (.*)$/i) {
chomp;
@@ -1236,23 +1266,12 @@ foreach my $t (@files) {
$c, $_) unless $quiet;
}
}
- close F;
-
- if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
- open(F, "$cc_cmd \Q$t\E |")
- or die "(cc-cmd) Could not execute '$cc_cmd'";
- while(<F>) {
- my $c = $_;
- $c =~ s/^\s*//g;
- $c =~ s/\n$//g;
- next if ($c eq $sender and $suppress_from);
- push @cc, $c;
- printf("(cc-cmd) Adding cc: %s from: '%s'\n",
- $c, $cc_cmd) unless $quiet;
- }
- close F
- or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
- }
+ close $fh;
+
+ push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t)
+ if defined $to_cmd;
+ push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t)
+ if defined $cc_cmd && !$suppress_cc{'cccmd'};
if ($broken_encoding{$t} && !$has_content_type) {
$has_content_type = 1;
@@ -1293,13 +1312,15 @@ foreach my $t (@files) {
($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
$needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
+ @to = (@initial_to, @to);
@cc = (@initial_cc, @cc);
my $message_was_sent = send_message();
# set up for the next message
if ($thread && $message_was_sent &&
- (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
+ (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 ||
+ $message_num == 1)) {
$reply_to = $message_id;
if (length $references > 0) {
$references .= "\n $message_id";
@@ -1310,15 +1331,38 @@ foreach my $t (@files) {
$message_id = undef;
}
+# Execute a command (e.g. $to_cmd) to get a list of email addresses
+# and return a results array
+sub recipients_cmd {
+ my ($prefix, $what, $cmd, $file) = @_;
+
+ my $sanitized_sender = sanitize_address($sender);
+ my @addresses = ();
+ open my $fh, "$cmd \Q$file\E |"
+ or die "($prefix) Could not execute '$cmd'";
+ while (my $address = <$fh>) {
+ $address =~ s/^\s*//g;
+ $address =~ s/\s*$//g;
+ $address = sanitize_address($address);
+ next if ($address eq $sanitized_sender and $suppress_from);
+ push @addresses, $address;
+ printf("($prefix) Adding %s: %s from: '%s'\n",
+ $what, $address, $cmd) unless $quiet;
+ }
+ close $fh
+ or die "($prefix) failed to close pipe to '$cmd'";
+ return @addresses;
+}
+
cleanup_compose_files();
-sub cleanup_compose_files() {
+sub cleanup_compose_files {
unlink($compose_filename, $compose_filename . ".final") if $compose;
}
$smtp->quit if $smtp;
-sub unique_email_list(@) {
+sub unique_email_list {
my %seen;
my @emails;
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
new file mode 100644
index 0000000..32ca59d
--- /dev/null
+++ b/git-sh-i18n.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+# This is a skeleton no-op implementation of gettext for Git. It'll be
+# replaced by something that uses gettext.sh in a future patch series.
+
+if test -z "$GIT_GETTEXT_POISON"
+then
+ gettext () {
+ printf "%s" "$1"
+ }
+
+ eval_gettext () {
+ printf "%s" "$1" | (
+ export PATH $(git sh-i18n--envsubst --variables "$1");
+ git sh-i18n--envsubst "$1"
+ )
+ }
+else
+ gettext () {
+ printf "%s" "# GETTEXT POISON #"
+ }
+
+ eval_gettext () {
+ printf "%s" "# GETTEXT POISON #"
+ }
+fi
+
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 6131670..8e427da 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -39,9 +39,15 @@ git_broken_path_fix () {
# @@BROKEN_PATH_FIX@@
-die() {
- echo >&2 "$@"
- exit 1
+die () {
+ die_with_status 1 "$@"
+}
+
+die_with_status () {
+ status=$1
+ shift
+ echo >&2 "$*"
+ exit "$status"
}
GIT_QUIET=
@@ -140,28 +146,61 @@ cd_to_toplevel () {
}
}
+require_work_tree_exists () {
+ if test "z$(git rev-parse --is-bare-repository)" != zfalse
+ then
+ die "fatal: $0 cannot be used without a working tree."
+ fi
+}
+
require_work_tree () {
test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
die "fatal: $0 cannot be used without a working tree."
}
+require_clean_work_tree () {
+ git rev-parse --verify HEAD >/dev/null || exit 1
+ git update-index -q --ignore-submodules --refresh
+ err=0
+
+ if ! git diff-files --quiet --ignore-submodules
+ then
+ echo >&2 "Cannot $1: You have unstaged changes."
+ err=1
+ fi
+
+ if ! git diff-index --cached --quiet --ignore-submodules HEAD --
+ then
+ if [ $err = 0 ]
+ then
+ echo >&2 "Cannot $1: Your index contains uncommitted changes."
+ else
+ echo >&2 "Additionally, your index contains uncommitted changes."
+ fi
+ err=1
+ fi
+
+ if [ $err = 1 ]
+ then
+ test -n "$2" && echo >&2 "$2"
+ exit 1
+ fi
+}
+
get_author_ident_from_commit () {
pick_author_script='
/^author /{
s/'\''/'\''\\'\'\''/g
h
s/^author \([^<]*\) <[^>]*> .*$/\1/
- s/'\''/'\''\'\'\''/g
s/.*/GIT_AUTHOR_NAME='\''&'\''/p
g
s/^author [^<]* <\([^>]*\)> .*$/\1/
- s/'\''/'\''\'\'\''/g
s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
g
s/^author [^<]* <[^>]*> \(.*\)$/\1/
- s/'\''/'\''\'\'\''/g
s/.*/GIT_AUTHOR_DATE='\''&'\''/p
q
@@ -209,5 +248,20 @@ case $(uname -s) in
find () {
/usr/bin/find "$@"
}
+ is_absolute_path () {
+ case "$1" in
+ [/\\]* | [A-Za-z]:*)
+ return 0 ;;
+ esac
+ return 1
+ }
;;
+*)
+ is_absolute_path () {
+ case "$1" in
+ /*)
+ return 0 ;;
+ esac
+ return 1
+ }
esac
diff --git a/git-stash.sh b/git-stash.sh
index 1d95447..5619da5 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -12,12 +12,15 @@ USAGE="list [<options>]
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
+START_DIR=`pwd`
. git-sh-setup
+. git-sh-i18n
require_work_tree
cd_to_toplevel
TMP="$GIT_DIR/.git-stash.$$"
-trap 'rm -f "$TMP-*"' 0
+TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+trap 'rm -f "$TMP-"* "$TMPindex"' 0
ref_stash=refs/stash
@@ -37,7 +40,7 @@ no_changes () {
clear_stash () {
if test $# != 0
then
- die "git stash clear with parameters is unimplemented"
+ die "$(gettext "git stash clear with parameters is unimplemented")"
fi
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
then
@@ -59,7 +62,7 @@ create_stash () {
then
head=$(git rev-list --oneline -n 1 HEAD --)
else
- die "You do not have the initial commit yet"
+ die "$(gettext "You do not have the initial commit yet")"
fi
if branch=$(git symbolic-ref -q HEAD)
@@ -74,23 +77,21 @@ create_stash () {
i_tree=$(git write-tree) &&
i_commit=$(printf 'index on %s\n' "$msg" |
git commit-tree $i_tree -p $b_commit) ||
- die "Cannot save the current index state"
+ die "$(gettext "Cannot save the current index state")"
if test -z "$patch_mode"
then
# state of the working tree
w_tree=$( (
- rm -f "$TMP-index" &&
- cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
- GIT_INDEX_FILE="$TMP-index" &&
+ git read-tree --index-output="$TMPindex" -m $i_tree &&
+ GIT_INDEX_FILE="$TMPindex" &&
export GIT_INDEX_FILE &&
- git read-tree -m $i_tree &&
git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
git write-tree &&
- rm -f "$TMP-index"
+ rm -f "$TMPindex"
) ) ||
- die "Cannot save the current worktree state"
+ die "$(gettext "Cannot save the current worktree state")"
else
@@ -103,14 +104,14 @@ create_stash () {
# state of the working tree
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
- die "Cannot save the current worktree state"
+ die "$(gettext "Cannot save the current worktree state")"
git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
test -s "$TMP-patch" ||
- die "No changes selected"
+ die "$(gettext "No changes selected")"
rm -f "$TMP-index" ||
- die "Cannot remove temporary index (can't happen)"
+ die "$(gettext "Cannot remove temporary index (can't happen)")"
fi
@@ -123,7 +124,7 @@ create_stash () {
fi
w_commit=$(printf '%s\n' "$stash_msg" |
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
- die "Cannot record working tree state"
+ die "$(gettext "Cannot record working tree state")"
}
save_stash () {
@@ -136,11 +137,12 @@ save_stash () {
keep_index=t
;;
--no-keep-index)
- keep_index=
+ keep_index=n
;;
-p|--patch)
patch_mode=t
- keep_index=t
+ # only default to keep if we don't already have an override
+ test -z "$keep_index" && keep_index=t
;;
-q|--quiet)
GIT_QUIET=t
@@ -150,8 +152,19 @@ save_stash () {
break
;;
-*)
- echo "error: unknown option for 'stash save': $1"
- echo " To provide a message, use git stash save -- '$1'"
+ option="$1"
+ # TRANSLATORS: $option is an invalid option, like
+ # `--blah-blah'. The 7 spaces at the beginning of the
+ # second line correspond to "error: ". So you should line
+ # up the second line with however many characters the
+ # translation of "error: " takes in your language. E.g. in
+ # English this is:
+ #
+ # $ git stash save --blah-blah 2>&1 | head -n 2
+ # error: unknown option for 'stash save': --blah-blah
+ # To provide a message, use git stash save -- '--blah-blah'
+ eval_gettext "$("error: unknown option for 'stash save': \$option
+ To provide a message, use git stash save -- '\$option'")"; echo
usage
;;
*)
@@ -166,11 +179,11 @@ save_stash () {
git update-index -q --refresh
if no_changes
then
- say 'No local changes to save'
+ say "$(gettext "No local changes to save")"
exit 0
fi
test -f "$GIT_DIR/logs/$ref_stash" ||
- clear_stash || die "Cannot initialize stash"
+ clear_stash || die "$(gettext "Cannot initialize stash")"
create_stash "$stash_msg"
@@ -178,22 +191,22 @@ save_stash () {
: >>"$GIT_DIR/logs/$ref_stash"
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
- die "Cannot save the current status"
+ die "$(gettext "Cannot save the current status")"
say Saved working directory and index state "$stash_msg"
if test -z "$patch_mode"
then
git reset --hard ${GIT_QUIET:+-q}
- if test -n "$keep_index" && test -n $i_tree
+ if test "$keep_index" = "t" && test -n $i_tree
then
git read-tree --reset -u $i_tree
fi
else
git apply -R < "$TMP-patch" ||
- die "Cannot remove worktree changes"
+ die "$(gettext "Cannot remove worktree changes")"
- if test -z "$keep_index"
+ if test "$keep_index" != "t"
then
git reset
fi
@@ -210,74 +223,154 @@ list_stash () {
}
show_stash () {
- have_stash || die 'No stash found'
-
- flags=$(git rev-parse --no-revs --flags "$@")
- if test -z "$flags"
- then
- flags=--stat
- fi
+ assert_stash_like "$@"
- w_commit=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
- b_commit=$(git rev-parse --quiet --verify "$w_commit^") ||
- die "'$*' is not a stash"
-
- git diff $flags $b_commit $w_commit
+ git diff ${FLAGS:---stat} $b_commit $w_commit
}
-apply_stash () {
- applied_stash=
- unstash_index=
-
- while test $# != 0
+#
+# Parses the remaining options looking for flags and
+# at most one revision defaulting to ${ref_stash}@{0}
+# if none found.
+#
+# Derives related tree and commit objects from the
+# revision, if one is found.
+#
+# stash records the work tree, and is a merge between the
+# base commit (first parent) and the index tree (second parent).
+#
+# REV is set to the symbolic version of the specified stash-like commit
+# IS_STASH_LIKE is non-blank if ${REV} looks like a stash
+# IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
+# s is set to the SHA1 of the stash commit
+# w_commit is set to the commit containing the working tree
+# b_commit is set to the base commit
+# i_commit is set to the commit containing the index tree
+# w_tree is set to the working tree
+# b_tree is set to the base tree
+# i_tree is set to the index tree
+#
+# GIT_QUIET is set to t if -q is specified
+# INDEX_OPTION is set to --index if --index is specified.
+# FLAGS is set to the remaining flags
+#
+# dies if:
+# * too many revisions specified
+# * no revision is specified and there is no stash stack
+# * a revision is specified which cannot be resolve to a SHA1
+# * a non-existent stash reference is specified
+#
+
+parse_flags_and_rev()
+{
+ test "$PARSE_CACHE" = "$*" && return 0 # optimisation
+ PARSE_CACHE="$*"
+
+ IS_STASH_LIKE=
+ IS_STASH_REF=
+ INDEX_OPTION=
+ s=
+ w_commit=
+ b_commit=
+ i_commit=
+ w_tree=
+ b_tree=
+ i_tree=
+
+ REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
+
+ FLAGS=
+ for opt
do
- case "$1" in
- --index)
- unstash_index=t
+ case "$opt" in
+ -q|--quiet)
+ GIT_QUIET=-t
;;
- -q|--quiet)
- GIT_QUIET=t
+ --index)
+ INDEX_OPTION=--index
;;
- *)
- break
+ -*)
+ FLAGS="${FLAGS}${FLAGS:+ }$opt"
;;
esac
- shift
done
- if test $# = 0
- then
- have_stash || die 'Nothing to apply'
- applied_stash="$ref_stash@{0}"
- else
- applied_stash="$*"
- fi
+ set -- $REV
+
+ case $# in
+ 0)
+ have_stash || die "$(gettext "No stash found.")"
+ set -- ${ref_stash}@{0}
+ ;;
+ 1)
+ :
+ ;;
+ *)
+ die "$(eval_gettext "Too many revisions specified: \$REV")"
+ ;;
+ esac
+
+ REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || {
+ reference="$1"
+ die "$(eval_gettext "\$reference is not valid reference")"
+ }
+
+ i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
+ set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
+ s=$1 &&
+ w_commit=$1 &&
+ b_commit=$2 &&
+ w_tree=$3 &&
+ b_tree=$4 &&
+ i_tree=$5 &&
+ IS_STASH_LIKE=t &&
+ test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
+ IS_STASH_REF=t
+}
+
+is_stash_like()
+{
+ parse_flags_and_rev "$@"
+ test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+ is_stash_like "$@" || {
+ args="$*"
+ die "$(eval_gettext "'\$args' is not a stash-like commit")"
+ }
+}
- # stash records the work tree, and is a merge between the
- # base commit (first parent) and the index tree (second parent).
- s=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
- w_tree=$(git rev-parse --quiet --verify "$s:") &&
- b_tree=$(git rev-parse --quiet --verify "$s^1:") &&
- i_tree=$(git rev-parse --quiet --verify "$s^2:") ||
- die "$*: no valid stashed state found"
+is_stash_ref() {
+ is_stash_like "$@" && test -n "$IS_STASH_REF"
+}
+
+assert_stash_ref() {
+ is_stash_ref "$@" || {
+ args="$*"
+ die "$(eval_gettext "'\$args' is not a stash reference")"
+ }
+}
+
+apply_stash () {
+
+ assert_stash_like "$@"
- git update-index -q --refresh &&
- git diff-files --quiet --ignore-submodules ||
- die 'Cannot apply to a dirty working tree, please stage your changes'
+ git update-index -q --refresh || die "$(gettext "unable to refresh index")"
# current index state
c_tree=$(git write-tree) ||
- die 'Cannot apply a stash in the middle of a merge'
+ die "$(gettext "Cannot apply a stash in the middle of a merge")"
unstashed_index_tree=
- if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
+ if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
test "$c_tree" != "$i_tree"
then
git diff-tree --binary $s^2^..$s^2 | git apply --cached
test $? -ne 0 &&
- die 'Conflicts in index. Try without --index.'
+ die "$(gettext "Conflicts in index. Try without --index.")"
unstashed_index_tree=$(git write-tree) ||
- die 'Could not save index tree'
+ die "$(gettext "Could not save index tree")"
git reset
fi
@@ -290,7 +383,7 @@ apply_stash () {
if test -n "$GIT_QUIET"
then
- export GIT_MERGE_VERBOSITY=0
+ GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
fi
if git merge-recursive $b_tree -- $c_tree $w_tree
then
@@ -303,7 +396,7 @@ apply_stash () {
git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
git read-tree --reset $c_tree &&
git update-index --add --stdin <"$a" ||
- die "Cannot unstage modified files"
+ die "$(gettext "Cannot unstage modified files")"
rm -f "$a"
fi
squelch=
@@ -311,70 +404,54 @@ apply_stash () {
then
squelch='>/dev/null 2>&1'
fi
- eval "git status $squelch" || :
+ (cd "$START_DIR" && eval "git status $squelch") || :
else
# Merge conflict; keep the exit status from merge-recursive
status=$?
- if test -n "$unstash_index"
+ if test -n "$INDEX_OPTION"
then
- echo >&2 'Index was not unstashed.'
+ (
+ gettext "Index was not unstashed." &&
+ echo
+ ) >&2
fi
exit $status
fi
}
-drop_stash () {
- have_stash || die 'No stash entries to drop'
+pop_stash() {
+ assert_stash_ref "$@"
- while test $# != 0
- do
- case "$1" in
- -q|--quiet)
- GIT_QUIET=t
- ;;
- *)
- break
- ;;
- esac
- shift
- done
+ apply_stash "$@" &&
+ drop_stash "$@"
+}
- if test $# = 0
- then
- set x "$ref_stash@{0}"
- shift
- fi
- # Verify supplied argument looks like a stash entry
- s=$(git rev-parse --verify "$@") &&
- git rev-parse --verify "$s:" > /dev/null 2>&1 &&
- git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
- git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
- die "$*: not a valid stashed state"
+drop_stash () {
+ assert_stash_ref "$@"
- git reflog delete --updateref --rewrite "$@" &&
- say "Dropped $* ($s)" || die "$*: Could not drop stash entry"
+ git reflog delete --updateref --rewrite "${REV}" &&
+ say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
+ die "$(eval_gettext "\${REV}: Could not drop stash entry")"
# clear_stash if we just dropped the last stash entry
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
}
apply_to_branch () {
- have_stash || die 'Nothing to apply'
-
- test -n "$1" || die 'No branch name specified'
+ test -n "$1" || die "$(gettext "No branch name specified")"
branch=$1
+ shift 1
- if test -z "$2"
- then
- set x "$ref_stash@{0}"
- fi
- stash=$2
+ set -- --index "$@"
+ assert_stash_like "$@"
- git checkout -b $branch $stash^ &&
- apply_stash --index $stash &&
- drop_stash $stash
+ git checkout -b $branch $REV^ &&
+ apply_stash "$@" && {
+ test -z "$IS_STASH_REF" || drop_stash "$@"
+ }
}
+PARSE_CACHE='--not-parsed'
# The default command is "save" if nothing but options are given
seen_non_option=
for opt
@@ -422,10 +499,7 @@ drop)
;;
pop)
shift
- if apply_stash "$@"
- then
- drop_stash "$applied_stash"
- fi
+ pop_stash "$@"
;;
branch)
shift
@@ -435,7 +509,7 @@ branch)
case $# in
0)
save_stash &&
- say '(To restore them type "git stash apply")'
+ say "$(gettext "(To restore them type \"git stash apply\")")"
;;
*)
usage
diff --git a/git-submodule.sh b/git-submodule.sh
index d9950c2..bc1d3fa 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -5,20 +5,22 @@
# Copyright (c) 2007 Lars Hjemli
dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> [<path>]
+USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>]
or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
or: $dashless [--quiet] init [--] [<path>...]
- or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+ or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
or: $dashless [--quiet] foreach [--recursive] <command>
or: $dashless [--quiet] sync [--] [<path>...]"
OPTIONS_SPEC=
. git-sh-setup
+. git-sh-i18n
. git-parse-remote
require_work_tree
command=
branch=
+force=
reference=
cached=
recursive=
@@ -33,15 +35,27 @@ resolve_relative_url ()
{
remote=$(get_default_remote)
remoteurl=$(git config "remote.$remote.url") ||
- die "remote ($remote) does not have a url defined in .git/config"
+ remoteurl=$(pwd) # the repository is its own authoritative upstream
url="$1"
remoteurl=${remoteurl%/}
+ sep=/
while test -n "$url"
do
case "$url" in
../*)
url="${url#../}"
- remoteurl="${remoteurl%/*}"
+ case "$remoteurl" in
+ */*)
+ remoteurl="${remoteurl%/*}"
+ ;;
+ *:*)
+ remoteurl="${remoteurl%:*}"
+ sep=:
+ ;;
+ *)
+ die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ ;;
+ esac
;;
./*)
url="${url#./}"
@@ -50,7 +64,7 @@ resolve_relative_url ()
break;;
esac
done
- echo "$remoteurl/${url%/}"
+ echo "$remoteurl$sep${url%/}"
}
#
@@ -59,7 +73,24 @@ resolve_relative_url ()
#
module_list()
{
- git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 '
+ git ls-files --error-unmatch --stage -- "$@" |
+ perl -e '
+ my %unmerged = ();
+ my ($null_sha1) = ("0" x 40);
+ while (<STDIN>) {
+ chomp;
+ my ($mode, $sha1, $stage, $path) =
+ /^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
+ next unless $mode eq "160000";
+ if ($stage ne "0") {
+ if (!$unmerged{$path}++) {
+ print "$mode $null_sha1 U\t$path\n";
+ }
+ next;
+ }
+ print "$_\n";
+ }
+ '
}
#
@@ -74,7 +105,7 @@ module_name()
name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
test -z "$name" &&
- die "No submodule mapping found in .gitmodules for path '$path'"
+ die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
echo "$name"
}
@@ -92,27 +123,13 @@ module_clone()
url=$2
reference="$3"
- # If there already is a directory at the submodule path,
- # expect it to be empty (since that is the default checkout
- # action) and try to remove it.
- # Note: if $path is a symlink to a directory the test will
- # succeed but the rmdir will fail. We might want to fix this.
- if test -d "$path"
- then
- rmdir "$path" 2>/dev/null ||
- die "Directory '$path' exists, but is neither empty nor a git repository"
- fi
-
- test -e "$path" &&
- die "A file already exist at path '$path'"
-
if test -n "$reference"
then
git-clone "$reference" -n "$url" "$path"
else
git-clone -n "$url" "$path"
fi ||
- die "Clone of '$url' into submodule path '$path' failed"
+ die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
}
#
@@ -133,6 +150,9 @@ cmd_add()
branch=$2
shift
;;
+ -f | --force)
+ force=$1
+ ;;
-q|--quiet)
GIT_QUIET=1
;;
@@ -182,7 +202,7 @@ cmd_add()
realrepo=$repo
;;
*)
- die "repo URL: '$repo' must be absolute or begin with ./|../"
+ die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
;;
esac
@@ -199,27 +219,29 @@ cmd_add()
s|/*$||
')
git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
- die "'$path' already exists in the index"
+ die "$(eval_gettext "'\$path' already exists in the index")"
+
+ if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
+ then
+ (
+ eval_gettext "The following path is ignored by one of your .gitignore files:
+\$path
+Use -f if you really want to add it." &&
+ echo
+ ) >&2
+ exit 1
+ fi
# perhaps the path exists and is already a git repo, else clone it
if test -e "$path"
then
if test -d "$path"/.git -o -f "$path"/.git
then
- echo "Adding existing repo at '$path' to the index"
+ eval_gettext "Adding existing repo at '\$path' to the index"; echo
else
- die "'$path' already exists and is not a valid git repo"
+ die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
fi
- case "$repo" in
- ./*|../*)
- url=$(resolve_relative_url "$repo") || exit
- ;;
- *)
- url="$repo"
- ;;
- esac
- git config submodule."$path".url "$url"
else
module_clone "$path" "$realrepo" "$reference" || exit
@@ -229,18 +251,19 @@ cmd_add()
# ash fails to wordsplit ${branch:+-b "$branch"...}
case "$branch" in
'') git checkout -f -q ;;
- ?*) git checkout -f -q -b "$branch" "origin/$branch" ;;
+ ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
esac
- ) || die "Unable to checkout submodule '$path'"
+ ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")"
fi
+ git config submodule."$path".url "$realrepo"
- git add "$path" ||
- die "Failed to add submodule '$path'"
+ git add $force "$path" ||
+ die "$(eval_gettext "Failed to add submodule '\$path'")"
git config -f .gitmodules submodule."$path".path "$path" &&
git config -f .gitmodules submodule."$path".url "$repo" &&
- git add .gitmodules ||
- die "Failed to register submodule '$path'"
+ git add --force .gitmodules ||
+ die "$(eval_gettext "Failed to register submodule '\$path'")"
}
#
@@ -273,12 +296,16 @@ cmd_foreach()
toplevel=$(pwd)
+ # dup stdin so that it can be restored when running the external
+ # command in the subshell (and a recursive call to this function)
+ exec 3<&0
+
module_list |
while read mode sha1 stage path
do
if test -e "$path"/.git
then
- say "Entering '$prefix$path'"
+ say "$(eval_gettext "Entering '\$prefix\$path'")"
name=$(module_name "$path")
(
prefix="$prefix$path/"
@@ -289,8 +316,8 @@ cmd_foreach()
then
cmd_foreach "--recursive" "$@"
fi
- ) ||
- die "Stopping at '$path'; script returned non-zero status."
+ ) <&3 3<&- ||
+ die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")"
fi
done
}
@@ -328,29 +355,30 @@ cmd_init()
do
# Skip already registered paths
name=$(module_name "$path") || exit
- url=$(git config submodule."$name".url)
- test -z "$url" || continue
-
- url=$(git config -f .gitmodules submodule."$name".url)
- test -z "$url" &&
- die "No url found for submodule path '$path' in .gitmodules"
-
- # Possibly a url relative to parent
- case "$url" in
- ./*|../*)
- url=$(resolve_relative_url "$url") || exit
- ;;
- esac
-
- git config submodule."$name".url "$url" ||
- die "Failed to register url for submodule path '$path'"
+ if test -z "$(git config "submodule.$name.url")"
+ then
+ url=$(git config -f .gitmodules submodule."$name".url)
+ test -z "$url" &&
+ die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")"
+
+ # Possibly a url relative to parent
+ case "$url" in
+ ./*|../*)
+ url=$(resolve_relative_url "$url") || exit
+ ;;
+ esac
+ git config submodule."$name".url "$url" ||
+ die "$(eval_gettext "Failed to register url for submodule path '\$path'")"
+ fi
+ # Copy "update" setting when it is not set yet
upd="$(git config -f .gitmodules submodule."$name".update)"
test -z "$upd" ||
+ test -n "$(git config submodule."$name".update)" ||
git config submodule."$name".update "$upd" ||
- die "Failed to register update mode for submodule path '$path'"
+ die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")"
- say "Submodule '$name' ($url) registered for path '$path'"
+ say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")"
done
}
@@ -362,41 +390,38 @@ cmd_init()
cmd_update()
{
# parse $args after "submodule ... update".
- orig_args="$@"
+ orig_flags=
while test $# -ne 0
do
case "$1" in
-q|--quiet)
- shift
GIT_QUIET=1
;;
-i|--init)
init=1
- shift
;;
-N|--no-fetch)
- shift
nofetch=1
;;
+ -f|--force)
+ force=$1
+ ;;
-r|--rebase)
- shift
update="rebase"
;;
--reference)
case "$2" in '') usage ;; esac
reference="--reference=$2"
- shift 2
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
;;
--reference=*)
reference="$1"
- shift
;;
-m|--merge)
- shift
update="merge"
;;
--recursive)
- shift
recursive=1
;;
--)
@@ -410,6 +435,8 @@ cmd_update()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
done
if test -n "$init"
@@ -417,9 +444,16 @@ cmd_update()
cmd_init "--" "$@" || return
fi
- module_list "$@" |
+ cloned_modules=
+ module_list "$@" | {
+ err=
while read mode sha1 stage path
do
+ if test "$stage" = U
+ then
+ echo >&2 "Skipping unmerged submodule $path"
+ continue
+ fi
name=$(module_name "$path") || exit
url=$(git config submodule."$name".url)
update_module=$(git config submodule."$name".update)
@@ -428,19 +462,20 @@ cmd_update()
# Only mention uninitialized submodules when its
# path have been specified
test "$#" != "0" &&
- say "Submodule path '$path' not initialized" &&
- say "Maybe you want to use 'update --init'?"
+ say "$(eval_gettext "Submodule path '\$path' not initialized
+Maybe you want to use 'update --init'?")"
continue
fi
if ! test -d "$path"/.git -o -f "$path"/.git
then
module_clone "$path" "$url" "$reference"|| exit
+ cloned_modules="$cloned_modules;$name"
subsha1=
else
subsha1=$(clear_local_git_env; cd "$path" &&
git rev-parse --verify HEAD) ||
- die "Unable to find current revision in submodule path '$path'"
+ die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
fi
if ! test -z "$update"
@@ -450,48 +485,96 @@ cmd_update()
if test "$subsha1" != "$sha1"
then
- force=
- if test -z "$subsha1"
+ subforce=$force
+ # If we don't already have a -f flag and the submodule has never been checked out
+ if test -z "$subsha1" -a -z "$force"
then
- force="-f"
+ subforce="-f"
fi
if test -z "$nofetch"
then
+ # Run fetch only if $sha1 isn't present or it
+ # is not reachable from a ref.
(clear_local_git_env; cd "$path" &&
- git-fetch) ||
- die "Unable to fetch in submodule path '$path'"
+ ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
+ test -z "$rev") || git-fetch)) ||
+ die "$(eval_gettext "Unable to fetch in submodule path '\$path'")"
fi
+ # Is this something we just cloned?
+ case ";$cloned_modules;" in
+ *";$name;"*)
+ # then there is no local change to integrate
+ update_module= ;;
+ esac
+
+ must_die_on_failure=
case "$update_module" in
rebase)
command="git rebase"
- action="rebase"
- msg="rebased onto"
+ die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")"
+ say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")"
+ must_die_on_failure=yes
;;
merge)
command="git merge"
- action="merge"
- msg="merged in"
+ die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")"
+ say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")"
+ must_die_on_failure=yes
;;
*)
- command="git checkout $force -q"
- action="checkout"
- msg="checked out"
+ command="git checkout $subforce -q"
+ die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")"
+ say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")"
;;
esac
- (clear_local_git_env; cd "$path" && $command "$sha1") ||
- die "Unable to $action '$sha1' in submodule path '$path'"
- say "Submodule path '$path': $msg '$sha1'"
+ if (clear_local_git_env; cd "$path" && $command "$sha1")
+ then
+ say "$say_msg"
+ elif test -n "$must_die_on_failure"
+ then
+ die_with_status 2 "$die_msg"
+ else
+ err="${err};$die_msg"
+ continue
+ fi
fi
if test -n "$recursive"
then
- (clear_local_git_env; cd "$path" && cmd_update $orig_args) ||
- die "Failed to recurse into submodule path '$path'"
+ (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags")
+ res=$?
+ if test $res -gt 0
+ then
+ die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+ if test $res -eq 1
+ then
+ err="${err};$die_msg"
+ continue
+ else
+ die_with_status $res "$die_msg"
+ fi
+ fi
fi
done
+
+ if test -n "$err"
+ then
+ OIFS=$IFS
+ IFS=';'
+ for e in $err
+ do
+ if test -n "$e"
+ then
+ echo >&2 "$e"
+ fi
+ done
+ IFS=$OIFS
+ exit 1
+ fi
+ }
}
set_name_rev () {
@@ -573,7 +656,7 @@ cmd_summary() {
if [ -n "$files" ]
then
test -n "$cached" &&
- die "--cached cannot be used with --files"
+ die "$(gettext -- "--cached cannot be used with --files")"
diff_cmd=diff-files
head=
fi
@@ -613,7 +696,10 @@ cmd_summary() {
;; # removed
*)
# unexpected type
- echo >&2 "unexpected mode $mod_dst"
+ (
+ eval_gettext "unexpected mode \$mod_dst" &&
+ echo
+ ) >&2
continue ;;
esac
fi
@@ -631,13 +717,13 @@ cmd_summary() {
total_commits=
case "$missing_src,$missing_dst" in
t,)
- errmsg=" Warn: $name doesn't contain commit $sha1_src"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_src")"
;;
,t)
- errmsg=" Warn: $name doesn't contain commit $sha1_dst"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_dst")"
;;
t,t)
- errmsg=" Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
+ errmsg="$(eval_gettext " Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
;;
*)
errmsg=
@@ -662,11 +748,13 @@ cmd_summary() {
sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
if test $status = T
then
+ blob="$(gettext "blob")"
+ submodule="$(gettext "submodule")"
if test $mod_dst = 160000
then
- echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
+ echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
else
- echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
+ echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
fi
else
echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
@@ -698,9 +786,9 @@ cmd_summary() {
done |
if test -n "$for_status"; then
if [ -n "$files" ]; then
- echo "# Submodules changed but not updated:"
+ gettext "# Submodules changed but not updated:"; echo
else
- echo "# Submodule changes to be committed:"
+ gettext "# Submodule changes to be committed:"; echo
fi
echo "#"
sed -e 's|^|# |' -e 's|^# $|#|'
@@ -721,7 +809,7 @@ cmd_summary() {
cmd_status()
{
# parse $args after "submodule ... status".
- orig_args="$@"
+ orig_flags=
while test $# -ne 0
do
case "$1" in
@@ -745,6 +833,7 @@ cmd_status()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
shift
done
@@ -754,6 +843,11 @@ cmd_status()
name=$(module_name "$path") || exit
url=$(git config submodule."$name".url)
displaypath="$prefix$path"
+ if test "$stage" = U
+ then
+ say "U$sha1 $displaypath"
+ continue
+ fi
if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
then
say "-$sha1 $displaypath"
@@ -778,9 +872,9 @@ cmd_status()
prefix="$displaypath/"
clear_local_git_env
cd "$path" &&
- cmd_status $orig_args
+ eval cmd_status "$orig_args"
) ||
- die "Failed to recurse into submodule path '$path'"
+ die "$(eval_gettext "Failed to recurse into submodule path '\$path'")"
fi
done
}
@@ -824,15 +918,20 @@ cmd_sync()
;;
esac
- if test -e "$path"/.git
+ if git config "submodule.$name.url" >/dev/null 2>/dev/null
then
- (
- clear_local_git_env
- cd "$path"
- remote=$(get_default_remote)
- say "Synchronizing submodule url for '$name'"
- git config remote."$remote".url "$url"
- )
+ say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
+ git config submodule."$name".url "$url"
+
+ if test -e "$path"/.git
+ then
+ (
+ clear_local_git_env
+ cd "$path"
+ remote=$(get_default_remote)
+ git config remote."$remote".url "$url"
+ )
+ fi
fi
done
}
diff --git a/git-svn.perl b/git-svn.perl
index 19d6848..89f83fd 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -1,6 +1,7 @@
#!/usr/bin/env perl
# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
# License: GPL v2 or later
+use 5.008;
use warnings;
use strict;
use vars qw/ $AUTHOR $VERSION
@@ -58,6 +59,7 @@ use File::Find;
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
use IPC::Open3;
use Git;
+use Memoize; # core since 5.8.0, Jul 2002
BEGIN {
# import functions from Git into our packages, en masse
@@ -71,6 +73,8 @@ BEGIN {
*{"${package}::$_"} = \&{"Git::$_"};
}
}
+ Memoize::memoize 'Git::config';
+ Memoize::memoize 'Git::config_bool';
}
my ($SVN);
@@ -83,7 +87,7 @@ my ($_stdin, $_help, $_edit,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_merge, $_strategy, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
- $_git_format, $_commit_url, $_tag);
+ $_git_format, $_commit_url, $_tag, $_merge_info);
$Git::SVN::_follow_parent = 1;
$_q ||= 0;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
@@ -153,6 +157,7 @@ my %cmd = (
'commit-url=s' => \$_commit_url,
'revision|r=i' => \$_revision,
'no-rebase' => \$_no_rebase,
+ 'mergeinfo=s' => \$_merge_info,
%cmt_opts, %fc_opts } ],
branch => [ \&cmd_branch,
'Create a branch in the SVN repository',
@@ -494,6 +499,7 @@ sub cmd_set_tree {
sub cmd_dcommit {
my $head = shift;
+ command_noisy(qw/update-index --refresh/);
git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
'Cannot dcommit with a dirty index. Commit your changes first, '
. "or stash them with `git stash'.\n";
@@ -525,7 +531,7 @@ sub cmd_dcommit {
$url = eval { command_oneline('config', '--get',
"svn-remote.$gs->{repo_id}.commiturl") };
if (!$url) {
- $url = $gs->full_url
+ $url = $gs->full_pushurl
}
}
@@ -567,6 +573,7 @@ sub cmd_dcommit {
print "Committed r$_[0]\n";
$cmt_rev = $_[0];
},
+ mergeinfo => $_merge_info,
svn_path => '');
if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
@@ -672,7 +679,7 @@ sub cmd_branch {
$head ||= 'HEAD';
my (undef, $rev, undef, $gs) = working_head_info($head);
- my $src = $gs->full_url;
+ my $src = $gs->full_pushurl;
my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
@@ -723,7 +730,7 @@ sub cmd_branch {
$url = eval { command_oneline('config', '--get',
"svn-remote.$gs->{repo_id}.commiturl") };
if (!$url) {
- $url = $remote->{url};
+ $url = $remote->{pushurl} || $remote->{url};
}
}
my $dst = join '/', $url, $lft, $branch_name, ($rgt || ());
@@ -777,6 +784,15 @@ sub cmd_find_rev {
print "$result\n" if $result;
}
+sub auto_create_empty_directories {
+ my ($gs) = @_;
+ my $var = eval { command_oneline('config', '--get', '--bool',
+ "svn-remote.$gs->{repo_id}.automkdirs") };
+ # By default, create empty directories by consulting the unhandled log,
+ # but allow setting it to 'false' to skip it.
+ return !($var && $var eq 'false');
+}
+
sub cmd_rebase {
command_noisy(qw/update-index --refresh/);
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
@@ -800,7 +816,9 @@ sub cmd_rebase {
$_fetch_all ? $gs->fetch_all : $gs->fetch;
}
command_noisy(rebase_cmd(), $gs->refname);
- $gs->mkemptydirs;
+ if (auto_create_empty_directories($gs)) {
+ $gs->mkemptydirs;
+ }
}
sub cmd_show_ignore {
@@ -1238,7 +1256,9 @@ sub post_fetch_checkout {
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
$gs->full_url, " r", $gs->last_rev, "\n";
- $gs->mkemptydirs($gs->last_rev);
+ if (auto_create_empty_directories($gs)) {
+ $gs->mkemptydirs($gs->last_rev);
+ }
}
sub complete_svn_url {
@@ -1512,7 +1532,8 @@ sub cmt_sha2rev_batch {
sub working_head_info {
my ($head, $refs) = @_;
- my @args = ('log', '--no-color', '--first-parent', '--pretty=medium');
+ my @args = qw/log --no-color --no-decorate --first-parent
+ --pretty=medium/;
my ($fh, $ctx) = command_output_pipe(@args, $head);
my $hash;
my %max;
@@ -1819,18 +1840,22 @@ sub read_all_remotes {
die("svn-remote.$remote: remote ref '$remote_ref' "
. "must start with 'refs/'\n")
unless $remote_ref =~ m{^refs/};
+ $local_ref = uri_decode($local_ref);
$r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
$r->{$remote}->{svm} = {} if $use_svm_props;
} elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
$r->{$1}->{svm} = {};
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
$r->{$1}->{url} = $2;
+ } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
+ $r->{$1}->{pushurl} = $2;
} elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
my ($remote, $t, $local_ref, $remote_ref) =
($1, $2, $3, $4);
die("svn-remote.$remote: remote ref '$remote_ref' ($t) "
. "must start with 'refs/'\n")
unless $remote_ref =~ m{^refs/};
+ $local_ref = uri_decode($local_ref);
my $rs = {
t => $t,
remote => $remote,
@@ -2061,6 +2086,8 @@ sub new {
$self->{url} = command_oneline('config', '--get',
"svn-remote.$repo_id.url") or
die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
+ $self->{pushurl} = eval { command_oneline('config', '--get',
+ "svn-remote.$repo_id.pushurl") };
$self->rebuild;
$self;
}
@@ -2538,6 +2565,15 @@ sub full_url {
$self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
}
+sub full_pushurl {
+ my ($self) = @_;
+ if ($self->{pushurl}) {
+ return $self->{pushurl} . (length $self->{path} ? '/' .
+ $self->{path} : '');
+ } else {
+ return $self->full_url;
+ }
+}
sub set_commit_header_env {
my ($log_entry) = @_;
@@ -2956,18 +2992,29 @@ sub other_gs {
my $gs = Git::SVN->find_by_url($new_url, $url, $branch_from);
unless ($gs) {
my $ref_id = $old_ref_id;
- $ref_id =~ s/\@\d+$//;
+ $ref_id =~ s/\@\d+-*$//;
$ref_id .= "\@$r";
# just grow a tail if we're not unique enough :x
$ref_id .= '-' while find_ref($ref_id);
- print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
if ($u =~ s#^\Q$url\E(/|$)##) {
$p = $u;
$u = $url;
$repo_id = $self->{repo_id};
}
- $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+ while (1) {
+ # It is possible to tag two different subdirectories at
+ # the same revision. If the url for an existing ref
+ # does not match, we must either find a ref with a
+ # matching url or create a new ref by growing a tail.
+ $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+ my (undef, $max_commit) = $gs->rev_map_max(1);
+ last if (!$max_commit);
+ my ($url) = ::cmt_metadata($max_commit);
+ last if ($url eq $gs->full_url);
+ $ref_id .= '-';
+ }
+ print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
}
$gs
}
@@ -3077,8 +3124,12 @@ sub lookup_svn_merge {
next;
}
- push @merged_commit_ranges,
- "$bottom_commit^..$top_commit";
+ if (scalar(command('rev-parse', "$bottom_commit^@"))) {
+ push @merged_commit_ranges,
+ "$bottom_commit^..$top_commit";
+ } else {
+ push @merged_commit_ranges, "$top_commit";
+ }
if ( !defined $tip or $top > $tip ) {
$tip = $top;
@@ -3104,11 +3155,12 @@ sub _rev_list {
sub check_cherry_pick {
my $base = shift;
my $tip = shift;
+ my $parents = shift;
my @ranges = @_;
my %commits = map { $_ => 1 }
- _rev_list("--no-merges", $tip, "--not", $base);
+ _rev_list("--no-merges", $tip, "--not", $base, @$parents, "--");
for my $range ( @ranges ) {
- delete @commits{_rev_list($range)};
+ delete @commits{_rev_list($range, "--")};
}
for my $commit (keys %commits) {
if (has_no_changes($commit)) {
@@ -3169,6 +3221,24 @@ sub has_no_changes {
LIST_CACHE => 'FAULT',
;
}
+
+ sub unmemoize_svn_mergeinfo_functions {
+ return if not $memoized;
+ $memoized = 0;
+
+ Memoize::unmemoize 'lookup_svn_merge';
+ Memoize::unmemoize 'check_cherry_pick';
+ Memoize::unmemoize 'has_no_changes';
+ }
+
+ Memoize::memoize 'Git::SVN::repos_root';
+}
+
+END {
+ # Force cache writeout explicitly instead of waiting for
+ # global destruction to avoid segfault in Storable:
+ # http://rt.cpan.org/Public/Bug/Display.html?id=36087
+ unmemoize_svn_mergeinfo_functions();
}
sub parents_exclude {
@@ -3266,6 +3336,7 @@ sub find_extra_svn_parents {
# double check that there are no missing non-merge commits
my (@incomplete) = check_cherry_pick(
$merge_base, $merge_tip,
+ $parents,
@$ranges,
);
@@ -4034,6 +4105,7 @@ sub new {
$self->{absent_dir} = {};
$self->{absent_file} = {};
$self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
+ $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
$self;
}
@@ -4117,6 +4189,10 @@ sub open_directory {
sub git_path {
my ($self, $path) = @_;
+ if (my $enc = $self->{pathnameencoding}) {
+ require Encode;
+ Encode::from_to($path, 'UTF-8', $enc);
+ }
if ($self->{path_strip}) {
$path =~ s!$self->{path_strip}!! or
die "Failed to strip path '$path' ($self->{path_strip})\n";
@@ -4412,6 +4488,7 @@ sub new {
$self->{path_prefix} = length $self->{svn_path} ?
"$self->{svn_path}/" : '';
$self->{config} = $opts->{config};
+ $self->{mergeinfo} = $opts->{mergeinfo};
return $self;
}
@@ -4505,6 +4582,10 @@ sub split_path {
sub repo_path {
my ($self, $path) = @_;
+ if (my $enc = $self->{pathnameencoding}) {
+ require Encode;
+ Encode::from_to($path, $enc, 'UTF-8');
+ }
$self->{path_prefix}.(defined $path ? $path : '');
}
@@ -4717,6 +4798,11 @@ sub change_file_prop {
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
}
+sub change_dir_prop {
+ my ($self, $pbat, $pname, $pval) = @_;
+ $self->SUPER::change_dir_prop($pbat, $pname, $pval, $self->{pool});
+}
+
sub _chg_file_get_blob ($$$$) {
my ($self, $fbat, $m, $which) = @_;
my $fh = $::_repository->temp_acquire("git_blob_$which");
@@ -4810,6 +4896,11 @@ sub apply_diff {
fatal("Invalid change type: $f");
}
}
+
+ if (defined($self->{mergeinfo})) {
+ $self->change_dir_prop($self->{bat}{''}, "svn:mergeinfo",
+ $self->{mergeinfo});
+ }
$self->rmdirs if $_rmdir;
if (@$mods == 0) {
$self->abort_edit;
@@ -5678,7 +5769,7 @@ sub cmd_show_log {
my (@k, $c, $d, $stat);
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
while (<$log>) {
- if (/^${esc_color}commit -?($::sha1_short)/o) {
+ if (/^${esc_color}commit (?:- )?($::sha1_short)/o) {
my $cmt = $1;
if ($c && cmt_showable($c) && $c->{r} != $r_last) {
$r_last = $c->{r};
diff --git a/git-web--browse.sh b/git-web--browse.sh
index dbded76..e9de241 100755
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
@@ -31,154 +31,161 @@ valid_custom_tool()
valid_tool() {
case "$1" in
- firefox | iceweasel | chrome | chromium | konqueror | w3m | links | lynx | dillo | open | start)
- ;; # happy
- *)
- valid_custom_tool "$1" || return 1
- ;;
+ firefox | iceweasel | seamonkey | iceape | \
+ chrome | google-chrome | chromium | chromium-browser |\
+ konqueror | opera | w3m | elinks | links | lynx | dillo | open | start)
+ ;; # happy
+ *)
+ valid_custom_tool "$1" || return 1
+ ;;
esac
}
init_browser_path() {
browser_path=$(git config "browser.$1.path")
- test -z "$browser_path" && browser_path="$1"
+ if test -z "$browser_path" &&
+ test "$1" = chromium &&
+ type chromium-browser >/dev/null 2>&1
+ then
+ browser_path=chromium-browser
+ fi
+ : ${browser_path:="$1"}
}
while test $# != 0
do
- case "$1" in
+ case "$1" in
-b|--browser*|-t|--tool*)
- case "$#,$1" in
+ case "$#,$1" in
*,*=*)
- browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- ;;
+ browser=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
1,*)
- usage ;;
+ usage ;;
*)
- browser="$2"
- shift ;;
- esac
- ;;
+ browser="$2"
+ shift ;;
+ esac
+ ;;
-c|--config*)
- case "$#,$1" in
+ case "$#,$1" in
*,*=*)
- conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- ;;
+ conf=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
1,*)
- usage ;;
+ usage ;;
*)
- conf="$2"
- shift ;;
- esac
- ;;
+ conf="$2"
+ shift ;;
+ esac
+ ;;
--)
- break
- ;;
+ break
+ ;;
-*)
- usage
- ;;
+ usage
+ ;;
*)
- break
- ;;
- esac
- shift
+ break
+ ;;
+ esac
+ shift
done
test $# = 0 && usage
if test -z "$browser"
then
- for opt in "$conf" "web.browser"
- do
- test -z "$opt" && continue
- browser="`git config $opt`"
- test -z "$browser" || break
- done
- if test -n "$browser" && ! valid_tool "$browser"; then
- echo >&2 "git config option $opt set to unknown browser: $browser"
- echo >&2 "Resetting to default..."
- unset browser
- fi
+ for opt in "$conf" "web.browser"
+ do
+ test -z "$opt" && continue
+ browser="`git config $opt`"
+ test -z "$browser" || break
+ done
+ if test -n "$browser" && ! valid_tool "$browser"; then
+ echo >&2 "git config option $opt set to unknown browser: $browser"
+ echo >&2 "Resetting to default..."
+ unset browser
+ fi
fi
if test -z "$browser" ; then
- if test -n "$DISPLAY"; then
- browser_candidates="firefox iceweasel chrome chromium konqueror w3m links lynx dillo"
- if test "$KDE_FULL_SESSION" = "true"; then
- browser_candidates="konqueror $browser_candidates"
+ if test -n "$DISPLAY"; then
+ browser_candidates="firefox iceweasel google-chrome chrome chromium chromium-browser konqueror opera seamonkey iceape w3m elinks links lynx dillo"
+ if test "$KDE_FULL_SESSION" = "true"; then
+ browser_candidates="konqueror $browser_candidates"
+ fi
+ else
+ browser_candidates="w3m elinks links lynx"
fi
- else
- browser_candidates="w3m links lynx"
- fi
- # SECURITYSESSIONID indicates an OS X GUI login session
- if test -n "$SECURITYSESSIONID" \
- -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
- browser_candidates="open $browser_candidates"
- fi
- # /bin/start indicates MinGW
- if test -x /bin/start; then
- browser_candidates="start $browser_candidates"
- fi
-
- for i in $browser_candidates; do
- init_browser_path $i
- if type "$browser_path" > /dev/null 2>&1; then
- browser=$i
- break
+ # SECURITYSESSIONID indicates an OS X GUI login session
+ if test -n "$SECURITYSESSIONID" \
+ -o "$TERM_PROGRAM" = "Apple_Terminal" ; then
+ browser_candidates="open $browser_candidates"
fi
- done
- test -z "$browser" && die "No known browser available."
+ # /bin/start indicates MinGW
+ if test -x /bin/start; then
+ browser_candidates="start $browser_candidates"
+ fi
+
+ for i in $browser_candidates; do
+ init_browser_path $i
+ if type "$browser_path" > /dev/null 2>&1; then
+ browser=$i
+ break
+ fi
+ done
+ test -z "$browser" && die "No known browser available."
else
- valid_tool "$browser" || die "Unknown browser '$browser'."
+ valid_tool "$browser" || die "Unknown browser '$browser'."
- init_browser_path "$browser"
+ init_browser_path "$browser"
- if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
- die "The browser $browser is not available as '$browser_path'."
- fi
+ if test -z "$browser_cmd" && ! type "$browser_path" > /dev/null 2>&1; then
+ die "The browser $browser is not available as '$browser_path'."
+ fi
fi
case "$browser" in
- firefox|iceweasel)
+firefox|iceweasel|seamonkey|iceape)
# Check version because firefox < 2.0 does not support "-new-tab".
vers=$(expr "$($browser_path -version)" : '.* \([0-9][0-9]*\)\..*')
NEWTAB='-new-tab'
test "$vers" -lt 2 && NEWTAB=''
"$browser_path" $NEWTAB "$@" &
;;
- chrome|chromium)
- # Actual command for chromium is chromium-browser.
+google-chrome|chrome|chromium|chromium-browser)
# No need to specify newTab. It's default in chromium
eval "$browser_path" "$@" &
;;
- konqueror)
+konqueror)
case "$(basename "$browser_path")" in
- konqueror)
+ konqueror)
# It's simpler to use kfmclient to open a new tab in konqueror.
browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
eval "$browser_path" newTab "$@"
;;
- kfmclient)
+ kfmclient)
eval "$browser_path" newTab "$@"
;;
- *)
+ *)
"$browser_path" "$@" &
;;
esac
;;
- w3m|links|lynx|open)
+w3m|elinks|links|lynx|open)
eval "$browser_path" "$@"
;;
- start)
- exec "$browser_path" '"web-browse"' "$@"
- ;;
- dillo)
+start)
+ exec "$browser_path" '"web-browse"' "$@"
+ ;;
+opera|dillo)
"$browser_path" "$@" &
;;
- *)
+*)
if test -n "$browser_cmd"; then
- ( eval $browser_cmd "$@" )
+ ( eval $browser_cmd "$@" )
fi
;;
esac
diff --git a/git.c b/git.c
index 265fa09..8828c18 100644
--- a/git.c
+++ b/git.c
@@ -1,30 +1,40 @@
#include "builtin.h"
-#include "exec_cmd.h"
#include "cache.h"
+#include "exec_cmd.h"
+#include "help.h"
#include "quote.h"
#include "run-command.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
+ "git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
" [-p|--paginate|--no-pager] [--no-replace-objects]\n"
- " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n"
- " [-c name=value\n"
- " [--help] COMMAND [ARGS]";
+ " [--bare] [--git-dir=<path>] [--work-tree=<path>]\n"
+ " [-c name=value] [--help]\n"
+ " <command> [<args>]";
const char git_more_info_string[] =
- "See 'git help COMMAND' for more information on a specific command.";
+ "See 'git help <command>' for more information on a specific command.";
+static struct startup_info git_startup_info;
static int use_pager = -1;
struct pager_config {
const char *cmd;
- int val;
+ int want;
+ char *value;
};
static int pager_command_config(const char *var, const char *value, void *data)
{
struct pager_config *c = data;
- if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd))
- c->val = git_config_bool(var, value);
+ if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) {
+ int b = git_config_maybe_bool(var, value);
+ if (b >= 0)
+ c->want = b;
+ else {
+ c->want = 1;
+ c->value = xstrdup(value);
+ }
+ }
return 0;
}
@@ -33,9 +43,12 @@ int check_pager_config(const char *cmd)
{
struct pager_config c;
c.cmd = cmd;
- c.val = -1;
+ c.want = -1;
+ c.value = NULL;
git_config(pager_command_config, &c);
- return c.val;
+ if (c.value)
+ pager_program = c.value;
+ return c.want;
}
static void commit_pager_choice(void) {
@@ -53,10 +66,7 @@ static void commit_pager_choice(void) {
static int handle_options(const char ***argv, int *argc, int *envchanged)
{
- int handled = 0;
-
- if (!getenv("GIT_ASKPASS") && getenv("SSH_ASKPASS"))
- setenv("GIT_ASKPASS", getenv("SSH_ASKPASS"), 1);
+ const char **orig_argv = *argv;
while (*argc > 0) {
const char *cmd = (*argv)[0];
@@ -85,6 +95,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
} else if (!strcmp(cmd, "--html-path")) {
puts(system_path(GIT_HTML_PATH));
exit(0);
+ } else if (!strcmp(cmd, "--man-path")) {
+ puts(system_path(GIT_MAN_PATH));
+ exit(0);
+ } else if (!strcmp(cmd, "--info-path")) {
+ puts(system_path(GIT_INFO_PATH));
+ exit(0);
} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
use_pager = 1;
} else if (!strcmp(cmd, "--no-pager")) {
@@ -106,7 +122,6 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
*envchanged = 1;
(*argv)++;
(*argc)--;
- handled++;
} else if (!prefixcmp(cmd, "--git-dir=")) {
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
if (envchanged)
@@ -136,7 +151,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
fprintf(stderr, "-c expects a configuration string\n" );
usage(git_usage_string);
}
- git_config_parse_parameter((*argv)[1]);
+ git_config_push_parameter((*argv)[1]);
(*argv)++;
(*argc)--;
} else {
@@ -146,9 +161,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
(*argv)++;
(*argc)--;
- handled++;
}
- return handled;
+ return (*argv) - orig_argv;
}
static int handle_alias(int *argcp, const char ***argv)
@@ -167,27 +181,29 @@ static int handle_alias(int *argcp, const char ***argv)
alias_string = alias_lookup(alias_command);
if (alias_string) {
if (alias_string[0] == '!') {
- if (*argcp > 1) {
- struct strbuf buf;
-
- strbuf_init(&buf, PATH_MAX);
- strbuf_addstr(&buf, alias_string);
- sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
- free(alias_string);
- alias_string = buf.buf;
- }
- trace_printf("trace: alias to shell cmd: %s => %s\n",
- alias_command, alias_string + 1);
- ret = system(alias_string + 1);
- if (ret >= 0 && WIFEXITED(ret) &&
- WEXITSTATUS(ret) != 127)
- exit(WEXITSTATUS(ret));
- die("Failed to run '%s' when expanding alias '%s'",
- alias_string + 1, alias_command);
+ const char **alias_argv;
+ int argc = *argcp, i;
+
+ commit_pager_choice();
+
+ /* build alias_argv */
+ alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
+ alias_argv[0] = alias_string + 1;
+ for (i = 1; i < argc; ++i)
+ alias_argv[i] = (*argv)[i];
+ alias_argv[argc] = NULL;
+
+ ret = run_command_v_opt(alias_argv, RUN_USING_SHELL);
+ if (ret >= 0) /* normal exit */
+ exit(ret);
+
+ die_errno("While expanding alias '%s': '%s'",
+ alias_command, alias_string + 1);
}
count = split_cmdline(alias_string, &new_argv);
if (count < 0)
- die("Bad alias.%s string", alias_command);
+ die("Bad alias.%s string: %s", alias_command,
+ split_cmdline_strerror(count));
option_count = handle_options(&new_argv, &count, &envchanged);
if (envchanged)
die("alias '%s' changes environment variables\n"
@@ -228,13 +244,14 @@ static int handle_alias(int *argcp, const char ***argv)
const char git_version_string[] = GIT_VERSION;
-#define RUN_SETUP (1<<0)
-#define USE_PAGER (1<<1)
+#define RUN_SETUP (1<<0)
+#define RUN_SETUP_GENTLY (1<<1)
+#define USE_PAGER (1<<2)
/*
* require working tree to be present -- anything uses this needs
* RUN_SETUP for reading from the configuration file.
*/
-#define NEED_WORK_TREE (1<<2)
+#define NEED_WORK_TREE (1<<3)
struct cmd_struct {
const char *cmd;
@@ -253,11 +270,19 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
if (!help) {
if (p->option & RUN_SETUP)
prefix = setup_git_directory();
+ if (p->option & RUN_SETUP_GENTLY) {
+ int nongit_ok;
+ prefix = setup_git_directory_gently(&nongit_ok);
+ }
- if (use_pager == -1 && p->option & RUN_SETUP)
+ if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY))
use_pager = check_pager_config(p->cmd);
if (use_pager == -1 && p->option & USE_PAGER)
use_pager = 1;
+
+ if ((p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) &&
+ startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */
+ trace_repo_setup(prefix);
}
commit_pager_choice();
@@ -292,27 +317,26 @@ static void handle_internal_command(int argc, const char **argv)
const char *cmd = argv[0];
static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
- { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP },
- { "apply", cmd_apply },
+ { "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive },
{ "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP },
- { "bundle", cmd_bundle },
+ { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
{ "cat-file", cmd_cat_file, RUN_SETUP },
+ { "check-attr", cmd_check_attr, RUN_SETUP },
+ { "check-ref-format", cmd_check_ref_format },
{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
- { "check-ref-format", cmd_check_ref_format },
- { "check-attr", cmd_check_attr, RUN_SETUP },
{ "cherry", cmd_cherry, RUN_SETUP },
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
- { "clone", cmd_clone },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
+ { "clone", cmd_clone },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
- { "config", cmd_config },
+ { "config", cmd_config, RUN_SETUP_GENTLY },
{ "count-objects", cmd_count_objects, RUN_SETUP },
{ "describe", cmd_describe, RUN_SETUP },
{ "diff", cmd_diff },
@@ -329,21 +353,21 @@ static void handle_internal_command(int argc, const char **argv)
{ "fsck-objects", cmd_fsck, RUN_SETUP },
{ "gc", cmd_gc, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
- { "grep", cmd_grep },
+ { "grep", cmd_grep, RUN_SETUP_GENTLY },
{ "hash-object", cmd_hash_object },
{ "help", cmd_help },
- { "index-pack", cmd_index_pack },
+ { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
{ "init", cmd_init_db },
{ "init-db", cmd_init_db },
- { "log", cmd_log, RUN_SETUP | USE_PAGER },
+ { "log", cmd_log, RUN_SETUP },
{ "ls-files", cmd_ls_files, RUN_SETUP },
+ { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
- { "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
- { "merge-file", cmd_merge_file },
+ { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
{ "merge-index", cmd_merge_index, RUN_SETUP },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
{ "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
@@ -358,8 +382,9 @@ static void handle_internal_command(int argc, const char **argv)
{ "notes", cmd_notes, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+ { "pack-refs", cmd_pack_refs, RUN_SETUP },
{ "patch-id", cmd_patch_id },
- { "peek-remote", cmd_ls_remote },
+ { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
@@ -368,8 +393,10 @@ static void handle_internal_command(int argc, const char **argv)
{ "receive-pack", cmd_receive_pack },
{ "reflog", cmd_reflog, RUN_SETUP },
{ "remote", cmd_remote, RUN_SETUP },
+ { "remote-ext", cmd_remote_ext },
+ { "remote-fd", cmd_remote_fd },
{ "replace", cmd_replace, RUN_SETUP },
- { "repo-config", cmd_config },
+ { "repo-config", cmd_repo_config, RUN_SETUP_GENTLY },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
@@ -377,9 +404,11 @@ static void handle_internal_command(int argc, const char **argv)
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
{ "rm", cmd_rm, RUN_SETUP },
{ "send-pack", cmd_send_pack, RUN_SETUP },
- { "shortlog", cmd_shortlog, USE_PAGER },
+ { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
+ { "show", cmd_show, RUN_SETUP },
{ "show-branch", cmd_show_branch, RUN_SETUP },
- { "show", cmd_show, RUN_SETUP | USE_PAGER },
+ { "show-ref", cmd_show_ref, RUN_SETUP },
+ { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
{ "stripspace", cmd_stripspace },
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
@@ -391,14 +420,12 @@ static void handle_internal_command(int argc, const char **argv)
{ "update-ref", cmd_update_ref, RUN_SETUP },
{ "update-server-info", cmd_update_server_info, RUN_SETUP },
{ "upload-archive", cmd_upload_archive },
- { "var", cmd_var },
+ { "var", cmd_var, RUN_SETUP_GENTLY },
+ { "verify-pack", cmd_verify_pack },
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
{ "version", cmd_version },
- { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
+ { "whatchanged", cmd_whatchanged, RUN_SETUP },
{ "write-tree", cmd_write_tree, RUN_SETUP },
- { "verify-pack", cmd_verify_pack },
- { "show-ref", cmd_show_ref, RUN_SETUP },
- { "pack-refs", cmd_pack_refs, RUN_SETUP },
};
int i;
static const char ext[] = STRIP_EXTENSION;
@@ -432,6 +459,8 @@ static void execv_dashed_external(const char **argv)
const char *tmp;
int status;
+ commit_pager_choice();
+
strbuf_addf(&cmd, "git-%s", argv[0]);
/*
@@ -486,6 +515,8 @@ int main(int argc, const char **argv)
{
const char *cmd;
+ startup_info = &git_startup_info;
+
cmd = git_extract_argv0_path(argv[0]);
if (!cmd)
cmd = "git-help";
@@ -511,12 +542,12 @@ int main(int argc, const char **argv)
argv++;
argc--;
handle_options(&argv, &argc, NULL);
- commit_pager_choice();
if (argc > 0) {
if (!prefixcmp(argv[0], "--"))
argv[0] += 2;
} else {
/* The user didn't specify a command; give them help */
+ commit_pager_choice();
printf("usage: %s\n\n", git_usage_string);
list_common_cmds_help();
printf("\n%s\n", git_more_info_string);
diff --git a/git_remote_helpers/setup.cfg b/git_remote_helpers/setup.cfg
new file mode 100644
index 0000000..4bff887
--- /dev/null
+++ b/git_remote_helpers/setup.cfg
@@ -0,0 +1,3 @@
+[build]
+build_purelib = build/lib
+build_platlib = build/lib
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 1b0e09a..4cde0c4 100644..100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -131,6 +131,7 @@ proc unmerged_files {files} {
proc parseviewargs {n arglist} {
global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
+ global worddiff git_version
set vdatemode($n) 0
set vmergeonly($n) 0
@@ -168,7 +169,7 @@ proc parseviewargs {n arglist} {
lappend diffargs $arg
}
"--raw" - "--patch-with-raw" - "--patch-with-stat" -
- "--name-only" - "--name-status" - "--color" - "--color-words" -
+ "--name-only" - "--name-status" - "--color" -
"--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
"--cc" - "-z" - "--header" - "--parents" - "--boundary" -
"--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
@@ -177,6 +178,18 @@ proc parseviewargs {n arglist} {
# These cause our parsing of git log's output to fail, or else
# they're options we want to set ourselves, so ignore them.
}
+ "--color-words*" - "--word-diff=color" {
+ # These trigger a word diff in the console interface,
+ # so help the user by enabling our own support
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ set worddiff [mc "Color words"]
+ }
+ }
+ "--word-diff*" {
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ set worddiff [mc "Markup words"]
+ }
+ }
"--stat=*" - "--numstat" - "--shortstat" - "--summary" -
"--check" - "--exit-code" - "--quiet" - "--topo-order" -
"--full-history" - "--dense" - "--sparse" -
@@ -313,6 +326,7 @@ proc start_rev_list {view} {
global viewactive viewinstances vmergeonly
global mainheadid viewmainheadid viewmainheadid_orig
global vcanopt vflags vrevs vorigargs
+ global show_notes
set startmsecs [clock clicks -milliseconds]
set commitidx($view) 0
@@ -361,8 +375,8 @@ proc start_rev_list {view} {
}
if {[catch {
- set fd [open [concat | git log --no-color -z --pretty=raw --parents \
- --boundary $args "--" $files] r]
+ set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+ --parents --boundary $args "--" $files] r]
} err]} {
error_popup "[mc "Error executing git log:"] $err"
return 0
@@ -456,6 +470,7 @@ proc updatecommits {} {
global mainheadid viewmainheadid viewmainheadid_orig pending_select
global isworktree
global varcid vposids vnegids vflags vrevs
+ global show_notes
set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
rereadrefs
@@ -508,8 +523,8 @@ proc updatecommits {} {
set args $vorigargs($view)
}
if {[catch {
- set fd [open [concat | git log --no-color -z --pretty=raw --parents \
- --boundary $args "--" $vfilelimit($view)] r]
+ set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+ --parents --boundary $args "--" $vfilelimit($view)] r]
} err]} {
error_popup "[mc "Error executing git log:"] $err"
return
@@ -1970,6 +1985,8 @@ proc makewindow {} {
global fprogitem fprogcoord lastprogupdate progupdatepending
global rprogitem rprogcoord rownumsel numcommits
global have_tk85 use_ttk NS
+ global git_version
+ global worddiff
# The "mc" arguments here are purely so that xgettext
# sees the following string as needing to be translated
@@ -2243,6 +2260,15 @@ proc makewindow {} {
${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
-command changeignorespace -variable ignorespace
pack .bleft.mid.ignspace -side left -padx 5
+
+ set worddiff [mc "Line diff"]
+ if {[package vcompare $git_version "1.7.2"] >= 0} {
+ makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
+ [mc "Markup words"] [mc "Color words"]
+ trace add variable worddiff write changeworddiff
+ pack .bleft.mid.worddiff -side left -padx 5
+ }
+
set ctext .bleft.bottom.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
-state disabled -font textfont \
@@ -2451,6 +2477,7 @@ proc makewindow {} {
global ctxbut
bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
+ bind $ctext <Button-1> {focus %W}
set maincursor [. cget -cursor]
set textcursor [$ctext cget -cursor]
@@ -2625,7 +2652,7 @@ proc savestuff {w} {
global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
global cmitmode wrapcomment datetimeformat limitdiffs
global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
- global autoselect extdifftool perfile_attrs markbgcolor use_ttk
+ global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
global hideremotes want_ttk
if {$stuffsaved} return
@@ -2646,6 +2673,7 @@ proc savestuff {w} {
puts $f [list set cmitmode $cmitmode]
puts $f [list set wrapcomment $wrapcomment]
puts $f [list set autoselect $autoselect]
+ puts $f [list set autosellen $autosellen]
puts $f [list set showneartags $showneartags]
puts $f [list set hideremotes $hideremotes]
puts $f [list set showlocalchanges $showlocalchanges]
@@ -6273,6 +6301,7 @@ proc drawtags {id x xt y1} {
-width $lthickness -fill black -tags tag.$id]
$canv lower $t
foreach tag $marks x $xvals wid $wvals {
+ set tag_quoted [string map {% %%} $tag]
set xl [expr {$x + $delta}]
set xr [expr {$x + $delta + $wid + $lthickness}]
set font mainfont
@@ -6281,7 +6310,7 @@ proc drawtags {id x xt y1} {
set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
$xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
-width 1 -outline black -fill yellow -tags tag.$id]
- $canv bind $t <1> [list showtag $tag 1]
+ $canv bind $t <1> [list showtag $tag_quoted 1]
set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
} else {
# draw a head or other ref
@@ -6308,9 +6337,9 @@ proc drawtags {id x xt y1} {
set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
-font $font -tags [list tag.$id text]]
if {$ntags >= 0} {
- $canv bind $t <1> [list showtag $tag 1]
+ $canv bind $t <1> [list showtag $tag_quoted 1]
} elseif {$nheads >= 0} {
- $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
+ $canv bind $t $ctxbut [list headmenu %X %Y $id $tag_quoted]
}
}
return $xt
@@ -6869,7 +6898,7 @@ proc selectline {l isnew {desired_loc {}}} {
global mergemax numcommits pending_select
global cmitmode showneartags allcommits
global targetrow targetid lastscrollrows
- global autoselect jump_to_here
+ global autoselect autosellen jump_to_here
catch {unset pending_select}
$canv delete hover
@@ -6931,7 +6960,7 @@ proc selectline {l isnew {desired_loc {}}} {
$sha1entry delete 0 end
$sha1entry insert 0 $id
if {$autoselect} {
- $sha1entry selection range 0 end
+ $sha1entry selection range 0 $autosellen
}
rhighlight_sel $id
@@ -7301,6 +7330,7 @@ proc getblobline {bf id} {
[lindex [split $commentend .] 0]}]
mark_ctext_line $lnum
}
+ $ctext config -state disabled
return 0
}
$ctext config -state disabled
@@ -7502,11 +7532,16 @@ proc changeignorespace {} {
reselectline
}
+proc changeworddiff {name ix op} {
+ reselectline
+}
+
proc getblobdiffs {ids} {
global blobdifffd diffids env
global diffinhdr treediffs
global diffcontext
global ignorespace
+ global worddiff
global limitdiffs vfilelimit curview
global diffencoding targetline diffnparents
global git_version currdiffsubmod
@@ -7523,6 +7558,9 @@ proc getblobdiffs {ids} {
if {$ignorespace} {
append cmd " -w"
}
+ if {$worddiff ne [mc "Line diff"]} {
+ append cmd " --word-diff=porcelain"
+ }
if {$limitdiffs && $vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)]
}
@@ -7608,6 +7646,7 @@ proc getblobdiffline {bdf ids} {
global ctext_file_names ctext_file_lines
global diffinhdr treediffs mergemax diffnparents
global diffencoding jump_to_here targetline diffline currdiffsubmod
+ global worddiff
set nr 0
$ctext conf -state normal
@@ -7747,15 +7786,28 @@ proc getblobdiffline {bdf ids} {
# parse the prefix - one ' ', '-' or '+' for each parent
set prefix [string range $line 0 [expr {$diffnparents - 1}]]
set tag [expr {$diffnparents > 1? "m": "d"}]
+ set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
+ set words_pre_markup ""
+ set words_post_markup ""
if {[string trim $prefix " -+"] eq {}} {
# prefix only has " ", "-" and "+" in it: normal diff line
set num [string first "-" $prefix]
+ if {$dowords} {
+ set line [string range $line 1 end]
+ }
if {$num >= 0} {
# removed line, first parent with line is $num
if {$num >= $mergemax} {
set num "max"
}
- $ctext insert end "$line\n" $tag$num
+ if {$dowords && $worddiff eq [mc "Markup words"]} {
+ $ctext insert end "\[-$line-\]" $tag$num
+ } else {
+ $ctext insert end "$line" $tag$num
+ }
+ if {!$dowords} {
+ $ctext insert end "\n" $tag$num
+ }
} else {
set tags {}
if {[string first "+" $prefix] >= 0} {
@@ -7770,6 +7822,8 @@ proc getblobdiffline {bdf ids} {
lappend tags m$num
}
}
+ set words_pre_markup "{+"
+ set words_post_markup "+}"
}
if {$targetline ne {}} {
if {$diffline == $targetline} {
@@ -7779,8 +7833,17 @@ proc getblobdiffline {bdf ids} {
incr diffline
}
}
- $ctext insert end "$line\n" $tags
+ if {$dowords && $worddiff eq [mc "Markup words"]} {
+ $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
+ } else {
+ $ctext insert end "$line" $tags
+ }
+ if {!$dowords} {
+ $ctext insert end "\n" $tags
+ }
}
+ } elseif {$dowords && $prefix eq "~"} {
+ $ctext insert end "\n" {}
} else {
# "\ No newline at end of file",
# or something else we don't recognize
@@ -9002,7 +9065,7 @@ proc cherrypick {} {
to file '%s'.\nPlease commit, reset or stash\
your changes and try again." $fname]
} elseif {[regexp -line \
- {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
+ {^(CONFLICT \(.*\):|Automatic cherry-pick failed|error: could not apply)} \
$err]} {
if {[confirm_popup [mc "Cherry-pick failed because of merge\
conflict.\nDo you wish to run git citool to\
@@ -10695,7 +10758,7 @@ proc doprefs {} {
global maxwidth maxgraphpct use_ttk NS
global oldprefs prefstop showneartags showlocalchanges
global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
- global tabstop limitdiffs autoselect extdifftool perfile_attrs
+ global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
global hideremotes want_ttk have_ttk
set top .gitkprefs
@@ -10723,9 +10786,10 @@ proc doprefs {} {
${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
-variable showlocalchanges
grid x $top.showlocal -sticky w
- ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
+ ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1 (length)"] \
-variable autoselect
- grid x $top.autoselect -sticky w
+ spinbox $top.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
+ grid x $top.autoselect $top.autosellen -sticky w
${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
-variable hideremotes
grid x $top.hideremotes -sticky w
@@ -11367,6 +11431,7 @@ set showlocalchanges 1
set limitdiffs 1
set datetimeformat "%Y-%m-%d %H:%M:%S"
set autoselect 1
+set autosellen 40
set perfile_attrs 0
set want_ttk 1
@@ -11391,6 +11456,7 @@ if {[tk windowingsystem] eq "win32"} {
set diffcolors {red "#00a000" blue}
set diffcontext 3
set ignorespace 0
+set worddiff ""
set markbgcolor "#e0e0ff"
set circlecolors {white blue gray blue blue}
@@ -11519,7 +11585,12 @@ if {![info exists have_ttk]} {
set use_ttk [expr {$have_ttk && $want_ttk}]
set NS [expr {$use_ttk ? "ttk" : ""}]
-set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
+regexp {^git version ([\d.]*\d)} [exec git version] _ git_version
+
+set show_notes {}
+if {[package vcompare $git_version "1.6.6.2"] >= 0} {
+ set show_notes "--show-notes"
+}
set runq {}
set history {}
diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po
new file mode 100644
index 0000000..1486e32
--- /dev/null
+++ b/gitk-git/po/pt_br.po
@@ -0,0 +1,1277 @@
+# Translation of gitk to Brazilian Portuguese.
+# Copyright (C) 2007 Paul Mackerras, et al.
+# This file is distributed under the same license as the gitk package.
+#
+# Alexandre Erwin Ittner <alexandre@ittner.com.br>, 2010.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"PO-Revision-Date: 2010-12-06 23:39-0200\n"
+"Last-Translator: Alexandre Erwin Ittner <alexandre@ittner.com.br>\n"
+"Language-Team: Brazilian Portuguese <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:115
+msgid "Couldn't get list of unmerged files:"
+msgstr "Não foi possível obter a lista dos arquivos não mesclados:"
+
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Erro ao interpretar revisões:"
+
+#: gitk:330
+msgid "Error executing --argscmd command:"
+msgstr "Erro ao executar o comando--argscmd:"
+
+#: gitk:343
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados."
+
+#: gitk:346
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-"
+"mesclados dentro dos limites."
+
+#: gitk:368 gitk:516
+msgid "Error executing git log:"
+msgstr "Erro ao executar git log:"
+
+#: gitk:386 gitk:532
+msgid "Reading"
+msgstr "Lendo"
+
+#: gitk:446 gitk:4271
+msgid "Reading commits..."
+msgstr "Lendo revisões..."
+
+#: gitk:449 gitk:1580 gitk:4274
+msgid "No commits selected"
+msgstr "Nenhuma revisão foi selecionada"
+
+#: gitk:1456
+msgid "Can't parse git log output:"
+msgstr "Não foi possível interpretar a saída do \"git log\":"
+
+#: gitk:1676
+msgid "No commit information available"
+msgstr "Não há informações disponíveis sobre a revisão"
+
+#: gitk:1818
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
+msgid "OK"
+msgstr "Ok"
+
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:1980
+msgid "Update"
+msgstr "Atualizar"
+
+#: gitk:1981
+msgid "Reload"
+msgstr "Recarregar"
+
+#: gitk:1982
+msgid "Reread references"
+msgstr "Ler as referências novamente"
+
+#: gitk:1983
+msgid "List references"
+msgstr "Listar referências"
+
+#: gitk:1985
+msgid "Start git gui"
+msgstr "Iniciar Git GUI"
+
+#: gitk:1987
+msgid "Quit"
+msgstr "Sair"
+
+#: gitk:1979
+msgid "File"
+msgstr "Arquivo"
+
+#: gitk:1991
+msgid "Preferences"
+msgstr "Preferências"
+
+#: gitk:1990
+msgid "Edit"
+msgstr "Editar"
+
+#: gitk:1995
+msgid "New view..."
+msgstr "Nova vista..."
+
+#: gitk:1996
+msgid "Edit view..."
+msgstr "Editar vista..."
+
+#: gitk:1997
+msgid "Delete view"
+msgstr "Apagar vista"
+
+#: gitk:1999
+msgid "All files"
+msgstr "Todos os arquivos"
+
+#: gitk:1994 gitk:3817
+msgid "View"
+msgstr "Exibir"
+
+#: gitk:2004 gitk:2014 gitk:2787
+msgid "About gitk"
+msgstr "Sobre o gitk"
+
+#: gitk:2005 gitk:2019
+msgid "Key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2003 gitk:2018
+msgid "Help"
+msgstr "Ajuda"
+
+#: gitk:2096 gitk:8132
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:2127
+msgid "Row"
+msgstr "Linha"
+
+#: gitk:2165
+msgid "Find"
+msgstr "Encontrar"
+
+#: gitk:2166
+msgid "next"
+msgstr "Próximo"
+
+#: gitk:2167
+msgid "prev"
+msgstr "Anterior"
+
+#: gitk:2168
+msgid "commit"
+msgstr "Revisão"
+
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
+msgid "containing:"
+msgstr "contendo:"
+
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
+msgid "touching paths:"
+msgstr "envolvendo os caminhos:"
+
+#: gitk:2175 gitk:4512
+msgid "adding/removing string:"
+msgstr "Adicionando/removendo texto:"
+
+#: gitk:2184 gitk:2186
+msgid "Exact"
+msgstr "Exatamente"
+
+#: gitk:2186 gitk:4587 gitk:6388
+msgid "IgnCase"
+msgstr "Ignorar maiúsculas/minúsculas"
+
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
+msgid "Regexp"
+msgstr "Expressão regular"
+
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
+msgid "All fields"
+msgstr "Todos os campos"
+
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
+msgid "Headline"
+msgstr "Assunto"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
+msgid "Comments"
+msgstr "Descrição da revisão"
+
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
+msgid "Committer"
+msgstr "Revisor"
+
+#: gitk:2221
+msgid "Search"
+msgstr "Buscar"
+
+#: gitk:2229
+msgid "Diff"
+msgstr "Diferenças"
+
+#: gitk:2231
+msgid "Old version"
+msgstr "Versão antiga"
+
+#: gitk:2233
+msgid "New version"
+msgstr "Versão nova"
+
+#: gitk:2235
+msgid "Lines of context"
+msgstr "Número de linhas de contexto"
+
+#: gitk:2245
+msgid "Ignore space change"
+msgstr "Ignorar mudanças de caixa"
+
+#: gitk:2304
+msgid "Patch"
+msgstr "Diferenças"
+
+#: gitk:2306
+msgid "Tree"
+msgstr "Árvore"
+
+#: gitk:2463 gitk:2480
+msgid "Diff this -> selected"
+msgstr "Comparar esta revisão com a selecionada"
+
+#: gitk:2464 gitk:2481
+msgid "Diff selected -> this"
+msgstr "Comparar a revisão selecionada com esta"
+
+#: gitk:2465 gitk:2482
+msgid "Make patch"
+msgstr "Criar patch"
+
+#: gitk:2466 gitk:8715
+msgid "Create tag"
+msgstr "Criar etiqueta"
+
+#: gitk:2467 gitk:8831
+msgid "Write commit to file"
+msgstr "Salvar revisão para um arquivo"
+
+#: gitk:2468 gitk:8888
+msgid "Create new branch"
+msgstr "Criar novo ramo"
+
+#: gitk:2469
+msgid "Cherry-pick this commit"
+msgstr "Fazer cherry-pick desta revisão"
+
+#: gitk:2470
+msgid "Reset HEAD branch to here"
+msgstr "Redefinir HEAD para cá"
+
+#: gitk:2471
+msgid "Mark this commit"
+msgstr "Marcar esta revisão"
+
+#: gitk:2472
+msgid "Return to mark"
+msgstr "Voltar à marca"
+
+#: gitk:2473
+msgid "Find descendant of this and mark"
+msgstr "Encontrar descendente e marcar"
+
+#: gitk:2474
+msgid "Compare with marked commit"
+msgstr "Comparar com a revisão marcada"
+
+#: gitk:2488
+msgid "Check out this branch"
+msgstr "Efetuar checkout deste ramo"
+
+#: gitk:2489
+msgid "Remove this branch"
+msgstr "Excluir este ramo"
+
+#: gitk:2496
+msgid "Highlight this too"
+msgstr "Marcar este também"
+
+#: gitk:2497
+msgid "Highlight this only"
+msgstr "Marcar apenas este"
+
+#: gitk:2498
+msgid "External diff"
+msgstr "Diff externo"
+
+#: gitk:2499
+msgid "Blame parent commit"
+msgstr "Anotar revisão anterior"
+
+#: gitk:2506
+msgid "Show origin of this line"
+msgstr "Exibir origem desta linha"
+
+#: gitk:2507
+msgid "Run git gui blame on this line"
+msgstr "Executar 'git blame' nesta linha"
+
+#: gitk:2789
+msgid "\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr "\n"
+"Gitk - um visualizador de revisões para o git \n"
+"\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
+"\n"
+"Uso e distribuição segundo os termos da Licença Pública Geral GNU"
+
+#: gitk:2797 gitk:2862 gitk:9253
+msgid "Close"
+msgstr "Fechar"
+
+#: gitk:2818
+msgid "Gitk key bindings"
+msgstr "Atalhos de teclado"
+
+#: gitk:2821
+msgid "Gitk key bindings:"
+msgstr "Atalhos de teclado:"
+
+#: gitk:2823
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSair"
+
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tFechar janela"
+
+#: gitk:2825
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tIr para a primeira revisão"
+
+#: gitk:2826
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tIr para a última revisão"
+
+#: gitk:2827
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tIr para uma revisão acima"
+
+#: gitk:2828
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tIr para uma revisão abaixo"
+
+#: gitk:2829
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tVoltar no histórico"
+
+#: gitk:2830
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tAvançar no histórico"
+
+#: gitk:2831
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tSubir uma página na lista de revisões"
+
+#: gitk:2832
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tDescer uma página na lista de revisões"
+
+#: gitk:2833
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tRolar para o início da lista de revisões"
+
+#: gitk:2834
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tRolar para o final da lista de revisões"
+
+#: gitk:2835
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tRolar uma linha acima na lista de revisões"
+
+#: gitk:2836
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tRolar uma linha abaixo na lista de revisões"
+
+#: gitk:2837
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tRolar uma página acima na lista de revisões"
+
+#: gitk:2838
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tRolar uma página abaixo na lista de revisões"
+
+#: gitk:2839
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\tProcurar próxima (revisões mas recentes)"
+
+#: gitk:2840
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tProcurar anterior (revisões mais antigas)"
+
+#: gitk:2841
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tRola alterações uma página acima"
+
+#: gitk:2842
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tRolar alterações uma página abaixo"
+
+#: gitk:2843
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tRolar alterações uma página abaixo"
+
+#: gitk:2844
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tRolar alterações 18 linhas acima"
+
+#: gitk:2845
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tRolar alterações 18 linhas abaixo"
+
+#: gitk:2846
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tProcurar"
+
+#: gitk:2847
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tIr para a próxima ocorrência"
+
+#: gitk:2848
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tIr para a próxima ocorrência"
+
+#: gitk:2849
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tPor foco na caixa de busca"
+
+#: gitk:2850
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tIr para a ocorrência anterior"
+
+#: gitk:2851
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tRolar alterações para o próximo arquivo"
+
+#: gitk:2852
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tProcurar a próxima ocorrência na lista de alterações"
+
+#: gitk:2853
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tProcurar ocorrência anterior na lista de alterações"
+
+#: gitk:2854
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar tamanho da fonte"
+
+#: gitk:2855
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAumentar tamanho da fonte"
+
+#: gitk:2856
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tReduzir tamanho da fonte"
+
+#: gitk:2857
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tReduzir tamanho da fonte"
+
+#: gitk:2858
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tAtualizar"
+
+#: gitk:3313 gitk:3322
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erro ao criar o diretório temporário %s:"
+
+#: gitk:3335
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erro ao ler \"%s\" de %s:"
+
+#: gitk:3398
+msgid "command failed:"
+msgstr "O comando falhou:"
+
+#: gitk:3547
+msgid "No such commit"
+msgstr "Revisão não encontrada"
+
+#: gitk:3561
+msgid "git gui blame: command failed:"
+msgstr "Comando 'git gui blame' falhou:"
+
+#: gitk:3592
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossível ler merge head: %s"
+
+#: gitk:3600
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erro ao ler o índice: %s"
+
+#: gitk:3625
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Não foi possível inciar o 'git blame': %s"
+
+#: gitk:3628 gitk:6419
+msgid "Searching"
+msgstr "Procurando"
+
+#: gitk:3660
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erro ao executar 'git blame': %s"
+
+#: gitk:3688
+#, tcl-format
+msgid "That line comes from commit %s, which is not in this view"
+msgstr "Esta linha vem da revisão %s, que não está nesta vista"
+
+#: gitk:3702
+msgid "External diff viewer failed:"
+msgstr "Erro do visualizador de alterações externo:"
+
+#: gitk:3820
+msgid "Gitk view definition"
+msgstr "Definir vista"
+
+#: gitk:3824
+msgid "Remember this view"
+msgstr "Lembrar esta vista"
+
+#: gitk:3825
+msgid "References (space separated list):"
+msgstr "Referências (separar a lista com um espaço):"
+
+#: gitk:3826
+msgid "Branches & tags:"
+msgstr "Ramos & etiquetas:"
+
+#: gitk:3827
+msgid "All refs"
+msgstr "Todas as referências"
+
+#: gitk:3828
+msgid "All (local) branches"
+msgstr "Todos os ramos locais"
+
+#: gitk:3829
+msgid "All tags"
+msgstr "Todas as etiquetas"
+
+#: gitk:3830
+msgid "All remote-tracking branches"
+msgstr "Todos os ramos de rastreio"
+
+#: gitk:3831
+msgid "Commit Info (regular expressions):"
+msgstr "Informações da revisão (expressões regulares):"
+
+#: gitk:3832
+msgid "Author:"
+msgstr "Autor:"
+
+#: gitk:3833
+msgid "Committer:"
+msgstr "Revisor:"
+
+#: gitk:3834
+msgid "Commit Message:"
+msgstr "Descrição da revisão:"
+
+#: gitk:3835
+msgid "Matches all Commit Info criteria"
+msgstr "Coincidir todos os critérios de informações da revisão"
+
+#: gitk:3836
+msgid "Changes to Files:"
+msgstr "Mudanças para os arquivos:"
+
+#: gitk:3837
+msgid "Fixed String"
+msgstr "Texto fixo"
+
+#: gitk:3838
+msgid "Regular Expression"
+msgstr "Expressão regular"
+
+#: gitk:3839
+msgid "Search string:"
+msgstr "Texto de busca"
+
+#: gitk:3840
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Datas de revisão (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3841
+msgid "Since:"
+msgstr "Desde:"
+
+#: gitk:3842
+msgid "Until:"
+msgstr "Até:"
+
+#: gitk:3843
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):"
+
+#: gitk:3844
+msgid "Number to show:"
+msgstr "Número para mostrar:"
+
+#: gitk:3845
+msgid "Number to skip:"
+msgstr "Número para ignorar:"
+
+#: gitk:3846
+msgid "Miscellaneous options:"
+msgstr "Opções diversas:"
+
+#: gitk:3847
+msgid "Strictly sort by date"
+msgstr "Ordenar estritamente pela data"
+
+#: gitk:3848
+msgid "Mark branch sides"
+msgstr "Marcar os dois lados do ramo"
+
+#: gitk:3849
+msgid "Limit to first parent"
+msgstr "Limitar ao primeiro antecessor"
+
+#: gitk:3850
+msgid "Simple history"
+msgstr "Histórico simplificado"
+
+#: gitk:3851
+msgid "Additional arguments to git log:"
+msgstr "Argumentos adicionais para o 'git log':"
+
+#: gitk:3852
+msgid "Enter files and directories to include, one per line:"
+msgstr "Arquivos e diretórios para incluir, um por linha"
+
+#: gitk:3853
+msgid "Command to generate more commits to include:"
+msgstr "Comando para gerar mais revisões para incluir:"
+
+#: gitk:3977
+msgid "Gitk: edit view"
+msgstr "Gitk: editar vista"
+
+#: gitk:3985
+msgid "-- criteria for selecting revisions"
+msgstr "-- critérios para selecionar revisões"
+
+#: gitk:3990
+msgid "View Name"
+msgstr "Nome da vista"
+
+#: gitk:4065
+msgid "Apply (F5)"
+msgstr "Aplicar (F5)"
+
+#: gitk:4103
+msgid "Error in commit selection arguments:"
+msgstr "Erro nos argumentos de seleção de revisões:"
+
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
+msgid "None"
+msgstr "Nenhum"
+
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
+msgid "Date"
+msgstr "Data"
+
+#: gitk:4604 gitk:6451
+msgid "CDate"
+msgstr "DataR"
+
+#: gitk:4753 gitk:4758
+msgid "Descendant"
+msgstr "Descendente de"
+
+#: gitk:4754
+msgid "Not descendant"
+msgstr "Não descendente de"
+
+#: gitk:4761 gitk:4766
+msgid "Ancestor"
+msgstr "Antecessor de"
+
+#: gitk:4762
+msgid "Not ancestor"
+msgstr "Não antecessor de"
+
+#: gitk:5052
+msgid "Local changes checked in to index but not committed"
+msgstr "Mudanças locais marcadas, porém não salvas"
+
+#: gitk:5088
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Mudanças locais não marcadas"
+
+#: gitk:6769
+msgid "many"
+msgstr "muitas"
+
+#: gitk:6952
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: gitk:6969 gitk:6975 gitk:8302
+msgid "Parent"
+msgstr "Antecessor"
+
+#: gitk:6980
+msgid "Child"
+msgstr "Descendente"
+
+#: gitk:6989
+msgid "Branch"
+msgstr "Ramo"
+
+#: gitk:6992
+msgid "Follows"
+msgstr "Segue"
+
+#: gitk:6995
+msgid "Precedes"
+msgstr "Precede"
+
+#: gitk:7532
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erro ao obter diferenças: %s"
+
+#: gitk:8130
+msgid "Goto:"
+msgstr "Ir para:"
+
+#: gitk:8151
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "O id SHA1 %s é ambíguo"
+
+#: gitk:8158
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Revisão %s desconhecida"
+
+#: gitk:8168
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "Id SHA1 %s desconhecido"
+
+#: gitk:8170
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A revisão %s não está na vista atual"
+
+#: gitk:8312
+msgid "Children"
+msgstr "Descendentes"
+
+#: gitk:8370
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Redefinir ramo %s para este ponto"
+
+#: gitk:8372
+msgid "Detached head: can't reset"
+msgstr "Detached head: impossível redefinir"
+
+#: gitk:8481 gitk:8487
+msgid "Skipping merge commit "
+msgstr "Saltando revisão de mesclagem"
+
+#: gitk:8496 gitk:8501
+msgid "Error getting patch ID for "
+msgstr "Erro ao obter patch ID para"
+
+#: gitk:8497 gitk:8502
+msgid " - stopping\n"
+msgstr "- parando\n"
+
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
+msgid "Commit "
+msgstr "Revisão"
+
+#: gitk:8511
+msgid ""
+" is the same patch as\n"
+" "
+msgstr ""
+"é o mesmo patch que\n"
+" "
+
+#: gitk:8519
+msgid ""
+" differs from\n"
+" "
+msgstr "difere de"
+
+#: gitk:8521
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Diferença de revisões:\n"
+"\n"
+
+#: gitk:8533 gitk:8542
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "possui %s descendentes - parando\n"
+
+#: gitk:8561
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Erro ao salvar revisão para o arquivo: %s"
+
+#: gitk:8567
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Erro ao comparar revisões: %s"
+
+#: gitk:8598
+msgid "Top"
+msgstr "Início"
+
+#: gitk:8599
+msgid "From"
+msgstr "De"
+
+#: gitk:8604
+msgid "To"
+msgstr "Para"
+
+#: gitk:8628
+msgid "Generate patch"
+msgstr "Gerar patch"
+
+#: gitk:8630
+msgid "From:"
+msgstr "De:"
+
+#: gitk:8639
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:8648
+msgid "Reverse"
+msgstr "Inverter"
+
+#: gitk:8650 gitk:8845
+msgid "Output file:"
+msgstr "Arquivo de saída:"
+
+#: gitk:8656
+msgid "Generate"
+msgstr "Gerar"
+
+#: gitk:8694
+msgid "Error creating patch:"
+msgstr "Erro ao criar patch:"
+
+#: gitk:8717 gitk:8833 gitk:8890
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8726
+msgid "Tag name:"
+msgstr "Nome da etiqueta:"
+
+#: gitk:8729
+msgid "Tag message is optional"
+msgstr "A descrição da etiqueta é opcional"
+
+#: gitk:8731
+msgid "Tag message:"
+msgstr "Descrição da etiqueta"
+
+#: gitk:8735 gitk:8899
+msgid "Create"
+msgstr "Criar"
+
+#: gitk:8753
+msgid "No tag name specified"
+msgstr "Nome da etiqueta não indicado"
+
+#: gitk:8757
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Etiqueta \"%s\" já existe"
+
+#: gitk:8767
+msgid "Error creating tag:"
+msgstr "Erro ao criar etiqueta:"
+
+#: gitk:8842
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:8850
+msgid "Write"
+msgstr "Exportar"
+
+#: gitk:8868
+msgid "Error writing commit:"
+msgstr "Erro ao exportar revisão"
+
+#: gitk:8895
+msgid "Name:"
+msgstr "Nome:"
+
+#: gitk:8918
+msgid "Please specify a name for the new branch"
+msgstr "Indique um nome para o novo ramo"
+
+#: gitk:8923
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "O ramo \"%s\" já existe. Sobrescrever?"
+
+#: gitk:8989
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?"
+
+#: gitk:8994
+msgid "Cherry-picking"
+msgstr "Cherry-picking"
+
+#: gitk:9003
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"O cherry-pick falhou porque o arquivo \"%s\" possui mudanças locais.\n"
+"Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente "
+"novamente."
+
+#: gitk:9009
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"O cherry-pick falhou porque houve um conflito na mesclagem.\n"
+"Executar o 'git citool' para resolvê-lo?"
+
+#: gitk:9025
+msgid "No changes committed"
+msgstr "Nenhuma revisão foi salva"
+
+#: gitk:9051
+msgid "Confirm reset"
+msgstr "Confirmar redefinição"
+
+#: gitk:9053
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Você realmente deseja redefinir o ramo %s para %s?"
+
+#: gitk:9055
+msgid "Reset type:"
+msgstr "Tipo de redefinição"
+
+#: gitk:9058
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: deixa a árvore de trabalho e o índice intocados"
+
+#: gitk:9061
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Misto: Deixa a árvore de trabalho intocada, redefine o índice"
+
+#: gitk:9064
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: Redefine a árvore de trabalho e o índice\n"
+"(descarta TODAS as mudanças locais)"
+
+#: gitk:9081
+msgid "Resetting"
+msgstr "Redefinindo"
+
+#: gitk:9141
+msgid "Checking out"
+msgstr "Abrindo"
+
+#: gitk:9194
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Impossível excluir o ramo atualmente aberto"
+
+#: gitk:9200
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"As revisões do ramo \"%s\" não existem em nenhum outro ramo.\n"
+"Você realmente deseja excluir ramo \"%s\"?"
+
+#: gitk:9231
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Referências: %s"
+
+#: gitk:9246
+msgid "Filter"
+msgstr "Filtro"
+
+#: gitk:9541
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erro ao ler a topologia das revisões; as informações dos ramos e etiquetas "
+"antecessoras/sucessoras estarão incompletas"
+
+#: gitk:10527
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: gitk:10527
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10576
+msgid "Gitk font chooser"
+msgstr "Selecionar fontes do Gitk"
+
+#: gitk:10593
+msgid "B"
+msgstr "B"
+
+#: gitk:10596
+msgid "I"
+msgstr "I"
+
+#: gitk:10714
+msgid "Gitk preferences"
+msgstr "Preferências do Gitk"
+
+#: gitk:10716
+msgid "Commit list display options"
+msgstr "Opções da lista de revisões"
+
+#: gitk:10719
+msgid "Maximum graph width (lines)"
+msgstr "Largura máxima do grafo (linhas)"
+
+#: gitk:10722
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Largura máxima do grafo (% do painel)"
+
+#: gitk:10725
+msgid "Show local changes"
+msgstr "Exibir mudanças locais"
+
+#: gitk:10728
+msgid "Auto-select SHA1"
+msgstr "Selecionar o SHA1 automaticamente"
+
+#: gitk:10731
+msgid "Hide remote refs"
+msgstr "Ocultar referências remotas"
+
+#: gitk:10735
+msgid "Diff display options"
+msgstr "Opções de exibição das alterações"
+
+#: gitk:10737
+msgid "Tab spacing"
+msgstr "Espaços por tabulação"
+
+#: gitk:10740
+msgid "Display nearby tags"
+msgstr "Exibir etiquetas próximas"
+
+#: gitk:10743
+msgid "Limit diffs to listed paths"
+msgstr "Limitar diferenças aos caminhos listados"
+
+#: gitk:10746
+msgid "Support per-file encodings"
+msgstr "Usar codificações distintas por arquivo"
+
+#: gitk:10752 gitk:10832
+msgid "External diff tool"
+msgstr "Ferramenta 'diff' externa"
+
+#: gitk:10753
+msgid "Choose..."
+msgstr "Selecionar..."
+
+#: gitk:10758
+msgid "General options"
+msgstr "Opções gerais"
+
+#: gitk:10761
+msgid "Use themed widgets"
+msgstr "Usar temas para as janelas"
+
+#: gitk:10763
+msgid "(change requires restart)"
+msgstr "(exige reinicialização)"
+
+#: gitk:10765
+msgid "(currently unavailable)"
+msgstr "(atualmente indisponível)"
+
+#: gitk:10769
+msgid "Colors: press to choose"
+msgstr "Cores: clique para escolher"
+
+#: gitk:10772
+msgid "Interface"
+msgstr "Interface"
+
+#: gitk:10773
+msgid "interface"
+msgstr "interface"
+
+#: gitk:10776
+msgid "Background"
+msgstr "Segundo plano"
+
+#: gitk:10777 gitk:10807
+msgid "background"
+msgstr "segundo plano"
+
+#: gitk:10780
+msgid "Foreground"
+msgstr "Primeiro plano"
+
+#: gitk:10781
+msgid "foreground"
+msgstr "primeiro plano"
+
+#: gitk:10784
+msgid "Diff: old lines"
+msgstr "Diff: linhas excluídas"
+
+#: gitk:10785
+msgid "diff old lines"
+msgstr "linhas excluídas"
+
+#: gitk:10789
+msgid "Diff: new lines"
+msgstr "Diff: linhas adicionadas"
+
+#: gitk:10790
+msgid "diff new lines"
+msgstr "linhas adicionadas"
+
+#: gitk:10794
+msgid "Diff: hunk header"
+msgstr "Diff: cabeçalho do bloco"
+
+#: gitk:10796
+msgid "diff hunk header"
+msgstr "cabeçalho do bloco"
+
+#: gitk:10800
+msgid "Marked line bg"
+msgstr "2º plano da linha marcada"
+
+#: gitk:10802
+msgid "marked line background"
+msgstr "segundo plano da linha marcada"
+
+#: gitk:10806
+msgid "Select bg"
+msgstr "2º plano da seleção"
+
+#: gitk:10810
+msgid "Fonts: press to choose"
+msgstr "Fontes: clique para escolher"
+
+#: gitk:10812
+msgid "Main font"
+msgstr "Fonte principal"
+
+#: gitk:10813
+msgid "Diff display font"
+msgstr "Fonte da lista de mudanças"
+
+#: gitk:10814
+msgid "User interface font"
+msgstr "Fonte da interface"
+
+#: gitk:10842
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: selecionar cor para %s"
+
+#: gitk:11445
+msgid "Cannot find a git repository here."
+msgstr "Não há nenhum repositório git aqui."
+
+#: gitk:11449
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Impossível encontrar o diretório git \"%s\"."
+
+#: gitk:11496
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr ""
+"O argumento \"%s\" é ambíguo (especifica tanto uma revisão e um nome de "
+"arquivo)"
+
+#: gitk:11508
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos incorretos para o gitk:"
+
+#: gitk:11604
+msgid "Command line"
+msgstr "Linha de comando"
diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po
index c3d0285..5987303 100644
--- a/gitk-git/po/ru.po
+++ b/gitk-git/po/ru.po
@@ -24,7 +24,7 @@ msgstr "Ошибка в идентификаторе версии:"
#: gitk:323
msgid "Error executing --argscmd command:"
-msgstr "Ошибка выполнения команды заданой --argscmd:"
+msgstr "Ошибка выполнения команды заданной --argscmd:"
#: gitk:336
msgid "No files selected: --merge specified but no files are unmerged."
@@ -37,7 +37,7 @@ msgid ""
"No files selected: --merge specified but no unmerged files are within file "
"limit."
msgstr ""
-"Файлы не выбраны: указан --merge, но в рамках указаного "
+"Файлы не выбраны: указан --merge, но в рамках указанного "
"ограничения на имена файлов нет ни одного "
"где эта операция должна быть завершена."
@@ -246,11 +246,11 @@ msgstr "Файлы"
#: gitk:2326 gitk:2339
msgid "Diff this -> selected"
-msgstr "Сравнить это состояние с выделеным"
+msgstr "Сравнить это состояние с выделенным"
#: gitk:2327 gitk:2340
msgid "Diff selected -> this"
-msgstr "Сравнить выделеное с этим состоянием"
+msgstr "Сравнить выделенное с этим состоянием"
#: gitk:2328 gitk:2341
msgid "Make patch"
@@ -440,11 +440,11 @@ msgstr "<%s-F>\t\tПоиск"
#: gitk:2666
#, tcl-format
msgid "<%s-G>\t\tMove to next find hit"
-msgstr "<%s-G>\t\tПерейти к следующему найденому состоянию"
+msgstr "<%s-G>\t\tПерейти к следующему найденному состоянию"
#: gitk:2667
msgid "<Return>\tMove to next find hit"
-msgstr "<Return>\tПерейти к следующему найденому состоянию"
+msgstr "<Return>\tПерейти к следующему найденному состоянию"
#: gitk:2668
msgid "/\t\tFocus the search box"
@@ -452,7 +452,7 @@ msgstr "/\t\tПерейти к полю поиска"
#: gitk:2669
msgid "?\t\tMove to previous find hit"
-msgstr "?\t\tПерейти к предыдущему найденому состоянию"
+msgstr "?\t\tПерейти к предыдущему найденному состоянию"
#: gitk:2670
msgid "f\t\tScroll diff view to next file"
@@ -466,7 +466,7 @@ msgstr "<%s-S>\t\tПродолжить поиск в списке изменен
#: gitk:2672
#, tcl-format
msgid "<%s-R>\t\tSearch for previous hit in diff view"
-msgstr "<%s-R>\t\tПерейти к предыдущему найденому тексту в списке изменений"
+msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений"
#: gitk:2673
#, tcl-format
@@ -855,7 +855,7 @@ msgstr "Лёгкий: оставить рабочий каталог и инде
#: gitk:8472
msgid "Mixed: Leave working tree untouched, reset index"
msgstr ""
-"Смешаный: оставить рабочий каталог неизменным, установить индекс"
+"Смешанный: оставить рабочий каталог неизменным, установить индекс"
#: gitk:8475
msgid ""
@@ -962,7 +962,7 @@ msgstr "Показывать близкие метки"
#: gitk:10126
msgid "Limit diffs to listed paths"
-msgstr "Ограничить показ изменений выбраными файлами"
+msgstr "Ограничить показ изменений выбранными файлами"
#: gitk:10129
msgid "Support per-file encodings"
@@ -1022,11 +1022,11 @@ msgstr "заголовок блока изменений"
#: gitk:10169
msgid "Marked line bg"
-msgstr "Фон выбраной строки"
+msgstr "Фон выбранной строки"
#: gitk:10171
msgid "marked line background"
-msgstr "фон выбраной строки"
+msgstr "фон выбранной строки"
#: gitk:10175
msgid "Select bg"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
index 386763a..2f07a2e 100644
--- a/gitk-git/po/sv.po
+++ b/gitk-git/po/sv.po
@@ -1,5 +1,5 @@
# Swedish translation for gitk
-# Copyright (C) 2005-2009 Paul Mackerras
+# Copyright (C) 2005-2010 Paul Mackerras
# This file is distributed under the same license as the gitk package.
#
# Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: sv\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-28 13:16+0100\n"
-"PO-Revision-Date: 2010-01-28 13:48+0100\n"
+"POT-Creation-Date: 2010-09-12 21:14+0100\n"
+"PO-Revision-Date: 2010-09-12 21:16+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"MIME-Version: 1.0\n"
@@ -24,17 +24,17 @@ msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
msgid "Error parsing revisions:"
msgstr "Fel vid tolkning av revisioner:"
-#: gitk:329
+#: gitk:330
msgid "Error executing --argscmd command:"
msgstr "Fel vid körning av --argscmd-kommando:"
-#: gitk:342
+#: gitk:343
msgid "No files selected: --merge specified but no files are unmerged."
msgstr ""
"Inga filer valdes: --merge angavs men det finns inga filer som inte har "
"slagits samman."
-#: gitk:345
+#: gitk:346
msgid ""
"No files selected: --merge specified but no unmerged files are within file "
"limit."
@@ -42,600 +42,605 @@ msgstr ""
"Inga filer valdes: --merge angavs men det finns inga filer inom "
"filbegränsningen."
-#: gitk:367 gitk:514
+#: gitk:368 gitk:516
msgid "Error executing git log:"
msgstr "Fel vid körning av git log:"
-#: gitk:385 gitk:530
+#: gitk:386 gitk:532
msgid "Reading"
msgstr "Läser"
-#: gitk:445 gitk:4261
+#: gitk:446 gitk:4271
msgid "Reading commits..."
msgstr "Läser incheckningar..."
-#: gitk:448 gitk:1578 gitk:4264
+#: gitk:449 gitk:1580 gitk:4274
msgid "No commits selected"
msgstr "Inga incheckningar markerade"
-#: gitk:1454
+#: gitk:1456
msgid "Can't parse git log output:"
msgstr "Kan inte tolka utdata från git log:"
-#: gitk:1674
+#: gitk:1676
msgid "No commit information available"
msgstr "Ingen incheckningsinformation är tillgänglig"
-#: gitk:1816
+#: gitk:1818
msgid "mc"
msgstr "mc"
-#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
+#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
msgid "OK"
msgstr "OK"
-#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
-#: gitk:10586 gitk:10805
+#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
+#: gitk:10608 gitk:10818
msgid "Cancel"
msgstr "Avbryt"
-#: gitk:1975
+#: gitk:1980
msgid "Update"
msgstr "Uppdatera"
-#: gitk:1976
+#: gitk:1981
msgid "Reload"
msgstr "Ladda om"
-#: gitk:1977
+#: gitk:1982
msgid "Reread references"
msgstr "Läs om referenser"
-#: gitk:1978
+#: gitk:1983
msgid "List references"
msgstr "Visa referenser"
-#: gitk:1980
+#: gitk:1985
msgid "Start git gui"
msgstr "Starta git gui"
-#: gitk:1982
+#: gitk:1987
msgid "Quit"
msgstr "Avsluta"
-#: gitk:1974
+#: gitk:1979
msgid "File"
msgstr "Arkiv"
-#: gitk:1986
+#: gitk:1991
msgid "Preferences"
msgstr "Inställningar"
-#: gitk:1985
+#: gitk:1990
msgid "Edit"
msgstr "Redigera"
-#: gitk:1990
+#: gitk:1995
msgid "New view..."
msgstr "Ny vy..."
-#: gitk:1991
+#: gitk:1996
msgid "Edit view..."
msgstr "Ändra vy..."
-#: gitk:1992
+#: gitk:1997
msgid "Delete view"
msgstr "Ta bort vy"
-#: gitk:1994
+#: gitk:1999
msgid "All files"
msgstr "Alla filer"
-#: gitk:1989 gitk:3808
+#: gitk:1994 gitk:3817
msgid "View"
msgstr "Visa"
-#: gitk:1999 gitk:2009 gitk:2780
+#: gitk:2004 gitk:2014 gitk:2787
msgid "About gitk"
msgstr "Om gitk"
-#: gitk:2000 gitk:2014
+#: gitk:2005 gitk:2019
msgid "Key bindings"
msgstr "Tangentbordsbindningar"
-#: gitk:1998 gitk:2013
+#: gitk:2003 gitk:2018
msgid "Help"
msgstr "Hjälp"
-#: gitk:2091 gitk:8110
+#: gitk:2096 gitk:8132
msgid "SHA1 ID:"
msgstr "SHA1-id:"
-#: gitk:2122
+#: gitk:2127
msgid "Row"
msgstr "Rad"
-#: gitk:2160
+#: gitk:2165
msgid "Find"
msgstr "Sök"
-#: gitk:2161
+#: gitk:2166
msgid "next"
msgstr "nästa"
-#: gitk:2162
+#: gitk:2167
msgid "prev"
msgstr "föreg"
-#: gitk:2163
+#: gitk:2168
msgid "commit"
msgstr "incheckning"
-#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
-#: gitk:6566
+#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
+#: gitk:6576
msgid "containing:"
msgstr "som innehåller:"
-#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
+#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
msgid "touching paths:"
msgstr "som rör sökväg:"
-#: gitk:2170 gitk:4502
+#: gitk:2175 gitk:4512
msgid "adding/removing string:"
msgstr "som lägger/till tar bort sträng:"
-#: gitk:2179 gitk:2181
+#: gitk:2184 gitk:2186
msgid "Exact"
msgstr "Exakt"
-#: gitk:2181 gitk:4577 gitk:6378
+#: gitk:2186 gitk:4587 gitk:6388
msgid "IgnCase"
msgstr "IgnVersaler"
-#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
+#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
msgid "Regexp"
msgstr "Reg.uttr."
-#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
+#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
msgid "All fields"
msgstr "Alla fält"
-#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
+#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
msgid "Headline"
msgstr "Rubrik"
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
msgid "Comments"
msgstr "Kommentarer"
-#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
-#: gitk:8300
+#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
+#: gitk:8322
msgid "Author"
msgstr "Författare"
-#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
+#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
msgid "Committer"
msgstr "Incheckare"
-#: gitk:2216
+#: gitk:2221
msgid "Search"
msgstr "Sök"
-#: gitk:2224
+#: gitk:2229
msgid "Diff"
msgstr "Diff"
-#: gitk:2226
+#: gitk:2231
msgid "Old version"
msgstr "Gammal version"
-#: gitk:2228
+#: gitk:2233
msgid "New version"
msgstr "Ny version"
-#: gitk:2230
+#: gitk:2235
msgid "Lines of context"
msgstr "Rader sammanhang"
-#: gitk:2240
+#: gitk:2245
msgid "Ignore space change"
msgstr "Ignorera ändringar i blanksteg"
-#: gitk:2299
+#: gitk:2304
msgid "Patch"
msgstr "Patch"
-#: gitk:2301
+#: gitk:2306
msgid "Tree"
msgstr "Träd"
-#: gitk:2456 gitk:2473
+#: gitk:2463 gitk:2480
msgid "Diff this -> selected"
msgstr "Diff denna -> markerad"
-#: gitk:2457 gitk:2474
+#: gitk:2464 gitk:2481
msgid "Diff selected -> this"
msgstr "Diff markerad -> denna"
-#: gitk:2458 gitk:2475
+#: gitk:2465 gitk:2482
msgid "Make patch"
msgstr "Skapa patch"
-#: gitk:2459 gitk:8692
+#: gitk:2466 gitk:8715
msgid "Create tag"
msgstr "Skapa tagg"
-#: gitk:2460 gitk:8808
+#: gitk:2467 gitk:8831
msgid "Write commit to file"
msgstr "Skriv incheckning till fil"
-#: gitk:2461 gitk:8865
+#: gitk:2468 gitk:8888
msgid "Create new branch"
msgstr "Skapa ny gren"
-#: gitk:2462
+#: gitk:2469
msgid "Cherry-pick this commit"
msgstr "Plocka denna incheckning"
-#: gitk:2463
+#: gitk:2470
msgid "Reset HEAD branch to here"
msgstr "Återställ HEAD-grenen hit"
-#: gitk:2464
+#: gitk:2471
msgid "Mark this commit"
msgstr "Markera denna incheckning"
-#: gitk:2465
+#: gitk:2472
msgid "Return to mark"
msgstr "Återgå till markering"
-#: gitk:2466
+#: gitk:2473
msgid "Find descendant of this and mark"
msgstr "Hitta efterföljare till denna och markera"
-#: gitk:2467
+#: gitk:2474
msgid "Compare with marked commit"
msgstr "Jämför med markerad incheckning"
-#: gitk:2481
+#: gitk:2488
msgid "Check out this branch"
msgstr "Checka ut denna gren"
-#: gitk:2482
+#: gitk:2489
msgid "Remove this branch"
msgstr "Ta bort denna gren"
-#: gitk:2489
+#: gitk:2496
msgid "Highlight this too"
msgstr "Markera även detta"
-#: gitk:2490
+#: gitk:2497
msgid "Highlight this only"
msgstr "Markera bara detta"
-#: gitk:2491
+#: gitk:2498
msgid "External diff"
msgstr "Extern diff"
-#: gitk:2492
+#: gitk:2499
msgid "Blame parent commit"
msgstr "Klandra föräldraincheckning"
-#: gitk:2499
+#: gitk:2506
msgid "Show origin of this line"
msgstr "Visa ursprunget för den här raden"
-#: gitk:2500
+#: gitk:2507
msgid "Run git gui blame on this line"
msgstr "Kör git gui blame på den här raden"
-#: gitk:2782
+#: gitk:2789
msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
"\n"
"Use and redistribute under the terms of the GNU General Public License"
msgstr ""
"\n"
"Gitk - en incheckningsvisare för git\n"
"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2010 Paul Mackerras\n"
"\n"
"Använd och vidareförmedla enligt villkoren i GNU General Public License"
-#: gitk:2790 gitk:2854 gitk:9230
+#: gitk:2797 gitk:2862 gitk:9253
msgid "Close"
msgstr "Stäng"
-#: gitk:2811
+#: gitk:2818
msgid "Gitk key bindings"
msgstr "Tangentbordsbindningar för Gitk"
-#: gitk:2814
+#: gitk:2821
msgid "Gitk key bindings:"
msgstr "Tangentbordsbindningar för Gitk:"
-#: gitk:2816
+#: gitk:2823
#, tcl-format
msgid "<%s-Q>\t\tQuit"
msgstr "<%s-Q>\t\tAvsluta"
-#: gitk:2817
+#: gitk:2824
+#, tcl-format
+msgid "<%s-W>\t\tClose window"
+msgstr "<%s-W>\t\tStäng fönster"
+
+#: gitk:2825
msgid "<Home>\t\tMove to first commit"
msgstr "<Home>\t\tGå till första incheckning"
-#: gitk:2818
+#: gitk:2826
msgid "<End>\t\tMove to last commit"
msgstr "<End>\t\tGå till sista incheckning"
-#: gitk:2819
+#: gitk:2827
msgid "<Up>, p, i\tMove up one commit"
msgstr "<Upp>, p, i\tGå en incheckning upp"
-#: gitk:2820
+#: gitk:2828
msgid "<Down>, n, k\tMove down one commit"
msgstr "<Ned>, n, k\tGå en incheckning ned"
-#: gitk:2821
+#: gitk:2829
msgid "<Left>, z, j\tGo back in history list"
msgstr "<Vänster>, z, j\tGå bakåt i historiken"
-#: gitk:2822
+#: gitk:2830
msgid "<Right>, x, l\tGo forward in history list"
msgstr "<Höger>, x, l\tGå framåt i historiken"
-#: gitk:2823
+#: gitk:2831
msgid "<PageUp>\tMove up one page in commit list"
msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
-#: gitk:2824
+#: gitk:2832
msgid "<PageDown>\tMove down one page in commit list"
msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
-#: gitk:2825
+#: gitk:2833
#, tcl-format
msgid "<%s-Home>\tScroll to top of commit list"
msgstr "<%s-Home>\tRulla till början av incheckningslistan"
-#: gitk:2826
+#: gitk:2834
#, tcl-format
msgid "<%s-End>\tScroll to bottom of commit list"
msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
-#: gitk:2827
+#: gitk:2835
#, tcl-format
msgid "<%s-Up>\tScroll commit list up one line"
msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
-#: gitk:2828
+#: gitk:2836
#, tcl-format
msgid "<%s-Down>\tScroll commit list down one line"
msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
-#: gitk:2829
+#: gitk:2837
#, tcl-format
msgid "<%s-PageUp>\tScroll commit list up one page"
msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
-#: gitk:2830
+#: gitk:2838
#, tcl-format
msgid "<%s-PageDown>\tScroll commit list down one page"
msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
-#: gitk:2831
+#: gitk:2839
msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
-#: gitk:2832
+#: gitk:2840
msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
-#: gitk:2833
+#: gitk:2841
msgid "<Delete>, b\tScroll diff view up one page"
msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
-#: gitk:2834
+#: gitk:2842
msgid "<Backspace>\tScroll diff view up one page"
msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
-#: gitk:2835
+#: gitk:2843
msgid "<Space>\t\tScroll diff view down one page"
msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
-#: gitk:2836
+#: gitk:2844
msgid "u\t\tScroll diff view up 18 lines"
msgstr "u\t\tRulla diffvisningen upp 18 rader"
-#: gitk:2837
+#: gitk:2845
msgid "d\t\tScroll diff view down 18 lines"
msgstr "d\t\tRulla diffvisningen ned 18 rader"
-#: gitk:2838
+#: gitk:2846
#, tcl-format
msgid "<%s-F>\t\tFind"
msgstr "<%s-F>\t\tSök"
-#: gitk:2839
+#: gitk:2847
#, tcl-format
msgid "<%s-G>\t\tMove to next find hit"
msgstr "<%s-G>\t\tGå till nästa sökträff"
-#: gitk:2840
+#: gitk:2848
msgid "<Return>\tMove to next find hit"
msgstr "<Return>\t\tGå till nästa sökträff"
-#: gitk:2841
+#: gitk:2849
msgid "/\t\tFocus the search box"
msgstr "/\t\tFokusera sökrutan"
-#: gitk:2842
+#: gitk:2850
msgid "?\t\tMove to previous find hit"
msgstr "?\t\tGå till föregående sökträff"
-#: gitk:2843
+#: gitk:2851
msgid "f\t\tScroll diff view to next file"
msgstr "f\t\tRulla diffvisningen till nästa fil"
-#: gitk:2844
+#: gitk:2852
#, tcl-format
msgid "<%s-S>\t\tSearch for next hit in diff view"
msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
-#: gitk:2845
+#: gitk:2853
#, tcl-format
msgid "<%s-R>\t\tSearch for previous hit in diff view"
msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
-#: gitk:2846
+#: gitk:2854
#, tcl-format
msgid "<%s-KP+>\tIncrease font size"
msgstr "<%s-Num+>\tÖka teckenstorlek"
-#: gitk:2847
+#: gitk:2855
#, tcl-format
msgid "<%s-plus>\tIncrease font size"
msgstr "<%s-plus>\tÖka teckenstorlek"
-#: gitk:2848
+#: gitk:2856
#, tcl-format
msgid "<%s-KP->\tDecrease font size"
msgstr "<%s-Num->\tMinska teckenstorlek"
-#: gitk:2849
+#: gitk:2857
#, tcl-format
msgid "<%s-minus>\tDecrease font size"
msgstr "<%s-minus>\tMinska teckenstorlek"
-#: gitk:2850
+#: gitk:2858
msgid "<F5>\t\tUpdate"
msgstr "<F5>\t\tUppdatera"
-#: gitk:3305 gitk:3314
+#: gitk:3313 gitk:3322
#, tcl-format
msgid "Error creating temporary directory %s:"
msgstr "Fel vid skapande av temporär katalog %s:"
-#: gitk:3327
+#: gitk:3335
#, tcl-format
msgid "Error getting \"%s\" from %s:"
msgstr "Fel vid hämtning av \"%s\" från %s:"
-#: gitk:3390
+#: gitk:3398
msgid "command failed:"
msgstr "kommando misslyckades:"
-#: gitk:3539
+#: gitk:3547
msgid "No such commit"
msgstr "Incheckning saknas"
-#: gitk:3553
+#: gitk:3561
msgid "git gui blame: command failed:"
msgstr "git gui blame: kommando misslyckades:"
-#: gitk:3584
+#: gitk:3592
#, tcl-format
msgid "Couldn't read merge head: %s"
msgstr "Kunde inte läsa sammanslagningshuvud: %s"
-#: gitk:3592
+#: gitk:3600
#, tcl-format
msgid "Error reading index: %s"
msgstr "Fel vid läsning av index: %s"
-#: gitk:3617
+#: gitk:3625
#, tcl-format
msgid "Couldn't start git blame: %s"
msgstr "Kunde inte starta git blame: %s"
-#: gitk:3620 gitk:6409
+#: gitk:3628 gitk:6419
msgid "Searching"
msgstr "Söker"
-#: gitk:3652
+#: gitk:3660
#, tcl-format
msgid "Error running git blame: %s"
msgstr "Fel vid körning av git blame: %s"
-#: gitk:3680
+#: gitk:3688
#, tcl-format
msgid "That line comes from commit %s, which is not in this view"
msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
-#: gitk:3694
+#: gitk:3702
msgid "External diff viewer failed:"
msgstr "Externt diff-verktyg misslyckades:"
-#: gitk:3812
+#: gitk:3820
msgid "Gitk view definition"
msgstr "Definition av Gitk-vy"
-#: gitk:3816
+#: gitk:3824
msgid "Remember this view"
msgstr "Spara denna vy"
-#: gitk:3817
+#: gitk:3825
msgid "References (space separated list):"
msgstr "Referenser (blankstegsavdelad lista):"
-#: gitk:3818
+#: gitk:3826
msgid "Branches & tags:"
msgstr "Grenar & taggar:"
-#: gitk:3819
+#: gitk:3827
msgid "All refs"
msgstr "Alla referenser"
-#: gitk:3820
+#: gitk:3828
msgid "All (local) branches"
msgstr "Alla (lokala) grenar"
-#: gitk:3821
+#: gitk:3829
msgid "All tags"
msgstr "Alla taggar"
-#: gitk:3822
+#: gitk:3830
msgid "All remote-tracking branches"
msgstr "Alla fjärrspårande grenar"
-#: gitk:3823
+#: gitk:3831
msgid "Commit Info (regular expressions):"
msgstr "Incheckningsinfo (reguljära uttryck):"
-#: gitk:3824
+#: gitk:3832
msgid "Author:"
msgstr "Författare:"
-#: gitk:3825
+#: gitk:3833
msgid "Committer:"
msgstr "Incheckare:"
-#: gitk:3826
+#: gitk:3834
msgid "Commit Message:"
msgstr "Incheckningsmeddelande:"
-#: gitk:3827
+#: gitk:3835
msgid "Matches all Commit Info criteria"
msgstr "Motsvarar alla kriterier för incheckningsinfo"
-#: gitk:3828
+#: gitk:3836
msgid "Changes to Files:"
msgstr "Ändringar av filer:"
-#: gitk:3829
+#: gitk:3837
msgid "Fixed String"
msgstr "Fast sträng"
-#: gitk:3830
+#: gitk:3838
msgid "Regular Expression"
msgstr "Reguljärt uttryck"
-#: gitk:3831
+#: gitk:3839
msgid "Search string:"
msgstr "Söksträng:"
-#: gitk:3832
+#: gitk:3840
msgid ""
"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
@@ -643,201 +648,201 @@ msgstr ""
"Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
-#: gitk:3833
+#: gitk:3841
msgid "Since:"
msgstr "Från:"
-#: gitk:3834
+#: gitk:3842
msgid "Until:"
msgstr "Till:"
-#: gitk:3835
+#: gitk:3843
msgid "Limit and/or skip a number of revisions (positive integer):"
msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
-#: gitk:3836
+#: gitk:3844
msgid "Number to show:"
msgstr "Antal att visa:"
-#: gitk:3837
+#: gitk:3845
msgid "Number to skip:"
msgstr "Antal att hoppa över:"
-#: gitk:3838
+#: gitk:3846
msgid "Miscellaneous options:"
msgstr "Diverse alternativ:"
-#: gitk:3839
+#: gitk:3847
msgid "Strictly sort by date"
msgstr "Strikt datumsortering"
-#: gitk:3840
+#: gitk:3848
msgid "Mark branch sides"
msgstr "Markera sidogrenar"
-#: gitk:3841
+#: gitk:3849
msgid "Limit to first parent"
msgstr "Begränsa till första förälder"
-#: gitk:3842
+#: gitk:3850
msgid "Simple history"
msgstr "Enkel historik"
-#: gitk:3843
+#: gitk:3851
msgid "Additional arguments to git log:"
msgstr "Ytterligare argument till git log:"
-#: gitk:3844
+#: gitk:3852
msgid "Enter files and directories to include, one per line:"
msgstr "Ange filer och kataloger att ta med, en per rad:"
-#: gitk:3845
+#: gitk:3853
msgid "Command to generate more commits to include:"
msgstr "Kommando för att generera fler incheckningar att ta med:"
-#: gitk:3967
+#: gitk:3977
msgid "Gitk: edit view"
msgstr "Gitk: redigera vy"
-#: gitk:3975
+#: gitk:3985
msgid "-- criteria for selecting revisions"
msgstr " - kriterier för val av revisioner"
-#: gitk:3980
+#: gitk:3990
msgid "View Name"
msgstr "Namn på vy"
-#: gitk:4055
+#: gitk:4065
msgid "Apply (F5)"
msgstr "Använd (F5)"
-#: gitk:4093
+#: gitk:4103
msgid "Error in commit selection arguments:"
msgstr "Fel i argument för val av incheckningar:"
-#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
+#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
msgid "None"
msgstr "Inget"
-#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
+#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
msgid "Date"
msgstr "Datum"
-#: gitk:4594 gitk:6441
+#: gitk:4604 gitk:6451
msgid "CDate"
msgstr "Skapat datum"
-#: gitk:4743 gitk:4748
+#: gitk:4753 gitk:4758
msgid "Descendant"
msgstr "Avkomling"
-#: gitk:4744
+#: gitk:4754
msgid "Not descendant"
msgstr "Inte avkomling"
-#: gitk:4751 gitk:4756
+#: gitk:4761 gitk:4766
msgid "Ancestor"
msgstr "Förfader"
-#: gitk:4752
+#: gitk:4762
msgid "Not ancestor"
msgstr "Inte förfader"
-#: gitk:5042
+#: gitk:5052
msgid "Local changes checked in to index but not committed"
msgstr "Lokala ändringar sparade i indexet men inte incheckade"
-#: gitk:5078
+#: gitk:5088
msgid "Local uncommitted changes, not checked in to index"
msgstr "Lokala ändringar, ej sparade i indexet"
-#: gitk:6759
+#: gitk:6769
msgid "many"
msgstr "många"
-#: gitk:6942
+#: gitk:6952
msgid "Tags:"
msgstr "Taggar:"
-#: gitk:6959 gitk:6965 gitk:8280
+#: gitk:6969 gitk:6975 gitk:8302
msgid "Parent"
msgstr "Förälder"
-#: gitk:6970
+#: gitk:6980
msgid "Child"
msgstr "Barn"
-#: gitk:6979
+#: gitk:6989
msgid "Branch"
msgstr "Gren"
-#: gitk:6982
+#: gitk:6992
msgid "Follows"
msgstr "Följer"
-#: gitk:6985
+#: gitk:6995
msgid "Precedes"
msgstr "Föregår"
-#: gitk:7522
+#: gitk:7532
#, tcl-format
msgid "Error getting diffs: %s"
msgstr "Fel vid hämtning av diff: %s"
-#: gitk:8108
+#: gitk:8130
msgid "Goto:"
msgstr "Gå till:"
-#: gitk:8129
+#: gitk:8151
#, tcl-format
msgid "Short SHA1 id %s is ambiguous"
msgstr "Förkortat SHA1-id %s är tvetydigt"
-#: gitk:8136
+#: gitk:8158
#, tcl-format
msgid "Revision %s is not known"
msgstr "Revisionen %s är inte känd"
-#: gitk:8146
+#: gitk:8168
#, tcl-format
msgid "SHA1 id %s is not known"
msgstr "SHA-id:t %s är inte känt"
-#: gitk:8148
+#: gitk:8170
#, tcl-format
msgid "Revision %s is not in the current view"
msgstr "Revisionen %s finns inte i den nuvarande vyn"
-#: gitk:8290
+#: gitk:8312
msgid "Children"
msgstr "Barn"
-#: gitk:8348
+#: gitk:8370
#, tcl-format
msgid "Reset %s branch to here"
msgstr "Återställ grenen %s hit"
-#: gitk:8350
+#: gitk:8372
msgid "Detached head: can't reset"
msgstr "Frånkopplad head: kan inte återställa"
-#: gitk:8459 gitk:8465
+#: gitk:8481 gitk:8487
msgid "Skipping merge commit "
msgstr "Hoppar över sammanslagningsincheckning "
-#: gitk:8474 gitk:8479
+#: gitk:8496 gitk:8501
msgid "Error getting patch ID for "
msgstr "Fel vid hämtning av patch-id för "
-#: gitk:8475 gitk:8480
+#: gitk:8497 gitk:8502
msgid " - stopping\n"
msgstr " - stannar\n"
-#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
msgid "Commit "
msgstr "Incheckning "
-#: gitk:8489
+#: gitk:8511
msgid ""
" is the same patch as\n"
" "
@@ -845,7 +850,7 @@ msgstr ""
" är samma patch som\n"
" "
-#: gitk:8497
+#: gitk:8519
msgid ""
" differs from\n"
" "
@@ -853,139 +858,139 @@ msgstr ""
" skiljer sig från\n"
" "
-#: gitk:8499
+#: gitk:8521
msgid ""
"Diff of commits:\n"
"\n"
-msgstr "Skillnad mellan incheckningar:\n"
+msgstr ""
+"Skillnad mellan incheckningar:\n"
"\n"
-""
-#: gitk:8511 gitk:8520
+#: gitk:8533 gitk:8542
#, tcl-format
msgid " has %s children - stopping\n"
msgstr " har %s barn - stannar\n"
-#: gitk:8539
+#: gitk:8561
#, tcl-format
msgid "Error writing commit to file: %s"
msgstr "Fel vid skrivning av incheckning till fil: %s"
-#: gitk:8545
+#: gitk:8567
#, tcl-format
msgid "Error diffing commits: %s"
msgstr "Fel vid jämförelse av incheckningar: %s"
-#: gitk:8575
+#: gitk:8598
msgid "Top"
msgstr "Topp"
-#: gitk:8576
+#: gitk:8599
msgid "From"
msgstr "Från"
-#: gitk:8581
+#: gitk:8604
msgid "To"
msgstr "Till"
-#: gitk:8605
+#: gitk:8628
msgid "Generate patch"
msgstr "Generera patch"
-#: gitk:8607
+#: gitk:8630
msgid "From:"
msgstr "Från:"
-#: gitk:8616
+#: gitk:8639
msgid "To:"
msgstr "Till:"
-#: gitk:8625
+#: gitk:8648
msgid "Reverse"
msgstr "Vänd"
-#: gitk:8627 gitk:8822
+#: gitk:8650 gitk:8845
msgid "Output file:"
msgstr "Utdatafil:"
-#: gitk:8633
+#: gitk:8656
msgid "Generate"
msgstr "Generera"
-#: gitk:8671
+#: gitk:8694
msgid "Error creating patch:"
msgstr "Fel vid generering av patch:"
-#: gitk:8694 gitk:8810 gitk:8867
+#: gitk:8717 gitk:8833 gitk:8890
msgid "ID:"
msgstr "Id:"
-#: gitk:8703
+#: gitk:8726
msgid "Tag name:"
msgstr "Taggnamn:"
-#: gitk:8706
+#: gitk:8729
msgid "Tag message is optional"
msgstr "Taggmeddelandet är valfritt"
-#: gitk:8708
+#: gitk:8731
msgid "Tag message:"
msgstr "Taggmeddelande:"
-#: gitk:8712 gitk:8876
+#: gitk:8735 gitk:8899
msgid "Create"
msgstr "Skapa"
-#: gitk:8730
+#: gitk:8753
msgid "No tag name specified"
msgstr "Inget taggnamn angavs"
-#: gitk:8734
+#: gitk:8757
#, tcl-format
msgid "Tag \"%s\" already exists"
msgstr "Taggen \"%s\" finns redan"
-#: gitk:8744
+#: gitk:8767
msgid "Error creating tag:"
msgstr "Fel vid skapande av tagg:"
-#: gitk:8819
+#: gitk:8842
msgid "Command:"
msgstr "Kommando:"
-#: gitk:8827
+#: gitk:8850
msgid "Write"
msgstr "Skriv"
-#: gitk:8845
+#: gitk:8868
msgid "Error writing commit:"
msgstr "Fel vid skrivning av incheckning:"
-#: gitk:8872
+#: gitk:8895
msgid "Name:"
msgstr "Namn:"
-#: gitk:8895
+#: gitk:8918
msgid "Please specify a name for the new branch"
msgstr "Ange ett namn för den nya grenen"
-#: gitk:8900
+#: gitk:8923
#, tcl-format
msgid "Branch '%s' already exists. Overwrite?"
msgstr "Grenen \"%s\" finns redan. Skriva över?"
-#: gitk:8966
+#: gitk:8989
#, tcl-format
msgid "Commit %s is already included in branch %s -- really re-apply it?"
msgstr ""
"Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
"på nytt?"
-#: gitk:8971
+#: gitk:8994
msgid "Cherry-picking"
msgstr "Plockar"
-#: gitk:8980
+#: gitk:9003
#, tcl-format
msgid ""
"Cherry-pick failed because of local changes to file '%s'.\n"
@@ -995,7 +1000,7 @@ msgstr ""
"Checka in, återställ eller spara undan (stash) dina ändringar och försök "
"igen."
-#: gitk:8986
+#: gitk:9009
msgid ""
"Cherry-pick failed because of merge conflict.\n"
"Do you wish to run git citool to resolve it?"
@@ -1003,32 +1008,32 @@ msgstr ""
"Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
"Vill du köra git citool för att lösa den?"
-#: gitk:9002
+#: gitk:9025
msgid "No changes committed"
msgstr "Inga ändringar incheckade"
-#: gitk:9028
+#: gitk:9051
msgid "Confirm reset"
msgstr "Bekräfta återställning"
-#: gitk:9030
+#: gitk:9053
#, tcl-format
msgid "Reset branch %s to %s?"
msgstr "Återställa grenen %s till %s?"
-#: gitk:9032
+#: gitk:9055
msgid "Reset type:"
msgstr "Typ av återställning:"
-#: gitk:9035
+#: gitk:9058
msgid "Soft: Leave working tree and index untouched"
msgstr "Mjuk: Rör inte utcheckning och index"
-#: gitk:9038
+#: gitk:9061
msgid "Mixed: Leave working tree untouched, reset index"
msgstr "Blandad: Rör inte utcheckning, återställ index"
-#: gitk:9041
+#: gitk:9064
msgid ""
"Hard: Reset working tree and index\n"
"(discard ALL local changes)"
@@ -1036,19 +1041,19 @@ msgstr ""
"Hård: Återställ utcheckning och index\n"
"(förkastar ALLA lokala ändringar)"
-#: gitk:9058
+#: gitk:9081
msgid "Resetting"
msgstr "Återställer"
-#: gitk:9118
+#: gitk:9141
msgid "Checking out"
msgstr "Checkar ut"
-#: gitk:9171
+#: gitk:9194
msgid "Cannot delete the currently checked-out branch"
msgstr "Kan inte ta bort den just nu utcheckade grenen"
-#: gitk:9177
+#: gitk:9200
#, tcl-format
msgid ""
"The commits on branch %s aren't on any other branch.\n"
@@ -1057,16 +1062,16 @@ msgstr ""
"Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
"Vill du verkligen ta bort grenen %s?"
-#: gitk:9208
+#: gitk:9231
#, tcl-format
msgid "Tags and heads: %s"
msgstr "Taggar och huvuden: %s"
-#: gitk:9223
+#: gitk:9246
msgid "Filter"
msgstr "Filter"
-#: gitk:9518
+#: gitk:9541
msgid ""
"Error reading commit topology information; branch and preceding/following "
"tag information will be incomplete."
@@ -1074,203 +1079,203 @@ msgstr ""
"Fel vid läsning av information om incheckningstopologi; information om "
"grenar och föregående/senare taggar kommer inte vara komplett."
-#: gitk:10504
+#: gitk:10527
msgid "Tag"
msgstr "Tagg"
-#: gitk:10504
+#: gitk:10527
msgid "Id"
msgstr "Id"
-#: gitk:10554
+#: gitk:10576
msgid "Gitk font chooser"
msgstr "Teckensnittsväljare för Gitk"
-#: gitk:10571
+#: gitk:10593
msgid "B"
msgstr "F"
-#: gitk:10574
+#: gitk:10596
msgid "I"
msgstr "K"
-#: gitk:10692
+#: gitk:10714
msgid "Gitk preferences"
msgstr "Inställningar för Gitk"
-#: gitk:10694
+#: gitk:10716
msgid "Commit list display options"
msgstr "Alternativ för incheckningslistvy"
-#: gitk:10697
+#: gitk:10719
msgid "Maximum graph width (lines)"
msgstr "Maximal grafbredd (rader)"
-#: gitk:10700
+#: gitk:10722
#, tcl-format
msgid "Maximum graph width (% of pane)"
msgstr "Maximal grafbredd (% av ruta)"
-#: gitk:10703
+#: gitk:10725
msgid "Show local changes"
msgstr "Visa lokala ändringar"
-#: gitk:10706
+#: gitk:10728
msgid "Auto-select SHA1"
msgstr "Välj SHA1 automatiskt"
-#: gitk:10709
+#: gitk:10731
msgid "Hide remote refs"
msgstr "Dölj fjärr-referenser"
-#: gitk:10713
+#: gitk:10735
msgid "Diff display options"
msgstr "Alternativ för diffvy"
-#: gitk:10715
+#: gitk:10737
msgid "Tab spacing"
msgstr "Blanksteg för tabulatortecken"
-#: gitk:10718
+#: gitk:10740
msgid "Display nearby tags"
msgstr "Visa närliggande taggar"
-#: gitk:10721
+#: gitk:10743
msgid "Limit diffs to listed paths"
msgstr "Begränsa diff till listade sökvägar"
-#: gitk:10724
+#: gitk:10746
msgid "Support per-file encodings"
msgstr "Stöd för filspecifika teckenkodningar"
-#: gitk:10730 gitk:10819
+#: gitk:10752 gitk:10832
msgid "External diff tool"
msgstr "Externt diff-verktyg"
-#: gitk:10731
+#: gitk:10753
msgid "Choose..."
msgstr "Välj..."
-#: gitk:10736
+#: gitk:10758
msgid "General options"
msgstr "Allmänna inställningar"
-#: gitk:10739
+#: gitk:10761
msgid "Use themed widgets"
msgstr "Använd tema på fönsterelement"
-#: gitk:10741
+#: gitk:10763
msgid "(change requires restart)"
msgstr "(ändringen kräver omstart)"
-#: gitk:10743
+#: gitk:10765
msgid "(currently unavailable)"
msgstr "(för närvarande inte tillgängligt)"
-#: gitk:10747
+#: gitk:10769
msgid "Colors: press to choose"
msgstr "Färger: tryck för att välja"
-#: gitk:10750
+#: gitk:10772
msgid "Interface"
msgstr "Gränssnitt"
-#: gitk:10751
+#: gitk:10773
msgid "interface"
msgstr "gränssnitt"
-#: gitk:10754
+#: gitk:10776
msgid "Background"
msgstr "Bakgrund"
-#: gitk:10755 gitk:10785
+#: gitk:10777 gitk:10807
msgid "background"
msgstr "bakgrund"
-#: gitk:10758
+#: gitk:10780
msgid "Foreground"
msgstr "Förgrund"
-#: gitk:10759
+#: gitk:10781
msgid "foreground"
msgstr "förgrund"
-#: gitk:10762
+#: gitk:10784
msgid "Diff: old lines"
msgstr "Diff: gamla rader"
-#: gitk:10763
+#: gitk:10785
msgid "diff old lines"
msgstr "diff gamla rader"
-#: gitk:10767
+#: gitk:10789
msgid "Diff: new lines"
msgstr "Diff: nya rader"
-#: gitk:10768
+#: gitk:10790
msgid "diff new lines"
msgstr "diff nya rader"
-#: gitk:10772
+#: gitk:10794
msgid "Diff: hunk header"
msgstr "Diff: delhuvud"
-#: gitk:10774
+#: gitk:10796
msgid "diff hunk header"
msgstr "diff delhuvud"
-#: gitk:10778
+#: gitk:10800
msgid "Marked line bg"
msgstr "Markerad rad bakgrund"
-#: gitk:10780
+#: gitk:10802
msgid "marked line background"
msgstr "markerad rad bakgrund"
-#: gitk:10784
+#: gitk:10806
msgid "Select bg"
msgstr "Markerad bakgrund"
-#: gitk:10788
+#: gitk:10810
msgid "Fonts: press to choose"
msgstr "Teckensnitt: tryck för att välja"
-#: gitk:10790
+#: gitk:10812
msgid "Main font"
msgstr "Huvudteckensnitt"
-#: gitk:10791
+#: gitk:10813
msgid "Diff display font"
msgstr "Teckensnitt för diffvisning"
-#: gitk:10792
+#: gitk:10814
msgid "User interface font"
msgstr "Teckensnitt för användargränssnitt"
-#: gitk:10829
+#: gitk:10842
#, tcl-format
msgid "Gitk: choose color for %s"
msgstr "Gitk: välj färg för %s"
-#: gitk:11433
+#: gitk:11445
msgid "Cannot find a git repository here."
-msgstr "Hittar inget gitk-arkiv här."
+msgstr "Hittar inget git-arkiv här."
-#: gitk:11437
+#: gitk:11449
#, tcl-format
msgid "Cannot find the git directory \"%s\"."
msgstr "Hittar inte git-katalogen \"%s\"."
-#: gitk:11484
+#: gitk:11496
#, tcl-format
msgid "Ambiguous argument '%s': both revision and filename"
msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
-#: gitk:11496
+#: gitk:11508
msgid "Bad arguments to gitk:"
msgstr "Felaktiga argument till gitk:"
-#: gitk:11587
+#: gitk:11604
msgid "Command line"
msgstr "Kommandorad"
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index 8230531..c5236fe 100644
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
@@ -25,11 +25,25 @@ The above example assumes that your web server is configured to run
scripts).
+Requirements
+------------
+
+ - Core git tools
+ - Perl
+ - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
+ - web server
+
+The following optional Perl modules are required for extra features
+ - Digest::MD5 - for gravatar support
+ - CGI::Fast and FCGI - for running gitweb as FastCGI script
+ - HTML::TagCloud - for fancy tag cloud in project list view
+ - HTTP::Date or Time::ParseDate - to support If-Modified-Since for feeds
+
+
Build time configuration
------------------------
-See also "How to configure gitweb for your local system" in README
-file for gitweb (in gitweb/README).
+See also "How to configure gitweb for your local system" section below.
- There are many configuration variables which affect building of
gitweb.cgi; see "default configuration for gitweb" section in main
@@ -73,6 +87,125 @@ file for gitweb (in gitweb/README).
substitute gitweb.min.js and gitweb.min.css for all uses of gitweb.js and
gitweb.css in the help files.
+
+How to configure gitweb for your local system
+---------------------------------------------
+
+You can specify the following configuration variables when building GIT:
+
+ * GIT_BINDIR
+ Points where to find the git executable. You should set it up to
+ the place where the git binary was installed (usually /usr/bin) if you
+ don't install git from sources together with gitweb. [Default: $(bindir)]
+ * GITWEB_SITENAME
+ Shown in the title of all generated pages, defaults to the server name
+ (SERVER_NAME CGI environment variable) if not set. [No default]
+ * GITWEB_PROJECTROOT
+ The root directory for all projects shown by gitweb. Must be set
+ correctly for gitweb to find repositories to display. See also
+ "Gitweb repositories" in the INSTALL file for gitweb. [Default: /pub/git]
+ * GITWEB_PROJECT_MAXDEPTH
+ The filesystem traversing limit for getting the project list; the number
+ is taken as depth relative to the projectroot. It is used when
+ GITWEB_LIST is a directory (or is not set; then project root is used).
+ This is meant to speed up project listing on large work trees by limiting
+ search depth. [Default: 2007]
+ * GITWEB_LIST
+ Points to a directory to scan for projects (defaults to project root
+ if not set / if empty) or to a file with explicit listing of projects
+ (together with projects' ownership). See "Generating projects list
+ using gitweb" in INSTALL file for gitweb to find out how to generate
+ such file from scan of a directory. [No default, which means use root
+ directory for projects]
+ * GITWEB_EXPORT_OK
+ Show repository only if this file exists (in repository). Only
+ effective if this variable evaluates to true. [No default / Not set]
+ * GITWEB_STRICT_EXPORT
+ Only allow viewing of repositories also shown on the overview page.
+ This for example makes GITWEB_EXPORT_OK to decide if repository is
+ available and not only if it is shown. If GITWEB_LIST points to
+ file with list of project, only those repositories listed would be
+ available for gitweb. [No default]
+ * GITWEB_HOMETEXT
+ Points to an .html file which is included on the gitweb project
+ overview page ('projects_list' view), if it exists. Relative to
+ gitweb.cgi script. [Default: indextext.html]
+ * GITWEB_SITE_HEADER
+ Filename of html text to include at top of each page. Relative to
+ gitweb.cgi script. [No default]
+ * GITWEB_SITE_FOOTER
+ Filename of html text to include at bottom of each page. Relative to
+ gitweb.cgi script. [No default]
+ * GITWEB_HOME_LINK_STR
+ String of the home link on top of all pages, leading to $home_link
+ (usually main gitweb page, which means projects list). Used as first
+ part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
+ [Default: projects]
+ * GITWEB_SITENAME
+ Name of your site or organization to appear in page titles. Set it
+ to something descriptive for clearer bookmarks etc. If not set
+ (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
+ SERVER_NAME CGI environment variable is not set (e.g. if running
+ gitweb as standalone script). [No default]
+ * GITWEB_BASE_URL
+ Git base URLs used for URL to where fetch project from, i.e. full
+ URL is "$git_base_url/$project". Shown on projects summary page.
+ Repository URL for project can be also configured per repository; this
+ takes precedence over URLs composed from base URL and a project name.
+ Note that you can setup multiple base URLs (for example one for
+ git:// protocol access, another for http:// access) from the gitweb
+ config file. [No default]
+ * GITWEB_CSS
+ Points to the location where you put gitweb.css on your web server
+ (or to be more generic, the URI of gitweb stylesheet). Relative to the
+ base URI of gitweb. Note that you can setup multiple stylesheets from
+ the gitweb config file. [Default: static/gitweb.css (or
+ static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
+ is used)]
+ * GITWEB_JS
+ Points to the location where you put gitweb.js on your web server
+ (or to be more generic URI of JavaScript code used by gitweb).
+ Relative to base URI of gitweb. [Default: static/gitweb.js (or
+ static/gitweb.min.js if JSMIN build variable is defined / JavaScript
+ minifier is used)]
+ * CSSMIN, JSMIN
+ Invocation of a CSS minifier or a JavaScript minifier, respectively,
+ working as a filter (source on standard input, minified result on
+ standard output). If set, it is used to generate a minified version of
+ 'static/gitweb.css' or 'static/gitweb.js', respectively. *Note* that
+ minified files would have *.min.css and *.min.js extension, which is
+ important if you also set GITWEB_CSS and/or GITWEB_JS. [No default]
+ * GITWEB_LOGO
+ Points to the location where you put git-logo.png on your web server
+ (or to be more generic URI of logo, 72x27 size, displayed in top right
+ corner of each gitweb page, and used as logo for Atom feed). Relative
+ to base URI of gitweb. [Default: static/git-logo.png]
+ * GITWEB_FAVICON
+ Points to the location where you put git-favicon.png on your web server
+ (or to be more generic URI of favicon, assumed to be image/png type;
+ web browsers that support favicons (website icons) may display them
+ in the browser's URL bar and next to site name in bookmarks). Relative
+ to base URI of gitweb. [Default: static/git-favicon.png]
+ * GITWEB_CONFIG
+ This Perl file will be loaded using 'do' and can be used to override any
+ of the options above as well as some other options -- see the "Runtime
+ gitweb configuration" section below, and top of 'gitweb.cgi' for their
+ full list and description. If the environment variable GITWEB_CONFIG
+ is set when gitweb.cgi is executed, then the file specified in the
+ environment variable will be loaded instead of the file specified
+ when gitweb.cgi was created. [Default: gitweb_config.perl]
+ * GITWEB_CONFIG_SYSTEM
+ This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
+ does not exist. If the environment variable GITWEB_CONFIG_SYSTEM is set
+ when gitweb.cgi is executed, then the file specified in the environment
+ variable will be loaded instead of the file specified when gitweb.cgi was
+ created. [Default: /etc/gitweb.conf]
+ * HIGHLIGHT_BIN
+ Path to the highlight executable to use (must be the one from
+ http://www.andre-simon.de due to assumptions about parameters and output).
+ Useful if highlight is not installed on your webserver's PATH.
+ [Default: highlight]
+
Build example
~~~~~~~~~~~~~
@@ -229,15 +362,6 @@ $projects_list variable in gitweb config):
perl -- /var/www/cgi-bin/gitweb.cgi
-Requirements
-------------
-
- - Core git tools
- - Perl
- - Perl modules: CGI, Encode, Fcntl, File::Find, File::Basename.
- - web server
-
-
Example web server configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/gitweb/Makefile b/gitweb/Makefile
index 2fb7c2d..5d20515 100644
--- a/gitweb/Makefile
+++ b/gitweb/Makefile
@@ -35,10 +35,12 @@ GITWEB_FAVICON = static/git-favicon.png
GITWEB_JS = static/gitweb.js
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
+HIGHLIGHT_BIN = highlight
# include user config
-include ../config.mak.autogen
-include ../config.mak
+-include config.mak
# determine version
../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@@ -84,7 +86,7 @@ ifndef V
endif
endif
-all:: gitweb.cgi
+all:: gitweb.cgi static/gitweb.js
GITWEB_PROGRAMS = gitweb.cgi
@@ -110,6 +112,18 @@ endif
GITWEB_FILES += static/git-logo.png static/git-favicon.png
+# JavaScript files that are composed (concatenated) to form gitweb.js
+#
+# js/lib/common-lib.js should be always first, then js/lib/*.js,
+# then the rest of files; js/gitweb.js should be last (if it exists)
+GITWEB_JSLIB_FILES += static/js/lib/common-lib.js
+GITWEB_JSLIB_FILES += static/js/lib/datetime.js
+GITWEB_JSLIB_FILES += static/js/lib/cookies.js
+GITWEB_JSLIB_FILES += static/js/javascript-detection.js
+GITWEB_JSLIB_FILES += static/js/adjust-timezone.js
+GITWEB_JSLIB_FILES += static/js/blame_incremental.js
+
+
GITWEB_REPLACE = \
-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
-e 's|++GIT_BINDIR++|$(bindir)|g' \
@@ -129,7 +143,8 @@ GITWEB_REPLACE = \
-e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
-e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
-e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
- -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g'
+ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
+ -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g'
GITWEB-BUILD-OPTIONS: FORCE
@rm -f $@+
@@ -143,6 +158,20 @@ gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS
chmod +x $@+ && \
mv $@+ $@
+static/gitweb.js: $(GITWEB_JSLIB_FILES)
+ $(QUIET_GEN)$(RM) $@ $@+ && \
+ cat $^ >$@+ && \
+ mv $@+ $@
+
+### Testing rules
+
+test:
+ $(MAKE) -C ../t gitweb-test
+
+test-installed:
+ GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \
+ $(MAKE) -C ../t gitweb-test
+
### Installation rules
install: all
@@ -156,5 +185,5 @@ install: all
clean:
$(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
-.PHONY: all clean install .FORCE-GIT-VERSION-FILE FORCE
+.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE
diff --git a/gitweb/README b/gitweb/README
index 0e19be8..a4cfcb4 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -7,115 +7,6 @@ The one working on:
From the git version 1.4.0 gitweb is bundled with git.
-How to configure gitweb for your local system
----------------------------------------------
-
-See also the "Build time configuration" section in the INSTALL
-file for gitweb (in gitweb/INSTALL).
-
-You can specify the following configuration variables when building GIT:
- * GIT_BINDIR
- Points where to find the git executable. You should set it up to
- the place where the git binary was installed (usually /usr/bin) if you
- don't install git from sources together with gitweb. [Default: $(bindir)]
- * GITWEB_SITENAME
- Shown in the title of all generated pages, defaults to the server name
- (SERVER_NAME CGI environment variable) if not set. [No default]
- * GITWEB_PROJECTROOT
- The root directory for all projects shown by gitweb. Must be set
- correctly for gitweb to find repositories to display. See also
- "Gitweb repositories" in the INSTALL file for gitweb. [Default: /pub/git]
- * GITWEB_PROJECT_MAXDEPTH
- The filesystem traversing limit for getting the project list; the number
- is taken as depth relative to the projectroot. It is used when
- GITWEB_LIST is a directory (or is not set; then project root is used).
- Is is meant to speed up project listing on large work trees by limiting
- search depth. [Default: 2007]
- * GITWEB_LIST
- Points to a directory to scan for projects (defaults to project root
- if not set / if empty) or to a file with explicit listing of projects
- (together with projects' ownership). See "Generating projects list
- using gitweb" in INSTALL file for gitweb to find out how to generate
- such file from scan of a directory. [No default, which means use root
- directory for projects]
- * GITWEB_EXPORT_OK
- Show repository only if this file exists (in repository). Only
- effective if this variable evaluates to true. [No default / Not set]
- * GITWEB_STRICT_EXPORT
- Only allow viewing of repositories also shown on the overview page.
- This for example makes GITWEB_EXPORT_OK to decide if repository is
- available and not only if it is shown. If GITWEB_LIST points to
- file with list of project, only those repositories listed would be
- available for gitweb. [No default]
- * GITWEB_HOMETEXT
- Points to an .html file which is included on the gitweb project
- overview page ('projects_list' view), if it exists. Relative to
- gitweb.cgi script. [Default: indextext.html]
- * GITWEB_SITE_HEADER
- Filename of html text to include at top of each page. Relative to
- gitweb.cgi script. [No default]
- * GITWEB_SITE_FOOTER
- Filename of html text to include at bottom of each page. Relative to
- gitweb.cgi script. [No default]
- * GITWEB_HOME_LINK_STR
- String of the home link on top of all pages, leading to $home_link
- (usually main gitweb page, which means projects list). Used as first
- part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
- [Default: projects]
- * GITWEB_SITENAME
- Name of your site or organization to appear in page titles. Set it
- to something descriptive for clearer bookmarks etc. If not set
- (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
- SERVER_NAME CGI environment variable is not set (e.g. if running
- gitweb as standalone script). [No default]
- * GITWEB_BASE_URL
- Git base URLs used for URL to where fetch project from, i.e. full
- URL is "$git_base_url/$project". Shown on projects summary page.
- Repository URL for project can be also configured per repository; this
- takes precedence over URLs composed from base URL and a project name.
- Note that you can setup multiple base URLs (for example one for
- git:// protocol access, another for http:// access) from the gitweb
- config file. [No default]
- * GITWEB_CSS
- Points to the location where you put gitweb.css on your web server
- (or to be more generic, the URI of gitweb stylesheet). Relative to the
- base URI of gitweb. Note that you can setup multiple stylesheets from
- the gitweb config file. [Default: static/gitweb.css (or
- static/gitweb.min.css if the CSSMIN variable is defined / CSS minifier
- is used)]
- * GITWEB_LOGO
- Points to the location where you put git-logo.png on your web server
- (or to be more generic URI of logo, 72x27 size, displayed in top right
- corner of each gitweb page, and used as logo for Atom feed). Relative
- to base URI of gitweb. [Default: static/git-logo.png]
- * GITWEB_FAVICON
- Points to the location where you put git-favicon.png on your web server
- (or to be more generic URI of favicon, assumed to be image/png type;
- web browsers that support favicons (website icons) may display them
- in the browser's URL bar and next to site name in bookmarks). Relative
- to base URI of gitweb. [Default: static/git-favicon.png]
- * GITWEB_JS
- Points to the localtion where you put gitweb.js on your web server
- (or to be more generic URI of JavaScript code used by gitweb).
- Relative to base URI of gitweb. [Default: static/gitweb.js (or
- static/gitweb.min.js if JSMIN build variable is defined / JavaScript
- minifier is used)]
- * GITWEB_CONFIG
- This Perl file will be loaded using 'do' and can be used to override any
- of the options above as well as some other options -- see the "Runtime
- gitweb configuration" section below, and top of 'gitweb.cgi' for their
- full list and description. If the environment variable GITWEB_CONFIG
- is set when gitweb.cgi is executed, then the file specified in the
- environment variable will be loaded instead of the file specified
- when gitweb.cgi was created. [Default: gitweb_config.perl]
- * GITWEB_CONFIG_SYSTEM
- This Perl file will be loaded using 'do' as a fallback if GITWEB_CONFIG
- does not exist. If the environment variable GITWEB_CONFIG_SYSTEM is set
- when gitweb.cgi is executed, then the file specified in the environment
- variable will be loaded instead of the file specified when gitweb.cgi was
- created. [Default: /etc/gitweb.conf]
-
-
Runtime gitweb configuration
----------------------------
@@ -172,13 +63,15 @@ not include variables usually directly set during build):
* $my_url, $my_uri
Full URL and absolute URL of gitweb script;
in earlier versions of gitweb you might have need to set those
- variables, now there should be no need to do it.
+ variables, now there should be no need to do it. See
+ $per_request_config if you need to set them still.
* $base_url
Base URL for relative URLs in pages generated by gitweb,
(e.g. $logo, $favicon, @stylesheets if they are relative URLs),
needed and used only for URLs with nonempty PATH_INFO via
<base href="$base_url">. Usually gitweb sets its value correctly,
and there is no need to set this variable, e.g. to $my_uri or "/".
+ See $per_request_config if you need to set it anyway.
* $home_link
Target of the home link on top of all pages (the first part of view
"breadcrumbs"). By default set to absolute URI of a page ($my_uri).
@@ -200,6 +93,15 @@ not include variables usually directly set during build):
full description is available as 'title' attribute (usually shown on
mouseover). By default set to 25, which might be too small if you
use long project descriptions.
+ * $projects_list_group_categories
+ Enables the grouping of projects by category on the project list page.
+ The category of a project is determined by the $GIT_DIR/category
+ file or the 'gitweb.category' variable in its repository configuration.
+ Disabled by default.
+ * $project_list_default_category
+ Default category for projects for which none is specified. If set
+ to the empty string, such projects will remain uncategorized and
+ listed at the top, above categorized projects.
* @git_base_url_list
List of git base URLs used for URL to where fetch project from, shown
in project summary page. Full URL is "$git_base_url/$project".
@@ -233,10 +135,24 @@ not include variables usually directly set during build):
is false.
* $maxload
Used to set the maximum load that we will still respond to gitweb queries.
- If server load exceed this value then return "503 Service Unavaliable" error.
+ If server load exceed this value then return "503 Service Unavailable" error.
Server load is taken to be 0 if gitweb cannot determine its value. Set it to
undefined value to turn it off. The default is 300.
-
+ * $highlight_bin
+ Path to the highlight executable to use (must be the one from
+ http://www.andre-simon.de due to assumptions about parameters and output).
+ Useful if highlight is not installed on your webserver's PATH.
+ [Default: highlight]
+ * $per_request_config
+ If set to code reference, it would be run once per each request. You can
+ set parts of configuration that change per session, e.g. by setting it to
+ sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
+ Otherwise it is treated as boolean value: if true gitweb would process
+ config file once per request, if false it would process config file only
+ once. Note: $my_url, $my_uri, and $base_url are overwritten with
+ their default values before every request, so if you want to change
+ them, be sure to set this variable to true or a code reference effecting
+ the desired changes. The default is true.
Projects list file format
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -293,6 +209,13 @@ You can use the following files in repository:
from the template during repository creation. You can use the
gitweb.description repo configuration variable, but the file takes
precedence.
+ * category (or gitweb.category)
+ Singe line category of a project, used to group projects if
+ $projects_list_group_categories is enabled. By default (file and
+ configuration variable absent), uncategorized projects are put in
+ the $project_list_default_category category. You can use the
+ gitweb.category repo configuration variable, but the file takes
+ precedence.
* cloneurl (or multiple-valued gitweb.url)
File with repository URL (used for clone and fetch), one per line.
Displayed in the project summary page. You can use multiple-valued
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 9446376..dab89f2 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -7,6 +7,7 @@
#
# This program is licensed under the GPLv2
+use 5.008;
use strict;
use warnings;
use CGI qw(:standard :escapeHTML -nosticky);
@@ -16,12 +17,10 @@ use Encode;
use Fcntl ':mode';
use File::Find qw();
use File::Basename qw(basename);
+use Time::HiRes qw(gettimeofday tv_interval);
binmode STDOUT, ':utf8';
-our $t0;
-if (eval { require Time::HiRes; 1; }) {
- $t0 = [Time::HiRes::gettimeofday()];
-}
+our $t0 = [ gettimeofday() ];
our $number_of_git_cmds = 0;
BEGIN {
@@ -116,6 +115,14 @@ our $projects_list = "++GITWEB_LIST++";
# the width (in characters) of the projects list "Description" column
our $projects_list_description_width = 25;
+# group projects by category on the projects list
+# (enabled if this variable evaluates to true)
+our $projects_list_group_categories = 0;
+
+# default category if none specified
+# (leave the empty string for no category)
+our $project_list_default_category = "";
+
# default order of projects list
# valid values are none, project, descr, owner, and age
our $default_projects_order = "project";
@@ -165,6 +172,12 @@ our @diff_opts = ('-M'); # taken from git_commit
# the gitweb domain.
our $prevent_xss = 0;
+# Path to the highlight executable to use (must be the one from
+# http://www.andre-simon.de due to assumptions about parameters and output).
+# Useful if highlight is not installed on your webserver's PATH.
+# [Default: highlight]
+our $highlight_bin = "++HIGHLIGHT_BIN++";
+
# information about snapshot formats that gitweb is capable of serving
our %known_snapshot_formats = (
# name => {
@@ -181,7 +194,7 @@ our %known_snapshot_formats = (
'type' => 'application/x-gzip',
'suffix' => '.tar.gz',
'format' => 'tar',
- 'compressor' => ['gzip']},
+ 'compressor' => ['gzip', '-n']},
'tbz2' => {
'display' => 'tar.bz2',
@@ -232,6 +245,30 @@ our %avatar_size = (
# Leave it undefined (or set to 'undef') to turn off load checking.
our $maxload = 300;
+# configuration for 'highlight' (http://www.andre-simon.de/)
+# match by basename
+our %highlight_basename = (
+ #'Program' => 'py',
+ #'Library' => 'py',
+ 'SConstruct' => 'py', # SCons equivalent of Makefile
+ 'Makefile' => 'make',
+);
+# match by extension
+our %highlight_ext = (
+ # main extensions, defining name of syntax;
+ # see files in /usr/share/highlight/langDefs/ directory
+ map { $_ => $_ }
+ qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make),
+ # alternate extensions, see /etc/highlight/filetypes.conf
+ 'h' => 'c',
+ map { $_ => 'sh' } qw(bash zsh ksh),
+ map { $_ => 'cpp' } qw(cxx c++ cc),
+ map { $_ => 'php' } qw(php3 php4 php5 phps),
+ map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi'
+ map { $_ => 'make'} qw(mak mk),
+ map { $_ => 'xml' } qw(xhtml html htm),
+);
+
# You define site-wide feature defaults here; override them with
# $GITWEB_CONFIG as necessary.
our %feature = (
@@ -245,7 +282,7 @@ our %feature = (
# return value of feature-sub indicates if to enable specified feature
#
# if there is no 'sub' key (no feature-sub), then feature cannot be
- # overriden
+ # overridden
#
# use gitweb_get_feature(<feature>) to retrieve the <feature> value
# (an array) or gitweb_check_feature(<feature>) to check if <feature>
@@ -284,6 +321,10 @@ our %feature = (
# Enable text search, which will list the commits which match author,
# committer or commit text to a given string. Enabled by default.
# Project specific override is not supported.
+ #
+ # Note that this controls all search features, which means that if
+ # it is disabled, then 'grep' and 'pickaxe' search would also be
+ # disabled.
'search' => {
'override' => 0,
'default' => [1]},
@@ -291,6 +332,7 @@ our %feature = (
# Enable grep search, which will list the files in currently selected
# tree containing the given string. Enabled by default. This can be
# potentially CPU-intensive, of course.
+ # Note that you need to have 'search' feature enabled too.
# To enable system wide have in $GITWEB_CONFIG
# $feature{'grep'}{'default'} = [1];
@@ -305,6 +347,7 @@ our %feature = (
# Enable the pickaxe search, which will list the commits that modified
# a given string in a file. This can be practical and quite faster
# alternative to 'blame', but still potentially CPU-intensive.
+ # Note that you need to have 'search' feature enabled too.
# To enable system wide have in $GITWEB_CONFIG
# $feature{'pickaxe'}{'default'} = [1];
@@ -383,20 +426,23 @@ our %feature = (
'override' => 0,
'default' => []},
- # Allow gitweb scan project content tags described in ctags/
- # of project repository, and display the popular Web 2.0-ish
- # "tag cloud" near the project list. Note that this is something
- # COMPLETELY different from the normal Git tags.
+ # Allow gitweb scan project content tags of project repository,
+ # and display the popular Web 2.0-ish "tag cloud" near the projects
+ # list. Note that this is something COMPLETELY different from the
+ # normal Git tags.
# gitweb by itself can show existing tags, but it does not handle
- # tagging itself; you need an external application for that.
- # For an example script, check Girocco's cgi/tagproj.cgi.
+ # tagging itself; you need to do it externally, outside gitweb.
+ # The format is described in git_get_project_ctags() subroutine.
# You may want to install the HTML::TagCloud Perl module to get
# a pretty tag cloud instead of just a list of tags.
# To enable system wide have in $GITWEB_CONFIG
- # $feature{'ctags'}{'default'} = ['path_to_tag_script'];
+ # $feature{'ctags'}{'default'} = [1];
# Project specific override is not supported.
+
+ # In the future whether ctags editing is enabled might depend
+ # on the value, but using 1 should always mean no editing of ctags.
'ctags' => {
'override' => 0,
'default' => [0]},
@@ -451,6 +497,18 @@ our %feature = (
'override' => 0,
'default' => [0]},
+ # Enable and configure ability to change common timezone for dates
+ # in gitweb output via JavaScript. Enabled by default.
+ # Project specific override is not supported.
+ 'javascript-timezone' => {
+ 'override' => 0,
+ 'default' => [
+ 'local', # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
+ # or undef to turn off this feature
+ 'gitweb_tz', # name of cookie where to store selected timezone
+ 'datetime', # CSS class used to mark up dates for manipulation
+ ]},
+
# Syntax highlighting support. This is based on Daniel Svensson's
# and Sham Chukoury's work in gitweb-xmms2.git.
# It requires the 'highlight' program present in $PATH,
@@ -463,6 +521,18 @@ our %feature = (
'sub' => sub { feature_bool('highlight', @_) },
'override' => 0,
'default' => [0]},
+
+ # Enable displaying of remote heads in the heads list
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'remote_heads'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'remote_heads'}{'override'} = 1;
+ # and in project config gitweb.remote_heads = 0|1;
+ 'remote_heads' => {
+ 'sub' => sub { feature_bool('remote_heads', @_) },
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_get_feature {
@@ -571,18 +641,38 @@ sub filter_snapshot_fmts {
!$known_snapshot_formats{$_}{'disabled'}} @fmts;
}
+# If it is set to code reference, it is code that it is to be run once per
+# request, allowing updating configurations that change with each request,
+# while running other code in config file only once.
+#
+# Otherwise, if it is false then gitweb would process config file only once;
+# if it is true then gitweb config would be run for each request.
+our $per_request_config = 1;
+
+# read and parse gitweb config file given by its parameter.
+# returns true on success, false on recoverable error, allowing
+# to chain this subroutine, using first file that exists.
+# dies on errors during parsing config file, as it is unrecoverable.
+sub read_config_file {
+ my $filename = shift;
+ return unless defined $filename;
+ # die if there are errors parsing config file
+ if (-e $filename) {
+ do $filename;
+ die $@ if $@;
+ return 1;
+ }
+ return;
+}
+
our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
sub evaluate_gitweb_config {
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
- # die if there are errors parsing config file
- if (-e $GITWEB_CONFIG) {
- do $GITWEB_CONFIG;
- die $@ if $@;
- } elsif (-e $GITWEB_CONFIG_SYSTEM) {
- do $GITWEB_CONFIG_SYSTEM;
- die $@ if $@;
- }
+
+ # use first config file that exists
+ read_config_file($GITWEB_CONFIG) or
+ read_config_file($GITWEB_CONFIG_SYSTEM);
}
# Get loadavg of system, to compare against $maxload.
@@ -654,6 +744,7 @@ our @cgi_param_mapping = (
snapshot_format => "sf",
extra_options => "opt",
search_use_regexp => "sr",
+ ctag => "by_tag",
# this must be last entry (for manipulation from JavaScript)
javascript => "js"
);
@@ -677,6 +768,7 @@ our %actions = (
"log" => \&git_log,
"patch" => \&git_patch,
"patches" => \&git_patches,
+ "remotes" => \&git_remotes,
"rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search,
@@ -751,10 +843,10 @@ sub evaluate_path_info {
'history',
);
- # we want to catch
+ # we want to catch, among others
# [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
my ($parentrefname, $parentpathname, $refname, $pathname) =
- ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
+ ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
# first, analyze the 'current' part
if (defined $pathname) {
@@ -790,8 +882,15 @@ sub evaluate_path_info {
# hash_base instead. It should also be noted that hand-crafted
# links having 'history' as an action and no pathname or hash
# set will fail, but that happens regardless of PATH_INFO.
- $input_params{'action'} ||= "shortlog";
- if (grep { $_ eq $input_params{'action'} } @wants_base) {
+ if (defined $parentrefname) {
+ # if there is parent let the default be 'shortlog' action
+ # (for http://git.example.com/repo.git/A..B links); if there
+ # is no parent, dispatch will detect type of object and set
+ # action appropriately if required (if action is not set)
+ $input_params{'action'} ||= "shortlog";
+ }
+ if ($input_params{'action'} &&
+ grep { $_ eq $input_params{'action'} } @wants_base) {
$input_params{'hash_base'} ||= $refname;
} else {
$input_params{'hash'} ||= $refname;
@@ -1027,13 +1126,28 @@ sub dispatch {
$actions{$action}->();
}
-sub run_request {
- our $t0 = [Time::HiRes::gettimeofday()]
+sub reset_timer {
+ our $t0 = [ gettimeofday() ]
if defined $t0;
+ our $number_of_git_cmds = 0;
+}
+
+our $first_request = 1;
+sub run_request {
+ reset_timer();
evaluate_uri();
- evaluate_gitweb_config();
- evaluate_git_version();
+ if ($first_request) {
+ evaluate_gitweb_config();
+ evaluate_git_version();
+ }
+ if ($per_request_config) {
+ if (ref($per_request_config) eq 'CODE') {
+ $per_request_config->();
+ } elsif (!$first_request) {
+ evaluate_gitweb_config();
+ }
+ }
check_loadavg();
# $projectroot and $projects_list might be set in gitweb config file
@@ -1087,6 +1201,7 @@ sub evaluate_argv {
sub run {
evaluate_argv();
+ $first_request = 1;
$pre_listen_hook->()
if $pre_listen_hook;
@@ -1097,8 +1212,9 @@ sub run {
run_request();
- $pre_dispatch_hook->()
+ $post_dispatch_hook->()
if $post_dispatch_hook;
+ $first_request = 0;
last REQUEST if ($is_last_request->());
}
@@ -1125,11 +1241,15 @@ if (defined caller) {
# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base)
# -replay => 1 - start from a current view (replay with modifications)
# -path_info => 0|1 - don't use/use path_info URL (if possible)
+# -anchor => ANCHOR - add #ANCHOR to end of URL, implies -replay if used alone
sub href {
my %params = @_;
# default is to use -absolute url() i.e. $my_uri
my $href = $params{-full} ? $my_url : $my_uri;
+ # implicit -replay, must be first of implicit params
+ $params{-replay} = 1 if (keys %params == 1 && $params{-anchor});
+
$params{'project'} = $project unless exists $params{'project'};
if ($params{-replay}) {
@@ -1157,7 +1277,7 @@ sub href {
$href =~ s,/$,,;
# Then add the project name, if present
- $href .= "/".esc_url($params{'project'});
+ $href .= "/".esc_path_info($params{'project'});
delete $params{'project'};
# since we destructively absorb parameters, we keep this
@@ -1167,7 +1287,8 @@ sub href {
# Summary just uses the project path URL, any other action is
# added to the URL
if (defined $params{'action'}) {
- $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
+ $href .= "/".esc_path_info($params{'action'})
+ unless $params{'action'} eq 'summary';
delete $params{'action'};
}
@@ -1177,13 +1298,13 @@ sub href {
|| $params{'hash_parent'} || $params{'hash'});
if (defined $params{'hash_base'}) {
if (defined $params{'hash_parent_base'}) {
- $href .= esc_url($params{'hash_parent_base'});
+ $href .= esc_path_info($params{'hash_parent_base'});
# skip the file_parent if it's the same as the file_name
if (defined $params{'file_parent'}) {
if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
delete $params{'file_parent'};
} elsif ($params{'file_parent'} !~ /\.\./) {
- $href .= ":/".esc_url($params{'file_parent'});
+ $href .= ":/".esc_path_info($params{'file_parent'});
delete $params{'file_parent'};
}
}
@@ -1191,19 +1312,19 @@ sub href {
delete $params{'hash_parent'};
delete $params{'hash_parent_base'};
} elsif (defined $params{'hash_parent'}) {
- $href .= esc_url($params{'hash_parent'}). "..";
+ $href .= esc_path_info($params{'hash_parent'}). "..";
delete $params{'hash_parent'};
}
- $href .= esc_url($params{'hash_base'});
+ $href .= esc_path_info($params{'hash_base'});
if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
- $href .= ":/".esc_url($params{'file_name'});
+ $href .= ":/".esc_path_info($params{'file_name'});
delete $params{'file_name'};
}
delete $params{'hash'};
delete $params{'hash_base'};
} elsif (defined $params{'hash'}) {
- $href .= esc_url($params{'hash'});
+ $href .= esc_path_info($params{'hash'});
delete $params{'hash'};
}
@@ -1236,6 +1357,13 @@ sub href {
}
$href .= "?" . join(';', @result) if scalar @result;
+ # final transformation: trailing spaces must be escaped (URI-encoded)
+ $href =~ s/(\s+)$/CGI::escape($1)/e;
+
+ if ($params{-anchor}) {
+ $href .= "#".esc_param($params{-anchor});
+ }
+
return $href;
}
@@ -1318,16 +1446,33 @@ sub esc_param {
return $str;
}
-# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+# the quoting rules for path_info fragment are slightly different
+sub esc_path_info {
+ my $str = shift;
+ return undef unless defined $str;
+
+ # path_info doesn't treat '+' as space (specially), but '?' must be escaped
+ $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg;
+
+ return $str;
+}
+
+# quote unsafe chars in whole URL, so some characters cannot be quoted
sub esc_url {
my $str = shift;
return undef unless defined $str;
- $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
- $str =~ s/\+/%2B/g;
+ $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
$str =~ s/ /\+/g;
return $str;
}
+# quote unsafe characters in HTML attributes
+sub esc_attr {
+
+ # for XHTML conformance escaping '"' to '&quot;' is not enough
+ return esc_html(@_);
+}
+
# replace invalid utf8 character with SUBSTITUTION sequence
sub esc_html {
my $str = shift;
@@ -1733,7 +1878,7 @@ sub format_ref_marker {
hash=>$dest
)}, $name);
- $markers .= " <span class=\"$class\" title=\"$ref\">" .
+ $markers .= " <span class=\"".esc_attr($class)."\" title=\"".esc_attr($ref)."\">" .
$link . "</span>";
}
}
@@ -1817,7 +1962,7 @@ sub git_get_avatar {
return $pre_white .
"<img width=\"$size\" " .
"class=\"avatar\" " .
- "src=\"$url\" " .
+ "src=\"".esc_url($url)."\" " .
"alt=\"\" " .
"/>" . $post_white;
} else {
@@ -2455,37 +2600,94 @@ sub git_get_path_by_hash {
## ......................................................................
## git utility functions, directly accessing git repository
-sub git_get_project_description {
- my $path = shift;
+# get the value of config variable either from file named as the variable
+# itself in the repository ($GIT_DIR/$name file), or from gitweb.$name
+# configuration variable in the repository config file.
+sub git_get_file_or_project_config {
+ my ($path, $name) = @_;
$git_dir = "$projectroot/$path";
- open my $fd, '<', "$git_dir/description"
- or return git_get_project_config('description');
- my $descr = <$fd>;
+ open my $fd, '<', "$git_dir/$name"
+ or return git_get_project_config($name);
+ my $conf = <$fd>;
close $fd;
- if (defined $descr) {
- chomp $descr;
+ if (defined $conf) {
+ chomp $conf;
}
- return $descr;
+ return $conf;
}
-sub git_get_project_ctags {
+sub git_get_project_description {
my $path = shift;
+ return git_get_file_or_project_config($path, 'description');
+}
+
+sub git_get_project_category {
+ my $path = shift;
+ return git_get_file_or_project_config($path, 'category');
+}
+
+
+# supported formats:
+# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory)
+# - if its contents is a number, use it as tag weight,
+# - otherwise add a tag with weight 1
+# * $GIT_DIR/ctags file, each line is a tag (with weight 1)
+# the same value multiple times increases tag weight
+# * `gitweb.ctag' multi-valued repo config variable
+sub git_get_project_ctags {
+ my $project = shift;
my $ctags = {};
- $git_dir = "$projectroot/$path";
- opendir my $dh, "$git_dir/ctags"
- or return $ctags;
- foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
- open my $ct, '<', $_ or next;
- my $val = <$ct>;
- chomp $val;
- close $ct;
- my $ctag = $_; $ctag =~ s#.*/##;
- $ctags->{$ctag} = $val;
+ $git_dir = "$projectroot/$project";
+ if (opendir my $dh, "$git_dir/ctags") {
+ my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh);
+ foreach my $tagfile (@files) {
+ open my $ct, '<', $tagfile
+ or next;
+ my $val = <$ct>;
+ chomp $val if $val;
+ close $ct;
+
+ (my $ctag = $tagfile) =~ s#.*/##;
+ if ($val =~ /^\d+$/) {
+ $ctags->{$ctag} = $val;
+ } else {
+ $ctags->{$ctag} = 1;
+ }
+ }
+ closedir $dh;
+
+ } elsif (open my $fh, '<', "$git_dir/ctags") {
+ while (my $line = <$fh>) {
+ chomp $line;
+ $ctags->{$line}++ if $line;
+ }
+ close $fh;
+
+ } else {
+ my $taglist = config_to_multi(git_get_project_config('ctag'));
+ foreach my $tag (@$taglist) {
+ $ctags->{$tag}++;
+ }
}
- closedir $dh;
- $ctags;
+
+ return $ctags;
+}
+
+# return hash, where keys are content tags ('ctags'),
+# and values are sum of weights of given tag in every project
+sub git_gather_all_ctags {
+ my $projects = shift;
+ my $ctags = {};
+
+ foreach my $p (@$projects) {
+ foreach my $ct (keys %{$p->{'ctags'}}) {
+ $ctags->{$ct} += $p->{'ctags'}->{$ct};
+ }
+ }
+
+ return $ctags;
}
sub git_populate_project_tagcloud {
@@ -2503,33 +2705,49 @@ sub git_populate_project_tagcloud {
}
my $cloud;
+ my $matched = $cgi->param('by_tag');
if (eval { require HTML::TagCloud; 1; }) {
$cloud = HTML::TagCloud->new;
- foreach (sort keys %ctags_lc) {
+ foreach my $ctag (sort keys %ctags_lc) {
# Pad the title with spaces so that the cloud looks
# less crammed.
- my $title = $ctags_lc{$_}->{topname};
+ my $title = esc_html($ctags_lc{$ctag}->{topname});
$title =~ s/ /&nbsp;/g;
$title =~ s/^/&nbsp;/g;
$title =~ s/$/&nbsp;/g;
- $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
+ if (defined $matched && $matched eq $ctag) {
+ $title = qq(<span class="match">$title</span>);
+ }
+ $cloud->add($title, href(project=>undef, ctag=>$ctag),
+ $ctags_lc{$ctag}->{count});
}
} else {
- $cloud = \%ctags_lc;
+ $cloud = {};
+ foreach my $ctag (keys %ctags_lc) {
+ my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1);
+ if (defined $matched && $matched eq $ctag) {
+ $title = qq(<span class="match">$title</span>);
+ }
+ $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count};
+ $cloud->{$ctag}{ctag} =
+ $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title);
+ }
}
- $cloud;
+ return $cloud;
}
sub git_show_project_tagcloud {
my ($cloud, $count) = @_;
- print STDERR ref($cloud)."..\n";
if (ref $cloud eq 'HTML::TagCloud') {
return $cloud->html_and_css($count);
} else {
- my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
- return '<p align="center">' . join (', ', map {
- "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
- } splice(@tags, 0, $count)) . '</p>';
+ my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud;
+ return
+ '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' .
+ join (', ', map {
+ $cloud->{$_}->{'ctag'}
+ } splice(@tags, 0, $count)) .
+ '</div>';
}
}
@@ -2548,21 +2766,23 @@ sub git_get_project_url_list {
}
sub git_get_projects_list {
- my ($filter) = @_;
+ my $filter = shift || '';
my @list;
- $filter ||= '';
$filter =~ s/\.git$//;
- my $check_forks = gitweb_check_feature('forks');
-
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list . ($filter ? "/$filter" : '');
+ my $dir = $projects_list;
# remove the trailing "/"
$dir =~ s!/+$!!;
- my $pfxlen = length("$dir");
- my $pfxdepth = ($dir =~ tr!/!!);
+ my $pfxlen = length("$projects_list");
+ my $pfxdepth = ($projects_list =~ tr!/!!);
+ # when filtering, search only given subdirectory
+ if ($filter) {
+ $dir .= "/$filter";
+ $dir =~ s!/+$!!;
+ }
File::Find::find({
follow_fast => 1, # follow symbolic links
@@ -2577,14 +2797,14 @@ sub git_get_projects_list {
# only directories can be git repositories
return unless (-d $_);
# don't traverse too deep (Find is super slow on os x)
+ # $project_maxdepth excludes depth of $projectroot
if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
$File::Find::prune = 1;
return;
}
- my $subdir = substr($File::Find::name, $pfxlen + 1);
+ my $path = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- my $path = ($filter ? "$filter/" : '') . $subdir;
if (check_export_ok("$projectroot/$path")) {
push @list, { path => $path };
$File::Find::prune = 1;
@@ -2597,7 +2817,6 @@ sub git_get_projects_list {
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
- my %paths;
open my $fd, '<', $projects_list or return;
PROJECT:
while (my $line = <$fd>) {
@@ -2608,32 +2827,9 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if ($filter ne '') {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PROJECT;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PROJECT;
- }
- } elsif ($check_forks) {
- PATH:
- foreach my $filter (keys %paths) {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PATH;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PATH;
- }
- # is a fork, don't include it in
- # the list
- next PROJECT;
- }
+ # if $filter is rpovided, check if $path begins with $filter
+ if ($filter && $path !~ m!^\Q$filter\E/!) {
+ next;
}
if (check_export_ok("$projectroot/$path")) {
my $pr = {
@@ -2641,8 +2837,6 @@ sub git_get_projects_list {
owner => to_utf8($owner),
};
push @list, $pr;
- (my $forks_path = $path) =~ s/\.git$//;
- $paths{$forks_path}++;
}
}
close $fd;
@@ -2650,6 +2844,98 @@ sub git_get_projects_list {
return @list;
}
+# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
+# as side effects it sets 'forks' field to list of forks for forked projects
+sub filter_forks_from_projects_list {
+ my $projects = shift;
+
+ my %trie; # prefix tree of directories (path components)
+ # generate trie out of those directories that might contain forks
+ foreach my $pr (@$projects) {
+ my $path = $pr->{'path'};
+ $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
+ next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
+ next unless ($path); # skip '.git' repository: tests, git-instaweb
+ next unless (-d $path); # containing directory exists
+ $pr->{'forks'} = []; # there can be 0 or more forks of project
+
+ # add to trie
+ my @dirs = split('/', $path);
+ # walk the trie, until either runs out of components or out of trie
+ my $ref = \%trie;
+ while (scalar @dirs &&
+ exists($ref->{$dirs[0]})) {
+ $ref = $ref->{shift @dirs};
+ }
+ # create rest of trie structure from rest of components
+ foreach my $dir (@dirs) {
+ $ref = $ref->{$dir} = {};
+ }
+ # create end marker, store $pr as a data
+ $ref->{''} = $pr if (!exists $ref->{''});
+ }
+
+ # filter out forks, by finding shortest prefix match for paths
+ my @filtered;
+ PROJECT:
+ foreach my $pr (@$projects) {
+ # trie lookup
+ my $ref = \%trie;
+ DIR:
+ foreach my $dir (split('/', $pr->{'path'})) {
+ if (exists $ref->{''}) {
+ # found [shortest] prefix, is a fork - skip it
+ push @{$ref->{''}{'forks'}}, $pr;
+ next PROJECT;
+ }
+ if (!exists $ref->{$dir}) {
+ # not in trie, cannot have prefix, not a fork
+ push @filtered, $pr;
+ next PROJECT;
+ }
+ # If the dir is there, we just walk one step down the trie.
+ $ref = $ref->{$dir};
+ }
+ # we ran out of trie
+ # (shouldn't happen: it's either no match, or end marker)
+ push @filtered, $pr;
+ }
+
+ return @filtered;
+}
+
+# note: fill_project_list_info must be run first,
+# for 'descr_long' and 'ctags' to be filled
+sub search_projects_list {
+ my ($projlist, %opts) = @_;
+ my $tagfilter = $opts{'tagfilter'};
+ my $searchtext = $opts{'searchtext'};
+
+ return @$projlist
+ unless ($tagfilter || $searchtext);
+
+ my @projects;
+ PROJECT:
+ foreach my $pr (@$projlist) {
+
+ if ($tagfilter) {
+ next unless ref($pr->{'ctags'}) eq 'HASH';
+ next unless
+ grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
+ }
+
+ if ($searchtext) {
+ next unless
+ $pr->{'path'} =~ /$searchtext/ ||
+ $pr->{'descr_long'} =~ /$searchtext/;
+ }
+
+ push @projects, $pr;
+ }
+
+ return @projects;
+}
+
our $gitweb_project_owner = undef;
sub git_get_project_list_from_file {
@@ -2718,6 +3004,44 @@ sub git_get_last_activity {
return (undef, undef);
}
+# Implementation note: when a single remote is wanted, we cannot use 'git
+# remote show -n' because that command always work (assuming it's a remote URL
+# if it's not defined), and we cannot use 'git remote show' because that would
+# try to make a network roundtrip. So the only way to find if that particular
+# remote is defined is to walk the list provided by 'git remote -v' and stop if
+# and when we find what we want.
+sub git_get_remotes_list {
+ my $wanted = shift;
+ my %remotes = ();
+
+ open my $fd, '-|' , git_cmd(), 'remote', '-v';
+ return unless $fd;
+ while (my $remote = <$fd>) {
+ chomp $remote;
+ $remote =~ s!\t(.*?)\s+\((\w+)\)$!!;
+ next if $wanted and not $remote eq $wanted;
+ my ($url, $key) = ($1, $2);
+
+ $remotes{$remote} ||= { 'heads' => () };
+ $remotes{$remote}{$key} = $url;
+ }
+ close $fd or return;
+ return wantarray ? %remotes : \%remotes;
+}
+
+# Takes a hash of remotes as first parameter and fills it by adding the
+# available remote heads for each of the indicated remotes.
+sub fill_remote_heads {
+ my $remotes = shift;
+ my @heads = map { "remotes/$_" } keys %$remotes;
+ my @remoteheads = git_get_heads_list(undef, @heads);
+ foreach my $remote (keys %$remotes) {
+ $remotes->{$remote}{'heads'} = [ grep {
+ $_->{'name'} =~ s!^$remote/!!
+ } @remoteheads ];
+ }
+}
+
sub git_get_references {
my $type = shift || "";
my %refs;
@@ -2780,8 +3104,10 @@ sub parse_date {
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
- $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
- my $local = $epoch + ((int $1 + ($2/60)) * 3600);
+ my ($tz_sign, $tz_hour, $tz_min) =
+ ($tz =~ m/^([-+])(\d\d)(\d\d)$/);
+ $tz_sign = ($tz_sign eq '-' ? -1 : +1);
+ my $local = $epoch + $tz_sign*((($tz_hour*60) + $tz_min)*60);
($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
@@ -3116,13 +3442,15 @@ sub parse_from_to_diffinfo {
## parse to array of hashes functions
sub git_get_heads_list {
- my $limit = shift;
+ my ($limit, @classes) = @_;
+ @classes = ('heads') unless @classes;
+ my @patterns = map { "refs/$_" } @classes;
my @headslist;
open my $fd, '-|', git_cmd(), 'for-each-ref',
($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
'--format=%(objectname) %(refname) %(subject)%00%(committer)',
- 'refs/heads'
+ @patterns
or return;
while (my $line = <$fd>) {
my %ref_item;
@@ -3133,7 +3461,7 @@ sub git_get_heads_list {
my ($committer, $epoch, $tz) =
($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
$ref_item{'fullname'} = $name;
- $name =~ s!^refs/heads/!!;
+ $name =~ s!^refs/(?:head|remote)s/!!;
$ref_item{'name'} = $name;
$ref_item{'id'} = $hash;
@@ -3238,12 +3566,9 @@ sub mimetype_guess_file {
open(my $mh, '<', $mimemap) or return undef;
while (<$mh>) {
next if m/^#/; # skip comments
- my ($mimetype, $exts) = split(/\t+/);
- if (defined $exts) {
- my @exts = split(/\s+/, $exts);
- foreach my $ext (@exts) {
- $mimemap{$ext} = $mimetype;
- }
+ my ($mimetype, @exts) = split(/\s+/);
+ foreach my $ext (@exts) {
+ $mimemap{$ext} = $mimetype;
}
}
close($mh);
@@ -3312,30 +3637,6 @@ sub blob_contenttype {
sub guess_file_syntax {
my ($highlight, $mimetype, $file_name) = @_;
return undef unless ($highlight && defined $file_name);
-
- # configuration for 'highlight' (http://www.andre-simon.de/)
- # match by basename
- my %highlight_basename = (
- #'Program' => 'py',
- #'Library' => 'py',
- 'SConstruct' => 'py', # SCons equivalent of Makefile
- 'Makefile' => 'make',
- );
- # match by extension
- my %highlight_ext = (
- # main extensions, defining name of syntax;
- # see files in /usr/share/highlight/langDefs/ directory
- map { $_ => $_ }
- qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
- # alternate extensions, see /etc/highlight/filetypes.conf
- 'h' => 'c',
- map { $_ => 'cpp' } qw(cxx c++ cc),
- map { $_ => 'php' } qw(php3 php4),
- map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi'
- 'mak' => 'make',
- map { $_ => 'xml' } qw(xhtml html htm),
- );
-
my $basename = basename($file_name, '.in');
return $highlight_basename{$basename}
if exists $highlight_basename{$basename};
@@ -3354,10 +3655,10 @@ sub run_highlighter {
my ($fd, $highlight, $syntax) = @_;
return $fd unless ($highlight && defined $syntax);
- close $fd
- or die_error(404, "Reading blob failed");
+ close $fd;
open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
- "highlight --xhtml --fragment --syntax $syntax |"
+ quote_command($highlight_bin).
+ " --replace-tabs=8 --fragment --syntax $syntax |"
or die_error(500, "Couldn't open file or run syntax highlighter");
return $fd;
}
@@ -3383,13 +3684,7 @@ sub get_page_title {
return $title;
}
-sub git_header_html {
- my $status = shift || "200 OK";
- my $expires = shift;
- my %opts = @_;
-
- my $title = get_page_title();
- my $content_type;
+sub get_content_type_html {
# require explicit support from the UA if we are to send the page as
# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
# we have to do this because MSIE sometimes globs '*/*', pretending to
@@ -3397,52 +3692,24 @@ sub git_header_html {
if (defined $cgi->http('HTTP_ACCEPT') &&
$cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
$cgi->Accept('application/xhtml+xml') != 0) {
- $content_type = 'application/xhtml+xml';
- } else {
- $content_type = 'text/html';
- }
- print $cgi->header(-type=>$content_type, -charset => 'utf-8',
- -status=> $status, -expires => $expires)
- unless ($opts{'-no_http_header'});
- my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
- print <<EOF;
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
-<!-- git core binaries version $git_version -->
-<head>
-<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
-<meta name="robots" content="index, nofollow"/>
-<title>$title</title>
-EOF
- # the stylesheet, favicon etc urls won't work correctly with path_info
- # unless we set the appropriate base URL
- if ($ENV{'PATH_INFO'}) {
- print "<base href=\"".esc_url($base_url)."\" />\n";
- }
- # print out each stylesheet that exist, providing backwards capability
- # for those people who defined $stylesheet in a config file
- if (defined $stylesheet) {
- print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ return 'application/xhtml+xml';
} else {
- foreach my $stylesheet (@stylesheets) {
- next unless $stylesheet;
- print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
- }
+ return 'text/html';
}
+}
+
+sub print_feed_meta {
if (defined $project) {
my %href_params = get_feed_info();
if (!exists $href_params{'-title'}) {
$href_params{'-title'} = 'log';
}
- foreach my $format qw(RSS Atom) {
+ foreach my $format (qw(RSS Atom)) {
my $type = lc($format);
my %link_attr = (
'-rel' => 'alternate',
- '-title' => "$project - $href_params{'-title'} - $format feed",
+ '-title' => esc_attr("$project - $href_params{'-title'} - $format feed"),
'-type' => "application/$type+xml"
);
@@ -3469,71 +3736,141 @@ EOF
} else {
printf('<link rel="alternate" title="%s projects list" '.
'href="%s" type="text/plain; charset=utf-8" />'."\n",
- $site_name, href(project=>undef, action=>"project_index"));
+ esc_attr($site_name), href(project=>undef, action=>"project_index"));
printf('<link rel="alternate" title="%s projects feeds" '.
'href="%s" type="text/x-opml" />'."\n",
- $site_name, href(project=>undef, action=>"opml"));
- }
- if (defined $favicon) {
- print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
+ esc_attr($site_name), href(project=>undef, action=>"opml"));
}
+}
- print "</head>\n" .
- "<body>\n";
+sub print_header_links {
+ my $status = shift;
- if (defined $site_header && -f $site_header) {
- insert_file($site_header);
+ # print out each stylesheet that exist, providing backwards capability
+ # for those people who defined $stylesheet in a config file
+ if (defined $stylesheet) {
+ print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+ }
}
+ print_feed_meta()
+ if ($status eq '200 OK');
+ if (defined $favicon) {
+ print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
+ }
+}
+
+sub print_nav_breadcrumbs {
+ my %opts = @_;
- print "<div class=\"page_header\">\n" .
- $cgi->a({-href => esc_url($logo_url),
- -title => $logo_label},
- qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
if (defined $project) {
print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
if (defined $action) {
- print " / $action";
+ my $action_print = $action ;
+ if (defined $opts{-action_extra}) {
+ $action_print = $cgi->a({-href => href(action=>$action)},
+ $action);
+ }
+ print " / $action_print";
+ }
+ if (defined $opts{-action_extra}) {
+ print " / $opts{-action_extra}";
}
print "\n";
}
+}
+
+sub print_search_form {
+ if (!defined $searchtext) {
+ $searchtext = "";
+ }
+ my $search_hash;
+ if (defined $hash_base) {
+ $search_hash = $hash_base;
+ } elsif (defined $hash) {
+ $search_hash = $hash;
+ } else {
+ $search_hash = "HEAD";
+ }
+ my $action = $my_uri;
+ my $use_pathinfo = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ $action .= "/".esc_url($project);
+ }
+ print $cgi->startform(-method => "get", -action => $action) .
+ "<div class=\"search\">\n" .
+ (!$use_pathinfo &&
+ $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+ $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+ $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
+ $cgi->popup_menu(-name => 'st', -default => 'commit',
+ -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
+ $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+ " search:\n",
+ $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+ "<span title=\"Extended regular expression\">" .
+ $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+ -checked => $search_use_regexp) .
+ "</span>" .
+ "</div>" .
+ $cgi->end_form() . "\n";
+}
+
+sub git_header_html {
+ my $status = shift || "200 OK";
+ my $expires = shift;
+ my %opts = @_;
+
+ my $title = get_page_title();
+ my $content_type = get_content_type_html();
+ print $cgi->header(-type=>$content_type, -charset => 'utf-8',
+ -status=> $status, -expires => $expires)
+ unless ($opts{'-no_http_header'});
+ my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
+ print <<EOF;
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- git core binaries version $git_version -->
+<head>
+<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
+<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
+<meta name="robots" content="index, nofollow"/>
+<title>$title</title>
+EOF
+ # the stylesheet, favicon etc urls won't work correctly with path_info
+ # unless we set the appropriate base URL
+ if ($ENV{'PATH_INFO'}) {
+ print "<base href=\"".esc_url($base_url)."\" />\n";
+ }
+ print_header_links($status);
+ print "</head>\n" .
+ "<body>\n";
+
+ if (defined $site_header && -f $site_header) {
+ insert_file($site_header);
+ }
+
+ print "<div class=\"page_header\">\n";
+ if (defined $logo) {
+ print $cgi->a({-href => esc_url($logo_url),
+ -title => $logo_label},
+ $cgi->img({-src => esc_url($logo),
+ -width => 72, -height => 27,
+ -alt => "git",
+ -class => "logo"}));
+ }
+ print_nav_breadcrumbs(%opts);
print "</div>\n";
my $have_search = gitweb_check_feature('search');
if (defined $project && $have_search) {
- if (!defined $searchtext) {
- $searchtext = "";
- }
- my $search_hash;
- if (defined $hash_base) {
- $search_hash = $hash_base;
- } elsif (defined $hash) {
- $search_hash = $hash;
- } else {
- $search_hash = "HEAD";
- }
- my $action = $my_uri;
- my $use_pathinfo = gitweb_check_feature('pathinfo');
- if ($use_pathinfo) {
- $action .= "/".esc_url($project);
- }
- print $cgi->startform(-method => "get", -action => $action) .
- "<div class=\"search\">\n" .
- (!$use_pathinfo &&
- $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
- $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
- $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
- $cgi->popup_menu(-name => 'st', -default => 'commit',
- -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
- $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
- " search:\n",
- $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
- "<span title=\"Extended regular expression\">" .
- $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
- -checked => $search_use_regexp) .
- "</span>" .
- "</div>" .
- $cgi->end_form() . "\n";
+ print_search_form();
}
}
@@ -3553,7 +3890,7 @@ sub git_footer_html {
}
$href_params{'-title'} ||= 'log';
- foreach my $format qw(RSS Atom) {
+ foreach my $format (qw(RSS Atom)) {
$href_params{'action'} = lc($format);
print $cgi->a({-href => href(%href_params),
-title => "$href_params{'-title'} $format feed",
@@ -3572,7 +3909,7 @@ sub git_footer_html {
print "<div id=\"generating_info\">\n";
print 'This page took '.
'<span id="generating_time" class="time_span">'.
- Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+ tv_interval($t0, [ gettimeofday() ]).
' seconds </span>'.
' and '.
'<span id="generating_cmd">'.
@@ -3586,16 +3923,27 @@ sub git_footer_html {
insert_file($site_footer);
}
- print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+ print qq!<script type="text/javascript" src="!.esc_url($javascript).qq!"></script>\n!;
if (defined $action &&
$action eq 'blame_incremental') {
print qq!<script type="text/javascript">\n!.
qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
qq! "!. href() .qq!");\n!.
qq!</script>\n!;
- } elsif (gitweb_check_feature('javascript-actions')) {
+ } else {
+ my ($jstimezone, $tz_cookie, $datetime_class) =
+ gitweb_get_feature('javascript-timezone');
+
print qq!<script type="text/javascript">\n!.
- qq!window.onload = fixLinks;\n!.
+ qq!window.onload = function () {\n!;
+ if (gitweb_check_feature('javascript-actions')) {
+ print qq! fixLinks();\n!;
+ }
+ if ($jstimezone && $tz_cookie && $datetime_class) {
+ print qq! var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days
+ qq! onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!;
+ }
+ print qq!};\n!.
qq!</script>\n!;
}
@@ -3700,6 +4048,19 @@ sub git_print_page_nav {
"</div>\n";
}
+# returns a submenu for the nagivation of the refs views (tags, heads,
+# remotes) with the current view disabled and the remotes view only
+# available if the feature is enabled
+sub format_ref_views {
+ my ($current) = @_;
+ my @ref_views = qw{tags heads};
+ push @ref_views, 'remotes' if gitweb_check_feature('remote_heads');
+ return join " | ", map {
+ $_ eq $current ? $_ :
+ $cgi->a({-href => href(action=>$_)}, $_)
+ } @ref_views
+}
+
sub format_paging_nav {
my ($action, $page, $has_next_link) = @_;
my $paging_nav;
@@ -3743,22 +4104,68 @@ sub git_print_header_div {
"\n</div>\n";
}
-sub print_local_time {
- print format_local_time(@_);
+sub format_repo_url {
+ my ($name, $url) = @_;
+ return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\n";
}
-sub format_local_time {
- my $localtime = '';
- my %date = @_;
- if ($date{'hour_local'} < 6) {
- $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
- $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
- } else {
- $localtime .= sprintf(" (%02d:%02d %s)",
- $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+# Group output by placing it in a DIV element and adding a header.
+# Options for start_div() can be provided by passing a hash reference as the
+# first parameter to the function.
+# Options to git_print_header_div() can be provided by passing an array
+# reference. This must follow the options to start_div if they are present.
+# The content can be a scalar, which is output as-is, a scalar reference, which
+# is output after html escaping, an IO handle passed either as *handle or
+# *handle{IO}, or a function reference. In the latter case all following
+# parameters will be taken as argument to the content function call.
+sub git_print_section {
+ my ($div_args, $header_args, $content);
+ my $arg = shift;
+ if (ref($arg) eq 'HASH') {
+ $div_args = $arg;
+ $arg = shift;
+ }
+ if (ref($arg) eq 'ARRAY') {
+ $header_args = $arg;
+ $arg = shift;
+ }
+ $content = $arg;
+
+ print $cgi->start_div($div_args);
+ git_print_header_div(@$header_args);
+
+ if (ref($content) eq 'CODE') {
+ $content->(@_);
+ } elsif (ref($content) eq 'SCALAR') {
+ print esc_html($$content);
+ } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') {
+ print <$content>;
+ } elsif (!ref($content) && defined($content)) {
+ print $content;
+ }
+
+ print $cgi->end_div;
+}
+
+sub format_timestamp_html {
+ my $date = shift;
+ my $strtime = $date->{'rfc2822'};
+
+ my (undef, undef, $datetime_class) =
+ gitweb_get_feature('javascript-timezone');
+ if ($datetime_class) {
+ $strtime = qq!<span class="$datetime_class">$strtime</span>!;
+ }
+
+ my $localtime_format = '(%02d:%02d %s)';
+ if ($date->{'hour_local'} < 6) {
+ $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)';
}
+ $strtime .= ' ' .
+ sprintf($localtime_format,
+ $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'});
- return $localtime;
+ return $strtime;
}
# Outputs the author name and date in long form
@@ -3771,16 +4178,15 @@ sub git_print_authorship {
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
print "<$tag class=\"author_date\">" .
format_search_author($author, "author", esc_html($author)) .
- " [$ad{'rfc2822'}";
- print_local_time(%ad) if ($opts{-localtime});
- print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
- . "</$tag>\n";
+ " [".format_timestamp_html(\%ad)."]".
+ git_get_avatar($co->{'author_email'}, -pad_before => 1) .
+ "</$tag>\n";
}
# Outputs table rows containing the full author or committer information,
-# in the format expected for 'commit' view (& similia).
+# in the format expected for 'commit' view (& similar).
# Parameters are a commit hash reference, followed by the list of people
-# to output information for. If the list is empty it defalts to both
+# to output information for. If the list is empty it defaults to both
# author and committer.
sub git_print_authorship_rows {
my $co = shift;
@@ -3791,16 +4197,16 @@ sub git_print_authorship_rows {
my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
print "<tr><td>$who</td><td>" .
format_search_author($co->{"${who}_name"}, $who,
- esc_html($co->{"${who}_name"})) . " " .
+ esc_html($co->{"${who}_name"})) . " " .
format_search_author($co->{"${who}_email"}, $who,
- esc_html("<" . $co->{"${who}_email"} . ">")) .
+ esc_html("<" . $co->{"${who}_email"} . ">")) .
"</td><td rowspan=\"2\">" .
git_get_avatar($co->{"${who}_email"}, -size => 'double') .
"</td></tr>\n" .
"<tr>" .
- "<td></td><td> $wd{'rfc2822'}";
- print_local_time(%wd);
- print "</td>" .
+ "<td></td><td>" .
+ format_timestamp_html(\%wd) .
+ "</td>" .
"</tr>\n";
}
}
@@ -4150,7 +4556,8 @@ sub git_difftree_body {
# link to patch
$patchno++;
print "<td class=\"link\">" .
- $cgi->a({-href => "#patch$patchno"}, "patch") .
+ $cgi->a({-href => href(-anchor=>"patch$patchno")},
+ "patch") .
" | " .
"</td>\n";
}
@@ -4227,7 +4634,7 @@ sub git_difftree_body {
}
if ($diff->{'from_mode'} ne ('0' x 6)) {
$from_mode_oct = oct $diff->{'from_mode'};
- if (S_ISREG($to_mode_oct)) { # only for regular file
+ if (S_ISREG($from_mode_oct)) { # only for regular file
$from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
}
$from_file_type = file_type($diff->{'from_mode'});
@@ -4247,8 +4654,9 @@ sub git_difftree_body {
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- print " | ";
+ print $cgi->a({-href => href(-anchor=>"patch$patchno")},
+ "patch") .
+ " | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$hash, file_name=>$diff->{'file'})},
@@ -4267,8 +4675,9 @@ sub git_difftree_body {
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- print " | ";
+ print $cgi->a({-href => href(-anchor=>"patch$patchno")},
+ "patch") .
+ " | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
hash_base=>$parent, file_name=>$diff->{'file'})},
@@ -4309,7 +4718,8 @@ sub git_difftree_body {
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ print $cgi->a({-href => href(-anchor=>"patch$patchno")},
+ "patch") .
" | ";
} elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
# "commit" view and modified file (not onlu mode changed)
@@ -4354,7 +4764,8 @@ sub git_difftree_body {
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ print $cgi->a({-href => href(-anchor=>"patch$patchno")},
+ "patch") .
" | ";
} elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
# "commit" view and modified file (not only pure rename or copy)
@@ -4509,8 +4920,8 @@ sub git_patchset_body {
print "</div>\n"; # class="patch"
}
- # for compact combined (--cc) format, with chunk and patch simpliciaction
- # patchset might be empty, but there might be unprocessed raw lines
+ # for compact combined (--cc) format, with chunk and patch simplification
+ # the patchset might be empty, but there might be unprocessed raw lines
for (++$patch_idx if $patch_number > 0;
$patch_idx < @$difftree;
++$patch_idx) {
@@ -4538,11 +4949,12 @@ sub git_patchset_body {
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-# fills project list info (age, description, owner, forks) for each
-# project in the list, removing invalid projects from returned list
+# fills project list info (age, description, owner, category, forks)
+# for each project in the list, removing invalid projects from
+# returned list
# NOTE: modifies $projlist, but does not remove entries from it
sub fill_project_list_info {
- my ($projlist, $check_forks) = @_;
+ my $projlist = shift;
my @projects;
my $show_ctags = gitweb_check_feature('ctags');
@@ -4562,23 +4974,59 @@ sub fill_project_list_info {
if (!defined $pr->{'owner'}) {
$pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
}
- if ($check_forks) {
- my $pname = $pr->{'path'};
- if (($pname =~ s/\.git$//) &&
- ($pname !~ /\/$/) &&
- (-d "$projectroot/$pname")) {
- $pr->{'forks'} = "-d $projectroot/$pname";
- } else {
- $pr->{'forks'} = 0;
- }
+ if ($show_ctags) {
+ $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
+ }
+ if ($projects_list_group_categories && !defined $pr->{'category'}) {
+ my $cat = git_get_project_category($pr->{'path'}) ||
+ $project_list_default_category;
+ $pr->{'category'} = to_utf8($cat);
}
- $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
+
push @projects, $pr;
}
return @projects;
}
+sub sort_projects_list {
+ my ($projlist, $order) = @_;
+ my @projects;
+
+ my %order_info = (
+ project => { key => 'path', type => 'str' },
+ descr => { key => 'descr_long', type => 'str' },
+ owner => { key => 'owner', type => 'str' },
+ age => { key => 'age', type => 'num' }
+ );
+ my $oi = $order_info{$order};
+ return @$projlist unless defined $oi;
+ if ($oi->{'type'} eq 'str') {
+ @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist;
+ } else {
+ @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist;
+ }
+
+ return @projects;
+}
+
+# returns a hash of categories, containing the list of project
+# belonging to each category
+sub build_projlist_by_category {
+ my ($projlist, $from, $to) = @_;
+ my %categories;
+
+ $from = 0 unless defined $from;
+ $to = $#$projlist if (!defined $to || $#$projlist < $to);
+
+ for (my $i = $from; $i <= $to; $i++) {
+ my $pr = $projlist->[$i];
+ push @{$categories{ $pr->{'category'} }}, $pr;
+ }
+
+ return wantarray ? %categories : \%categories;
+}
+
# print 'sort by' <th> element, generating 'sort by $name' replay link
# if that order is not selected
sub print_sort_th {
@@ -4602,70 +5050,15 @@ sub format_sort_th {
return $sort_th;
}
-sub git_project_list_body {
- # actually uses global variable $project
- my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
-
- my $check_forks = gitweb_check_feature('forks');
- my @projects = fill_project_list_info($projlist, $check_forks);
+sub git_project_list_rows {
+ my ($projlist, $from, $to, $check_forks) = @_;
- $order ||= $default_projects_order;
$from = 0 unless defined $from;
- $to = $#projects if (!defined $to || $#projects < $to);
-
- my %order_info = (
- project => { key => 'path', type => 'str' },
- descr => { key => 'descr_long', type => 'str' },
- owner => { key => 'owner', type => 'str' },
- age => { key => 'age', type => 'num' }
- );
- my $oi = $order_info{$order};
- if ($oi->{'type'} eq 'str') {
- @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;
- } else {
- @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
- }
-
- my $show_ctags = gitweb_check_feature('ctags');
- if ($show_ctags) {
- my %ctags;
- foreach my $p (@projects) {
- foreach my $ct (keys %{$p->{'ctags'}}) {
- $ctags{$ct} += $p->{'ctags'}->{$ct};
- }
- }
- my $cloud = git_populate_project_tagcloud(\%ctags);
- print git_show_project_tagcloud($cloud, 64);
- }
+ $to = $#$projlist if (!defined $to || $#$projlist < $to);
- print "<table class=\"project_list\">\n";
- unless ($no_header) {
- print "<tr>\n";
- if ($check_forks) {
- print "<th></th>\n";
- }
- print_sort_th('project', $order, 'Project');
- print_sort_th('descr', $order, 'Description');
- print_sort_th('owner', $order, 'Owner');
- print_sort_th('age', $order, 'Last Change');
- print "<th></th>\n" . # for links
- "</tr>\n";
- }
my $alternate = 1;
- my $tagfilter = $cgi->param('by_tag');
for (my $i = $from; $i <= $to; $i++) {
- my $pr = $projects[$i];
-
- next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};
- next if $searchtext and not $pr->{'path'} =~ /$searchtext/
- and not $pr->{'descr_long'} =~ /$searchtext/;
- # Weed out forks or non-matching entries of search
- if ($check_forks) {
- my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#;
- $forkbase="^$forkbase" if $forkbase;
- next if not $searchtext and not $tagfilter and $show_ctags
- and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe
- }
+ my $pr = $projlist->[$i];
if ($alternate) {
print "<tr class=\"dark\">\n";
@@ -4673,11 +5066,17 @@ sub git_project_list_body {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+
if ($check_forks) {
print "<td>";
if ($pr->{'forks'}) {
- print "<!-- $pr->{'forks'} -->\n";
- print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+ my $nforks = scalar @{$pr->{'forks'}};
+ if ($nforks > 0) {
+ print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
+ -title => "$nforks forks"}, "+");
+ } else {
+ print $cgi->span({-title => "$nforks forks"}, "+");
+ }
}
print "</td>\n";
}
@@ -4698,6 +5097,84 @@ sub git_project_list_body {
"</td>\n" .
"</tr>\n";
}
+}
+
+sub git_project_list_body {
+ # actually uses global variable $project
+ my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+ my @projects = @$projlist;
+
+ my $check_forks = gitweb_check_feature('forks');
+ my $show_ctags = gitweb_check_feature('ctags');
+ my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
+ $check_forks = undef
+ if ($tagfilter || $searchtext);
+
+ # filtering out forks before filling info allows to do less work
+ @projects = filter_forks_from_projects_list(\@projects)
+ if ($check_forks);
+ @projects = fill_project_list_info(\@projects);
+ # searching projects require filling to be run before it
+ @projects = search_projects_list(\@projects,
+ 'searchtext' => $searchtext,
+ 'tagfilter' => $tagfilter)
+ if ($tagfilter || $searchtext);
+
+ $order ||= $default_projects_order;
+ $from = 0 unless defined $from;
+ $to = $#projects if (!defined $to || $#projects < $to);
+
+ # short circuit
+ if ($from > $to) {
+ print "<center>\n".
+ "<b>No such projects found</b><br />\n".
+ "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n".
+ "</center>\n<br />\n";
+ return;
+ }
+
+ @projects = sort_projects_list(\@projects, $order);
+
+ if ($show_ctags) {
+ my $ctags = git_gather_all_ctags(\@projects);
+ my $cloud = git_populate_project_tagcloud($ctags);
+ print git_show_project_tagcloud($cloud, 64);
+ }
+
+ print "<table class=\"project_list\">\n";
+ unless ($no_header) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<th></th>\n";
+ }
+ print_sort_th('project', $order, 'Project');
+ print_sort_th('descr', $order, 'Description');
+ print_sort_th('owner', $order, 'Owner');
+ print_sort_th('age', $order, 'Last Change');
+ print "<th></th>\n" . # for links
+ "</tr>\n";
+ }
+
+ if ($projects_list_group_categories) {
+ # only display categories with projects in the $from-$to window
+ @projects = sort {$a->{'category'} cmp $b->{'category'}} @projects[$from..$to];
+ my %categories = build_projlist_by_category(\@projects, $from, $to);
+ foreach my $cat (sort keys %categories) {
+ unless ($cat eq "") {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<td></td>\n";
+ }
+ print "<td class=\"category\" colspan=\"5\">".esc_html($cat)."</td>\n";
+ print "</tr>\n";
+ }
+
+ git_project_list_rows($categories{$cat}, undef, undef, $check_forks);
+ }
+ } else {
+ git_project_list_rows(\@projects, $from, $to, $check_forks);
+ }
+
if (defined $extra) {
print "<tr>\n";
if ($check_forks) {
@@ -4721,7 +5198,6 @@ sub git_log_body {
next if !%co;
my $commit = $co{'id'};
my $ref = format_ref_marker($refs, $commit);
- my %ad = parse_date($co{'author_epoch'});
git_print_header_div('commit',
"<span class=\"age\">$co{'age_string'}</span>" .
esc_html($co{'title'}) . $ref,
@@ -4942,7 +5418,7 @@ sub git_heads_body {
"<td class=\"link\">" .
$cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
$cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
+ $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
"</td>\n" .
"</tr>";
}
@@ -4954,6 +5430,311 @@ sub git_heads_body {
print "</table>\n";
}
+# Display a single remote block
+sub git_remote_block {
+ my ($remote, $rdata, $limit, $head) = @_;
+
+ my $heads = $rdata->{'heads'};
+ my $fetch = $rdata->{'fetch'};
+ my $push = $rdata->{'push'};
+
+ my $urls_table = "<table class=\"projects_list\">\n" ;
+
+ if (defined $fetch) {
+ if ($fetch eq $push) {
+ $urls_table .= format_repo_url("URL", $fetch);
+ } else {
+ $urls_table .= format_repo_url("Fetch URL", $fetch);
+ $urls_table .= format_repo_url("Push URL", $push) if defined $push;
+ }
+ } elsif (defined $push) {
+ $urls_table .= format_repo_url("Push URL", $push);
+ } else {
+ $urls_table .= format_repo_url("", "No remote URL");
+ }
+
+ $urls_table .= "</table>\n";
+
+ my $dots;
+ if (defined $limit && $limit < @$heads) {
+ $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "...");
+ }
+
+ print $urls_table;
+ git_heads_body($heads, $head, 0, $limit, $dots);
+}
+
+# Display a list of remote names with the respective fetch and push URLs
+sub git_remotes_list {
+ my ($remotedata, $limit) = @_;
+ print "<table class=\"heads\">\n";
+ my $alternate = 1;
+ my @remotes = sort keys %$remotedata;
+
+ my $limited = $limit && $limit < @remotes;
+
+ $#remotes = $limit - 1 if $limited;
+
+ while (my $remote = shift @remotes) {
+ my $rdata = $remotedata->{$remote};
+ my $fetch = $rdata->{'fetch'};
+ my $push = $rdata->{'push'};
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td>" .
+ $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
+ -class=> "list name"},esc_html($remote)) .
+ "</td>";
+ print "<td class=\"link\">" .
+ (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
+ " | " .
+ (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
+ "</td>";
+
+ print "</tr>\n";
+ }
+
+ if ($limited) {
+ print "<tr>\n" .
+ "<td colspan=\"3\">" .
+ $cgi->a({-href => href(action=>"remotes")}, "...") .
+ "</td>\n" . "</tr>\n";
+ }
+
+ print "</table>";
+}
+
+# Display remote heads grouped by remote, unless there are too many
+# remotes, in which case we only display the remote names
+sub git_remotes_body {
+ my ($remotedata, $limit, $head) = @_;
+ if ($limit and $limit < keys %$remotedata) {
+ git_remotes_list($remotedata, $limit);
+ } else {
+ fill_remote_heads($remotedata);
+ while (my ($remote, $rdata) = each %$remotedata) {
+ git_print_section({-class=>"remote", -id=>$remote},
+ ["remotes", $remote, $remote], sub {
+ git_remote_block($remote, $rdata, $limit, $head);
+ });
+ }
+ }
+}
+
+sub git_search_message {
+ my %co = @_;
+
+ my $greptype;
+ if ($searchtype eq 'commit') {
+ $greptype = "--grep=";
+ } elsif ($searchtype eq 'author') {
+ $greptype = "--author=";
+ } elsif ($searchtype eq 'committer') {
+ $greptype = "--committer=";
+ }
+ $greptype .= $searchtext;
+ my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
+ $greptype, '--regexp-ignore-case',
+ $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
+
+ my $paging_nav = '';
+ if ($page > 0) {
+ $paging_nav .=
+ $cgi->a({-href => href(-replay=>1, page=>undef)},
+ "first") .
+ " &sdot; " .
+ $cgi->a({-href => href(-replay=>1, page=>$page-1),
+ -accesskey => "p", -title => "Alt-p"}, "prev");
+ } else {
+ $paging_nav .= "first &sdot; prev";
+ }
+ my $next_link = '';
+ if ($#commitlist >= 100) {
+ $next_link =
+ $cgi->a({-href => href(-replay=>1, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ $paging_nav .= " &sdot; $next_link";
+ } else {
+ $paging_nav .= " &sdot; next";
+ }
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+ if ($page == 0 && !@commitlist) {
+ print "<p>No match.</p>\n";
+ } else {
+ git_search_grep_body(\@commitlist, 0, 99, $next_link);
+ }
+
+ git_footer_html();
+}
+
+sub git_search_changes {
+ my %co = @_;
+
+ local $/ = "\n";
+ open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
+ '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
+ ($search_use_regexp ? '--pickaxe-regex' : ())
+ or die_error(500, "Open git-log failed");
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+ print "<table class=\"pickaxe search\">\n";
+ my $alternate = 1;
+ undef %co;
+ my @files;
+ while (my $line = <$fd>) {
+ chomp $line;
+ next unless $line;
+
+ my %set = parse_difftree_raw_line($line);
+ if (defined $set{'commit'}) {
+ # finish previous commit
+ if (%co) {
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+ "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+ hash_base=>$co{'id'})},
+ "tree") .
+ "</td>\n" .
+ "</tr>\n";
+ }
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ %co = parse_commit($set{'commit'});
+ my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>$author</i></td>\n" .
+ "<td>" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+ -class => "list subject"},
+ chop_and_escape_str($co{'title'}, 50) . "<br/>");
+ } elsif (defined $set{'to_id'}) {
+ next if ($set{'to_id'} =~ m/^0{40}$/);
+
+ print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
+ hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
+ -class => "list"},
+ "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
+ "<br/>\n";
+ }
+ }
+ close $fd;
+
+ # finish last commit (warning: repetition!)
+ if (%co) {
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})},
+ "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'},
+ hash_base=>$co{'id'})},
+ "tree") .
+ "</td>\n" .
+ "</tr>\n";
+ }
+
+ print "</table>\n";
+
+ git_footer_html();
+}
+
+sub git_search_files {
+ my %co = @_;
+
+ local $/ = "\n";
+ open my $fd, "-|", git_cmd(), 'grep', '-n',
+ $search_use_regexp ? ('-E', '-i') : '-F',
+ $searchtext, $co{'tree'}
+ or die_error(500, "Open git-grep failed");
+
+ git_header_html();
+
+ git_print_page_nav('','', $hash,$co{'tree'},$hash);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+ print "<table class=\"grep_search\">\n";
+ my $alternate = 1;
+ my $matches = 0;
+ my $lastfile = '';
+ while (my $line = <$fd>) {
+ chomp $line;
+ my ($file, $lno, $ltext, $binary);
+ last if ($matches++ > 1000);
+ if ($line =~ /^Binary file (.+) matches$/) {
+ $file = $1;
+ $binary = 1;
+ } else {
+ (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
+ }
+ if ($file ne $lastfile) {
+ $lastfile and print "</td></tr>\n";
+ if ($alternate++) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ print "<td class=\"list\">".
+ $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+ file_name=>"$file"),
+ -class => "list"}, esc_path($file));
+ print "</td><td>\n";
+ $lastfile = $file;
+ }
+ if ($binary) {
+ print "<div class=\"binary\">Binary file</div>\n";
+ } else {
+ $ltext = untabify($ltext);
+ if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
+ $ltext = esc_html($1, -nbsp=>1);
+ $ltext .= '<span class="match">';
+ $ltext .= esc_html($2, -nbsp=>1);
+ $ltext .= '</span>';
+ $ltext .= esc_html($3, -nbsp=>1);
+ } else {
+ $ltext = esc_html($ltext, -nbsp=>1);
+ }
+ print "<div class=\"pre\">" .
+ $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+ file_name=>"$file").'#l'.$lno,
+ -class => "linenr"}, sprintf('%4i', $lno))
+ . ' ' . $ltext . "</div>\n";
+ }
+ }
+ if ($lastfile) {
+ print "</td></tr>\n";
+ if ($matches > 1000) {
+ print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
+ }
+ } else {
+ print "<div class=\"diff nodifferences\">No matches found</div>\n";
+ }
+ close $fd;
+
+ print "</table>\n";
+
+ git_footer_html();
+}
+
sub git_search_grep_body {
my ($commitlist, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
@@ -5063,7 +5844,10 @@ sub git_forks {
}
sub git_project_index {
- my @projects = git_get_projects_list($project);
+ my @projects = git_get_projects_list();
+ if (!@projects) {
+ die_error(404, "No projects found");
+ }
print $cgi->header(
-type => 'text/plain',
@@ -5091,6 +5875,7 @@ sub git_summary {
my %co = parse_commit("HEAD");
my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
my $head = $co{'id'};
+ my $remote_heads = gitweb_check_feature('remote_heads');
my $owner = git_get_project_owner($project);
@@ -5099,11 +5884,16 @@ sub git_summary {
# there are more ...
my @taglist = git_get_tags_list(16);
my @headlist = git_get_heads_list(16);
+ my %remotedata = $remote_heads ? git_get_remotes_list() : ();
my @forklist;
my $check_forks = gitweb_check_feature('forks');
if ($check_forks) {
+ # find forks of a project
@forklist = git_get_projects_list($project);
+ # filter out forks of forks
+ @forklist = filter_forks_from_projects_list(\@forklist)
+ if (@forklist);
}
git_header_html();
@@ -5114,7 +5904,8 @@ sub git_summary {
"<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
"<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
if (defined $cd{'rfc2822'}) {
- print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+ print "<tr id=\"metadata_lchange\"><td>last change</td>" .
+ "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
}
# use per project git URL list in $projectroot/$project/cloneurl
@@ -5124,7 +5915,7 @@ sub git_summary {
@url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
foreach my $git_url (@url_list) {
next unless $git_url;
- print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
+ print format_repo_url($url_tag, $git_url);
$url_tag = "";
}
@@ -5132,13 +5923,14 @@ sub git_summary {
my $show_ctags = gitweb_check_feature('ctags');
if ($show_ctags) {
my $ctags = git_get_project_ctags($project);
- my $cloud = git_populate_project_tagcloud($ctags);
- print "<tr id=\"metadata_ctags\"><td>Content tags:<br />";
- print "</td>\n<td>" unless %$ctags;
- print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>";
- print "</td>\n<td>" if %$ctags;
- print git_show_project_tagcloud($cloud, 48);
- print "</td></tr>";
+ if (%$ctags) {
+ # without ability to add tags, don't show if there are none
+ my $cloud = git_populate_project_tagcloud($ctags);
+ print "<tr id=\"metadata_ctags\">" .
+ "<td>content tags</td>" .
+ "<td>".git_show_project_tagcloud($cloud, 48)."</td>" .
+ "</tr>\n";
+ }
}
print "</table>\n";
@@ -5176,6 +5968,11 @@ sub git_summary {
$cgi->a({-href => href(action=>"heads")}, "..."));
}
+ if (%remotedata) {
+ git_print_header_div('remotes');
+ git_remotes_body(\%remotedata, 15, $head);
+ }
+
if (@forklist) {
git_print_header_div('forks');
git_project_list_body(\@forklist, 'age', 0, 15,
@@ -5188,15 +5985,15 @@ sub git_summary {
}
sub git_tag {
- my $head = git_get_head_hash($project);
- git_header_html();
- git_print_page_nav('','', $head,undef,$head);
my %tag = parse_tag($hash);
if (! %tag) {
die_error(404, "Unknown tag object");
}
+ my $head = git_get_head_hash($project);
+ git_header_html();
+ git_print_page_nav('','', $head,undef,$head);
git_print_header_div('commit', esc_html($tag{'name'}), $hash);
print "<div class=\"title_text\">\n" .
"<table class=\"object_header\">\n" .
@@ -5280,7 +6077,7 @@ sub git_blame_common {
print 'END';
if (defined $t0 && gitweb_check_feature('timed')) {
print ' '.
- Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+ tv_interval($t0, [ gettimeofday() ]).
' '.$number_of_git_cmds;
}
print "\n";
@@ -5467,7 +6264,7 @@ sub git_blame_data {
sub git_tags {
my $head = git_get_head_hash($project);
git_header_html();
- git_print_page_nav('','', $head,undef,$head);
+ git_print_page_nav('','', $head,undef,$head,format_ref_views('tags'));
git_print_header_div('summary', $project);
my @tagslist = git_get_tags_list();
@@ -5480,7 +6277,7 @@ sub git_tags {
sub git_heads {
my $head = git_get_head_hash($project);
git_header_html();
- git_print_page_nav('','', $head,undef,$head);
+ git_print_page_nav('','', $head,undef,$head,format_ref_views('heads'));
git_print_header_div('summary', $project);
my @headslist = git_get_heads_list();
@@ -5490,6 +6287,39 @@ sub git_heads {
git_footer_html();
}
+# used both for single remote view and for list of all the remotes
+sub git_remotes {
+ gitweb_check_feature('remote_heads')
+ or die_error(403, "Remote heads view is disabled");
+
+ my $head = git_get_head_hash($project);
+ my $remote = $input_params{'hash'};
+
+ my $remotedata = git_get_remotes_list($remote);
+ die_error(500, "Unable to get remote information") unless defined $remotedata;
+
+ unless (%$remotedata) {
+ die_error(404, defined $remote ?
+ "Remote $remote not found" :
+ "No remotes found");
+ }
+
+ git_header_html(undef, undef, -action_extra => $remote);
+ git_print_page_nav('', '', $head, undef, $head,
+ format_ref_views($remote ? '' : 'remotes'));
+
+ fill_remote_heads($remotedata);
+ if (defined $remote) {
+ git_print_header_div('remotes', "$remote remote for $project");
+ git_remote_block($remote, $remotedata->{$remote}, undef, $head);
+ } else {
+ git_print_header_div('summary', "$project remotes");
+ git_remotes_body($remotedata, undef, $head);
+ }
+
+ git_footer_html();
+}
+
sub git_blob_plain {
my $type = shift;
my $expires;
@@ -5528,7 +6358,16 @@ sub git_blob_plain {
# want to be sure not to break that by serving the image as an
# attachment (though Firefox 3 doesn't seem to care).
my $sandbox = $prevent_xss &&
- $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+ $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
+
+ # serve text/* as text/plain
+ if ($prevent_xss &&
+ ($type =~ m!^text/[a-z]+\b(.*)$! ||
+ ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) {
+ my $rest = $1;
+ $rest = defined $rest ? $rest : '';
+ $type = "text/plain$rest";
+ }
print $cgi->header(
-type => $type,
@@ -5606,14 +6445,14 @@ sub git_blob {
} else {
print "<div class=\"page_nav\">\n" .
"<br/><br/></div>\n" .
- "<div class=\"title\">$hash</div>\n";
+ "<div class=\"title\">".esc_html($hash)."</div>\n";
}
git_print_page_path($file_name, "blob", $hash_base);
print "<div class=\"page_body\">\n";
if ($mimetype =~ m!^image/!) {
- print qq!<img type="$mimetype"!;
+ print qq!<img type="!.esc_attr($mimetype).qq!"!;
if ($file_name) {
- print qq! alt="$file_name" title="$file_name"!;
+ print qq! alt="!.esc_attr($file_name).qq!" title="!.esc_attr($file_name).qq!"!;
}
print qq! src="! .
href(action=>"blob_plain", hash=>$hash,
@@ -5626,7 +6465,7 @@ sub git_blob {
$nr++;
$line = untabify($line);
printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
- $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
+ $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
}
}
close $fd
@@ -5688,7 +6527,7 @@ sub git_tree {
undef $hash_base;
print "<div class=\"page_nav\">\n";
print "<br/><br/></div>\n";
- print "<div class=\"title\">$hash</div>\n";
+ print "<div class=\"title\">".esc_html($hash)."</div>\n";
}
if (defined $file_name) {
$basedir = $file_name;
@@ -6156,7 +6995,7 @@ sub git_blobdiff {
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
} else {
print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
- print "<div class=\"title\">$hash vs $hash_parent</div>\n";
+ print "<div class=\"title\">".esc_html("$hash vs $hash_parent")."</div>\n";
}
if (defined $file_name) {
git_print_page_path($file_name, "blob", $hash_base);
@@ -6451,7 +7290,23 @@ sub git_history {
}
sub git_search {
- gitweb_check_feature('search') or die_error(403, "Search is disabled");
+ $searchtype ||= 'commit';
+
+ # check if appropriate features are enabled
+ gitweb_check_feature('search')
+ or die_error(403, "Search is disabled");
+ if ($searchtype eq 'pickaxe') {
+ # pickaxe may take all resources of your box and run for several minutes
+ # with every query - so decide by yourself how public you make this feature
+ gitweb_check_feature('pickaxe')
+ or die_error(403, "Pickaxe search is disabled");
+ }
+ if ($searchtype eq 'grep') {
+ # grep search might be potentially CPU-intensive, too
+ gitweb_check_feature('grep')
+ or die_error(403, "Grep search is disabled");
+ }
+
if (!defined $searchtext) {
die_error(400, "Text field is empty");
}
@@ -6466,204 +7321,17 @@ sub git_search {
$page = 0;
}
- $searchtype ||= 'commit';
- if ($searchtype eq 'pickaxe') {
- # pickaxe may take all resources of your box and run for several minutes
- # with every query - so decide by yourself how public you make this feature
- gitweb_check_feature('pickaxe')
- or die_error(403, "Pickaxe is disabled");
- }
- if ($searchtype eq 'grep') {
- gitweb_check_feature('grep')
- or die_error(403, "Grep is disabled");
- }
-
- git_header_html();
-
- if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
- my $greptype;
- if ($searchtype eq 'commit') {
- $greptype = "--grep=";
- } elsif ($searchtype eq 'author') {
- $greptype = "--author=";
- } elsif ($searchtype eq 'committer') {
- $greptype = "--committer=";
- }
- $greptype .= $searchtext;
- my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
- $greptype, '--regexp-ignore-case',
- $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
-
- my $paging_nav = '';
- if ($page > 0) {
- $paging_nav .=
- $cgi->a({-href => href(action=>"search", hash=>$hash,
- searchtext=>$searchtext,
- searchtype=>$searchtype)},
- "first");
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(-replay=>1, page=>$page-1),
- -accesskey => "p", -title => "Alt-p"}, "prev");
- } else {
- $paging_nav .= "first";
- $paging_nav .= " &sdot; prev";
- }
- my $next_link = '';
- if ($#commitlist >= 100) {
- $next_link =
- $cgi->a({-href => href(-replay=>1, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- $paging_nav .= " &sdot; $next_link";
- } else {
- $paging_nav .= " &sdot; next";
- }
-
- if ($#commitlist >= 100) {
- }
-
- git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
- git_search_grep_body(\@commitlist, 0, 99, $next_link);
- }
-
- if ($searchtype eq 'pickaxe') {
- git_print_page_nav('','', $hash,$co{'tree'},$hash);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
- print "<table class=\"pickaxe search\">\n";
- my $alternate = 1;
- local $/ = "\n";
- open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
- '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
- ($search_use_regexp ? '--pickaxe-regex' : ());
- undef %co;
- my @files;
- while (my $line = <$fd>) {
- chomp $line;
- next unless $line;
-
- my %set = parse_difftree_raw_line($line);
- if (defined $set{'commit'}) {
- # finish previous commit
- if (%co) {
- print "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
- " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
- print "</td>\n" .
- "</tr>\n";
- }
-
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- %co = parse_commit($set{'commit'});
- my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
- print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
- "<td><i>$author</i></td>\n" .
- "<td>" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
- -class => "list subject"},
- chop_and_escape_str($co{'title'}, 50) . "<br/>");
- } elsif (defined $set{'to_id'}) {
- next if ($set{'to_id'} =~ m/^0{40}$/);
-
- print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
- hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
- -class => "list"},
- "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
- "<br/>\n";
- }
- }
- close $fd;
-
- # finish last commit (warning: repetition!)
- if (%co) {
- print "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
- " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
- print "</td>\n" .
- "</tr>\n";
- }
-
- print "</table>\n";
- }
-
- if ($searchtype eq 'grep') {
- git_print_page_nav('','', $hash,$co{'tree'},$hash);
- git_print_header_div('commit', esc_html($co{'title'}), $hash);
-
- print "<table class=\"grep_search\">\n";
- my $alternate = 1;
- my $matches = 0;
- local $/ = "\n";
- open my $fd, "-|", git_cmd(), 'grep', '-n',
- $search_use_regexp ? ('-E', '-i') : '-F',
- $searchtext, $co{'tree'};
- my $lastfile = '';
- while (my $line = <$fd>) {
- chomp $line;
- my ($file, $lno, $ltext, $binary);
- last if ($matches++ > 1000);
- if ($line =~ /^Binary file (.+) matches$/) {
- $file = $1;
- $binary = 1;
- } else {
- (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
- }
- if ($file ne $lastfile) {
- $lastfile and print "</td></tr>\n";
- if ($alternate++) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- print "<td class=\"list\">".
- $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
- file_name=>"$file"),
- -class => "list"}, esc_path($file));
- print "</td><td>\n";
- $lastfile = $file;
- }
- if ($binary) {
- print "<div class=\"binary\">Binary file</div>\n";
- } else {
- $ltext = untabify($ltext);
- if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
- $ltext = esc_html($1, -nbsp=>1);
- $ltext .= '<span class="match">';
- $ltext .= esc_html($2, -nbsp=>1);
- $ltext .= '</span>';
- $ltext .= esc_html($3, -nbsp=>1);
- } else {
- $ltext = esc_html($ltext, -nbsp=>1);
- }
- print "<div class=\"pre\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
- file_name=>"$file").'#l'.$lno,
- -class => "linenr"}, sprintf('%4i', $lno))
- . ' ' . $ltext . "</div>\n";
- }
- }
- if ($lastfile) {
- print "</td></tr>\n";
- if ($matches > 1000) {
- print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
- }
- } else {
- print "<div class=\"diff nodifferences\">No matches found</div>\n";
- }
- close $fd;
-
- print "</table>\n";
+ if ($searchtype eq 'commit' ||
+ $searchtype eq 'author' ||
+ $searchtype eq 'committer') {
+ git_search_message(%co);
+ } elsif ($searchtype eq 'pickaxe') {
+ git_search_changes(%co);
+ } elsif ($searchtype eq 'grep') {
+ git_search_files(%co);
+ } else {
+ die_error(400, "Unknown search type");
}
- git_footer_html();
}
sub git_search_help {
@@ -6743,7 +7411,7 @@ sub git_feed {
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
my $latest_epoch = $latest_commit{'committer_epoch'};
- %latest_date = parse_date($latest_epoch);
+ %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
if (defined $if_modified) {
my $since;
@@ -6853,7 +7521,7 @@ XML
if (defined $favicon) {
print "<icon>" . esc_url($favicon) . "</icon>\n";
}
- if (defined $logo_url) {
+ if (defined $logo) {
# not twice as wide as tall: 72 x 27 pixels
print "<logo>" . esc_url($logo) . "</logo>\n";
}
@@ -6874,7 +7542,7 @@ XML
if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
last;
}
- my %cd = parse_date($co{'author_epoch'});
+ my %cd = parse_date($co{'author_epoch'}, $co{'author_tz'});
# get list of changed files
open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
@@ -6984,6 +7652,9 @@ sub git_atom {
sub git_opml {
my @list = git_get_projects_list();
+ if (!@list) {
+ die_error(404, "No projects found");
+ }
print $cgi->header(
-type => 'text/xml',
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css
index 4132aab..7d88509 100644
--- a/gitweb/static/gitweb.css
+++ b/gitweb/static/gitweb.css
@@ -295,6 +295,13 @@ td.current_head {
text-decoration: underline;
}
+td.category {
+ background-color: #d9d8d1;
+ border-top: 1px solid #000000;
+ border-left: 1px solid #000000;
+ font-weight: bold;
+}
+
table.diff_tree span.file_status.new {
color: #008000;
}
@@ -573,6 +580,45 @@ div.binary {
font-style: italic;
}
+div.remote {
+ margin: .5em;
+ border: 1px solid #d9d8d1;
+ display: inline-block;
+}
+
+/* JavaScript-based timezone manipulation */
+
+.popup { /* timezone selection UI */
+ position: absolute;
+ /* "top: 0; right: 0;" would be better, if not for bugs in browsers */
+ top: 0; left: 0;
+ border: 1px solid;
+ padding: 2px;
+ background-color: #f0f0f0;
+ font-style: normal;
+ color: #000000;
+ cursor: auto;
+}
+
+.close-button { /* close timezone selection UI without selecting */
+ /* float doesn't work within absolutely positioned container,
+ * if width of container is not set explicitly */
+ /* float: right; */
+ position: absolute;
+ top: 0px; right: 0px;
+ border: 1px solid green;
+ margin: 1px 1px 1px 1px;
+ padding-bottom: 2px;
+ width: 12px;
+ height: 10px;
+ font-size: 9px;
+ font-weight: bold;
+ text-align: center;
+ background-color: #fff0f0;
+ cursor: pointer;
+}
+
+
/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
/* Highlighting theme definition: */
diff --git a/gitweb/static/js/README b/gitweb/static/js/README
new file mode 100644
index 0000000..f8460ed
--- /dev/null
+++ b/gitweb/static/js/README
@@ -0,0 +1,20 @@
+GIT web interface (gitweb) - JavaScript
+=======================================
+
+This directory holds JavaScript code used by gitweb (GIT web interface).
+Scripts from there would be concatenated together in the order specified
+by gitweb/Makefile into gitweb/static/gitweb.js, during building of
+gitweb/gitweb.cgi (during gitweb building). The resulting file (or its
+minification) would then be installed / deployed together with gitweb.
+
+Scripts in 'lib/' subdirectory compose generic JavaScript library,
+providing features required by gitweb but in no way limited to gitweb
+only. In the future those scripts could be replaced by some JavaScript
+library / framework, like e.g. jQuery, YUI, Prototype, MooTools, Dojo,
+ExtJS, Script.aculo.us or SproutCore.
+
+All scripts that manipulate gitweb output should be put outside 'lib/',
+directly in this directory ('gitweb/static/js/'). Those scripts would
+have to be rewritten if gitweb moves to using some JavaScript library.
+
+See also comments in gitweb/Makefile.
diff --git a/gitweb/static/js/adjust-timezone.js b/gitweb/static/js/adjust-timezone.js
new file mode 100644
index 0000000..0c67779
--- /dev/null
+++ b/gitweb/static/js/adjust-timezone.js
@@ -0,0 +1,330 @@
+// Copyright (C) 2011, John 'Warthog9' Hawley <warthog9@eaglescrag.net>
+// 2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Manipulate dates in gitweb output, adjusting timezone
+ * @license GPLv2 or later
+ */
+
+/**
+ * Get common timezone, add UI for changing timezones, and adjust
+ * dates to use requested common timezone.
+ *
+ * This function is called during onload event (added to window.onload).
+ *
+ * @param {String} tzDefault: default timezone, if there is no cookie
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzCookieInfo.name: name of cookie to store timezone
+ * @param {String} tzClassName: denotes elements with date to be adjusted
+ */
+function onloadTZSetup(tzDefault, tzCookieInfo, tzClassName) {
+ var tzCookieTZ = getCookie(tzCookieInfo.name, tzCookieInfo);
+ var tz = tzDefault;
+
+ if (tzCookieTZ) {
+ // set timezone to value saved in a cookie
+ tz = tzCookieTZ;
+ // refresh cookie, so its expiration counts from last use of gitweb
+ setCookie(tzCookieInfo.name, tzCookieTZ, tzCookieInfo);
+ }
+
+ // add UI for changing timezone
+ addChangeTZ(tz, tzCookieInfo, tzClassName);
+
+ // server-side of gitweb produces datetime in UTC,
+ // so if tz is 'utc' there is no need for changes
+ var nochange = tz === 'utc';
+
+ // adjust dates to use specified common timezone
+ fixDatetimeTZ(tz, tzClassName, nochange);
+}
+
+
+/* ...................................................................... */
+/* Changing dates to use requested timezone */
+
+/**
+ * Replace RFC-2822 dates contained in SPAN elements with tzClassName
+ * CSS class with equivalent dates in given timezone.
+ *
+ * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local'
+ * @param {String} tzClassName: specifies elements to be changed
+ * @param {Boolean} nochange: markup for timezone change, but don't change it
+ */
+function fixDatetimeTZ(tz, tzClassName, nochange) {
+ // sanity check, method should be ensured by common-lib.js
+ if (!document.getElementsByClassName) {
+ return;
+ }
+
+ // translate to timezone in '(-|+)HHMM' format
+ tz = normalizeTimezoneInfo(tz);
+
+ // NOTE: result of getElementsByClassName should probably be cached
+ var classesFound = document.getElementsByClassName(tzClassName, "span");
+ for (var i = 0, len = classesFound.length; i < len; i++) {
+ var curElement = classesFound[i];
+
+ curElement.title = 'Click to change timezone';
+ if (!nochange) {
+ // we use *.firstChild.data (W3C DOM) instead of *.innerHTML
+ // as the latter doesn't always work everywhere in every browser
+ var epoch = parseRFC2822Date(curElement.firstChild.data);
+ var adjusted = formatDateRFC2882(epoch, tz);
+
+ curElement.firstChild.data = adjusted;
+ }
+ }
+}
+
+
+/* ...................................................................... */
+/* Adding triggers, generating timezone menu, displaying and hiding */
+
+/**
+ * Adds triggers for UI to change common timezone used for dates in
+ * gitweb output: it marks up and/or creates item to click to invoke
+ * timezone change UI, creates timezone UI fragment to be attached,
+ * and installs appropriate onclick trigger (via event delegation).
+ *
+ * @param {String} tzSelected: pre-selected timezone,
+ * 'utc' or 'local' or '(-|+)HHMM'
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzClassName: specifies elements to install trigger
+ */
+function addChangeTZ(tzSelected, tzCookieInfo, tzClassName) {
+ // make link to timezone UI discoverable
+ addCssRule('.'+tzClassName + ':hover',
+ 'text-decoration: underline; cursor: help;');
+
+ // create form for selecting timezone (to be saved in a cookie)
+ var tzSelectFragment = document.createDocumentFragment();
+ tzSelectFragment = createChangeTZForm(tzSelectFragment,
+ tzSelected, tzCookieInfo, tzClassName);
+
+ // event delegation handler for timezone selection UI (clicking on entry)
+ // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/
+ // assumes that there is no existing document.onclick handler
+ document.onclick = function onclickHandler(event) {
+ //IE doesn't pass in the event object
+ event = event || window.event;
+
+ //IE uses srcElement as the target
+ var target = event.target || event.srcElement;
+
+ switch (target.className) {
+ case tzClassName:
+ // don't display timezone menu if it is already displayed
+ if (tzSelectFragment.childNodes.length > 0) {
+ displayChangeTZForm(target, tzSelectFragment);
+ }
+ break;
+ } // end switch
+ };
+}
+
+/**
+ * Create DocumentFragment with UI for changing common timezone in
+ * which dates are shown in.
+ *
+ * @param {DocumentFragment} documentFragment: where attach UI
+ * @param {String} tzSelected: default (pre-selected) timezone
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @returns {DocumentFragment}
+ */
+function createChangeTZForm(documentFragment, tzSelected, tzCookieInfo, tzClassName) {
+ var div = document.createElement("div");
+ div.className = 'popup';
+
+ /* '<div class="close-button" title="(click on this box to close)">X</div>' */
+ var closeButton = document.createElement('div');
+ closeButton.className = 'close-button';
+ closeButton.title = '(click on this box to close)';
+ closeButton.appendChild(document.createTextNode('X'));
+ closeButton.onclick = closeTZFormHandler(documentFragment, tzClassName);
+ div.appendChild(closeButton);
+
+ /* 'Select timezone: <br clear="all">' */
+ div.appendChild(document.createTextNode('Select timezone: '));
+ var br = document.createElement('br');
+ br.clear = 'all';
+ div.appendChild(br);
+
+ /* '<select name="tzoffset">
+ * ...
+ * <option value="-0700">UTC-07:00</option>
+ * <option value="-0600">UTC-06:00</option>
+ * ...
+ * </select>' */
+ var select = document.createElement("select");
+ select.name = "tzoffset";
+ //select.style.clear = 'all';
+ select.appendChild(generateTZOptions(tzSelected));
+ select.onchange = selectTZHandler(documentFragment, tzCookieInfo, tzClassName);
+ div.appendChild(select);
+
+ documentFragment.appendChild(div);
+
+ return documentFragment;
+}
+
+
+/**
+ * Hide (remove from DOM) timezone change UI, ensuring that it is not
+ * garbage collected and that it can be re-enabled later.
+ *
+ * @param {DocumentFragment} documentFragment: contains detached UI
+ * @param {HTMLSelectElement} target: select element inside of UI
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {DocumentFragment} documentFragment
+ */
+function removeChangeTZForm(documentFragment, target, tzClassName) {
+ // find containing element, where we appended timezone selection UI
+ // `target' is somewhere inside timezone menu
+ var container = target.parentNode, popup = target;
+ while (container &&
+ container.className !== tzClassName) {
+ popup = container;
+ container = container.parentNode;
+ }
+ // safety check if we found correct container,
+ // and if it isn't deleted already
+ if (!container || !popup ||
+ container.className !== tzClassName ||
+ popup.className !== 'popup') {
+ return documentFragment;
+ }
+
+ // timezone selection UI was appended as last child
+ // see also displayChangeTZForm function
+ var removed = popup.parentNode.removeChild(popup);
+ if (documentFragment.firstChild !== removed) { // the only child
+ // re-append it so it would be available for next time
+ documentFragment.appendChild(removed);
+ }
+ // all of inline style was added by this script
+ // it is not really needed to remove it, but it is a good practice
+ container.removeAttribute('style');
+
+ return documentFragment;
+}
+
+
+/**
+ * Display UI for changing common timezone for dates in gitweb output.
+ * To be used from 'onclick' event handler.
+ *
+ * @param {HTMLElement} target: where to install/display UI
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ */
+function displayChangeTZForm(target, tzSelectFragment) {
+ // for absolute positioning to be related to target element
+ target.style.position = 'relative';
+ target.style.display = 'inline-block';
+
+ // show/display UI for changing timezone
+ target.appendChild(tzSelectFragment);
+}
+
+
+/* ...................................................................... */
+/* List of timezones for timezone selection menu */
+
+/**
+ * Generate list of timezones for creating timezone select UI
+ *
+ * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' }
+ */
+function generateTZList() {
+ var timezones = [
+ { value: "utc", descr: "UTC/GMT"},
+ { value: "local", descr: "Local (per browser)"}
+ ];
+
+ // generate all full hour timezones (no fractional timezones)
+ for (var x = -12, idx = timezones.length; x <= +14; x++, idx++) {
+ var hours = (x >= 0 ? '+' : '-') + padLeft(x >=0 ? x : -x, 2);
+ timezones[idx] = { value: hours + '00', descr: 'UTC' + hours + ':00'};
+ if (x === 0) {
+ timezones[idx].descr = 'UTC\u00B100:00'; // 'UTC&plusmn;00:00'
+ }
+ }
+
+ return timezones;
+}
+
+/**
+ * Generate <options> elements for timezone select UI
+ *
+ * @param {String} tzSelected: default timezone
+ * @returns {DocumentFragment} list of options elements to appendChild
+ */
+function generateTZOptions(tzSelected) {
+ var elems = document.createDocumentFragment();
+ var timezones = generateTZList();
+
+ for (var i = 0, len = timezones.length; i < len; i++) {
+ var tzone = timezones[i];
+ var option = document.createElement("option");
+ if (tzone.value === tzSelected) {
+ option.defaultSelected = true;
+ }
+ option.value = tzone.value;
+ option.appendChild(document.createTextNode(tzone.descr));
+
+ elems.appendChild(option);
+ }
+
+ return elems;
+}
+
+
+/* ...................................................................... */
+/* Event handlers and/or their generators */
+
+/**
+ * Create event handler that select timezone and closes timezone select UI.
+ * To be used as $('select[name="tzselect"]').onchange handler.
+ *
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone
+ * @param {String} tzCookieInfo.name: name of cookie to save result of selection
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {Function} event handler
+ */
+function selectTZHandler(tzSelectFragment, tzCookieInfo, tzClassName) {
+ //return function selectTZ(event) {
+ return function (event) {
+ event = event || window.event;
+ var target = event.target || event.srcElement;
+
+ var selected = target.options.item(target.selectedIndex);
+ removeChangeTZForm(tzSelectFragment, target, tzClassName);
+
+ if (selected) {
+ selected.defaultSelected = true;
+ setCookie(tzCookieInfo.name, selected.value, tzCookieInfo);
+ fixDatetimeTZ(selected.value, tzClassName);
+ }
+ };
+}
+
+/**
+ * Create event handler that closes timezone select UI.
+ * To be used e.g. as $('.closebutton').onclick handler.
+ *
+ * @param {DocumentFragment} tzSelectFragment: timezone selection UI
+ * @param {String} tzClassName: specifies element where UI was installed
+ * @returns {Function} event handler
+ */
+function closeTZFormHandler(tzSelectFragment, tzClassName) {
+ //return function closeTZForm(event) {
+ return function (event) {
+ event = event || window.event;
+ var target = event.target || event.srcElement;
+
+ removeChangeTZForm(tzSelectFragment, target, tzClassName);
+ };
+}
+
+/* end of adjust-timezone.js */
diff --git a/gitweb/static/gitweb.js b/gitweb/static/js/blame_incremental.js
index 9c66928..db6eb50 100644
--- a/gitweb/static/gitweb.js
+++ b/gitweb/static/js/blame_incremental.js
@@ -1,45 +1,13 @@
// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
// 2007, Petr Baudis <pasky@suse.cz>
-// 2008-2009, Jakub Narebski <jnareb@gmail.com>
+// 2008-2011, Jakub Narebski <jnareb@gmail.com>
/**
- * @fileOverview JavaScript code for gitweb (git web interface).
+ * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb
* @license GPLv2 or later
*/
/* ============================================================ */
-/* functions for generic gitweb actions and views */
-
-/**
- * used to check if link has 'js' query parameter already (at end),
- * and other reasons to not add 'js=1' param at the end of link
- * @constant
- */
-var jsExceptionsRe = /[;?]js=[01]$/;
-
-/**
- * Add '?js=1' or ';js=1' to the end of every link in the document
- * that doesn't have 'js' query parameter set already.
- *
- * Links with 'js=1' lead to JavaScript version of given action, if it
- * exists (currently there is only 'blame_incremental' for 'blame')
- *
- * @globals jsExceptionsRe
- */
-function fixLinks() {
- var allLinks = document.getElementsByTagName("a") || document.links;
- for (var i = 0, len = allLinks.length; i < len; i++) {
- var link = allLinks[i];
- if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
- link.href +=
- (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
- }
- }
-}
-
-
-/* ============================================================ */
-
/*
* This code uses DOM methods instead of (nonstandard) innerHTML
* to modify page.
@@ -58,75 +26,9 @@ function fixLinks() {
*/
-/* ============================================================ */
-/* generic utility functions */
-
-
-/**
- * pad number N with nonbreakable spaces on the left, to WIDTH characters
- * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
- * ('\u00A0' is nonbreakable space)
- *
- * @param {Number|String} input: number to pad
- * @param {Number} width: visible width of output
- * @param {String} str: string to prefix to string, e.g. '\u00A0'
- * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
- */
-function padLeftStr(input, width, str) {
- var prefix = '';
-
- width -= input.toString().length;
- while (width > 0) {
- prefix += str;
- width--;
- }
- return prefix + input;
-}
-
-/**
- * Pad INPUT on the left to SIZE width, using given padding character CH,
- * for example padLeft('a', 3, '_') is '__a'.
- *
- * @param {String} input: input value converted to string.
- * @param {Number} width: desired length of output.
- * @param {String} ch: single character to prefix to string.
- *
- * @returns {String} Modified string, at least SIZE length.
- */
-function padLeft(input, width, ch) {
- var s = input + "";
- while (s.length < width) {
- s = ch + s;
- }
- return s;
-}
-
-/**
- * Create XMLHttpRequest object in cross-browser way
- * @returns XMLHttpRequest object, or null
- */
-function createRequestObject() {
- try {
- return new XMLHttpRequest();
- } catch (e) {}
- try {
- return window.createRequest();
- } catch (e) {}
- try {
- return new ActiveXObject("Msxml2.XMLHTTP");
- } catch (e) {}
- try {
- return new ActiveXObject("Microsoft.XMLHTTP");
- } catch (e) {}
-
- return null;
-}
-
-
-/* ============================================================ */
+/* ............................................................ */
/* utility/helper functions (and variables) */
-var xhr; // XMLHttpRequest object
var projectUrl; // partial query + separator ('?' or ';')
// 'commits' is an associative map. It maps SHA1s to Commit objects.
@@ -229,7 +131,7 @@ function writeTimeInterval() {
}
/**
- * show an error message alert to user within page (in prohress info area)
+ * show an error message alert to user within page (in progress info area)
* @param {String} str: plain text error message (no HTML)
*
* @globals div_progress_info
@@ -279,7 +181,7 @@ function getColorNo(tr) {
var colorsFreq = [0, 0, 0];
/**
- * return one of given possible colors (curently least used one)
+ * return one of given possible colors (currently least used one)
* example: chooseColorNoFrom(2, 3) returns 2 or 3
*
* @param {Number[]} arguments: one or more numbers
@@ -300,8 +202,8 @@ function chooseColorNoFrom() {
}
/**
- * given two neigbour <tr> elements, find color which would be different
- * from color of both of neighbours; used to 3-color blame table
+ * given two neighbor <tr> elements, find color which would be different
+ * from color of both of neighbors; used to 3-color blame table
*
* @param {HTMLElement} tr_prev
* @param {HTMLElement} tr_next
@@ -313,14 +215,14 @@ function findColorNo(tr_prev, tr_next) {
var color_next = getColorNo(tr_next);
- // neither of neighbours has color set
+ // neither of neighbors has color set
// THEN we can use any of 3 possible colors
if (!color_prev && !color_next) {
return chooseColorNoFrom(1,2,3);
}
- // either both neighbours have the same color,
- // or only one of neighbours have color set
+ // either both neighbors have the same color,
+ // or only one of neighbors have color set
// THEN we can use any color except given
var color;
if (color_prev === color_next) {
@@ -334,7 +236,7 @@ function findColorNo(tr_prev, tr_next) {
return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
}
- // neighbours have different colors
+ // neighbors have different colors
// THEN there is only one color left
return (3 - ((color_prev + color_next) % 3));
}
@@ -355,7 +257,7 @@ function isStartOfGroup(tr) {
/**
* change colors to use zebra coloring (2 colors) instead of 3 colors
- * concatenate neighbour commit groups belonging to the same commit
+ * concatenate neighbor commit groups belonging to the same commit
*
* @globals colorRe
*/
@@ -392,97 +294,6 @@ function fixColorsAndGroups() {
}
}
-/* ............................................................ */
-/* time and data */
-
-/**
- * used to extract hours and minutes from timezone info, e.g '-0900'
- * @constant
- */
-var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;
-
-/**
- * return date in local time formatted in iso-8601 like format
- * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
- *
- * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
- * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
- * @returns {String} date in local time in iso-8601 like format
- *
- * @globals tzRe
- */
-function formatDateISOLocal(epoch, timezoneInfo) {
- var match = tzRe.exec(timezoneInfo);
- // date corrected by timezone
- var localDate = new Date(1000 * (epoch +
- (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
- var localDateStr = // e.g. '2005-08-07'
- localDate.getUTCFullYear() + '-' +
- padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
- padLeft(localDate.getUTCDate(), 2, '0');
- var localTimeStr = // e.g. '21:49:46'
- padLeft(localDate.getUTCHours(), 2, '0') + ':' +
- padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
- padLeft(localDate.getUTCSeconds(), 2, '0');
-
- return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
-}
-
-/* ............................................................ */
-/* unquoting/unescaping filenames */
-
-/**#@+
- * @constant
- */
-var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
-var octEscRe = /^[0-7]{1,3}$/;
-var maybeQuotedRe = /^\"(.*)\"$/;
-/**#@-*/
-
-/**
- * unquote maybe git-quoted filename
- * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a'
- *
- * @param {String} str: git-quoted string
- * @returns {String} Unquoted and unescaped string
- *
- * @globals escCodeRe, octEscRe, maybeQuotedRe
- */
-function unquote(str) {
- function unq(seq) {
- var es = {
- // character escape codes, aka escape sequences (from C)
- // replacements are to some extent JavaScript specific
- t: "\t", // tab (HT, TAB)
- n: "\n", // newline (NL)
- r: "\r", // return (CR)
- f: "\f", // form feed (FF)
- b: "\b", // backspace (BS)
- a: "\x07", // alarm (bell) (BEL)
- e: "\x1B", // escape (ESC)
- v: "\v" // vertical tab (VT)
- };
-
- if (seq.search(octEscRe) !== -1) {
- // octal char sequence
- return String.fromCharCode(parseInt(seq, 8));
- } else if (seq in es) {
- // C escape sequence, aka character escape code
- return es[seq];
- }
- // quoted ordinary character
- return seq;
- }
-
- var match = str.match(maybeQuotedRe);
- if (match) {
- str = match[1];
- // perhaps str = eval('"'+str+'"'); would be enough?
- str = str.replace(escCodeRe,
- function (substr, p1, offset, s) { return unq(p1); });
- }
- return str;
-}
/* ============================================================ */
/* main part: parsing response */
@@ -608,8 +419,6 @@ function handleLine(commit, group) {
// ----------------------------------------------------------------------
-var inProgress = false; // are we processing response
-
/**#@+
* @constant
*/
@@ -621,8 +430,6 @@ var endRe = /^END ?([^ ]*) ?(.*)/;
var curCommit = new Commit();
var curGroup = {};
-var pollTimer = null;
-
/**
* Parse output from 'git blame --incremental [...]', received via
* XMLHttpRequest from server (blamedataUrl), and call handleLine
@@ -723,43 +530,51 @@ function processData(unprocessed, nextReadPos) {
* Handle XMLHttpRequest errors
*
* @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
*
- * @globals pollTimer, commits, inProgress
+ * @globals commits
*/
function handleError(xhr) {
errorInfo('Server error: ' +
xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
- clearInterval(pollTimer);
+ if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
commits = {}; // free memory
-
- inProgress = false;
}
/**
* Called after XMLHttpRequest finishes (loads)
*
- * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} [xhr.pollTimer] ID of the timeout to clear
*
- * @globals pollTimer, commits, inProgress
+ * @globals commits
*/
function responseLoaded(xhr) {
- clearInterval(pollTimer);
+ if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
fixColorsAndGroups();
writeTimeInterval();
commits = {}; // free memory
-
- inProgress = false;
}
/**
* handler for XMLHttpRequest onreadystatechange event
* @see startBlame
*
- * @globals xhr, inProgress
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length
+ * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText
+ * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel)
+ * @param {Boolean} fromTimer: if handler was called from timer
*/
-function handleResponse() {
+function handleResponse(xhr, fromTimer) {
/*
* xhr.readyState
@@ -797,32 +612,31 @@ function handleResponse() {
return;
}
- // in case we were called before finished processing
- if (inProgress) {
- return;
- } else {
- inProgress = true;
- }
// extract new whole (complete) lines, and process them
- while (xhr.prevDataLength !== xhr.responseText.length) {
- if (xhr.readyState === 4 &&
- xhr.prevDataLength === xhr.responseText.length) {
- break;
- }
-
+ if (xhr.prevDataLength !== xhr.responseText.length) {
xhr.prevDataLength = xhr.responseText.length;
var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
- } // end while
+ }
// did we finish work?
- if (xhr.readyState === 4 &&
- xhr.prevDataLength === xhr.responseText.length) {
+ if (xhr.readyState === 4) {
responseLoaded(xhr);
+ return;
}
- inProgress = false;
+ // if we get from timer, we have to restart it
+ // otherwise onreadystatechange gives us partial response, timer not needed
+ if (fromTimer) {
+ setTimeout(function () {
+ handleResponse(xhr, true);
+ }, 1000);
+
+ } else if (typeof xhr.pollTimer === "number") {
+ clearTimeout(xhr.pollTimer);
+ delete xhr.pollTimer;
+ }
}
// ============================================================
@@ -837,11 +651,11 @@ function handleResponse() {
* Called from 'blame_incremental' view after loading table with
* file contents, a base for blame view.
*
- * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+ * @globals t0, projectUrl, div_progress_bar, totalLines
*/
function startBlame(blamedataUrl, bUrl) {
- xhr = createRequestObject();
+ var xhr = createRequestObject();
if (!xhr) {
errorInfo('ERROR: XMLHttpRequest not supported');
return;
@@ -860,8 +674,9 @@ function startBlame(blamedataUrl, bUrl) {
xhr.prevDataLength = -1; // used to detect if we have new data
xhr.nextReadPos = 0; // where unread part of response starts
- xhr.onreadystatechange = handleResponse;
- //xhr.onreadystatechange = function () { handleResponse(xhr); };
+ xhr.onreadystatechange = function () {
+ handleResponse(xhr, false);
+ };
xhr.open('GET', blamedataUrl);
xhr.setRequestHeader('Accept', 'text/plain');
@@ -869,7 +684,9 @@ function startBlame(blamedataUrl, bUrl) {
// not all browsers call onreadystatechange event on each server flush
// poll response using timer every second to handle this issue
- pollTimer = setInterval(xhr.onreadystatechange, 1000);
+ xhr.pollTimer = setTimeout(function () {
+ handleResponse(xhr, true);
+ }, 1000);
}
-// end of gitweb.js
+/* end of blame_incremental.js */
diff --git a/gitweb/static/js/javascript-detection.js b/gitweb/static/js/javascript-detection.js
new file mode 100644
index 0000000..93dd2bd
--- /dev/null
+++ b/gitweb/static/js/javascript-detection.js
@@ -0,0 +1,43 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+// 2007, Petr Baudis <pasky@suse.cz>
+// 2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Detect if JavaScript is enabled, and pass it to server-side
+ * @license GPLv2 or later
+ */
+
+
+/* ============================================================ */
+/* Manipulating links */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * To be used as `window.onload` handler
+ *
+ * @globals jsExceptionsRe
+ */
+function fixLinks() {
+ var allLinks = document.getElementsByTagName("a") || document.links;
+ for (var i = 0, len = allLinks.length; i < len; i++) {
+ var link = allLinks[i];
+ if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
+ link.href +=
+ (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+ }
+ }
+}
+
+/* end of javascript-detection.js */
diff --git a/gitweb/static/js/lib/common-lib.js b/gitweb/static/js/lib/common-lib.js
new file mode 100644
index 0000000..018bbb7
--- /dev/null
+++ b/gitweb/static/js/lib/common-lib.js
@@ -0,0 +1,224 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+// 2007, Petr Baudis <pasky@suse.cz>
+// 2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Generic JavaScript code (helper functions)
+ * @license GPLv2 or later
+ */
+
+
+/* ============================================================ */
+/* ............................................................ */
+/* Padding */
+
+/**
+ * pad INPUT on the left with STR that is assumed to have visible
+ * width of single character (for example nonbreakable spaces),
+ * to WIDTH characters
+ *
+ * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
+ * ('\u00A0' is nonbreakable space)
+ *
+ * @param {Number|String} input: number to pad
+ * @param {Number} width: visible width of output
+ * @param {String} str: string to prefix to string, defaults to '\u00A0'
+ * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length)
+ */
+function padLeftStr(input, width, str) {
+ var prefix = '';
+ if (typeof str === 'undefined') {
+ ch = '\u00A0'; // using '&nbsp;' doesn't work in all browsers
+ }
+
+ width -= input.toString().length;
+ while (width > 0) {
+ prefix += str;
+ width--;
+ }
+ return prefix + input;
+}
+
+/**
+ * Pad INPUT on the left to WIDTH, using given padding character CH,
+ * for example padLeft('a', 3, '_') is '__a'
+ * padLeft(4, 2) is '04' (same as padLeft(4, 2, '0'))
+ *
+ * @param {String} input: input value converted to string.
+ * @param {Number} width: desired length of output.
+ * @param {String} ch: single character to prefix to string, defaults to '0'.
+ *
+ * @returns {String} Modified string, at least SIZE length.
+ */
+function padLeft(input, width, ch) {
+ var s = input + "";
+ if (typeof ch === 'undefined') {
+ ch = '0';
+ }
+
+ while (s.length < width) {
+ s = ch + s;
+ }
+ return s;
+}
+
+
+/* ............................................................ */
+/* Handling browser incompatibilities */
+
+/**
+ * Create XMLHttpRequest object in cross-browser way
+ * @returns XMLHttpRequest object, or null
+ */
+function createRequestObject() {
+ try {
+ return new XMLHttpRequest();
+ } catch (e) {}
+ try {
+ return window.createRequest();
+ } catch (e) {}
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {}
+ try {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (e) {}
+
+ return null;
+}
+
+
+/**
+ * Insert rule giving specified STYLE to given SELECTOR at the end of
+ * first CSS stylesheet.
+ *
+ * @param {String} selector: CSS selector, e.g. '.class'
+ * @param {String} style: rule contents, e.g. 'background-color: red;'
+ */
+function addCssRule(selector, style) {
+ var stylesheet = document.styleSheets[0];
+
+ var theRules = [];
+ if (stylesheet.cssRules) { // W3C way
+ theRules = stylesheet.cssRules;
+ } else if (stylesheet.rules) { // IE way
+ theRules = stylesheet.rules;
+ }
+
+ if (stylesheet.insertRule) { // W3C way
+ stylesheet.insertRule(selector + ' { ' + style + ' }', theRules.length);
+ } else if (stylesheet.addRule) { // IE way
+ stylesheet.addRule(selector, style);
+ }
+}
+
+
+/* ............................................................ */
+/* Support for legacy browsers */
+
+/**
+ * Provides getElementsByClassName method, if there is no native
+ * implementation of this method.
+ *
+ * NOTE that there are limits and differences compared to native
+ * getElementsByClassName as defined by e.g.:
+ * https://developer.mozilla.org/en/DOM/document.getElementsByClassName
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname
+ *
+ * Namely, this implementation supports only single class name as
+ * argument and not set of space-separated tokens representing classes,
+ * it returns Array of nodes rather than live NodeList, and has
+ * additional optional argument where you can limit search to given tags
+ * (via getElementsByTagName).
+ *
+ * Based on
+ * http://code.google.com/p/getelementsbyclassname/
+ * http://www.dustindiaz.com/getelementsbyclass/
+ * http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript
+ *
+ * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/
+ *
+ * @param {String} class: name of _single_ class to find
+ * @param {String} [taghint] limit search to given tags
+ * @returns {Node[]} array of matching elements
+ */
+if (!('getElementsByClassName' in document)) {
+ document.getElementsByClassName = function (classname, taghint) {
+ taghint = taghint || "*";
+ var elements = (taghint === "*" && document.all) ?
+ document.all :
+ document.getElementsByTagName(taghint);
+ var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)");
+ var matches= [];
+ for (var i = 0, j = 0, n = elements.length; i < n; i++) {
+ var el= elements[i];
+ if (el.className && pattern.test(el.className)) {
+ // matches.push(el);
+ matches[j] = el;
+ j++;
+ }
+ }
+ return matches;
+ };
+} // end if
+
+
+/* ............................................................ */
+/* unquoting/unescaping filenames */
+
+/**#@+
+ * @constant
+ */
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
+var octEscRe = /^[0-7]{1,3}$/;
+var maybeQuotedRe = /^\"(.*)\"$/;
+/**#@-*/
+
+/**
+ * unquote maybe C-quoted filename (as used by git, i.e. it is
+ * in double quotes '"' if there is any escape character used)
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a'
+ *
+ * @param {String} str: git-quoted string
+ * @returns {String} Unquoted and unescaped string
+ *
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
+ */
+function unquote(str) {
+ function unq(seq) {
+ var es = {
+ // character escape codes, aka escape sequences (from C)
+ // replacements are to some extent JavaScript specific
+ t: "\t", // tab (HT, TAB)
+ n: "\n", // newline (NL)
+ r: "\r", // return (CR)
+ f: "\f", // form feed (FF)
+ b: "\b", // backspace (BS)
+ a: "\x07", // alarm (bell) (BEL)
+ e: "\x1B", // escape (ESC)
+ v: "\v" // vertical tab (VT)
+ };
+
+ if (seq.search(octEscRe) !== -1) {
+ // octal char sequence
+ return String.fromCharCode(parseInt(seq, 8));
+ } else if (seq in es) {
+ // C escape sequence, aka character escape code
+ return es[seq];
+ }
+ // quoted ordinary character
+ return seq;
+ }
+
+ var match = str.match(maybeQuotedRe);
+ if (match) {
+ str = match[1];
+ // perhaps str = eval('"'+str+'"'); would be enough?
+ str = str.replace(escCodeRe,
+ function (substr, p1, offset, s) { return unq(p1); });
+ }
+ return str;
+}
+
+/* end of common-lib.js */
diff --git a/gitweb/static/js/lib/cookies.js b/gitweb/static/js/lib/cookies.js
new file mode 100644
index 0000000..72b51cd
--- /dev/null
+++ b/gitweb/static/js/lib/cookies.js
@@ -0,0 +1,114 @@
+/**
+ * @fileOverview Accessing cookies from JavaScript
+ * @license GPLv2 or later
+ */
+
+/*
+ * Based on subsection "Cookies in JavaScript" of "Professional
+ * JavaScript for Web Developers" by Nicholas C. Zakas and cookie
+ * plugin from jQuery (dual licensed under the MIT and GPL licenses)
+ */
+
+
+/**
+ * Create a cookie with the given name and value,
+ * and other optional parameters.
+ *
+ * @example
+ * setCookie('foo', 'bar'); // will be deleted when browser exits
+ * setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) });
+ * setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week
+ * setCookie('foo', 'bar', { expires: 14, path: '/' });
+ *
+ * @param {String} sName: Unique name of a cookie (letters, numbers, underscores).
+ * @param {String} sValue: The string value stored in a cookie.
+ * @param {Object} [options] An object literal containing key/value pairs
+ * to provide optional cookie attributes.
+ * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires,
+ * or an integer specifying the expiration date from now on in days,
+ * or a Date object to be used as cookie expiration date.
+ * If a negative value is specified or a date in the past),
+ * the cookie will be deleted.
+ * If set to null or omitted, the cookie will be a session cookie
+ * and will not be retained when the the browser exits.
+ * @param {String} [options.path] Restrict access of a cookie to particular directory
+ * (default: path of page that created the cookie).
+ * @param {String} [options.domain] Override what web sites are allowed to access cookie
+ * (default: domain of page that created the cookie).
+ * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set
+ * and the cookie would be accessible only from secure sites
+ * (cookie transmission will require secure protocol like HTTPS).
+ */
+function setCookie(sName, sValue, options) {
+ options = options || {};
+ if (sValue === null) {
+ sValue = '';
+ option.expires = 'delete';
+ }
+
+ var sCookie = sName + '=' + encodeURIComponent(sValue);
+
+ if (options.expires) {
+ var oExpires = options.expires, sDate;
+ if (oExpires === 'delete') {
+ sDate = 'Thu, 01 Jan 1970 00:00:00 GMT';
+ } else if (typeof oExpires === 'string') {
+ sDate = oExpires;
+ } else {
+ var oDate;
+ if (typeof oExpires === 'number') {
+ oDate = new Date();
+ oDate.setTime(oDate.getTime() + (oExpires * 24 * 60 * 60 * 1000)); // days to ms
+ } else {
+ oDate = oExpires;
+ }
+ sDate = oDate.toGMTString();
+ }
+ sCookie += '; expires=' + sDate;
+ }
+
+ if (options.path) {
+ sCookie += '; path=' + (options.path);
+ }
+ if (options.domain) {
+ sCookie += '; domain=' + (options.domain);
+ }
+ if (options.secure) {
+ sCookie += '; secure';
+ }
+ document.cookie = sCookie;
+}
+
+/**
+ * Get the value of a cookie with the given name.
+ *
+ * @param {String} sName: Unique name of a cookie (letters, numbers, underscores)
+ * @returns {String|null} The string value stored in a cookie
+ */
+function getCookie(sName) {
+ var sRE = '(?:; )?' + sName + '=([^;]*);?';
+ var oRE = new RegExp(sRE);
+ if (oRE.test(document.cookie)) {
+ return decodeURIComponent(RegExp['$1']);
+ } else {
+ return null;
+ }
+}
+
+/**
+ * Delete cookie with given name
+ *
+ * @param {String} sName: Unique name of a cookie (letters, numbers, underscores)
+ * @param {Object} [options] An object literal containing key/value pairs
+ * to provide optional cookie attributes.
+ * @param {String} [options.path] Must be the same as when setting a cookie
+ * @param {String} [options.domain] Must be the same as when setting a cookie
+ */
+function deleteCookie(sName, options) {
+ options = options || {};
+ options.expires = 'delete';
+
+ setCookie(sName, '', options);
+}
+
+/* end of cookies.js */
diff --git a/gitweb/static/js/lib/datetime.js b/gitweb/static/js/lib/datetime.js
new file mode 100644
index 0000000..f78c60a
--- /dev/null
+++ b/gitweb/static/js/lib/datetime.js
@@ -0,0 +1,176 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+// 2007, Petr Baudis <pasky@suse.cz>
+// 2008-2011, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview Datetime manipulation: parsing and formatting
+ * @license GPLv2 or later
+ */
+
+
+/* ............................................................ */
+/* parsing and retrieving datetime related information */
+
+/**
+ * used to extract hours and minutes from timezone info, e.g '-0900'
+ * @constant
+ */
+var tzRe = /^([+\-])([0-9][0-9])([0-9][0-9])$/;
+
+/**
+ * convert numeric timezone +/-ZZZZ to offset from UTC in seconds
+ *
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {Number} offset from UTC in seconds for timezone
+ *
+ * @globals tzRe
+ */
+function timezoneOffset(timezoneInfo) {
+ var match = tzRe.exec(timezoneInfo);
+ var tz_sign = (match[1] === '-' ? -1 : +1);
+ var tz_hour = parseInt(match[2],10);
+ var tz_min = parseInt(match[3],10);
+
+ return tz_sign*(((tz_hour*60) + tz_min)*60);
+}
+
+/**
+ * return local (browser) timezone as offset from UTC in seconds
+ *
+ * @returns {Number} offset from UTC in seconds for local timezone
+ */
+function localTimezoneOffset() {
+ // getTimezoneOffset returns the time-zone offset from UTC,
+ // in _minutes_, for the current locale
+ return ((new Date()).getTimezoneOffset() * -60);
+}
+
+/**
+ * return local (browser) timezone as numeric timezone '(+|-)HHMM'
+ *
+ * @returns {String} locat timezone as -/+ZZZZ
+ */
+function localTimezoneInfo() {
+ var tzOffsetMinutes = (new Date()).getTimezoneOffset() * -1;
+
+ return formatTimezoneInfo(0, tzOffsetMinutes);
+}
+
+
+/**
+ * Parse RFC-2822 date into a Unix timestamp (into epoch)
+ *
+ * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
+ * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC'
+ */
+function parseRFC2822Date(date) {
+ // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere)
+ // date syntax, which is defined in RFC 2822 (obsoletes RFC 822)
+ // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC
+ return Date.parse(date) / 1000;
+}
+
+
+/* ............................................................ */
+/* formatting date */
+
+/**
+ * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM'
+ *
+ * @param {Number} hours: offset in hours, e.g. 2 for '+0200'
+ * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030';
+ * it is split into hours if not 0 <= minutes < 60,
+ * for example 1200 would give '+0100';
+ * defaults to 0
+ * @param {String} [sep] separator between hours and minutes part,
+ * default is '', might be ':' for W3CDTF (rfc-3339)
+ * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format
+ */
+function formatTimezoneInfo(hours, minutes, sep) {
+ minutes = minutes || 0; // to be able to use formatTimezoneInfo(hh)
+ sep = sep || ''; // default format is +/-ZZZZ
+
+ if (minutes < 0 || minutes > 59) {
+ hours = minutes > 0 ? Math.floor(minutes / 60) : Math.ceil(minutes / 60);
+ minutes = Math.abs(minutes - 60*hours); // sign of minutes is sign of hours
+ // NOTE: this works correctly because there is no UTC-00:30 timezone
+ }
+
+ var tzSign = hours >= 0 ? '+' : '-';
+ if (hours < 0) {
+ hours = -hours; // sign is stored in tzSign
+ }
+
+ return tzSign + padLeft(hours, 2, '0') + sep + padLeft(minutes, 2, '0');
+}
+
+/**
+ * translate 'utc' and 'local' to numerical timezone
+ * @param {String} timezoneInfo: might be 'utc' or 'local' (browser)
+ */
+function normalizeTimezoneInfo(timezoneInfo) {
+ switch (timezoneInfo) {
+ case 'utc':
+ return '+0000';
+ case 'local': // 'local' is browser timezone
+ return localTimezoneInfo();
+ }
+ return timezoneInfo;
+}
+
+
+/**
+ * return date in local time formatted in iso-8601 like format
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {String} date in local time in iso-8601 like format
+ */
+function formatDateISOLocal(epoch, timezoneInfo) {
+ // date corrected by timezone
+ var localDate = new Date(1000 * (epoch +
+ timezoneOffset(timezoneInfo)));
+ var localDateStr = // e.g. '2005-08-07'
+ localDate.getUTCFullYear() + '-' +
+ padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
+ padLeft(localDate.getUTCDate(), 2, '0');
+ var localTimeStr = // e.g. '21:49:46'
+ padLeft(localDate.getUTCHours(), 2, '0') + ':' +
+ padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+ padLeft(localDate.getUTCSeconds(), 2, '0');
+
+ return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/**
+ * return date in local time formatted in rfc-2822 format
+ * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise
+ * @returns {String} date in local time in rfc-2822 format
+ */
+function formatDateRFC2882(epoch, timezoneInfo, padDay) {
+ // A short textual representation of a month, three letters
+ var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ // A textual representation of a day, three letters
+ var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ // date corrected by timezone
+ var localDate = new Date(1000 * (epoch +
+ timezoneOffset(timezoneInfo)));
+ var localDateStr = // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005'
+ days[localDate.getUTCDay()] + ', ' +
+ (padDay ? padLeft(localDate.getUTCDate(),2,'0') : localDate.getUTCDate()) + ' ' +
+ months[localDate.getUTCMonth()] + ' ' +
+ localDate.getUTCFullYear();
+ var localTimeStr = // e.g. '21:49:46'
+ padLeft(localDate.getUTCHours(), 2, '0') + ':' +
+ padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+ padLeft(localDate.getUTCSeconds(), 2, '0');
+
+ return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/* end of datetime.js */
diff --git a/graph.c b/graph.c
index ac7c605..2f6893d 100644
--- a/graph.c
+++ b/graph.c
@@ -8,17 +8,6 @@
/* Internal API */
/*
- * Output the next line for a graph.
- * This formats the next graph line into the specified strbuf. It is not
- * terminated with a newline.
- *
- * Returns 1 if the line includes the current commit, and 0 otherwise.
- * graph_next_line() will return 1 exactly once for each time
- * graph_update() is called.
- */
-static int graph_next_line(struct git_graph *graph, struct strbuf *sb);
-
-/*
* Output a padding line in the graph.
* This is similar to graph_next_line(). However, it is guaranteed to
* never print the current commit line. Instead, if the commit line is
@@ -70,39 +59,28 @@ enum graph_state {
GRAPH_COLLAPSING
};
-/*
- * The list of available column colors.
- */
-static char column_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_RED,
- GIT_COLOR_GREEN,
- GIT_COLOR_YELLOW,
- GIT_COLOR_BLUE,
- GIT_COLOR_MAGENTA,
- GIT_COLOR_CYAN,
- GIT_COLOR_BOLD_RED,
- GIT_COLOR_BOLD_GREEN,
- GIT_COLOR_BOLD_YELLOW,
- GIT_COLOR_BOLD_BLUE,
- GIT_COLOR_BOLD_MAGENTA,
- GIT_COLOR_BOLD_CYAN,
-};
+static const char **column_colors;
+static unsigned short column_colors_max;
-#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+void graph_set_column_colors(const char **colors, unsigned short colors_max)
+{
+ column_colors = colors;
+ column_colors_max = colors_max;
+}
-static const char *column_get_color_code(const struct column *c)
+static const char *column_get_color_code(unsigned short color)
{
- return column_colors[c->color];
+ return column_colors[color];
}
static void strbuf_write_column(struct strbuf *sb, const struct column *c,
char col_char)
{
- if (c->color < COLUMN_COLORS_MAX)
- strbuf_addstr(sb, column_get_color_code(c));
+ if (c->color < column_colors_max)
+ strbuf_addstr(sb, column_get_color_code(c->color));
strbuf_addch(sb, col_char);
- if (c->color < COLUMN_COLORS_MAX)
- strbuf_addstr(sb, GIT_COLOR_RESET);
+ if (c->color < column_colors_max)
+ strbuf_addstr(sb, column_get_color_code(column_colors_max));
}
struct git_graph {
@@ -226,6 +204,11 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
struct git_graph *graph_init(struct rev_info *opt)
{
struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+
+ if (!column_colors)
+ graph_set_column_colors(column_colors_ansi,
+ column_colors_ansi_max);
+
graph->commit = NULL;
graph->revs = opt;
graph->num_parents = 0;
@@ -242,7 +225,7 @@ struct git_graph *graph_init(struct rev_info *opt)
* always increment it for the first commit we output.
* This way we start at 0 for the first commit.
*/
- graph->default_column_color = COLUMN_COLORS_MAX - 1;
+ graph->default_column_color = column_colors_max - 1;
/*
* Allocate a reasonably large default number of columns
@@ -365,7 +348,7 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
static unsigned short graph_get_current_column_color(const struct git_graph *graph)
{
if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
- return COLUMN_COLORS_MAX;
+ return column_colors_max;
return graph->default_column_color;
}
@@ -375,7 +358,7 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra
static void graph_increment_column_color(struct git_graph *graph)
{
graph->default_column_color = (graph->default_column_color + 1) %
- COLUMN_COLORS_MAX;
+ column_colors_max;
}
static unsigned short graph_find_commit_color(const struct git_graph *graph,
@@ -439,7 +422,7 @@ static void graph_update_width(struct git_graph *graph,
max_cols++;
/*
- * We added a column for the the current commit as part of
+ * We added a column for the current commit as part of
* graph->num_parents. If the current commit was already in
* graph->columns, then we have double counted it.
*/
@@ -794,22 +777,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
}
/*
- * If revs->left_right is set, print '<' for commits that
- * come from the left side, and '>' for commits from the right
- * side.
- */
- if (graph->revs && graph->revs->left_right) {
- if (graph->commit->object.flags & SYMMETRIC_LEFT)
- strbuf_addch(sb, '<');
- else
- strbuf_addch(sb, '>');
- return;
- }
-
- /*
- * Print '*' in all other cases
+ * get_revision_mark() handles all other cases without assert()
*/
- strbuf_addch(sb, '*');
+ strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));
}
/*
@@ -1143,7 +1113,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
graph_update_state(graph, GRAPH_PADDING);
}
-static int graph_next_line(struct git_graph *graph, struct strbuf *sb)
+int graph_next_line(struct git_graph *graph, struct strbuf *sb)
{
switch (graph->state) {
case GRAPH_PADDING:
diff --git a/graph.h b/graph.h
index b82ae87..aff960c 100644
--- a/graph.h
+++ b/graph.h
@@ -5,6 +5,23 @@
struct git_graph;
/*
+ * Set up a custom scheme for column colors.
+ *
+ * The default column color scheme inserts ANSI color escapes to colorize
+ * the graph. The various color escapes are stored in an array of strings
+ * where each entry corresponds to a color, except for the last entry,
+ * which denotes the escape for resetting the color back to the default.
+ * When generating the graph, strings from this array are inserted before
+ * and after the various column characters.
+ *
+ * This function allows you to enable a custom array of color escapes.
+ * The 'colors_max' argument is the index of the last "reset" entry.
+ *
+ * This functions must be called BEFORE graph_init() is called.
+ */
+void graph_set_column_colors(const char **colors, unsigned short colors_max);
+
+/*
* Create a new struct git_graph.
*/
struct git_graph *graph_init(struct rev_info *opt);
@@ -32,6 +49,17 @@ void graph_update(struct git_graph *graph, struct commit *commit);
*/
int graph_is_commit_finished(struct git_graph const *graph);
+/*
+ * Output the next line for a graph.
+ * This formats the next graph line into the specified strbuf. It is not
+ * terminated with a newline.
+ *
+ * Returns 1 if the line includes the current commit, and 0 otherwise.
+ * graph_next_line() will return 1 exactly once for each time
+ * graph_update() is called.
+ */
+int graph_next_line(struct git_graph *graph, struct strbuf *sb);
+
/*
* graph_show_*: helper functions for printing to stdout
diff --git a/grep.c b/grep.c
index 82fb349..04e9ba4 100644
--- a/grep.c
+++ b/grep.c
@@ -59,6 +59,84 @@ struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
return ret;
}
+static NORETURN void compile_regexp_failed(const struct grep_pat *p,
+ const char *error)
+{
+ char where[1024];
+
+ if (p->no)
+ sprintf(where, "In '%s' at %d, ", p->origin, p->no);
+ else if (p->origin)
+ sprintf(where, "%s, ", p->origin);
+ else
+ where[0] = 0;
+
+ die("%s'%s': %s", where, p->pattern, error);
+}
+
+#ifdef USE_LIBPCRE
+static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
+{
+ const char *error;
+ int erroffset;
+ int options = 0;
+
+ if (opt->ignore_case)
+ options |= PCRE_CASELESS;
+
+ p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset,
+ NULL);
+ if (!p->pcre_regexp)
+ compile_regexp_failed(p, error);
+
+ p->pcre_extra_info = pcre_study(p->pcre_regexp, 0, &error);
+ if (!p->pcre_extra_info && error)
+ die("%s", error);
+}
+
+static int pcrematch(struct grep_pat *p, const char *line, const char *eol,
+ regmatch_t *match, int eflags)
+{
+ int ovector[30], ret, flags = 0;
+
+ if (eflags & REG_NOTBOL)
+ flags |= PCRE_NOTBOL;
+
+ ret = pcre_exec(p->pcre_regexp, p->pcre_extra_info, line, eol - line,
+ 0, flags, ovector, ARRAY_SIZE(ovector));
+ if (ret < 0 && ret != PCRE_ERROR_NOMATCH)
+ die("pcre_exec failed with error code %d", ret);
+ if (ret > 0) {
+ ret = 0;
+ match->rm_so = ovector[0];
+ match->rm_eo = ovector[1];
+ }
+
+ return ret;
+}
+
+static void free_pcre_regexp(struct grep_pat *p)
+{
+ pcre_free(p->pcre_regexp);
+ pcre_free(p->pcre_extra_info);
+}
+#else /* !USE_LIBPCRE */
+static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
+{
+ die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE");
+}
+
+static int pcrematch(struct grep_pat *p, const char *line, const char *eol,
+ regmatch_t *match, int eflags)
+{
+ return 1;
+}
+
+static void free_pcre_regexp(struct grep_pat *p)
+{
+}
+#endif /* !USE_LIBPCRE */
+
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int err;
@@ -70,20 +148,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
if (p->fixed)
return;
+ if (opt->pcre) {
+ compile_pcre_regexp(p, opt);
+ return;
+ }
+
err = regcomp(&p->regexp, p->pattern, opt->regflags);
if (err) {
char errbuf[1024];
- char where[1024];
- if (p->no)
- sprintf(where, "In '%s' at %d, ",
- p->origin, p->no);
- else if (p->origin)
- sprintf(where, "%s, ", p->origin);
- else
- where[0] = 0;
regerror(err, &p->regexp, errbuf, 1024);
regfree(&p->regexp);
- die("%s'%s': %s", where, p->pattern, errbuf);
+ compile_regexp_failed(p, errbuf);
}
}
@@ -189,29 +264,73 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
return compile_pattern_or(list);
}
-void compile_grep_patterns(struct grep_opt *opt)
+static struct grep_expr *grep_true_expr(void)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = GREP_NODE_TRUE;
+ return z;
+}
+
+static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
+{
+ struct grep_expr *z = xcalloc(1, sizeof(*z));
+ z->node = GREP_NODE_OR;
+ z->u.binary.left = left;
+ z->u.binary.right = right;
+ return z;
+}
+
+static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
{
struct grep_pat *p;
- struct grep_expr *header_expr = NULL;
-
- if (opt->header_list) {
- p = opt->header_list;
- header_expr = compile_pattern_expr(&p);
- if (p)
- die("incomplete pattern expression: %s", p->pattern);
- for (p = opt->header_list; p; p = p->next) {
- switch (p->token) {
- case GREP_PATTERN: /* atom */
- case GREP_PATTERN_HEAD:
- case GREP_PATTERN_BODY:
- compile_regexp(p, opt);
- break;
- default:
- opt->extended = 1;
- break;
- }
+ struct grep_expr *header_expr;
+ struct grep_expr *(header_group[GREP_HEADER_FIELD_MAX]);
+ enum grep_header_field fld;
+
+ if (!opt->header_list)
+ return NULL;
+ p = opt->header_list;
+ for (p = opt->header_list; p; p = p->next) {
+ if (p->token != GREP_PATTERN_HEAD)
+ die("bug: a non-header pattern in grep header list.");
+ if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field)
+ die("bug: unknown header field %d", p->field);
+ compile_regexp(p, opt);
+ }
+
+ for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++)
+ header_group[fld] = NULL;
+
+ for (p = opt->header_list; p; p = p->next) {
+ struct grep_expr *h;
+ struct grep_pat *pp = p;
+
+ h = compile_pattern_atom(&pp);
+ if (!h || pp != p->next)
+ die("bug: malformed header expr");
+ if (!header_group[p->field]) {
+ header_group[p->field] = h;
+ continue;
}
+ header_group[p->field] = grep_or_expr(h, header_group[p->field]);
+ }
+
+ header_expr = NULL;
+
+ for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) {
+ if (!header_group[fld])
+ continue;
+ if (!header_expr)
+ header_expr = grep_true_expr();
+ header_expr = grep_or_expr(header_group[fld], header_expr);
}
+ return header_expr;
+}
+
+void compile_grep_patterns(struct grep_opt *opt)
+{
+ struct grep_pat *p;
+ struct grep_expr *header_expr = prep_header_patterns(opt);
for (p = opt->pattern_list; p; p = p->next) {
switch (p->token) {
@@ -231,9 +350,6 @@ void compile_grep_patterns(struct grep_opt *opt)
else if (!opt->extended)
return;
- /* Then bundle them up in an expression.
- * A classic recursive descent parser would do.
- */
p = opt->pattern_list;
if (p)
opt->pattern_expression = compile_pattern_expr(&p);
@@ -243,22 +359,18 @@ void compile_grep_patterns(struct grep_opt *opt)
if (!header_expr)
return;
- if (opt->pattern_expression) {
- struct grep_expr *z;
- z = xcalloc(1, sizeof(*z));
- z->node = GREP_NODE_OR;
- z->u.binary.left = opt->pattern_expression;
- z->u.binary.right = header_expr;
- opt->pattern_expression = z;
- } else {
+ if (!opt->pattern_expression)
opt->pattern_expression = header_expr;
- }
+ else
+ opt->pattern_expression = grep_or_expr(opt->pattern_expression,
+ header_expr);
opt->all_match = 1;
}
static void free_pattern_expr(struct grep_expr *x)
{
switch (x->node) {
+ case GREP_NODE_TRUE:
case GREP_NODE_ATOM:
break;
case GREP_NODE_NOT:
@@ -283,7 +395,10 @@ void free_grep_patterns(struct grep_opt *opt)
case GREP_PATTERN: /* atom */
case GREP_PATTERN_HEAD:
case GREP_PATTERN_BODY:
- regfree(&p->regexp);
+ if (p->pcre_regexp)
+ free_pcre_regexp(p);
+ else
+ regfree(&p->regexp);
break;
default:
break;
@@ -375,6 +490,21 @@ static int regmatch(const regex_t *preg, char *line, char *eol,
return regexec(preg, line, 1, match, eflags);
}
+static int patmatch(struct grep_pat *p, char *line, char *eol,
+ regmatch_t *match, int eflags)
+{
+ int hit;
+
+ if (p->fixed)
+ hit = !fixmatch(p, line, eol, match);
+ else if (p->pcre_regexp)
+ hit = !pcrematch(p, line, eol, match, eflags);
+ else
+ hit = !regmatch(&p->regexp, line, eol, match, eflags);
+
+ return hit;
+}
+
static int strip_timestamp(char *bol, char **eol_p)
{
char *eol = *eol_p;
@@ -424,10 +554,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
}
again:
- if (p->fixed)
- hit = !fixmatch(p, bol, eol, pmatch);
- else
- hit = !regmatch(&p->regexp, bol, eol, pmatch, eflags);
+ hit = patmatch(p, bol, eol, pmatch, eflags);
if (hit && p->word_regexp) {
if ((pmatch[0].rm_so < 0) ||
@@ -487,6 +614,9 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
if (!x)
die("Not a valid grep expression");
switch (x->node) {
+ case GREP_NODE_TRUE:
+ h = 1;
+ break;
case GREP_NODE_ATOM:
h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
break;
@@ -591,7 +721,10 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
int rest = eol - bol;
char *line_color = NULL;
- if (opt->pre_context || opt->post_context) {
+ if (opt->file_break && opt->last_shown == 0) {
+ if (opt->show_hunk_mark)
+ opt->output(opt, "\n", 1);
+ } else if (opt->pre_context || opt->post_context) {
if (opt->last_shown == 0) {
if (opt->show_hunk_mark) {
output_color(opt, "--", 2, opt->color_sep);
@@ -602,9 +735,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
opt->output(opt, "\n", 1);
}
}
+ if (opt->heading && opt->last_shown == 0) {
+ output_color(opt, name, strlen(name), opt->color_filename);
+ opt->output(opt, "\n", 1);
+ }
opt->last_shown = lno;
- if (opt->pathname) {
+ if (!opt->heading && opt->pathname) {
output_color(opt, name, strlen(name), opt->color_filename);
output_sep(opt, sign);
}
@@ -751,10 +888,7 @@ static int look_ahead(struct grep_opt *opt,
int hit;
regmatch_t m;
- if (p->fixed)
- hit = !fixmatch(p, bol, bol + *left_p, &m);
- else
- hit = !regmatch(&p->regexp, bol, bol + *left_p, &m, 0);
+ hit = patmatch(p, bol, bol + *left_p, &m, 0);
if (!hit || m.rm_so < 0 || m.rm_eo < 0)
continue;
if (earliest < 0 || m.rm_so < earliest)
@@ -814,9 +948,18 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
if (!opt->output)
opt->output = std_output;
- if (opt->last_shown && (opt->pre_context || opt->post_context) &&
- opt->output == std_output)
- opt->show_hunk_mark = 1;
+ if (opt->pre_context || opt->post_context || opt->file_break) {
+ /* Show hunk marks, except for the first file. */
+ if (opt->last_shown)
+ opt->show_hunk_mark = 1;
+ /*
+ * If we're using threads then we can't easily identify
+ * the first file. Always put hunk marks in that case
+ * and skip the very first one later in work_done().
+ */
+ if (opt->output != std_output)
+ opt->show_hunk_mark = 1;
+ }
opt->last_shown = 0;
switch (opt->binary) {
@@ -851,7 +994,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
int hit;
/*
- * look_ahead() skips quicly to the line that possibly
+ * look_ahead() skips quickly to the line that possibly
* has the next hit; don't call it if we need to do
* something more than just skipping the current line
* in response to an unmatch for the current line. E.g.
diff --git a/grep.h b/grep.h
index efa8cff..c568297 100644
--- a/grep.h
+++ b/grep.h
@@ -1,6 +1,12 @@
#ifndef GREP_H
#define GREP_H
#include "color.h"
+#ifdef USE_LIBPCRE
+#include <pcre.h>
+#else
+typedef int pcre;
+typedef int pcre_extra;
+#endif
enum grep_pat_token {
GREP_PATTERN,
@@ -22,6 +28,7 @@ enum grep_header_field {
GREP_HEADER_AUTHOR = 0,
GREP_HEADER_COMMITTER
};
+#define GREP_HEADER_FIELD_MAX (GREP_HEADER_COMMITTER + 1)
struct grep_pat {
struct grep_pat *next;
@@ -32,6 +39,8 @@ struct grep_pat {
size_t patternlen;
enum grep_header_field field;
regex_t regexp;
+ pcre *pcre_regexp;
+ pcre_extra *pcre_extra_info;
unsigned fixed:1;
unsigned ignore_case:1;
unsigned word_regexp:1;
@@ -41,6 +50,7 @@ enum grep_expr_node {
GREP_NODE_ATOM,
GREP_NODE_NOT,
GREP_NODE_AND,
+ GREP_NODE_TRUE,
GREP_NODE_OR
};
@@ -81,6 +91,7 @@ struct grep_opt {
#define GREP_BINARY_TEXT 2
int binary;
int extended;
+ int pcre;
int relative;
int pathname;
int null_following_name;
@@ -99,6 +110,8 @@ struct grep_opt {
unsigned post_context;
unsigned last_shown;
int show_hunk_mark;
+ int file_break;
+ int heading;
void *priv;
void (*output)(struct grep_opt *opt, const void *data, size_t size);
diff --git a/hash.c b/hash.c
index 1cd4c9d..749ecfe 100644
--- a/hash.c
+++ b/hash.c
@@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
return insert_hash_entry(hash, ptr, table);
}
-int for_each_hash(const struct hash_table *table, int (*fn)(void *))
+int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data)
{
int sum = 0;
unsigned int i;
@@ -92,7 +92,7 @@ int for_each_hash(const struct hash_table *table, int (*fn)(void *))
void *ptr = array->ptr;
array++;
if (ptr) {
- int val = fn(ptr);
+ int val = fn(ptr, data);
if (val < 0)
return val;
sum += val;
diff --git a/hash.h b/hash.h
index 69e33a4..b875ce6 100644
--- a/hash.h
+++ b/hash.h
@@ -30,7 +30,7 @@ struct hash_table {
extern void *lookup_hash(unsigned int hash, const struct hash_table *table);
extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
-extern int for_each_hash(const struct hash_table *table, int (*fn)(void *));
+extern int for_each_hash(const struct hash_table *table, int (*fn)(void *, void *), void *data);
extern void free_hash(struct hash_table *table);
static inline void init_hash(struct hash_table *table)
diff --git a/help.c b/help.c
index 7f4928e..cbbe966 100644
--- a/help.c
+++ b/help.c
@@ -3,6 +3,7 @@
#include "exec_cmd.h"
#include "levenshtein.h"
#include "help.h"
+#include "common-cmds.h"
/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
@@ -126,7 +127,10 @@ static int is_executable(const char *name)
!S_ISREG(st.st_mode))
return 0;
-#ifdef WIN32
+#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(__CYGWIN__)
+if ((st.st_mode & S_IXUSR) == 0)
+#endif
{ /* cannot trust the executable bit, peek into the file instead */
char buf[3] = { 0 };
int n;
@@ -298,7 +302,12 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
}
/* An empirically derived magic number */
-#define SIMILAR_ENOUGH(x) ((x) < 6)
+#define SIMILARITY_FLOOR 7
+#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
+
+static const char bad_interpreter_advice[] =
+ N_("'%s' appears to be a git command, but we were not\n"
+ "able to execute it. Maybe git-%s is broken?");
const char *help_unknown_cmd(const char *cmd)
{
@@ -319,10 +328,36 @@ const char *help_unknown_cmd(const char *cmd)
sizeof(main_cmds.names), cmdname_compare);
uniq(&main_cmds);
- /* This reuses cmdname->len for similarity index */
- for (i = 0; i < main_cmds.cnt; ++i)
+ /* This abuses cmdname->len for levenshtein distance */
+ for (i = 0, n = 0; i < main_cmds.cnt; i++) {
+ int cmp = 0; /* avoid compiler stupidity */
+ const char *candidate = main_cmds.names[i]->name;
+
+ /*
+ * An exact match means we have the command, but
+ * for some reason exec'ing it gave us ENOENT; probably
+ * it's a bad interpreter in the #! line.
+ */
+ if (!strcmp(candidate, cmd))
+ die(_(bad_interpreter_advice), cmd, cmd);
+
+ /* Does the candidate appear in common_cmds list? */
+ while (n < ARRAY_SIZE(common_cmds) &&
+ (cmp = strcmp(common_cmds[n].name, candidate)) < 0)
+ n++;
+ if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
+ /* Yes, this is one of the common commands */
+ n++; /* use the entry from common_cmds[] */
+ if (!prefixcmp(candidate, cmd)) {
+ /* Give prefix match a very good score */
+ main_cmds.names[i]->len = 0;
+ continue;
+ }
+ }
+
main_cmds.names[i]->len =
- levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
+ levenshtein(cmd, candidate, 0, 2, 1, 4) + 1;
+ }
qsort(main_cmds.names, main_cmds.cnt,
sizeof(*main_cmds.names), levenshtein_compare);
@@ -330,10 +365,21 @@ const char *help_unknown_cmd(const char *cmd)
if (!main_cmds.cnt)
die ("Uh oh. Your system reports no Git commands at all.");
- best_similarity = main_cmds.names[0]->len;
- n = 1;
- while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
- ++n;
+ /* skip and count prefix matches */
+ for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
+ ; /* still counting */
+
+ if (main_cmds.cnt <= n) {
+ /* prefix matches with everything? that is too ambiguous */
+ best_similarity = SIMILARITY_FLOOR + 1;
+ } else {
+ /* count all the most similar ones */
+ for (best_similarity = main_cmds.names[n++]->len;
+ (n < main_cmds.cnt &&
+ best_similarity == main_cmds.names[n]->len);
+ n++)
+ ; /* still counting */
+ }
if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
diff --git a/help.h b/help.h
index 56bc154..b6b12d5 100644
--- a/help.h
+++ b/help.h
@@ -16,14 +16,17 @@ static inline void mput_char(char c, unsigned int num)
putchar(c);
}
-void load_command_list(const char *prefix,
- struct cmdnames *main_cmds,
- struct cmdnames *other_cmds);
-void add_cmdname(struct cmdnames *cmds, const char *name, int len);
+extern void list_common_cmds_help(void);
+extern const char *help_unknown_cmd(const char *cmd);
+extern void load_command_list(const char *prefix,
+ struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds);
+extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);
/* Here we require that excludes is a sorted list. */
-void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
-int is_in_cmdlist(struct cmdnames *c, const char *s);
-void list_commands(const char *title, struct cmdnames *main_cmds,
- struct cmdnames *other_cmds);
+extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
+extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
+extern void list_commands(const char *title,
+ struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds);
#endif /* HELP_H */
diff --git a/http-backend.c b/http-backend.c
index 14c90c2..59ad7da 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -271,16 +271,13 @@ static struct rpc_service *select_service(const char *name)
static void inflate_request(const char *prog_name, int out)
{
- z_stream stream;
+ git_zstream stream;
unsigned char in_buf[8192];
unsigned char out_buf[8192];
unsigned long cnt = 0;
- int ret;
memset(&stream, 0, sizeof(stream));
- ret = inflateInit2(&stream, (15 + 16));
- if (ret != Z_OK)
- die("cannot start zlib inflater, zlib err %d", ret);
+ git_inflate_init_gzip_only(&stream);
while (1) {
ssize_t n = xread(0, in_buf, sizeof(in_buf));
@@ -296,7 +293,7 @@ static void inflate_request(const char *prog_name, int out)
stream.next_out = out_buf;
stream.avail_out = sizeof(out_buf);
- ret = inflate(&stream, Z_NO_FLUSH);
+ ret = git_inflate(&stream, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END)
die("zlib error inflating request, result %d", ret);
@@ -311,7 +308,7 @@ static void inflate_request(const char *prog_name, int out)
}
done:
- inflateEnd(&stream);
+ git_inflate_end(&stream);
close(out);
}
@@ -510,9 +507,7 @@ static char* getdir(void)
die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
if (daemon_avoid_alias(pathinfo))
die("'%s': aliased", pathinfo);
- strbuf_addstr(&buf, root);
- if (buf.buf[buf.len - 1] != '/')
- strbuf_addch(&buf, '/');
+ end_url_with_slash(&buf, root);
if (pathinfo[0] == '/')
pathinfo++;
strbuf_addstr(&buf, pathinfo);
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..3af4c71b 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -8,14 +8,12 @@ static const char http_fetch_usage[] = "git http-fetch "
int main(int argc, const char **argv)
{
- const char *prefix;
struct walker *walker;
int commits_on_stdin = 0;
int commits;
const char **write_ref = NULL;
char **commit_id;
- const char *url;
- char *rewritten_url = NULL;
+ char *url = NULL;
int arg = 1;
int rc = 0;
int get_tree = 0;
@@ -57,18 +55,13 @@ int main(int argc, const char **argv)
commit_id = (char **) &argv[arg++];
commits = 1;
}
- url = argv[arg];
- prefix = setup_git_directory();
+ if (argv[arg])
+ str_end_url_with_slash(argv[arg], &url);
- git_config(git_default_config, NULL);
+ setup_git_directory();
- if (url && url[strlen(url)-1] != '/') {
- rewritten_url = xmalloc(strlen(url)+2);
- strcpy(rewritten_url, url);
- strcat(rewritten_url, "/");
- url = rewritten_url;
- }
+ git_config(git_default_config, NULL);
http_init(NULL);
walker = get_http_walker(url);
@@ -93,7 +86,7 @@ int main(int argc, const char **argv)
walker_free(walker);
http_cleanup();
- free(rewritten_url);
+ free(url);
return rc;
}
diff --git a/http-push.c b/http-push.c
index c9bcd11..6e8f6d0 100644
--- a/http-push.c
+++ b/http-push.c
@@ -82,8 +82,7 @@ static int helper_status;
static struct object_list *objects;
-struct repo
-{
+struct repo {
char *url;
char *path;
int path_len;
@@ -108,8 +107,7 @@ enum transfer_state {
COMPLETE
};
-struct transfer_request
-{
+struct transfer_request {
struct object *obj;
char *url;
char *dest;
@@ -127,8 +125,7 @@ struct transfer_request
static struct transfer_request *request_queue_head;
-struct xml_ctx
-{
+struct xml_ctx {
char *name;
int len;
char *cdata;
@@ -136,8 +133,7 @@ struct xml_ctx
void *userData;
};
-struct remote_lock
-{
+struct remote_lock {
char *url;
char *owner;
char *token;
@@ -156,8 +152,7 @@ struct remote_lock
/* Flags that remote_ls passes to callback functions */
#define IS_DIR (1u << 0)
-struct remote_ls_ctx
-{
+struct remote_ls_ctx {
char *path;
void (*userFunc)(struct remote_ls_ctx *ls);
void *userData;
@@ -174,7 +169,7 @@ enum dav_header_flag {
DAV_HEADER_TIMEOUT = (1u << 2)
};
-static char *xml_entities(char *s)
+static char *xml_entities(const char *s)
{
struct strbuf buf = STRBUF_INIT;
while (*s) {
@@ -202,6 +197,34 @@ static char *xml_entities(char *s)
return strbuf_detach(&buf, NULL);
}
+static void curl_setup_http_get(CURL *curl, const char *url,
+ const char *custom_req)
+{
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+}
+
+static void curl_setup_http(CURL *curl, const char *url,
+ const char *custom_req, struct buffer *buffer,
+ curl_write_callback write_fn)
+{
+ curl_easy_setopt(curl, CURLOPT_PUT, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_INFILE, buffer);
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(curl, CURLOPT_IOCTLDATA, &buffer);
+#endif
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn);
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+}
+
static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
{
struct strbuf buf = STRBUF_INIT;
@@ -277,11 +300,8 @@ static void start_mkcol(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_setup_http_get(slot->curl, request->url, DAV_MKCOL);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -357,15 +377,15 @@ static void start_put(struct transfer_request *request)
unsigned long len;
int hdrlen;
ssize_t size;
- z_stream stream;
+ git_zstream stream;
unpacked = read_sha1_file(request->obj->sha1, &type, &len);
hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
/* Set it up */
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
- size = deflateBound(&stream, len + hdrlen);
+ git_deflate_init(&stream, zlib_compression_level);
+ size = git_deflate_bound(&stream, len + hdrlen);
strbuf_init(&request->buffer.buf, size);
request->buffer.posn = 0;
@@ -376,15 +396,15 @@ static void start_put(struct transfer_request *request)
/* First header.. */
stream.next_in = (void *)hdr;
stream.avail_in = hdrlen;
- while (deflate(&stream, 0) == Z_OK)
- /* nothing */;
+ while (git_deflate(&stream, 0) == Z_OK)
+ ; /* nothing */
/* Then the data itself.. */
stream.next_in = unpacked;
stream.avail_in = len;
- while (deflate(&stream, Z_FINISH) == Z_OK)
- /* nothing */;
- deflateEnd(&stream);
+ while (git_deflate(&stream, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ git_deflate_end(&stream);
free(unpacked);
request->buffer.buf.len = stream.total_out;
@@ -400,19 +420,8 @@ static void start_put(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_setup_http(slot->curl, request->url, DAV_PUT,
+ &request->buffer, fwrite_null);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -432,13 +441,10 @@ static void start_move(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
+ curl_setup_http_get(slot->curl, request->url, DAV_MOVE);
dav_headers = curl_slist_append(dav_headers, request->dest);
dav_headers = curl_slist_append(dav_headers, "Overwrite: T");
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -463,10 +469,7 @@ static int refresh_lock(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_setup_http_get(slot->curl, lock->url, DAV_LOCK);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
if (start_active_slot(slot)) {
@@ -802,7 +805,7 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
}
}
-static void one_remote_ref(char *refname);
+static void one_remote_ref(const char *refname);
static void
xml_start_tag(void *userData, const char *name, const char **atts)
@@ -881,10 +884,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
ep[1] = '\0';
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_setup_http_get(slot->curl, url, DAV_MKCOL);
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result != CURLE_OK &&
@@ -914,19 +914,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
lock = xcalloc(1, sizeof(*lock));
lock->timeout = -1;
@@ -992,9 +982,7 @@ static int unlock_remote(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
+ curl_setup_http_get(slot->curl, lock->url, DAV_UNLOCK);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
if (start_active_slot(slot)) {
@@ -1090,6 +1078,10 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
if (tag_closed) {
if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
if (ls->dentry_flags & IS_DIR) {
+
+ /* ensure collection names end with slash */
+ str_end_url_with_slash(ls->dentry_name, &ls->dentry_name);
+
if (ls->flags & PROCESS_DIRS) {
ls->userFunc(ls);
}
@@ -1112,8 +1104,16 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
}
}
if (path) {
- path += repo->path_len;
- ls->dentry_name = xstrdup(path);
+ const char *url = repo->url;
+ if (repo->path)
+ url = repo->path;
+ if (strncmp(path, url, repo->path_len))
+ error("Parsed path '%s' does not match url: '%s'\n",
+ path, url);
+ else {
+ path += repo->path_len;
+ ls->dentry_name = xstrdup(path);
+ }
}
} else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
ls->dentry_flags |= IS_DIR;
@@ -1160,19 +1160,10 @@ static void remote_ls(const char *path, int flags,
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_setup_http(slot->curl, url, DAV_PROPFIND,
+ &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1243,19 +1234,10 @@ static int locking_available(void)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_setup_http(slot->curl, repo->url, DAV_PROPFIND,
+ &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1429,19 +1411,9 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_setup_http(slot->curl, lock->url, DAV_PUT,
+ &out_buffer, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1464,7 +1436,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
static struct ref *remote_refs;
-static void one_remote_ref(char *refname)
+static void one_remote_ref(const char *refname)
{
struct ref *ref;
struct object *obj;
@@ -1565,19 +1537,9 @@ static void update_remote_info_refs(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_setup_http(slot->curl, lock->url, DAV_PUT,
+ &buffer, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1653,7 +1615,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha
return (merge_bases && !merge_bases->next && merge_bases->item == branch);
}
-static int delete_remote_branch(char *pattern, int force)
+static int delete_remote_branch(const char *pattern, int force)
{
struct ref *refs = remote_refs;
struct ref *remote_ref = NULL;
@@ -1735,10 +1697,7 @@ static int delete_remote_branch(char *pattern, int force)
sprintf(url, "%s%s", repo->url, remote_ref->name);
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+ curl_setup_http_get(slot->curl, url, DAV_DELETE);
if (start_active_slot(slot)) {
run_active_slot(slot);
free(url);
@@ -1789,7 +1748,6 @@ int main(int argc, char **argv)
int new_refs;
struct ref *ref, *local_refs;
struct remote *remote;
- char *rewritten_url = NULL;
git_extract_argv0_path(argv[0]);
@@ -1835,8 +1793,8 @@ int main(int argc, char **argv)
}
if (!repo->url) {
char *path = strstr(arg, "//");
- repo->url = arg;
- repo->path_len = strlen(arg);
+ str_end_url_with_slash(arg, &repo->url);
+ repo->path_len = strlen(repo->url);
if (path) {
repo->path = strchr(path+2, '/');
if (repo->path)
@@ -1872,15 +1830,6 @@ int main(int argc, char **argv)
remote->url[remote->url_nr++] = repo->url;
http_init(remote);
- if (repo->url && repo->url[strlen(repo->url)-1] != '/') {
- rewritten_url = xmalloc(strlen(repo->url)+2);
- strcpy(rewritten_url, repo->url);
- strcat(rewritten_url, "/");
- repo->path = rewritten_url + (repo->path - repo->url);
- repo->path_len++;
- repo->url = rewritten_url;
- }
-
#ifdef USE_CURL_MULTI
is_running_queue = 0;
#endif
@@ -2088,7 +2037,6 @@ int main(int argc, char **argv)
}
cleanup:
- free(rewritten_url);
if (info_ref_lock)
unlock_remote(info_ref_lock);
free(repo);
diff --git a/http-walker.c b/http-walker.c
index 18bd650..51a906e 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -3,8 +3,7 @@
#include "walker.h"
#include "http.h"
-struct alt_base
-{
+struct alt_base {
char *base;
int got_indices;
struct packed_git *packs;
@@ -18,8 +17,7 @@ enum object_request_state {
COMPLETE
};
-struct object_request
-{
+struct object_request {
struct walker *walker;
unsigned char sha1[20];
struct alt_base *repo;
@@ -187,7 +185,7 @@ static void process_alternates_response(void *callback_data)
struct active_request_slot *slot = alt_req->slot;
struct alt_base *tail = cdata->alt;
const char *base = alt_req->base;
- static const char null_byte = '\0';
+ const char null_byte = '\0';
char *data;
int i = 0;
@@ -220,7 +218,7 @@ static void process_alternates_response(void *callback_data)
}
}
- fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
+ fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer);
alt_req->buffer->len--;
data = alt_req->buffer->buf;
diff --git a/http.c b/http.c
index 1320c50..a1ea3db 100644
--- a/http.c
+++ b/http.c
@@ -2,6 +2,7 @@
#include "pack.h"
#include "sideband.h"
#include "run-command.h"
+#include "url.h"
int data_received;
int active_requests;
@@ -40,7 +41,9 @@ static long curl_low_speed_limit = -1;
static long curl_low_speed_time = -1;
static int curl_ftp_no_epsv;
static const char *curl_http_proxy;
+static const char *curl_cookie_file;
static char *user_name, *user_pass;
+static const char *user_agent;
#if LIBCURL_VERSION_NUM >= 0x071700
/* Use CURLOPT_KEYPASSWD as is */
@@ -58,7 +61,7 @@ static struct curl_slist *no_pragma_header;
static struct active_request_slot *active_queue_head;
-size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
+size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
struct buffer *buffer = buffer_;
@@ -90,7 +93,7 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
}
#endif
-size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
+size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
struct strbuf *buffer = buffer_;
@@ -100,7 +103,7 @@ size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer
return size;
}
-size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
+size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
{
data_received++;
return eltsize * nmemb;
@@ -189,6 +192,9 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value);
+ if (!strcmp("http.cookiefile", var))
+ return git_config_string(&curl_cookie_file, var, value);
+
if (!strcmp("http.postbuffer", var)) {
http_post_buffer = git_config_int(var, value);
if (http_post_buffer < LARGE_PACKET_MAX)
@@ -196,6 +202,9 @@ static int http_options(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp("http.useragent", var))
+ return git_config_string(&user_agent, var, value);
+
/* Fall back on the default ones */
return git_default_config(var, value, cb);
}
@@ -275,11 +284,17 @@ static CURL *get_curl_handle(void)
}
curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+#if LIBCURL_VERSION_NUM >= 0x071301
+ curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
+#elif LIBCURL_VERSION_NUM >= 0x071101
+ curl_easy_setopt(result, CURLOPT_POST301, 1);
+#endif
if (getenv("GIT_CURL_VERBOSE"))
curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
+ curl_easy_setopt(result, CURLOPT_USERAGENT,
+ user_agent ? user_agent : GIT_HTTP_USER_AGENT);
if (curl_ftp_no_epsv)
curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
@@ -292,7 +307,7 @@ static CURL *get_curl_handle(void)
static void http_auth_init(const char *url)
{
- char *at, *colon, *cp, *slash;
+ char *at, *colon, *cp, *slash, *decoded;
int len;
cp = strstr(url, "://");
@@ -317,16 +332,25 @@ static void http_auth_init(const char *url)
user_name = xmalloc(len + 1);
memcpy(user_name, cp, len);
user_name[len] = '\0';
+ decoded = url_decode(user_name);
+ free(user_name);
+ user_name = decoded;
user_pass = NULL;
} else {
len = colon - cp;
user_name = xmalloc(len + 1);
memcpy(user_name, cp, len);
user_name[len] = '\0';
+ decoded = url_decode(user_name);
+ free(user_name);
+ user_name = decoded;
len = at - (colon + 1);
user_pass = xmalloc(len + 1);
memcpy(user_pass, colon + 1, len);
user_pass[len] = '\0';
+ decoded = url_decode(user_pass);
+ free(user_pass);
+ user_pass = decoded;
}
}
@@ -380,6 +404,8 @@ void http_init(struct remote *remote)
#endif
set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
+ set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
+
low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
if (low_speed_limit != NULL)
curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
@@ -509,11 +535,13 @@ struct active_request_slot *get_active_slot(void)
slot->finished = NULL;
slot->callback_data = NULL;
slot->callback_func = NULL;
+ curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
@@ -721,13 +749,6 @@ static inline int hex(int v)
return 'A' + v - 10;
}
-void end_url_with_slash(struct strbuf *buf, const char *url)
-{
- strbuf_addstr(buf, url);
- if (buf->len && buf->buf[buf->len - 1] != '/')
- strbuf_addstr(buf, "/");
-}
-
static char *quote_ref_url(const char *base, const char *ref)
{
struct strbuf buf = STRBUF_INIT;
@@ -1151,7 +1172,7 @@ abort:
}
/* Helpers for fetching objects (loose) */
-static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
void *data)
{
unsigned char expn[4096];
@@ -1168,7 +1189,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
} while (posn < size);
freq->stream.avail_in = size;
- freq->stream.next_in = ptr;
+ freq->stream.next_in = (void *)ptr;
do {
freq->stream.next_out = expn;
freq->stream.avail_out = sizeof(expn);
@@ -1187,7 +1208,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
char *filename;
char prevfile[PATH_MAX];
int prevlocal;
- unsigned char prev_buf[PREV_BUF_SIZE];
+ char prev_buf[PREV_BUF_SIZE];
ssize_t prev_read = 0;
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
diff --git a/http.h b/http.h
index a0b5901..0bf8592 100644
--- a/http.h
+++ b/http.h
@@ -8,6 +8,7 @@
#include "strbuf.h"
#include "remote.h"
+#include "url.h"
/*
* We detect based on the cURL version if multi-transfer is
@@ -23,10 +24,10 @@
#endif
#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while(0)
+#define curl_global_cleanup() do { /* nothing */ } while (0)
#endif
#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while(0)
+#define curl_global_init(a) do { /* nothing */ } while (0)
#endif
#if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
@@ -41,14 +42,12 @@
#define NO_CURL_IOCTL
#endif
-struct slot_results
-{
+struct slot_results {
CURLcode curl_result;
long http_code;
};
-struct active_request_slot
-{
+struct active_request_slot {
CURL *curl;
FILE *local;
int in_use;
@@ -61,16 +60,15 @@ struct active_request_slot
struct active_request_slot *next;
};
-struct buffer
-{
+struct buffer {
struct strbuf buf;
size_t posn;
};
/* Curl request read/write callbacks */
-extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
#ifndef NO_CURL_IOCTL
extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
#endif
@@ -117,7 +115,6 @@ extern void append_remote_object_url(struct strbuf *buf, const char *url,
int only_two_digit_prefix);
extern char *get_remote_object_url(const char *url, const char *hex,
int only_two_digit_prefix);
-extern void end_url_with_slash(struct strbuf *buf, const char *url);
/* Options for http_request_*() */
#define HTTP_NO_CACHE 1
@@ -149,8 +146,7 @@ extern int http_fetch_ref(const char *base, struct ref *ref);
extern int http_get_info_packs(const char *base_url,
struct packed_git **packs_head);
-struct http_pack_request
-{
+struct http_pack_request {
char *url;
struct packed_git *target;
struct packed_git **lst;
@@ -166,8 +162,7 @@ extern int finish_http_pack_request(struct http_pack_request *preq);
extern void release_http_pack_request(struct http_pack_request *preq);
/* Helpers for fetching object */
-struct http_object_request
-{
+struct http_object_request {
char *url;
char tmpfile[PATH_MAX];
int localfile;
@@ -177,7 +172,7 @@ struct http_object_request
unsigned char sha1[20];
unsigned char real_sha1[20];
git_SHA_CTX c;
- z_stream stream;
+ git_zstream stream;
int zret;
int rename;
struct active_request_slot *slot;
diff --git a/ident.c b/ident.c
index 9e24388..35a6f26 100644
--- a/ident.c
+++ b/ident.c
@@ -9,6 +9,12 @@
static char git_default_date[50];
+#ifdef NO_GECOS_IN_PWENT
+#define get_gecos(ignored) "&"
+#else
+#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
+#endif
+
static void copy_gecos(const struct passwd *w, char *name, size_t sz)
{
char *src, *dst;
@@ -20,7 +26,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
* with commas. Also & stands for capitalized form of the login name.
*/
- for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) {
+ for (len = 0, dst = name, src = get_gecos(w); len < sz; src++) {
int ch = *src;
if (ch != '&') {
*dst++ = ch;
@@ -34,6 +40,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
*dst++ = toupper(*w->pw_name);
memcpy(dst, w->pw_name + 1, nlen - 1);
dst += nlen - 1;
+ len += nlen;
}
}
if (len < sz)
@@ -217,8 +224,10 @@ const char *fmt_ident(const char *name, const char *email,
}
strcpy(date, git_default_date);
- if (!name_addr_only && date_str)
- parse_date(date_str, date, sizeof(date));
+ if (!name_addr_only && date_str && date_str[0]) {
+ if (parse_date(date_str, date, sizeof(date)) < 0)
+ die("invalid date format: %s", date_str);
+ }
i = copy(buffer, sizeof(buffer), 0, name);
i = add_raw(buffer, sizeof(buffer), i, " <");
diff --git a/imap-send.c b/imap-send.c
index 1a577a0..e1ad1a4 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -543,9 +543,13 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
while (imap->literal_pending)
get_cmd_result(ctx, NULL);
- bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
- "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
- cmd->tag, cmd->cmd, cmd->cb.dlen);
+ if (!cmd->cb.data)
+ bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd);
+ else
+ bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n",
+ cmd->tag, cmd->cmd, cmd->cb.dlen,
+ CAP(LITERALPLUS) ? "+" : "");
+
if (Verbose) {
if (imap->num_in_progress)
printf("(%d in progress) ", imap->num_in_progress);
@@ -1065,7 +1069,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
if (srvc->tunnel) {
const char *argv[] = { srvc->tunnel, NULL };
- struct child_process tunnel = {0};
+ struct child_process tunnel = {NULL};
imap_info("Starting tunnel '%s'... ", srvc->tunnel);
@@ -1086,7 +1090,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
int gai;
char portstr[6];
- snprintf(portstr, sizeof(portstr), "%hu", srvc->port);
+ snprintf(portstr, sizeof(portstr), "%d", srvc->port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
@@ -1189,13 +1193,13 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
if (!preauth) {
#ifndef NO_OPENSSL
if (!srvc->use_ssl && CAP(STARTTLS)) {
- if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK)
+ if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
goto bail;
if (ssl_socket_connect(&imap->buf.sock, 1,
srvc->ssl_verify))
goto bail;
/* capabilities may have changed, so get the new capabilities */
- if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK)
+ if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
goto bail;
}
#endif
diff --git a/list-objects.c b/list-objects.c
index 8953548..0fb44e7 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -61,12 +61,15 @@ static void process_tree(struct rev_info *revs,
struct tree *tree,
show_object_fn show,
struct name_path *path,
+ struct strbuf *base,
const char *name)
{
struct object *obj = &tree->object;
struct tree_desc desc;
struct name_entry entry;
struct name_path me;
+ int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0;
+ int baselen = base->len;
if (!revs->tree_objects)
return;
@@ -82,13 +85,28 @@ static void process_tree(struct rev_info *revs,
me.elem = name;
me.elem_len = strlen(name);
+ if (!match) {
+ strbuf_addstr(base, name);
+ if (base->len)
+ strbuf_addch(base, '/');
+ }
+
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
+ if (match != 2) {
+ match = tree_entry_interesting(&entry, base, 0,
+ &revs->diffopt.pathspec);
+ if (match < 0)
+ break;
+ if (match == 0)
+ continue;
+ }
+
if (S_ISDIR(entry.mode))
process_tree(revs,
lookup_tree(entry.sha1),
- show, &me, entry.path);
+ show, &me, base, entry.path);
else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
show, &me, entry.path);
@@ -97,6 +115,7 @@ static void process_tree(struct rev_info *revs,
lookup_blob(entry.sha1),
show, &me, entry.path);
}
+ strbuf_setlen(base, baselen);
free(tree->buffer);
tree->buffer = NULL;
}
@@ -146,9 +165,16 @@ void traverse_commit_list(struct rev_info *revs,
{
int i;
struct commit *commit;
+ struct strbuf base;
+ strbuf_init(&base, PATH_MAX);
while ((commit = get_revision(revs)) != NULL) {
- add_pending_tree(revs, commit->tree);
+ /*
+ * an uninteresting boundary commit may not have its tree
+ * parsed yet, but we are not going to show them anyway
+ */
+ if (commit->tree)
+ add_pending_tree(revs, commit->tree);
show_commit(commit, data);
}
for (i = 0; i < revs->pending.nr; i++) {
@@ -164,7 +190,7 @@ void traverse_commit_list(struct rev_info *revs,
}
if (obj->type == OBJ_TREE) {
process_tree(revs, (struct tree *)obj, show_object,
- NULL, name);
+ NULL, &base, name);
continue;
}
if (obj->type == OBJ_BLOB) {
@@ -181,4 +207,5 @@ void traverse_commit_list(struct rev_info *revs,
revs->pending.alloc = 0;
revs->pending.objects = NULL;
}
+ strbuf_release(&base);
}
diff --git a/ll-merge.c b/ll-merge.c
index 3764a1a..6ce512e 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -18,7 +18,7 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag,
+ const struct ll_merge_options *opts,
int marker_size);
struct ll_merge_driver {
@@ -39,14 +39,18 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
+ mmfile_t *stolen;
+ assert(opts);
+
/*
* The tentative merge result is "ours" for the final round,
* or common ancestor for an internal merge. Still return
* "conflicted merge" status.
*/
- mmfile_t *stolen = (flag & 01) ? orig : src1;
+ stolen = opts->virtual_ancestor ? orig : src1;
result->ptr = stolen->ptr;
result->size = stolen->size;
@@ -60,9 +64,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
xmparam_t xmp;
+ assert(opts);
if (buffer_is_binary(orig->ptr, orig->size) ||
buffer_is_binary(src1->ptr, src1->size) ||
@@ -74,12 +80,13 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
orig, orig_name,
src1, name1,
src2, name2,
- flag, marker_size);
+ opts, marker_size);
}
memset(&xmp, 0, sizeof(xmp));
xmp.level = XDL_MERGE_ZEALOUS;
- xmp.favor= (flag >> 1) & 03;
+ xmp.favor = opts->variant;
+ xmp.xpp.flags = opts->xdl_opts;
if (git_xmerge_style >= 0)
xmp.style = git_xmerge_style;
if (marker_size > 0)
@@ -96,14 +103,17 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
/* Use union favor */
- flag = (flag & 1) | (XDL_MERGE_FAVOR_UNION << 1);
+ struct ll_merge_options o;
+ assert(opts);
+ o = *opts;
+ o.variant = XDL_MERGE_FAVOR_UNION;
return ll_xdl_merge(drv_unused, result, path_unused,
orig, NULL, src1, NULL, src2, NULL,
- flag, marker_size);
- return 0;
+ &o, marker_size);
}
#define LL_BINARY_MERGE 0
@@ -135,7 +145,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
mmfile_t *orig, const char *orig_name,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int flag, int marker_size)
+ const struct ll_merge_options *opts,
+ int marker_size)
{
char temp[4][50];
struct strbuf cmd = STRBUF_INIT;
@@ -143,6 +154,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
+ assert(opts);
dict[0].placeholder = "O"; dict[0].value = temp[0];
dict[1].placeholder = "A"; dict[1].value = temp[1];
@@ -321,19 +333,37 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2]
return git_checkattr(path, 2, check);
}
+static void normalize_file(mmfile_t *mm, const char *path)
+{
+ struct strbuf strbuf = STRBUF_INIT;
+ if (renormalize_buffer(path, mm->ptr, mm->size, &strbuf)) {
+ free(mm->ptr);
+ mm->size = strbuf.len;
+ mm->ptr = strbuf_detach(&strbuf, NULL);
+ }
+}
+
int ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int flag)
+ const struct ll_merge_options *opts)
{
static struct git_attr_check check[2];
+ static const struct ll_merge_options default_opts;
const char *ll_driver_name = NULL;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
const struct ll_merge_driver *driver;
- int virtual_ancestor = flag & 01;
+ if (!opts)
+ opts = &default_opts;
+
+ if (opts->renormalize) {
+ normalize_file(ancestor, path);
+ normalize_file(ours, path);
+ normalize_file(theirs, path);
+ }
if (!git_path_check_merge(path, check)) {
ll_driver_name = check[0].value;
if (check[1].value) {
@@ -343,11 +373,11 @@ int ll_merge(mmbuffer_t *result_buf,
}
}
driver = find_ll_merge_driver(ll_driver_name);
- if (virtual_ancestor && driver->recursive)
+ if (opts->virtual_ancestor && driver->recursive)
driver = find_ll_merge_driver(driver->recursive);
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
ours, our_label, theirs, their_label,
- flag, marker_size);
+ opts, marker_size);
}
int ll_merge_marker_size(const char *path)
diff --git a/ll-merge.h b/ll-merge.h
index 57754cc..244a31f 100644
--- a/ll-merge.h
+++ b/ll-merge.h
@@ -5,12 +5,19 @@
#ifndef LL_MERGE_H
#define LL_MERGE_H
+struct ll_merge_options {
+ unsigned virtual_ancestor : 1;
+ unsigned variant : 2; /* favor ours, favor theirs, or union merge */
+ unsigned renormalize : 1;
+ long xdl_opts;
+};
+
int ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int flag);
+ const struct ll_merge_options *opts);
int ll_merge_marker_size(const char *path);
diff --git a/lockfile.c b/lockfile.c
index b0d74cd..c6fb77b 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -164,10 +164,10 @@ static char *unable_to_lock_message(const char *path, int err)
"If no other git process is currently running, this probably means a\n"
"git process crashed in this repository earlier. Make sure no other git\n"
"process is running and remove the file manually to continue.",
- make_nonrelative_path(path), strerror(err));
+ absolute_path(path), strerror(err));
} else
strbuf_addf(&buf, "Unable to create '%s.lock': %s",
- make_nonrelative_path(path), strerror(err));
+ absolute_path(path), strerror(err));
return strbuf_detach(&buf, NULL);
}
diff --git a/log-tree.c b/log-tree.c
index b46ed3b..e945701 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -294,8 +294,9 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
if (opt->total > 0) {
static char buffer[64];
snprintf(buffer, sizeof(buffer),
- "Subject: [%s %0*d/%d] ",
+ "Subject: [%s%s%0*d/%d] ",
opt->subject_prefix,
+ *opt->subject_prefix ? " " : "",
digits_in_number(opt->total),
opt->nr, opt->total);
subject = buffer;
@@ -380,18 +381,8 @@ void show_log(struct rev_info *opt)
if (!opt->verbose_header) {
graph_show_commit(opt->graph);
- if (!opt->graph) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & UNINTERESTING)
- putchar('^');
- else if (opt->left_right) {
- if (commit->object.flags & SYMMETRIC_LEFT)
- putchar('<');
- else
- putchar('>');
- }
- }
+ if (!opt->graph)
+ put_revision_mark(opt, commit);
fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
@@ -448,18 +439,8 @@ void show_log(struct rev_info *opt)
if (opt->commit_format != CMIT_FMT_ONELINE)
fputs("commit ", stdout);
- if (!opt->graph) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & UNINTERESTING)
- putchar('^');
- else if (opt->left_right) {
- if (commit->object.flags & SYMMETRIC_LEFT)
- putchar('<');
- else
- putchar('>');
- }
- }
+ if (!opt->graph)
+ put_revision_mark(opt, commit);
fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit),
stdout);
if (opt->print_parents)
@@ -504,8 +485,10 @@ void show_log(struct rev_info *opt)
ctx.date_mode = opt->date_mode;
ctx.abbrev = opt->diffopt.abbrev;
ctx.after_subject = extra_headers;
+ ctx.preserve_subject = opt->preserve_subject;
ctx.reflog_info = opt->reflog_info;
- pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
+ ctx.fmt = opt->commit_format;
+ pretty_print_commit(&ctx, commit, &msgbuf);
if (opt->add_signoff)
append_signoff(&msgbuf, opt->add_signoff);
diff --git a/mailmap.c b/mailmap.c
index f80b701..02fcfde 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -79,12 +79,14 @@ static void add_mapping(struct string_list *map,
if (old_name == NULL) {
debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
/* Replace current name and new email for simple entry */
- free(me->name);
- free(me->email);
- if (new_name)
+ if (new_name) {
+ free(me->name);
me->name = xstrdup(new_name);
- if (new_email)
+ }
+ if (new_email) {
+ free(me->email);
me->email = xstrdup(new_email);
+ }
} else {
struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
diff --git a/merge-file.c b/merge-file.c
index db4d0d5..7845528 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -3,6 +3,7 @@
#include "xdiff-interface.h"
#include "ll-merge.h"
#include "blob.h"
+#include "merge-file.h"
static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
{
@@ -37,7 +38,7 @@ static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our
* common ancestor.
*/
merge_status = ll_merge(&res, path, base, NULL,
- our, ".our", their, ".their", 0);
+ our, ".our", their, ".their", NULL);
if (merge_status < 0)
return NULL;
diff --git a/merge-file.h b/merge-file.h
new file mode 100644
index 0000000..9b3b83a
--- /dev/null
+++ b/merge-file.h
@@ -0,0 +1,7 @@
+#ifndef MERGE_FILE_H
+#define MERGE_FILE_H
+
+extern void *merge_file(const char *path, struct blob *base, struct blob *our,
+ struct blob *their, unsigned long *size);
+
+#endif
diff --git a/merge-recursive.c b/merge-recursive.c
index 856e98c..db9ba19 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -20,6 +20,7 @@
#include "attr.h"
#include "merge-recursive.h"
#include "dir.h"
+#include "submodule.h"
static struct tree *shift_tree_object(struct tree *one, struct tree *two,
const char *subtree_shift)
@@ -62,20 +63,62 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)
return a && b && hashcmp(a, b) == 0;
}
+enum rename_type {
+ RENAME_NORMAL = 0,
+ RENAME_DELETE,
+ RENAME_ONE_FILE_TO_TWO
+};
+
+struct rename_df_conflict_info {
+ enum rename_type rename_type;
+ struct diff_filepair *pair1;
+ struct diff_filepair *pair2;
+ const char *branch1;
+ const char *branch2;
+ struct stage_data *dst_entry1;
+ struct stage_data *dst_entry2;
+};
+
/*
* Since we want to write the index eventually, we cannot reuse the index
* for these (temporary) data.
*/
-struct stage_data
-{
- struct
- {
+struct stage_data {
+ struct {
unsigned mode;
unsigned char sha[20];
} stages[4];
+ struct rename_df_conflict_info *rename_df_conflict_info;
unsigned processed:1;
};
+static inline void setup_rename_df_conflict_info(enum rename_type rename_type,
+ struct diff_filepair *pair1,
+ struct diff_filepair *pair2,
+ const char *branch1,
+ const char *branch2,
+ struct stage_data *dst_entry1,
+ struct stage_data *dst_entry2)
+{
+ struct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));
+ ci->rename_type = rename_type;
+ ci->pair1 = pair1;
+ ci->branch1 = branch1;
+ ci->branch2 = branch2;
+
+ ci->dst_entry1 = dst_entry1;
+ dst_entry1->rename_df_conflict_info = ci;
+ dst_entry1->processed = 0;
+
+ assert(!pair2 == !dst_entry2);
+ if (dst_entry2) {
+ ci->dst_entry2 = dst_entry2;
+ ci->pair2 = pair2;
+ dst_entry2->rename_df_conflict_info = ci;
+ dst_entry2->processed = 0;
+ }
+}
+
static int show(struct merge_options *o, int v)
{
return (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;
@@ -92,7 +135,6 @@ static void flush_output(struct merge_options *o)
__attribute__((format (printf, 3, 4)))
static void output(struct merge_options *o, int v, const char *fmt, ...)
{
- int len;
va_list ap;
if (!show(o, v))
@@ -103,21 +145,9 @@ static void output(struct merge_options *o, int v, const char *fmt, ...)
strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
va_start(ap, fmt);
- len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
+ strbuf_vaddf(&o->obuf, fmt, ap);
va_end(ap);
- if (len < 0)
- len = 0;
- if (len >= strbuf_avail(&o->obuf)) {
- strbuf_grow(&o->obuf, len + 2);
- va_start(ap, fmt);
- len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
- va_end(ap);
- if (len >= strbuf_avail(&o->obuf)) {
- die("this should not happen, your snprintf is broken");
- }
- }
- strbuf_setlen(&o->obuf, o->obuf.len + len);
strbuf_add(&o->obuf, "\n", 1);
if (!o->buffer_output)
flush_output(o);
@@ -136,16 +166,10 @@ static void output_commit_title(struct merge_options *o, struct commit *commit)
if (parse_commit(commit) != 0)
printf("(bad commit)\n");
else {
- const char *s;
- int len;
- for (s = commit->buffer; *s; s++)
- if (*s == '\n' && s[1] == '\n') {
- s += 2;
- break;
- }
- for (len = 0; s[len] && '\n' != s[len]; len++)
- ; /* do nothing */
- printf("%.*s\n", len, s);
+ const char *title;
+ int len = find_commit_subject(commit->buffer, &title);
+ if (len)
+ printf("%.*s\n", len, title);
}
}
}
@@ -185,7 +209,7 @@ static int git_merge_trees(int index_only,
opts.fn = threeway_merge;
opts.src_index = &the_index;
opts.dst_index = &the_index;
- opts.msgs = get_porcelain_error_msgs();
+ setup_unpack_trees_porcelain(&opts, "merge");
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
@@ -249,7 +273,9 @@ static int save_files_dirs(const unsigned char *sha1,
static int get_files_dirs(struct merge_options *o, struct tree *tree)
{
int n;
- if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, o))
+ struct pathspec match_all;
+ init_pathspec(&match_all, NULL);
+ if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
return 0;
n = o->current_file_set.nr + o->current_directory_set.nr;
return n;
@@ -307,8 +333,62 @@ static struct string_list *get_unmerged(void)
return unmerged;
}
-struct rename
+static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
+ struct string_list *entries)
{
+ /* If there are D/F conflicts, and the paths currently exist
+ * in the working copy as a file, we want to remove them to
+ * make room for the corresponding directory. Such paths will
+ * later be processed in process_df_entry() at the end. If
+ * the corresponding directory ends up being removed by the
+ * merge, then the file will be reinstated at that time;
+ * otherwise, if the file is not supposed to be removed by the
+ * merge, the contents of the file will be placed in another
+ * unique filename.
+ *
+ * NOTE: This function relies on the fact that entries for a
+ * D/F conflict will appear adjacent in the index, with the
+ * entries for the file appearing before entries for paths
+ * below the corresponding directory.
+ */
+ const char *last_file = NULL;
+ int last_len = 0;
+ int i;
+
+ for (i = 0; i < entries->nr; i++) {
+ const char *path = entries->items[i].string;
+ int len = strlen(path);
+ struct stage_data *e = entries->items[i].util;
+
+ /*
+ * Check if last_file & path correspond to a D/F conflict;
+ * i.e. whether path is last_file+'/'+<something>.
+ * If so, remove last_file to make room for path and friends.
+ */
+ if (last_file &&
+ len > last_len &&
+ memcmp(path, last_file, last_len) == 0 &&
+ path[last_len] == '/') {
+ output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
+ unlink(last_file);
+ }
+
+ /*
+ * Determine whether path could exist as a file in the
+ * working directory as a possible D/F conflict. This
+ * will only occur when it exists in stage 2 as a
+ * file.
+ */
+ if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
+ last_file = path;
+ last_len = len;
+ } else {
+ last_file = NULL;
+ }
+ }
+}
+
+struct rename {
struct diff_filepair *pair;
struct stage_data *src_entry;
struct stage_data *dst_entry;
@@ -338,13 +418,16 @@ static struct string_list *get_renames(struct merge_options *o,
opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit :
- 500;
- opts.warn_on_too_large_rename = 1;
+ 1000;
+ opts.rename_score = o->rename_score;
+ opts.show_rename_progress = o->show_rename_progress;
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
if (diff_setup_done(&opts) < 0)
die("diff setup failed");
diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
diffcore_std(&opts);
+ if (opts.needed_rename_limit > o->needed_rename_limit)
+ o->needed_rename_limit = opts.needed_rename_limit;
for (i = 0; i < diff_queued_diff.nr; ++i) {
struct string_list_item *item;
struct rename *re;
@@ -378,11 +461,10 @@ static struct string_list *get_renames(struct merge_options *o,
return renames;
}
-static int update_stages(const char *path, struct diff_filespec *o,
+static int update_stages_options(const char *path, struct diff_filespec *o,
struct diff_filespec *a, struct diff_filespec *b,
- int clear)
+ int clear, int options)
{
- int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
if (clear)
if (remove_file_from_cache(path))
return -1;
@@ -398,6 +480,34 @@ static int update_stages(const char *path, struct diff_filespec *o,
return 0;
}
+static int update_stages(const char *path, struct diff_filespec *o,
+ struct diff_filespec *a, struct diff_filespec *b,
+ int clear)
+{
+ int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+ return update_stages_options(path, o, a, b, clear, options);
+}
+
+static int update_stages_and_entry(const char *path,
+ struct stage_data *entry,
+ struct diff_filespec *o,
+ struct diff_filespec *a,
+ struct diff_filespec *b,
+ int clear)
+{
+ int options;
+
+ entry->processed = 0;
+ entry->stages[1].mode = o->mode;
+ entry->stages[2].mode = a->mode;
+ entry->stages[3].mode = b->mode;
+ hashcpy(entry->stages[1].sha, o->sha1);
+ hashcpy(entry->stages[2].sha, a->sha1);
+ hashcpy(entry->stages[3].sha, b->sha1);
+ options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
+ return update_stages_options(path, o, a, b, clear, options);
+}
+
static int remove_file(struct merge_options *o, int clean,
const char *path, int no_wd)
{
@@ -525,13 +635,15 @@ static void update_file_flags(struct merge_options *o,
void *buf;
unsigned long size;
- if (S_ISGITLINK(mode))
+ if (S_ISGITLINK(mode)) {
/*
* We may later decide to recursively descend into
* the submodule directory and update its index
* and/or work tree, but we do not do that now.
*/
+ update_wd = 0;
goto update_index;
+ }
buf = read_sha1_file(sha, &type, &size);
if (!buf)
@@ -591,8 +703,7 @@ static void update_file(struct merge_options *o,
/* Low level file merging, update and removal */
-struct merge_file_info
-{
+struct merge_file_info {
unsigned char sha[20];
unsigned mode;
unsigned clean:1,
@@ -608,22 +719,26 @@ static int merge_3way(struct merge_options *o,
const char *branch2)
{
mmfile_t orig, src1, src2;
+ struct ll_merge_options ll_opts = {0};
char *base_name, *name1, *name2;
int merge_status;
- int favor;
- if (o->call_depth)
- favor = 0;
- else {
+ ll_opts.renormalize = o->renormalize;
+ ll_opts.xdl_opts = o->xdl_opts;
+
+ if (o->call_depth) {
+ ll_opts.virtual_ancestor = 1;
+ ll_opts.variant = 0;
+ } else {
switch (o->recursive_variant) {
case MERGE_RECURSIVE_OURS:
- favor = XDL_MERGE_FAVOR_OURS;
+ ll_opts.variant = XDL_MERGE_FAVOR_OURS;
break;
case MERGE_RECURSIVE_THEIRS:
- favor = XDL_MERGE_FAVOR_THEIRS;
+ ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
break;
default:
- favor = 0;
+ ll_opts.variant = 0;
break;
}
}
@@ -646,8 +761,7 @@ static int merge_3way(struct merge_options *o,
read_mmblob(&src2, b->sha1);
merge_status = ll_merge(result_buf, a->path, &orig, base_name,
- &src1, name1, &src2, name2,
- (!!o->call_depth) | (favor << 1));
+ &src1, name1, &src2, name2, &ll_opts);
free(name1);
free(name2);
@@ -716,8 +830,8 @@ static struct merge_file_info merge_file(struct merge_options *o,
free(result_buf.ptr);
result.clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
- result.clean = 0;
- hashcpy(result.sha, a->sha1);
+ result.clean = merge_submodule(result.sha, one->path, one->sha1,
+ a->sha1, b->sha1);
} else if (S_ISLNK(a->mode)) {
hashcpy(result.sha, a->sha1);
@@ -731,29 +845,56 @@ static struct merge_file_info merge_file(struct merge_options *o,
return result;
}
-static void conflict_rename_rename(struct merge_options *o,
- struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
+static void conflict_rename_delete(struct merge_options *o,
+ struct diff_filepair *pair,
+ const char *rename_branch,
+ const char *other_branch)
{
+ char *dest_name = pair->two->path;
+ int df_conflict = 0;
+ struct stat st;
+
+ output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
+ "and deleted in %s",
+ pair->one->path, pair->two->path, rename_branch,
+ other_branch);
+ if (!o->call_depth)
+ update_stages(dest_name, NULL,
+ rename_branch == o->branch1 ? pair->two : NULL,
+ rename_branch == o->branch1 ? NULL : pair->two,
+ 1);
+ if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
+ dest_name = unique_path(o, dest_name, rename_branch);
+ df_conflict = 1;
+ }
+ update_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);
+ if (df_conflict)
+ free(dest_name);
+}
+
+static void conflict_rename_rename_1to2(struct merge_options *o,
+ struct diff_filepair *pair1,
+ const char *branch1,
+ struct diff_filepair *pair2,
+ const char *branch2)
+{
+ /* One file was renamed in both branches, but to different names. */
char *del[2];
int delp = 0;
- const char *ren1_dst = ren1->pair->two->path;
- const char *ren2_dst = ren2->pair->two->path;
+ const char *ren1_dst = pair1->two->path;
+ const char *ren2_dst = pair2->two->path;
const char *dst_name1 = ren1_dst;
const char *dst_name2 = ren2_dst;
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ struct stat st;
+ if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
output(o, 1, "%s is a directory in %s adding as %s instead",
ren1_dst, branch2, dst_name1);
- remove_file(o, 0, ren1_dst, 0);
}
- if (string_list_has_string(&o->current_directory_set, ren2_dst)) {
+ if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
output(o, 1, "%s is a directory in %s adding as %s instead",
ren2_dst, branch1, dst_name2);
- remove_file(o, 0, ren2_dst, 0);
}
if (o->call_depth) {
remove_file_from_cache(dst_name1);
@@ -761,34 +902,27 @@ static void conflict_rename_rename(struct merge_options *o,
/*
* Uncomment to leave the conflicting names in the resulting tree
*
- * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
- * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+ * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+ * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
*/
} else {
- update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
- update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+ update_stages(ren1_dst, NULL, pair1->two, NULL, 1);
+ update_stages(ren2_dst, NULL, NULL, pair2->two, 1);
+
+ update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);
+ update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);
}
while (delp--)
free(del[delp]);
}
-static void conflict_rename_dir(struct merge_options *o,
- struct rename *ren1,
- const char *branch1)
-{
- char *new_path = unique_path(o, ren1->pair->two->path, branch1);
- output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
- remove_file(o, 0, ren1->pair->two->path, 0);
- update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
- free(new_path);
-}
-
-static void conflict_rename_rename_2(struct merge_options *o,
- struct rename *ren1,
- const char *branch1,
- struct rename *ren2,
- const char *branch2)
+static void conflict_rename_rename_2to1(struct merge_options *o,
+ struct rename *ren1,
+ const char *branch1,
+ struct rename *ren2,
+ const char *branch2)
{
+ /* Two files were renamed to the same thing. */
char *new_path1 = unique_path(o, ren1->pair->two->path, branch1);
char *new_path2 = unique_path(o, ren2->pair->two->path, branch2);
output(o, 1, "Renaming %s to %s and %s to %s instead",
@@ -806,7 +940,8 @@ static int process_renames(struct merge_options *o,
struct string_list *b_renames)
{
int clean_merge = 1, i, j;
- struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+ struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
+ struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
const struct rename *sre;
for (i = 0; i < a_renames->nr; i++) {
@@ -821,7 +956,6 @@ static int process_renames(struct merge_options *o,
}
for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
- char *src;
struct string_list *renames1, *renames2Dst;
struct rename *ren1 = NULL, *ren2 = NULL;
const char *branch1, *branch2;
@@ -856,7 +990,6 @@ static int process_renames(struct merge_options *o,
ren2 = ren1;
ren1 = tmp;
}
- src = ren1->pair->one->path;
ren1->dst_entry->processed = 1;
ren1->src_entry->processed = 1;
@@ -877,84 +1010,66 @@ static int process_renames(struct merge_options *o,
ren2->dst_entry->processed = 1;
ren2->processed = 1;
if (strcmp(ren1_dst, ren2_dst) != 0) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/rename): "
- "Rename \"%s\"->\"%s\" in branch \"%s\" "
- "rename \"%s\"->\"%s\" in \"%s\"%s",
- src, ren1_dst, branch1,
- src, ren2_dst, branch2,
- o->call_depth ? " (left unresolved)": "");
- if (o->call_depth) {
- remove_file_from_cache(src);
- update_file(o, 0, ren1->pair->one->sha1,
- ren1->pair->one->mode, src);
- }
- conflict_rename_rename(o, ren1, branch1, ren2, branch2);
+ setup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,
+ ren1->pair,
+ ren2->pair,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ ren2->dst_entry);
} else {
- struct merge_file_info mfi;
remove_file(o, 1, ren1_src, 1);
- mfi = merge_file(o,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- branch1,
- branch2);
- if (mfi.merge || !mfi.clean)
- output(o, 1, "Renaming %s->%s", src, ren1_dst);
-
- if (mfi.merge)
- output(o, 2, "Auto-merging %s", ren1_dst);
-
- if (!mfi.clean) {
- output(o, 1, "CONFLICT (content): merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!o->call_depth)
- update_stages(ren1_dst,
- ren1->pair->one,
- ren1->pair->two,
- ren2->pair->two,
- 1 /* clear */);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ update_stages_and_entry(ren1_dst,
+ ren1->dst_entry,
+ ren1->pair->one,
+ ren1->pair->two,
+ ren2->pair->two,
+ 1 /* clear */);
}
} else {
/* Renamed in 1, maybe changed in 2 */
struct string_list_item *item;
/* we only use sha1 and mode of these */
struct diff_filespec src_other, dst_other;
- int try_merge, stage = a_renames == renames1 ? 3: 2;
+ int try_merge;
- remove_file(o, 1, ren1_src, o->call_depth || stage == 3);
+ /*
+ * unpack_trees loads entries from common-commit
+ * into stage 1, from head-commit into stage 2, and
+ * from merge-commit into stage 3. We keep track
+ * of which side corresponds to the rename.
+ */
+ int renamed_stage = a_renames == renames1 ? 2 : 3;
+ int other_stage = a_renames == renames1 ? 3 : 2;
- hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
- src_other.mode = ren1->src_entry->stages[stage].mode;
- hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
- dst_other.mode = ren1->dst_entry->stages[stage].mode;
+ remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+ hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
+ src_other.mode = ren1->src_entry->stages[other_stage].mode;
+ hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
+ dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
try_merge = 0;
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
- " directory %s added in %s",
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- conflict_rename_dir(o, ren1, branch1);
- } else if (sha_eq(src_other.sha1, null_sha1)) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
- "and deleted in %s",
- ren1_src, ren1_dst, branch1,
- branch2);
- update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
- if (!o->call_depth)
- update_stages(ren1_dst, NULL,
- branch1 == o->branch1 ?
- ren1->pair->two : NULL,
- branch1 == o->branch1 ?
- NULL : ren1->pair->two, 1);
+ if (sha_eq(src_other.sha1, null_sha1)) {
+ if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ ren1->dst_entry->processed = 0;
+ setup_rename_df_conflict_info(RENAME_DELETE,
+ ren1->pair,
+ NULL,
+ branch1,
+ branch2,
+ ren1->dst_entry,
+ NULL);
+ } else {
+ clean_merge = 0;
+ conflict_rename_delete(o, ren1->pair, branch1, branch2);
+ }
+ } else if ((dst_other.mode == ren1->pair->two->mode) &&
+ sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
+ /* Added file on the other side
+ identical to the file being
+ renamed: clean merge */
+ update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
} else if (!sha_eq(dst_other.sha1, null_sha1)) {
const char *new_path;
clean_merge = 0;
@@ -983,6 +1098,7 @@ static int process_renames(struct merge_options *o,
mfi.sha,
mfi.mode,
ren1_dst);
+ try_merge = 0;
} else {
new_path = unique_path(o, ren1_dst, branch2);
output(o, 1, "Adding as %s instead", new_path);
@@ -997,13 +1113,12 @@ static int process_renames(struct merge_options *o,
"Rename %s->%s in %s",
ren1_src, ren1_dst, branch1,
ren2->pair->one->path, ren2->pair->two->path, branch2);
- conflict_rename_rename_2(o, ren1, branch1, ren2, branch2);
+ conflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);
} else
try_merge = 1;
if (try_merge) {
struct diff_filespec *one, *a, *b;
- struct merge_file_info mfi;
src_other.path = (char *)ren1_src;
one = ren1->pair->one;
@@ -1014,33 +1129,15 @@ static int process_renames(struct merge_options *o,
b = ren1->pair->two;
a = &src_other;
}
- mfi = merge_file(o, one, a, b,
- o->branch1, o->branch2);
-
- if (mfi.clean &&
- sha_eq(mfi.sha, ren1->pair->two->sha1) &&
- mfi.mode == ren1->pair->two->mode)
- /*
- * This messaged is part of
- * t6022 test. If you change
- * it update the test too.
- */
- output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
- else {
- if (mfi.merge || !mfi.clean)
- output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
- if (mfi.merge)
- output(o, 2, "Auto-merging %s", ren1_dst);
- if (!mfi.clean) {
- output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s",
- ren1_dst);
- clean_merge = 0;
-
- if (!o->call_depth)
- update_stages(ren1_dst,
- one, a, b, 1);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
+ if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ setup_rename_df_conflict_info(RENAME_NORMAL,
+ ren1->pair,
+ NULL,
+ branch1,
+ NULL,
+ ren1->dst_entry,
+ NULL);
}
}
}
@@ -1056,6 +1153,137 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
}
+static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
+{
+ void *buf;
+ enum object_type type;
+ unsigned long size;
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return error("cannot read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_BLOB) {
+ free(buf);
+ return error("object %s is not a blob", sha1_to_hex(sha1));
+ }
+ strbuf_attach(dst, buf, size, size + 1);
+ return 0;
+}
+
+static int blob_unchanged(const unsigned char *o_sha,
+ const unsigned char *a_sha,
+ int renormalize, const char *path)
+{
+ struct strbuf o = STRBUF_INIT;
+ struct strbuf a = STRBUF_INIT;
+ int ret = 0; /* assume changed for safety */
+
+ if (sha_eq(o_sha, a_sha))
+ return 1;
+ if (!renormalize)
+ return 0;
+
+ assert(o_sha && a_sha);
+ if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
+ goto error_return;
+ /*
+ * Note: binary | is used so that both renormalizations are
+ * performed. Comparison can be skipped if both files are
+ * unchanged since their sha1s have already been compared.
+ */
+ if (renormalize_buffer(path, o.buf, o.len, &o) |
+ renormalize_buffer(path, a.buf, o.len, &a))
+ ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
+
+error_return:
+ strbuf_release(&o);
+ strbuf_release(&a);
+ return ret;
+}
+
+static void handle_delete_modify(struct merge_options *o,
+ const char *path,
+ const char *new_path,
+ unsigned char *a_sha, int a_mode,
+ unsigned char *b_sha, int b_mode)
+{
+ if (!a_sha) {
+ output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree%s%s.",
+ path, o->branch1,
+ o->branch2, o->branch2, path,
+ path == new_path ? "" : " at ",
+ path == new_path ? "" : new_path);
+ update_file(o, 0, b_sha, b_mode, new_path);
+ } else {
+ output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree%s%s.",
+ path, o->branch2,
+ o->branch1, o->branch1, path,
+ path == new_path ? "" : " at ",
+ path == new_path ? "" : new_path);
+ update_file(o, 0, a_sha, a_mode, new_path);
+ }
+}
+
+static int merge_content(struct merge_options *o,
+ const char *path,
+ unsigned char *o_sha, int o_mode,
+ unsigned char *a_sha, int a_mode,
+ unsigned char *b_sha, int b_mode,
+ const char *df_rename_conflict_branch)
+{
+ const char *reason = "content";
+ struct merge_file_info mfi;
+ struct diff_filespec one, a, b;
+ struct stat st;
+ unsigned df_conflict_remains = 0;
+
+ if (!o_sha) {
+ reason = "add/add";
+ o_sha = (unsigned char *)null_sha1;
+ }
+ one.path = a.path = b.path = (char *)path;
+ hashcpy(one.sha1, o_sha);
+ one.mode = o_mode;
+ hashcpy(a.sha1, a_sha);
+ a.mode = a_mode;
+ hashcpy(b.sha1, b_sha);
+ b.mode = b_mode;
+
+ mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
+ if (df_rename_conflict_branch &&
+ lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ df_conflict_remains = 1;
+ }
+
+ if (mfi.clean && !df_conflict_remains &&
+ sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+ output(o, 3, "Skipped %s (merged same as existing)", path);
+ else
+ output(o, 2, "Auto-merging %s", path);
+
+ if (!mfi.clean) {
+ if (S_ISGITLINK(mfi.mode))
+ reason = "submodule";
+ output(o, 1, "CONFLICT (%s): Merge conflict in %s",
+ reason, path);
+ }
+
+ if (df_conflict_remains) {
+ const char *new_path;
+ update_file_flags(o, mfi.sha, mfi.mode, path,
+ o->call_depth || mfi.clean, 0);
+ new_path = unique_path(o, path, df_rename_conflict_branch);
+ mfi.clean = 0;
+ output(o, 1, "Adding as %s instead", new_path);
+ update_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);
+ } else {
+ update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+ }
+ return mfi.clean;
+
+}
+
/* Per entry merge function */
static int process_entry(struct merge_options *o,
const char *path, struct stage_data *entry)
@@ -1065,6 +1293,7 @@ static int process_entry(struct merge_options *o,
print_index_entry("\tpath: ", entry);
*/
int clean_merge = 1;
+ int normalize = o->renormalize;
unsigned o_mode = entry->stages[1].mode;
unsigned a_mode = entry->stages[2].mode;
unsigned b_mode = entry->stages[3].mode;
@@ -1072,65 +1301,49 @@ static int process_entry(struct merge_options *o,
unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+ if (entry->rename_df_conflict_info)
+ return 1; /* Such cases are handled elsewhere. */
+
+ entry->processed = 1;
if (o_sha && (!a_sha || !b_sha)) {
/* Case A: Deleted in one */
if ((!a_sha && !b_sha) ||
- (sha_eq(a_sha, o_sha) && !b_sha) ||
- (!a_sha && sha_eq(b_sha, o_sha))) {
+ (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+ (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
/* Deleted in both or deleted in one and
* unchanged in the other */
if (a_sha)
output(o, 2, "Removing %s", path);
/* do not touch working file if it did not exist */
remove_file(o, 1, path, !a_sha);
+ } else if (string_list_has_string(&o->current_directory_set,
+ path)) {
+ entry->processed = 0;
+ return 1; /* Assume clean until processed */
} else {
/* Deleted in one and changed in the other */
clean_merge = 0;
- if (!a_sha) {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, o->branch1,
- o->branch2, o->branch2, path);
- update_file(o, 0, b_sha, b_mode, path);
- } else {
- output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
- "and modified in %s. Version %s of %s left in tree.",
- path, o->branch2,
- o->branch1, o->branch1, path);
- update_file(o, 0, a_sha, a_mode, path);
- }
+ handle_delete_modify(o, path, path,
+ a_sha, a_mode, b_sha, b_mode);
}
} else if ((!o_sha && a_sha && !b_sha) ||
(!o_sha && !a_sha && b_sha)) {
/* Case B: Added in one. */
- const char *add_branch;
- const char *other_branch;
unsigned mode;
const unsigned char *sha;
- const char *conf;
if (a_sha) {
- add_branch = o->branch1;
- other_branch = o->branch2;
mode = a_mode;
sha = a_sha;
- conf = "file/directory";
} else {
- add_branch = o->branch2;
- other_branch = o->branch1;
mode = b_mode;
sha = b_sha;
- conf = "directory/file";
}
if (string_list_has_string(&o->current_directory_set, path)) {
- const char *new_path = unique_path(o, path, add_branch);
- clean_merge = 0;
- output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
- "Adding %s as %s",
- conf, path, other_branch, path, new_path);
- remove_file(o, 0, path, 0);
- update_file(o, 0, sha, mode, new_path);
+ /* Handle D->F conflicts after all subfiles */
+ entry->processed = 0;
+ return 1; /* Assume clean until processed */
} else {
output(o, 2, "Adding %s", path);
update_file(o, 1, sha, mode, path);
@@ -1138,34 +1351,9 @@ static int process_entry(struct merge_options *o,
} else if (a_sha && b_sha) {
/* Case C: Added in both (check for same permissions) and */
/* case D: Modified in both, but differently. */
- const char *reason = "content";
- struct merge_file_info mfi;
- struct diff_filespec one, a, b;
-
- if (!o_sha) {
- reason = "add/add";
- o_sha = (unsigned char *)null_sha1;
- }
- output(o, 2, "Auto-merging %s", path);
- one.path = a.path = b.path = (char *)path;
- hashcpy(one.sha1, o_sha);
- one.mode = o_mode;
- hashcpy(a.sha1, a_sha);
- a.mode = a_mode;
- hashcpy(b.sha1, b_sha);
- b.mode = b_mode;
-
- mfi = merge_file(o, &one, &a, &b,
- o->branch1, o->branch2);
-
- clean_merge = mfi.clean;
- if (!mfi.clean) {
- if (S_ISGITLINK(mfi.mode))
- reason = "submodule";
- output(o, 1, "CONFLICT (%s): Merge conflict in %s",
- reason, path);
- }
- update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
+ clean_merge = merge_content(o, path,
+ o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+ NULL);
} else if (!o_sha && !a_sha && !b_sha) {
/*
* this entry was deleted altogether. a_mode == 0 means
@@ -1178,26 +1366,116 @@ static int process_entry(struct merge_options *o,
return clean_merge;
}
-struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
+/*
+ * Per entry merge function for D/F (and/or rename) conflicts. In the
+ * cases we can cleanly resolve D/F conflicts, process_entry() can
+ * clean out all the files below the directory for us. All D/F
+ * conflict cases must be handled here at the end to make sure any
+ * directories that can be cleaned out, are.
+ *
+ * Some rename conflicts may also be handled here that don't necessarily
+ * involve D/F conflicts, since the code to handle them is generic enough
+ * to handle those rename conflicts with or without D/F conflicts also
+ * being involved.
+ */
+static int process_df_entry(struct merge_options *o,
+ const char *path, struct stage_data *entry)
{
- struct unpack_trees_error_msgs msgs = {
- /* would_overwrite */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_file */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_dir */
- "Updating '%s' would lose untracked files in it. Aborting.",
- /* would_lose_untracked */
- "Untracked working tree file '%s' would be %s by merge. Aborting",
- /* bind_overlap -- will not happen here */
- NULL,
- };
- if (advice_commit_before_merge) {
- msgs.would_overwrite = msgs.not_uptodate_file =
- "Your local changes to '%s' would be overwritten by merge. Aborting.\n"
- "Please, commit your changes or stash them before you can merge.";
+ int clean_merge = 1;
+ unsigned o_mode = entry->stages[1].mode;
+ unsigned a_mode = entry->stages[2].mode;
+ unsigned b_mode = entry->stages[3].mode;
+ unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+ unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+ unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
+ struct stat st;
+
+ entry->processed = 1;
+ if (entry->rename_df_conflict_info) {
+ struct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;
+ char *src;
+ switch (conflict_info->rename_type) {
+ case RENAME_NORMAL:
+ clean_merge = merge_content(o, path,
+ o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,
+ conflict_info->branch1);
+ break;
+ case RENAME_DELETE:
+ clean_merge = 0;
+ conflict_rename_delete(o, conflict_info->pair1,
+ conflict_info->branch1,
+ conflict_info->branch2);
+ break;
+ case RENAME_ONE_FILE_TO_TWO:
+ src = conflict_info->pair1->one->path;
+ clean_merge = 0;
+ output(o, 1, "CONFLICT (rename/rename): "
+ "Rename \"%s\"->\"%s\" in branch \"%s\" "
+ "rename \"%s\"->\"%s\" in \"%s\"%s",
+ src, conflict_info->pair1->two->path, conflict_info->branch1,
+ src, conflict_info->pair2->two->path, conflict_info->branch2,
+ o->call_depth ? " (left unresolved)" : "");
+ if (o->call_depth) {
+ remove_file_from_cache(src);
+ update_file(o, 0, conflict_info->pair1->one->sha1,
+ conflict_info->pair1->one->mode, src);
+ }
+ conflict_rename_rename_1to2(o, conflict_info->pair1,
+ conflict_info->branch1,
+ conflict_info->pair2,
+ conflict_info->branch2);
+ conflict_info->dst_entry2->processed = 1;
+ break;
+ default:
+ entry->processed = 0;
+ break;
+ }
+ } else if (o_sha && (!a_sha || !b_sha)) {
+ /* Modify/delete; deleted side may have put a directory in the way */
+ const char *new_path = path;
+ if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
+ new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
+ clean_merge = 0;
+ handle_delete_modify(o, path, new_path,
+ a_sha, a_mode, b_sha, b_mode);
+ } else if (!o_sha && !!a_sha != !!b_sha) {
+ /* directory -> (directory, file) */
+ const char *add_branch;
+ const char *other_branch;
+ unsigned mode;
+ const unsigned char *sha;
+ const char *conf;
+
+ if (a_sha) {
+ add_branch = o->branch1;
+ other_branch = o->branch2;
+ mode = a_mode;
+ sha = a_sha;
+ conf = "file/directory";
+ } else {
+ add_branch = o->branch2;
+ other_branch = o->branch1;
+ mode = b_mode;
+ sha = b_sha;
+ conf = "directory/file";
+ }
+ if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ const char *new_path = unique_path(o, path, add_branch);
+ clean_merge = 0;
+ output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
+ "Adding %s as %s",
+ conf, path, other_branch, path, new_path);
+ update_file(o, 0, sha, mode, new_path);
+ } else {
+ output(o, 2, "Adding %s", path);
+ update_file(o, 1, sha, mode, path);
+ }
+ } else {
+ entry->processed = 0;
+ return 1; /* not handled; assume clean until processed */
}
- return msgs;
+
+ return clean_merge;
}
int merge_trees(struct merge_options *o,
@@ -1214,7 +1492,7 @@ int merge_trees(struct merge_options *o,
}
if (sha_eq(common->object.sha1, merge->object.sha1)) {
- output(o, 0, "Already uptodate!");
+ output(o, 0, "Already up-to-date!");
*result = head;
return 1;
}
@@ -1239,6 +1517,7 @@ int merge_trees(struct merge_options *o,
get_files_dirs(o, merge);
entries = get_unmerged();
+ make_room_for_directories_of_df_conflicts(o, entries);
re_head = get_renames(o, head, common, head, merge, entries);
re_merge = get_renames(o, merge, common, head, merge, entries);
clean = process_renames(o, re_head, re_merge);
@@ -1249,6 +1528,19 @@ int merge_trees(struct merge_options *o,
&& !process_entry(o, path, e))
clean = 0;
}
+ for (i = 0; i < entries->nr; i++) {
+ const char *path = entries->items[i].string;
+ struct stage_data *e = entries->items[i].util;
+ if (!e->processed
+ && !process_df_entry(o, path, e))
+ clean = 0;
+ }
+ for (i = 0; i < entries->nr; i++) {
+ struct stage_data *e = entries->items[i].util;
+ if (!e->processed)
+ die("Unprocessed path??? %s",
+ entries->items[i].string);
+ }
string_list_clear(re_merge, 0);
string_list_clear(re_head, 0);
@@ -1357,6 +1649,9 @@ int merge_recursive(struct merge_options *o,
commit_list_insert(h2, &(*result)->parents->next);
}
flush_output(o);
+ if (show(o, 2))
+ diff_warn_rename_limit("merge.renamelimit",
+ o->needed_rename_limit, 0);
return clean;
}
@@ -1414,15 +1709,15 @@ int merge_recursive_generic(struct merge_options *o,
static int merge_recursive_config(const char *var, const char *value, void *cb)
{
struct merge_options *o = cb;
- if (!strcasecmp(var, "merge.verbosity")) {
+ if (!strcmp(var, "merge.verbosity")) {
o->verbosity = git_config_int(var, value);
return 0;
}
- if (!strcasecmp(var, "diff.renamelimit")) {
+ if (!strcmp(var, "diff.renamelimit")) {
o->diff_rename_limit = git_config_int(var, value);
return 0;
}
- if (!strcasecmp(var, "merge.renamelimit")) {
+ if (!strcmp(var, "merge.renamelimit")) {
o->merge_rename_limit = git_config_int(var, value);
return 0;
}
@@ -1436,6 +1731,7 @@ void init_merge_options(struct merge_options *o)
o->buffer_output = 1;
o->diff_rename_limit = -1;
o->merge_rename_limit = -1;
+ o->renormalize = 0;
git_config(merge_recursive_config, o);
if (getenv("GIT_MERGE_VERBOSITY"))
o->verbosity =
@@ -1448,3 +1744,37 @@ void init_merge_options(struct merge_options *o)
memset(&o->current_directory_set, 0, sizeof(struct string_list));
o->current_directory_set.strdup_strings = 1;
}
+
+int parse_merge_opt(struct merge_options *o, const char *s)
+{
+ if (!s || !*s)
+ return -1;
+ if (!strcmp(s, "ours"))
+ o->recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(s, "theirs"))
+ o->recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(s, "subtree"))
+ o->subtree_shift = "";
+ else if (!prefixcmp(s, "subtree="))
+ o->subtree_shift = s + strlen("subtree=");
+ else if (!strcmp(s, "patience"))
+ o->xdl_opts |= XDF_PATIENCE_DIFF;
+ else if (!strcmp(s, "ignore-space-change"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+ else if (!strcmp(s, "ignore-all-space"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE;
+ else if (!strcmp(s, "ignore-space-at-eol"))
+ o->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+ else if (!strcmp(s, "renormalize"))
+ o->renormalize = 1;
+ else if (!strcmp(s, "no-renormalize"))
+ o->renormalize = 0;
+ else if (!prefixcmp(s, "rename-threshold=")) {
+ const char *score = s + strlen("rename-threshold=");
+ if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
+ return -1;
+ }
+ else
+ return -1;
+ return 0;
+}
diff --git a/merge-recursive.h b/merge-recursive.h
index b831293..7e1e972 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -14,18 +14,20 @@ struct merge_options {
} recursive_variant;
const char *subtree_shift;
unsigned buffer_output : 1;
+ unsigned renormalize : 1;
+ long xdl_opts;
int verbosity;
int diff_rename_limit;
int merge_rename_limit;
+ int rename_score;
+ int needed_rename_limit;
+ int show_rename_progress;
int call_depth;
struct strbuf obuf;
struct string_list current_file_set;
struct string_list current_directory_set;
};
-/* Return a list of user-friendly error messages to be used by merge */
-struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
-
/* merge_trees() but with recursive ancestor consolidation */
int merge_recursive(struct merge_options *o,
struct commit *h1,
@@ -54,7 +56,11 @@ int merge_recursive_generic(struct merge_options *o,
void init_merge_options(struct merge_options *o);
struct tree *write_tree_from_memory(struct merge_options *o);
+int parse_merge_opt(struct merge_options *out, const char *s);
+
/* builtin/merge.c */
-int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
+int try_merge_command(const char *strategy, size_t xopts_nr,
+ const char **xopts, struct commit_list *common,
+ const char *head_arg, struct commit_list *remotes);
#endif
diff --git a/name-hash.c b/name-hash.c
index 0031d78..c6b6a3f 100644
--- a/name-hash.c
+++ b/name-hash.c
@@ -32,6 +32,42 @@ static unsigned int hash_name(const char *name, int namelen)
return hash;
}
+static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+{
+ /*
+ * Throw each directory component in the hash for quick lookup
+ * during a git status. Directory components are stored with their
+ * closing slash. Despite submodules being a directory, they never
+ * reach this point, because they are stored without a closing slash
+ * in the cache.
+ *
+ * Note that the cache_entry stored with the directory does not
+ * represent the directory itself. It is a pointer to an existing
+ * filename, and its only purpose is to represent existence of the
+ * directory in the cache. It is very possible multiple directory
+ * hash entries may point to the same cache_entry.
+ */
+ unsigned int hash;
+ void **pos;
+
+ const char *ptr = ce->name;
+ while (*ptr) {
+ while (*ptr && *ptr != '/')
+ ++ptr;
+ if (*ptr == '/') {
+ ++ptr;
+ hash = hash_name(ce->name, ptr - ce->name);
+ if (!lookup_hash(hash, &istate->name_hash)) {
+ pos = insert_hash(hash, ce, &istate->name_hash);
+ if (pos) {
+ ce->next = *pos;
+ *pos = ce;
+ }
+ }
+ }
+ }
+}
+
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
{
void **pos;
@@ -47,6 +83,9 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
ce->next = *pos;
*pos = ce;
}
+
+ if (ignore_case)
+ hash_index_entry_directories(istate, ce);
}
static void lazy_init_name_hash(struct index_state *istate)
@@ -97,7 +136,21 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
if (len == namelen && !cache_name_compare(name, namelen, ce->name, len))
return 1;
- return icase && slow_same_name(name, namelen, ce->name, len);
+ if (!icase)
+ return 0;
+
+ /*
+ * If the entry we're comparing is a filename (no trailing slash), then compare
+ * the lengths exactly.
+ */
+ if (name[namelen - 1] != '/')
+ return slow_same_name(name, namelen, ce->name, len);
+
+ /*
+ * For a directory, we point to an arbitrary cache_entry filename. Just
+ * make sure the directory portion matches.
+ */
+ return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
}
struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
@@ -115,5 +168,22 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
}
ce = ce->next;
}
+
+ /*
+ * Might be a submodule. Despite submodules being directories,
+ * they are stored in the name hash without a closing slash.
+ * When ignore_case is 1, directories are stored in the name hash
+ * with their closing slash.
+ *
+ * The side effect of this storage technique is we have need to
+ * remove the slash from name and perform the lookup again without
+ * the slash. If a match is made, S_ISGITLINK(ce->mode) will be
+ * true.
+ */
+ if (icase && name[namelen - 1] == '/') {
+ ce = index_name_exists(istate, name, namelen - 1, icase);
+ if (ce && S_ISGITLINK(ce->ce_mode))
+ return ce;
+ }
return NULL;
}
diff --git a/notes-cache.c b/notes-cache.c
index dee6d62..4c8984e 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -89,6 +89,5 @@ int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20],
if (write_sha1_file(data, size, "blob", value_sha1) < 0)
return -1;
- add_note(&c->tree, key_sha1, value_sha1, NULL);
- return 0;
+ return add_note(&c->tree, key_sha1, value_sha1, NULL);
}
diff --git a/notes-merge.c b/notes-merge.c
new file mode 100644
index 0000000..e1aaf43
--- /dev/null
+++ b/notes-merge.c
@@ -0,0 +1,737 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "dir.h"
+#include "notes.h"
+#include "notes-merge.h"
+#include "strbuf.h"
+
+struct notes_merge_pair {
+ unsigned char obj[20], base[20], local[20], remote[20];
+};
+
+void init_notes_merge_options(struct notes_merge_options *o)
+{
+ memset(o, 0, sizeof(struct notes_merge_options));
+ strbuf_init(&(o->commit_msg), 0);
+ o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
+}
+
+#define OUTPUT(o, v, ...) \
+ do { \
+ if ((o)->verbosity >= (v)) { \
+ printf(__VA_ARGS__); \
+ puts(""); \
+ } \
+ } while (0)
+
+static int path_to_sha1(const char *path, unsigned char *sha1)
+{
+ char hex_sha1[40];
+ int i = 0;
+ while (*path && i < 40) {
+ if (*path != '/')
+ hex_sha1[i++] = *path;
+ path++;
+ }
+ if (*path || i != 40)
+ return -1;
+ return get_sha1_hex(hex_sha1, sha1);
+}
+
+static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1)
+{
+ switch (p->status) {
+ case DIFF_STATUS_MODIFIED:
+ assert(p->one->mode == p->two->mode);
+ assert(!is_null_sha1(p->one->sha1));
+ assert(!is_null_sha1(p->two->sha1));
+ break;
+ case DIFF_STATUS_ADDED:
+ assert(is_null_sha1(p->one->sha1));
+ break;
+ case DIFF_STATUS_DELETED:
+ assert(is_null_sha1(p->two->sha1));
+ break;
+ default:
+ return -1;
+ }
+ assert(!strcmp(p->one->path, p->two->path));
+ return path_to_sha1(p->one->path, sha1);
+}
+
+static struct notes_merge_pair *find_notes_merge_pair_pos(
+ struct notes_merge_pair *list, int len, unsigned char *obj,
+ int insert_new, int *occupied)
+{
+ /*
+ * Both diff_tree_remote() and diff_tree_local() tend to process
+ * merge_pairs in ascending order. Therefore, cache last returned
+ * index, and search sequentially from there until the appropriate
+ * position is found.
+ *
+ * Since inserts only happen from diff_tree_remote() (which mainly
+ * _appends_), we don't care that inserting into the middle of the
+ * list is expensive (using memmove()).
+ */
+ static int last_index;
+ int i = last_index < len ? last_index : len - 1;
+ int prev_cmp = 0, cmp = -1;
+ while (i >= 0 && i < len) {
+ cmp = hashcmp(obj, list[i].obj);
+ if (!cmp) /* obj belongs @ i */
+ break;
+ else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */
+ i--;
+ else if (cmp < 0) /* obj belongs between i-1 and i */
+ break;
+ else if (cmp > 0 && prev_cmp >= 0) /* obj belongs > i */
+ i++;
+ else /* if (cmp > 0) */ { /* obj belongs between i and i+1 */
+ i++;
+ break;
+ }
+ prev_cmp = cmp;
+ }
+ if (i < 0)
+ i = 0;
+ /* obj belongs at, or immediately preceding, index i (0 <= i <= len) */
+
+ if (!cmp)
+ *occupied = 1;
+ else {
+ *occupied = 0;
+ if (insert_new && i < len) {
+ memmove(list + i + 1, list + i,
+ (len - i) * sizeof(struct notes_merge_pair));
+ memset(list + i, 0, sizeof(struct notes_merge_pair));
+ }
+ }
+ last_index = i;
+ return list + i;
+}
+
+static unsigned char uninitialized[20] =
+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \
+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
+
+static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
+ const unsigned char *base,
+ const unsigned char *remote,
+ int *num_changes)
+{
+ struct diff_options opt;
+ struct notes_merge_pair *changes;
+ int i, len = 0;
+
+ trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n",
+ sha1_to_hex(base), sha1_to_hex(remote));
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, RECURSIVE);
+ opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+ if (diff_setup_done(&opt) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(base, remote, "", &opt);
+ diffcore_std(&opt);
+
+ changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair));
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct notes_merge_pair *mp;
+ int occupied;
+ unsigned char obj[20];
+
+ if (verify_notes_filepair(p, obj)) {
+ trace_printf("\t\tCannot merge entry '%s' (%c): "
+ "%.7s -> %.7s. Skipping!\n", p->one->path,
+ p->status, sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+ mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied);
+ if (occupied) {
+ /* We've found an addition/deletion pair */
+ assert(!hashcmp(mp->obj, obj));
+ if (is_null_sha1(p->one->sha1)) { /* addition */
+ assert(is_null_sha1(mp->remote));
+ hashcpy(mp->remote, p->two->sha1);
+ } else if (is_null_sha1(p->two->sha1)) { /* deletion */
+ assert(is_null_sha1(mp->base));
+ hashcpy(mp->base, p->one->sha1);
+ } else
+ assert(!"Invalid existing change recorded");
+ } else {
+ hashcpy(mp->obj, obj);
+ hashcpy(mp->base, p->one->sha1);
+ hashcpy(mp->local, uninitialized);
+ hashcpy(mp->remote, p->two->sha1);
+ len++;
+ }
+ trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n",
+ sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+ sha1_to_hex(mp->remote));
+ }
+ diff_flush(&opt);
+ diff_tree_release_paths(&opt);
+
+ *num_changes = len;
+ return changes;
+}
+
+static void diff_tree_local(struct notes_merge_options *o,
+ struct notes_merge_pair *changes, int len,
+ const unsigned char *base,
+ const unsigned char *local)
+{
+ struct diff_options opt;
+ int i;
+
+ trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n",
+ len, sha1_to_hex(base), sha1_to_hex(local));
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, RECURSIVE);
+ opt.output_format = DIFF_FORMAT_NO_OUTPUT;
+ if (diff_setup_done(&opt) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(base, local, "", &opt);
+ diffcore_std(&opt);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct notes_merge_pair *mp;
+ int match;
+ unsigned char obj[20];
+
+ if (verify_notes_filepair(p, obj)) {
+ trace_printf("\t\tCannot merge entry '%s' (%c): "
+ "%.7s -> %.7s. Skipping!\n", p->one->path,
+ p->status, sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+ mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match);
+ if (!match) {
+ trace_printf("\t\tIgnoring local-only change for %s: "
+ "%.7s -> %.7s\n", sha1_to_hex(obj),
+ sha1_to_hex(p->one->sha1),
+ sha1_to_hex(p->two->sha1));
+ continue;
+ }
+
+ assert(!hashcmp(mp->obj, obj));
+ if (is_null_sha1(p->two->sha1)) { /* deletion */
+ /*
+ * Either this is a true deletion (1), or it is part
+ * of an A/D pair (2), or D/A pair (3):
+ *
+ * (1) mp->local is uninitialized; set it to null_sha1
+ * (2) mp->local is not uninitialized; don't touch it
+ * (3) mp->local is uninitialized; set it to null_sha1
+ * (will be overwritten by following addition)
+ */
+ if (!hashcmp(mp->local, uninitialized))
+ hashclr(mp->local);
+ } else if (is_null_sha1(p->one->sha1)) { /* addition */
+ /*
+ * Either this is a true addition (1), or it is part
+ * of an A/D pair (2), or D/A pair (3):
+ *
+ * (1) mp->local is uninitialized; set to p->two->sha1
+ * (2) mp->local is uninitialized; set to p->two->sha1
+ * (3) mp->local is null_sha1; set to p->two->sha1
+ */
+ assert(is_null_sha1(mp->local) ||
+ !hashcmp(mp->local, uninitialized));
+ hashcpy(mp->local, p->two->sha1);
+ } else { /* modification */
+ /*
+ * This is a true modification. p->one->sha1 shall
+ * match mp->base, and mp->local shall be uninitialized.
+ * Set mp->local to p->two->sha1.
+ */
+ assert(!hashcmp(p->one->sha1, mp->base));
+ assert(!hashcmp(mp->local, uninitialized));
+ hashcpy(mp->local, p->two->sha1);
+ }
+ trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n",
+ sha1_to_hex(mp->obj), sha1_to_hex(mp->base),
+ sha1_to_hex(mp->local));
+ }
+ diff_flush(&opt);
+ diff_tree_release_paths(&opt);
+}
+
+static void check_notes_merge_worktree(struct notes_merge_options *o)
+{
+ if (!o->has_worktree) {
+ /*
+ * Must establish NOTES_MERGE_WORKTREE.
+ * Abort if NOTES_MERGE_WORKTREE already exists
+ */
+ if (file_exists(git_path(NOTES_MERGE_WORKTREE))) {
+ if (advice_resolve_conflict)
+ die("You have not concluded your previous "
+ "notes merge (%s exists).\nPlease, use "
+ "'git notes merge --commit' or 'git notes "
+ "merge --abort' to commit/abort the "
+ "previous merge before you start a new "
+ "notes merge.", git_path("NOTES_MERGE_*"));
+ else
+ die("You have not concluded your notes merge "
+ "(%s exists).", git_path("NOTES_MERGE_*"));
+ }
+
+ if (safe_create_leading_directories(git_path(
+ NOTES_MERGE_WORKTREE "/.test")))
+ die_errno("unable to create directory %s",
+ git_path(NOTES_MERGE_WORKTREE));
+ o->has_worktree = 1;
+ } else if (!file_exists(git_path(NOTES_MERGE_WORKTREE)))
+ /* NOTES_MERGE_WORKTREE should already be established */
+ die("missing '%s'. This should not happen",
+ git_path(NOTES_MERGE_WORKTREE));
+}
+
+static void write_buf_to_worktree(const unsigned char *obj,
+ const char *buf, unsigned long size)
+{
+ int fd;
+ char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj));
+ if (safe_create_leading_directories(path))
+ die_errno("unable to create directory for '%s'", path);
+ if (file_exists(path))
+ die("found existing file at '%s'", path);
+
+ fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("failed to open '%s'", path);
+
+ while (size > 0) {
+ long ret = write_in_full(fd, buf, size);
+ if (ret < 0) {
+ /* Ignore epipe */
+ if (errno == EPIPE)
+ break;
+ die_errno("notes-merge");
+ } else if (!ret) {
+ die("notes-merge: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+
+ close(fd);
+}
+
+static void write_note_to_worktree(const unsigned char *obj,
+ const unsigned char *note)
+{
+ enum object_type type;
+ unsigned long size;
+ void *buf = read_sha1_file(note, &type, &size);
+
+ if (!buf)
+ die("cannot read note %s for object %s",
+ sha1_to_hex(note), sha1_to_hex(obj));
+ if (type != OBJ_BLOB)
+ die("blob expected in note %s for object %s",
+ sha1_to_hex(note), sha1_to_hex(obj));
+ write_buf_to_worktree(obj, buf, size);
+ free(buf);
+}
+
+static int ll_merge_in_worktree(struct notes_merge_options *o,
+ struct notes_merge_pair *p)
+{
+ mmbuffer_t result_buf;
+ mmfile_t base, local, remote;
+ int status;
+
+ read_mmblob(&base, p->base);
+ read_mmblob(&local, p->local);
+ read_mmblob(&remote, p->remote);
+
+ status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL,
+ &local, o->local_ref, &remote, o->remote_ref, NULL);
+
+ free(base.ptr);
+ free(local.ptr);
+ free(remote.ptr);
+
+ if ((status < 0) || !result_buf.ptr)
+ die("Failed to execute internal merge");
+
+ write_buf_to_worktree(p->obj, result_buf.ptr, result_buf.size);
+ free(result_buf.ptr);
+
+ return status;
+}
+
+static int merge_one_change_manual(struct notes_merge_options *o,
+ struct notes_merge_pair *p,
+ struct notes_tree *t)
+{
+ const char *lref = o->local_ref ? o->local_ref : "local version";
+ const char *rref = o->remote_ref ? o->remote_ref : "remote version";
+
+ trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, "
+ "local = %.7s, remote = %.7s)\n",
+ sha1_to_hex(p->obj), sha1_to_hex(p->base),
+ sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+ /* add "Conflicts:" section to commit message first time through */
+ if (!o->has_worktree)
+ strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n");
+
+ strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
+
+ OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
+ check_notes_merge_worktree(o);
+ if (is_null_sha1(p->local)) {
+ /* D/F conflict, checkout p->remote */
+ assert(!is_null_sha1(p->remote));
+ OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
+ write_note_to_worktree(p->obj, p->remote);
+ } else if (is_null_sha1(p->remote)) {
+ /* D/F conflict, checkout p->local */
+ assert(!is_null_sha1(p->local));
+ OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
+ "deleted in %s and modified in %s. Version from %s "
+ "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
+ write_note_to_worktree(p->obj, p->local);
+ } else {
+ /* "regular" conflict, checkout result of ll_merge() */
+ const char *reason = "content";
+ if (is_null_sha1(p->base))
+ reason = "add/add";
+ assert(!is_null_sha1(p->local));
+ assert(!is_null_sha1(p->remote));
+ OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
+ "object %s", reason, sha1_to_hex(p->obj));
+ ll_merge_in_worktree(o, p);
+ }
+
+ trace_printf("\t\t\tremoving from partial merge result\n");
+ remove_note(t, p->obj);
+
+ return 1;
+}
+
+static int merge_one_change(struct notes_merge_options *o,
+ struct notes_merge_pair *p, struct notes_tree *t)
+{
+ /*
+ * Return 0 if change is successfully resolved (stored in notes_tree).
+ * Return 1 is change results in a conflict (NOT stored in notes_tree,
+ * but instead written to NOTES_MERGE_WORKTREE with conflict markers).
+ */
+ switch (o->strategy) {
+ case NOTES_MERGE_RESOLVE_MANUAL:
+ return merge_one_change_manual(o, p, t);
+ case NOTES_MERGE_RESOLVE_OURS:
+ OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
+ /* nothing to do */
+ return 0;
+ case NOTES_MERGE_RESOLVE_THEIRS:
+ OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ return 0;
+ case NOTES_MERGE_RESOLVE_UNION:
+ OUTPUT(o, 2, "Concatenating local and remote notes for %s",
+ sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
+ die("failed to concatenate notes "
+ "(combine_notes_concatenate)");
+ return 0;
+ case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
+ OUTPUT(o, 2, "Concatenating unique lines in local and remote "
+ "notes for %s", sha1_to_hex(p->obj));
+ if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
+ die("failed to concatenate notes "
+ "(combine_notes_cat_sort_uniq)");
+ return 0;
+ }
+ die("Unknown strategy (%i).", o->strategy);
+}
+
+static int merge_changes(struct notes_merge_options *o,
+ struct notes_merge_pair *changes, int *num_changes,
+ struct notes_tree *t)
+{
+ int i, conflicts = 0;
+
+ trace_printf("\tmerge_changes(num_changes = %i)\n", *num_changes);
+ for (i = 0; i < *num_changes; i++) {
+ struct notes_merge_pair *p = changes + i;
+ trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n",
+ sha1_to_hex(p->obj), sha1_to_hex(p->base),
+ sha1_to_hex(p->local), sha1_to_hex(p->remote));
+
+ if (!hashcmp(p->base, p->remote)) {
+ /* no remote change; nothing to do */
+ trace_printf("\t\t\tskipping (no remote change)\n");
+ } else if (!hashcmp(p->local, p->remote)) {
+ /* same change in local and remote; nothing to do */
+ trace_printf("\t\t\tskipping (local == remote)\n");
+ } else if (!hashcmp(p->local, uninitialized) ||
+ !hashcmp(p->local, p->base)) {
+ /* no local change; adopt remote change */
+ trace_printf("\t\t\tno local change, adopted remote\n");
+ if (add_note(t, p->obj, p->remote,
+ combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ } else {
+ /* need file-level merge between local and remote */
+ trace_printf("\t\t\tneed content-level merge\n");
+ conflicts += merge_one_change(o, p, t);
+ }
+ }
+
+ return conflicts;
+}
+
+static int merge_from_diffs(struct notes_merge_options *o,
+ const unsigned char *base,
+ const unsigned char *local,
+ const unsigned char *remote, struct notes_tree *t)
+{
+ struct notes_merge_pair *changes;
+ int num_changes, conflicts;
+
+ trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, "
+ "remote = %.7s)\n", sha1_to_hex(base), sha1_to_hex(local),
+ sha1_to_hex(remote));
+
+ changes = diff_tree_remote(o, base, remote, &num_changes);
+ diff_tree_local(o, changes, num_changes, base, local);
+
+ conflicts = merge_changes(o, changes, &num_changes, t);
+ free(changes);
+
+ OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
+ conflicts, t->dirty ? "dirty" : "clean");
+
+ return conflicts ? -1 : 1;
+}
+
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const char *msg, unsigned char *result_sha1)
+{
+ unsigned char tree_sha1[20];
+
+ assert(t->initialized);
+
+ if (write_notes_tree(t, tree_sha1))
+ die("Failed to write notes tree to database");
+
+ if (!parents) {
+ /* Deduce parent commit from t->ref */
+ unsigned char parent_sha1[20];
+ if (!read_ref(t->ref, parent_sha1)) {
+ struct commit *parent = lookup_commit(parent_sha1);
+ if (!parent || parse_commit(parent))
+ die("Failed to find/parse commit %s", t->ref);
+ commit_list_insert(parent, &parents);
+ }
+ /* else: t->ref points to nothing, assume root/orphan commit */
+ }
+
+ if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+ die("Failed to commit notes tree to database");
+}
+
+int notes_merge(struct notes_merge_options *o,
+ struct notes_tree *local_tree,
+ unsigned char *result_sha1)
+{
+ unsigned char local_sha1[20], remote_sha1[20];
+ struct commit *local, *remote;
+ struct commit_list *bases = NULL;
+ const unsigned char *base_sha1, *base_tree_sha1;
+ int result = 0;
+
+ assert(o->local_ref && o->remote_ref);
+ assert(!strcmp(o->local_ref, local_tree->ref));
+ hashclr(result_sha1);
+
+ trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n",
+ o->local_ref, o->remote_ref);
+
+ /* Dereference o->local_ref into local_sha1 */
+ if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
+ die("Failed to resolve local notes ref '%s'", o->local_ref);
+ else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+ local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
+ else if (!(local = lookup_commit_reference(local_sha1)))
+ die("Could not parse local commit %s (%s)",
+ sha1_to_hex(local_sha1), o->local_ref);
+ trace_printf("\tlocal commit: %.7s\n", sha1_to_hex(local_sha1));
+
+ /* Dereference o->remote_ref into remote_sha1 */
+ if (get_sha1(o->remote_ref, remote_sha1)) {
+ /*
+ * Failed to get remote_sha1. If o->remote_ref looks like an
+ * unborn ref, perform the merge using an empty notes tree.
+ */
+ if (!check_ref_format(o->remote_ref)) {
+ hashclr(remote_sha1);
+ remote = NULL;
+ } else {
+ die("Failed to resolve remote notes ref '%s'",
+ o->remote_ref);
+ }
+ } else if (!(remote = lookup_commit_reference(remote_sha1))) {
+ die("Could not parse remote commit %s (%s)",
+ sha1_to_hex(remote_sha1), o->remote_ref);
+ }
+ trace_printf("\tremote commit: %.7s\n", sha1_to_hex(remote_sha1));
+
+ if (!local && !remote)
+ die("Cannot merge empty notes ref (%s) into empty notes ref "
+ "(%s)", o->remote_ref, o->local_ref);
+ if (!local) {
+ /* result == remote commit */
+ hashcpy(result_sha1, remote_sha1);
+ goto found_result;
+ }
+ if (!remote) {
+ /* result == local commit */
+ hashcpy(result_sha1, local_sha1);
+ goto found_result;
+ }
+ assert(local && remote);
+
+ /* Find merge bases */
+ bases = get_merge_bases(local, remote, 1);
+ if (!bases) {
+ base_sha1 = null_sha1;
+ base_tree_sha1 = EMPTY_TREE_SHA1_BIN;
+ OUTPUT(o, 4, "No merge base found; doing history-less merge");
+ } else if (!bases->next) {
+ base_sha1 = bases->item->object.sha1;
+ base_tree_sha1 = bases->item->tree->object.sha1;
+ OUTPUT(o, 4, "One merge base found (%.7s)",
+ sha1_to_hex(base_sha1));
+ } else {
+ /* TODO: How to handle multiple merge-bases? */
+ base_sha1 = bases->item->object.sha1;
+ base_tree_sha1 = bases->item->tree->object.sha1;
+ OUTPUT(o, 3, "Multiple merge bases found. Using the first "
+ "(%.7s)", sha1_to_hex(base_sha1));
+ }
+
+ OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
+ "merge-base %.7s", sha1_to_hex(remote->object.sha1),
+ sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
+
+ if (!hashcmp(remote->object.sha1, base_sha1)) {
+ /* Already merged; result == local commit */
+ OUTPUT(o, 2, "Already up-to-date!");
+ hashcpy(result_sha1, local->object.sha1);
+ goto found_result;
+ }
+ if (!hashcmp(local->object.sha1, base_sha1)) {
+ /* Fast-forward; result == remote commit */
+ OUTPUT(o, 2, "Fast-forward");
+ hashcpy(result_sha1, remote->object.sha1);
+ goto found_result;
+ }
+
+ result = merge_from_diffs(o, base_tree_sha1, local->tree->object.sha1,
+ remote->tree->object.sha1, local_tree);
+
+ if (result != 0) { /* non-trivial merge (with or without conflicts) */
+ /* Commit (partial) result */
+ struct commit_list *parents = NULL;
+ commit_list_insert(remote, &parents); /* LIFO order */
+ commit_list_insert(local, &parents);
+ create_notes_commit(local_tree, parents, o->commit_msg.buf,
+ result_sha1);
+ }
+
+found_result:
+ free_commit_list(bases);
+ strbuf_release(&(o->commit_msg));
+ trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n",
+ result, sha1_to_hex(result_sha1));
+ return result;
+}
+
+int notes_merge_commit(struct notes_merge_options *o,
+ struct notes_tree *partial_tree,
+ struct commit *partial_commit,
+ unsigned char *result_sha1)
+{
+ /*
+ * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
+ * found notes to 'partial_tree'. Write the updates notes tree to
+ * the DB, and commit the resulting tree object while reusing the
+ * commit message and parents from 'partial_commit'.
+ * Finally store the new commit object SHA1 into 'result_sha1'.
+ */
+ struct dir_struct dir;
+ const char *path = git_path(NOTES_MERGE_WORKTREE "/");
+ int path_len = strlen(path), i;
+ const char *msg = strstr(partial_commit->buffer, "\n\n");
+
+ OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
+ path_len - 1, path);
+
+ if (!msg || msg[2] == '\0')
+ die("partial notes commit has empty message");
+ msg += 2;
+
+ memset(&dir, 0, sizeof(dir));
+ read_directory(&dir, path, path_len, NULL);
+ for (i = 0; i < dir.nr; i++) {
+ struct dir_entry *ent = dir.entries[i];
+ struct stat st;
+ const char *relpath = ent->name + path_len;
+ unsigned char obj_sha1[20], blob_sha1[20];
+
+ if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
+ OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
+ continue;
+ }
+
+ /* write file as blob, and add to partial_tree */
+ if (stat(ent->name, &st))
+ die_errno("Failed to stat '%s'", ent->name);
+ if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT))
+ die("Failed to write blob object from '%s'", ent->name);
+ if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
+ die("Failed to add resolved note '%s' to notes tree",
+ ent->name);
+ OUTPUT(o, 4, "Added resolved note for object %s: %s",
+ sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+ }
+
+ create_notes_commit(partial_tree, partial_commit->parents, msg,
+ result_sha1);
+ OUTPUT(o, 4, "Finalized notes merge commit: %s",
+ sha1_to_hex(result_sha1));
+ return 0;
+}
+
+int notes_merge_abort(struct notes_merge_options *o)
+{
+ /* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
+ OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
+ ret = remove_dir_recursively(&buf, 0);
+ strbuf_release(&buf);
+ return ret;
+}
diff --git a/notes-merge.h b/notes-merge.h
new file mode 100644
index 0000000..168a672
--- /dev/null
+++ b/notes-merge.h
@@ -0,0 +1,98 @@
+#ifndef NOTES_MERGE_H
+#define NOTES_MERGE_H
+
+#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE"
+
+enum notes_merge_verbosity {
+ NOTES_MERGE_VERBOSITY_DEFAULT = 2,
+ NOTES_MERGE_VERBOSITY_MAX = 5
+};
+
+struct notes_merge_options {
+ const char *local_ref;
+ const char *remote_ref;
+ struct strbuf commit_msg;
+ int verbosity;
+ enum {
+ NOTES_MERGE_RESOLVE_MANUAL = 0,
+ NOTES_MERGE_RESOLVE_OURS,
+ NOTES_MERGE_RESOLVE_THEIRS,
+ NOTES_MERGE_RESOLVE_UNION,
+ NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ
+ } strategy;
+ unsigned has_worktree:1;
+};
+
+void init_notes_merge_options(struct notes_merge_options *o);
+
+/*
+ * Create new notes commit from the given notes tree
+ *
+ * Properties of the created commit:
+ * - tree: the result of converting t to a tree object with write_notes_tree().
+ * - parents: the given parents OR (if NULL) the commit referenced by t->ref.
+ * - author/committer: the default determined by commmit_tree().
+ * - commit message: msg
+ *
+ * The resulting commit SHA1 is stored in result_sha1.
+ */
+void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
+ const char *msg, unsigned char *result_sha1);
+
+/*
+ * Merge notes from o->remote_ref into o->local_ref
+ *
+ * The given notes_tree 'local_tree' must be the notes_tree referenced by the
+ * o->local_ref. This is the notes_tree in which the object-level merge is
+ * performed.
+ *
+ * The commits given by the two refs are merged, producing one of the following
+ * outcomes:
+ *
+ * 1. The merge trivially results in an existing commit (e.g. fast-forward or
+ * already-up-to-date). 'local_tree' is untouched, the SHA1 of the result
+ * is written into 'result_sha1' and 0 is returned.
+ * 2. The merge successfully completes, producing a merge commit. local_tree
+ * contains the updated notes tree, the SHA1 of the resulting commit is
+ * written into 'result_sha1', and 1 is returned.
+ * 3. The merge results in conflicts. This is similar to #2 in that the
+ * partial merge result (i.e. merge result minus the unmerged entries)
+ * are stored in 'local_tree', and the SHA1 or the resulting commit
+ * (to be amended when the conflicts have been resolved) is written into
+ * 'result_sha1'. The unmerged entries are written into the
+ * .git/NOTES_MERGE_WORKTREE directory with conflict markers.
+ * -1 is returned.
+ *
+ * Both o->local_ref and o->remote_ref must be given (non-NULL), but either ref
+ * (although not both) may refer to a non-existing notes ref, in which case
+ * that notes ref is interpreted as an empty notes tree, and the merge
+ * trivially results in what the other ref points to.
+ */
+int notes_merge(struct notes_merge_options *o,
+ struct notes_tree *local_tree,
+ unsigned char *result_sha1);
+
+/*
+ * Finalize conflict resolution from an earlier notes_merge()
+ *
+ * The given notes tree 'partial_tree' must be the notes_tree corresponding to
+ * the given 'partial_commit', the partial result commit created by a previous
+ * call to notes_merge().
+ *
+ * This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE
+ * to 'partial_tree', and create a final notes merge commit, the SHA1 of which
+ * will be stored in 'result_sha1'.
+ */
+int notes_merge_commit(struct notes_merge_options *o,
+ struct notes_tree *partial_tree,
+ struct commit *partial_commit,
+ unsigned char *result_sha1);
+
+/*
+ * Abort conflict resolution from an earlier notes_merge()
+ *
+ * Removes the notes merge worktree in .git/NOTES_MERGE_WORKTREE.
+ */
+int notes_merge_abort(struct notes_merge_options *o);
+
+#endif
diff --git a/notes.c b/notes.c
index 1978244..f6ce848 100644
--- a/notes.c
+++ b/notes.c
@@ -150,86 +150,6 @@ static struct leaf_node *note_tree_find(struct notes_tree *t,
}
/*
- * To insert a leaf_node:
- * Search to the tree location appropriate for the given leaf_node's key:
- * - If location is unused (NULL), store the tweaked pointer directly there
- * - If location holds a note entry that matches the note-to-be-inserted, then
- * combine the two notes (by calling the given combine_notes function).
- * - If location holds a note entry that matches the subtree-to-be-inserted,
- * then unpack the subtree-to-be-inserted into the location.
- * - If location holds a matching subtree entry, unpack the subtree at that
- * location, and restart the insert operation from that level.
- * - Else, create a new int_node, holding both the node-at-location and the
- * node-to-be-inserted, and store the new int_node into the location.
- */
-static void note_tree_insert(struct notes_tree *t, struct int_node *tree,
- unsigned char n, struct leaf_node *entry, unsigned char type,
- combine_notes_fn combine_notes)
-{
- struct int_node *new_node;
- struct leaf_node *l;
- void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
-
- assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
- l = (struct leaf_node *) CLR_PTR_TYPE(*p);
- switch (GET_PTR_TYPE(*p)) {
- case PTR_TYPE_NULL:
- assert(!*p);
- *p = SET_PTR_TYPE(entry, type);
- return;
- case PTR_TYPE_NOTE:
- switch (type) {
- case PTR_TYPE_NOTE:
- if (!hashcmp(l->key_sha1, entry->key_sha1)) {
- /* skip concatenation if l == entry */
- if (!hashcmp(l->val_sha1, entry->val_sha1))
- return;
-
- if (combine_notes(l->val_sha1, entry->val_sha1))
- die("failed to combine notes %s and %s"
- " for object %s",
- sha1_to_hex(l->val_sha1),
- sha1_to_hex(entry->val_sha1),
- sha1_to_hex(l->key_sha1));
- free(entry);
- return;
- }
- break;
- case PTR_TYPE_SUBTREE:
- if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
- entry->key_sha1)) {
- /* unpack 'entry' */
- load_subtree(t, entry, tree, n);
- free(entry);
- return;
- }
- break;
- }
- break;
- case PTR_TYPE_SUBTREE:
- if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
- /* unpack 'l' and restart insert */
- *p = NULL;
- load_subtree(t, l, tree, n);
- free(l);
- note_tree_insert(t, tree, n, entry, type,
- combine_notes);
- return;
- }
- break;
- }
-
- /* non-matching leaf_node */
- assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
- GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
- new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
- note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
- combine_notes);
- *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
- note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
-}
-
-/*
* How to consolidate an int_node:
* If there are > 1 non-NULL entries, give up and return non-zero.
* Otherwise replace the int_node at the given index in the given parent node
@@ -263,11 +183,13 @@ static int note_tree_consolidate(struct int_node *tree,
* To remove a leaf_node:
* Search to the tree location appropriate for the given leaf_node's key:
* - If location does not hold a matching entry, abort and do nothing.
+ * - Copy the matching entry's value into the given entry.
* - Replace the matching leaf_node with a NULL entry (and free the leaf_node).
* - Consolidate int_nodes repeatedly, while walking up the tree towards root.
*/
-static void note_tree_remove(struct notes_tree *t, struct int_node *tree,
- unsigned char n, struct leaf_node *entry)
+static void note_tree_remove(struct notes_tree *t,
+ struct int_node *tree, unsigned char n,
+ struct leaf_node *entry)
{
struct leaf_node *l;
struct int_node *parent_stack[20];
@@ -282,6 +204,7 @@ static void note_tree_remove(struct notes_tree *t, struct int_node *tree,
return; /* key mismatch, nothing to remove */
/* we have found a matching entry */
+ hashcpy(entry->val_sha1, l->val_sha1);
free(l);
*p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL);
@@ -302,6 +225,93 @@ static void note_tree_remove(struct notes_tree *t, struct int_node *tree,
i--;
}
+/*
+ * To insert a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location is unused (NULL), store the tweaked pointer directly there
+ * - If location holds a note entry that matches the note-to-be-inserted, then
+ * combine the two notes (by calling the given combine_notes function).
+ * - If location holds a note entry that matches the subtree-to-be-inserted,
+ * then unpack the subtree-to-be-inserted into the location.
+ * - If location holds a matching subtree entry, unpack the subtree at that
+ * location, and restart the insert operation from that level.
+ * - Else, create a new int_node, holding both the node-at-location and the
+ * node-to-be-inserted, and store the new int_node into the location.
+ */
+static int note_tree_insert(struct notes_tree *t, struct int_node *tree,
+ unsigned char n, struct leaf_node *entry, unsigned char type,
+ combine_notes_fn combine_notes)
+{
+ struct int_node *new_node;
+ struct leaf_node *l;
+ void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
+ int ret = 0;
+
+ assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+ l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+ switch (GET_PTR_TYPE(*p)) {
+ case PTR_TYPE_NULL:
+ assert(!*p);
+ if (is_null_sha1(entry->val_sha1))
+ free(entry);
+ else
+ *p = SET_PTR_TYPE(entry, type);
+ return 0;
+ case PTR_TYPE_NOTE:
+ switch (type) {
+ case PTR_TYPE_NOTE:
+ if (!hashcmp(l->key_sha1, entry->key_sha1)) {
+ /* skip concatenation if l == entry */
+ if (!hashcmp(l->val_sha1, entry->val_sha1))
+ return 0;
+
+ ret = combine_notes(l->val_sha1,
+ entry->val_sha1);
+ if (!ret && is_null_sha1(l->val_sha1))
+ note_tree_remove(t, tree, n, entry);
+ free(entry);
+ return ret;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
+ entry->key_sha1)) {
+ /* unpack 'entry' */
+ load_subtree(t, entry, tree, n);
+ free(entry);
+ return 0;
+ }
+ break;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
+ /* unpack 'l' and restart insert */
+ *p = NULL;
+ load_subtree(t, l, tree, n);
+ free(l);
+ return note_tree_insert(t, tree, n, entry, type,
+ combine_notes);
+ }
+ break;
+ }
+
+ /* non-matching leaf_node */
+ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
+ GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
+ if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */
+ free(entry);
+ return 0;
+ }
+ new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+ ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
+ combine_notes);
+ if (ret)
+ return ret;
+ *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+ return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
+}
+
/* Free the entire notes data contained in the given tree */
static void note_tree_free(struct int_node *tree)
{
@@ -442,8 +452,12 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
l->key_sha1[19] = (unsigned char) len;
type = PTR_TYPE_SUBTREE;
}
- note_tree_insert(t, node, n, l, type,
- combine_notes_concatenate);
+ if (note_tree_insert(t, node, n, l, type,
+ combine_notes_concatenate))
+ die("Failed to load %s %s into notes tree "
+ "from %s",
+ type == PTR_TYPE_NOTE ? "note" : "subtree",
+ sha1_to_hex(l->key_sha1), t->ref);
}
continue;
@@ -801,16 +815,17 @@ int combine_notes_concatenate(unsigned char *cur_sha1,
return 0;
}
- /* we will separate the notes by a newline anyway */
+ /* we will separate the notes by two newlines anyway */
if (cur_msg[cur_len - 1] == '\n')
cur_len--;
/* concatenate cur_msg and new_msg into buf */
- buf_len = cur_len + 1 + new_len;
+ buf_len = cur_len + 2 + new_len;
buf = (char *) xmalloc(buf_len);
memcpy(buf, cur_msg, cur_len);
buf[cur_len] = '\n';
- memcpy(buf + cur_len + 1, new_msg, new_len);
+ buf[cur_len + 1] = '\n';
+ memcpy(buf + cur_len + 2, new_msg, new_len);
free(cur_msg);
free(new_msg);
@@ -833,6 +848,82 @@ int combine_notes_ignore(unsigned char *cur_sha1,
return 0;
}
+static int string_list_add_note_lines(struct string_list *sort_uniq_list,
+ const unsigned char *sha1)
+{
+ char *data;
+ unsigned long len;
+ enum object_type t;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **lines = NULL;
+ int i, list_index;
+
+ if (is_null_sha1(sha1))
+ return 0;
+
+ /* read_sha1_file NUL-terminates */
+ data = read_sha1_file(sha1, &t, &len);
+ if (t != OBJ_BLOB || !data || !len) {
+ free(data);
+ return t != OBJ_BLOB || !data;
+ }
+
+ strbuf_attach(&buf, data, len, len + 1);
+ lines = strbuf_split(&buf, '\n');
+
+ for (i = 0; lines[i]; i++) {
+ if (lines[i]->buf[lines[i]->len - 1] == '\n')
+ strbuf_setlen(lines[i], lines[i]->len - 1);
+ if (!lines[i]->len)
+ continue; /* skip empty lines */
+ list_index = string_list_find_insert_index(sort_uniq_list,
+ lines[i]->buf, 0);
+ if (list_index < 0)
+ continue; /* skip duplicate lines */
+ string_list_insert_at_index(sort_uniq_list, list_index,
+ lines[i]->buf);
+ }
+
+ strbuf_list_free(lines);
+ strbuf_release(&buf);
+ return 0;
+}
+
+static int string_list_join_lines_helper(struct string_list_item *item,
+ void *cb_data)
+{
+ struct strbuf *buf = cb_data;
+ strbuf_addstr(buf, item->string);
+ strbuf_addch(buf, '\n');
+ return 0;
+}
+
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
+ const unsigned char *new_sha1)
+{
+ struct string_list sort_uniq_list = { NULL, 0, 0, 1 };
+ struct strbuf buf = STRBUF_INIT;
+ int ret = 1;
+
+ /* read both note blob objects into unique_lines */
+ if (string_list_add_note_lines(&sort_uniq_list, cur_sha1))
+ goto out;
+ if (string_list_add_note_lines(&sort_uniq_list, new_sha1))
+ goto out;
+
+ /* create a new blob object from sort_uniq_list */
+ if (for_each_string_list(&sort_uniq_list,
+ string_list_join_lines_helper, &buf))
+ goto out;
+
+ ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1);
+
+out:
+ strbuf_release(&buf);
+ string_list_clear(&sort_uniq_list, 0);
+ return ret;
+}
+
static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
int flag, void *cb)
{
@@ -877,14 +968,6 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,
strbuf_release(&globbuf);
}
-static int string_list_add_refs_from_list(struct string_list_item *item,
- void *cb)
-{
- struct string_list *list = cb;
- string_list_add_refs_by_glob(list, item->string);
- return 0;
-}
-
static int notes_display_config(const char *k, const char *v, void *cb)
{
int *load_refs = cb;
@@ -898,7 +981,7 @@ static int notes_display_config(const char *k, const char *v, void *cb)
return 0;
}
-static const char *default_notes_ref(void)
+const char *default_notes_ref(void)
{
const char *notes_ref = NULL;
if (!notes_ref)
@@ -940,37 +1023,25 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
return;
if (get_tree_entry(object_sha1, "", sha1, &mode))
die("Failed to read notes tree referenced by %s (%s)",
- notes_ref, object_sha1);
+ notes_ref, sha1_to_hex(object_sha1));
hashclr(root_tree.key_sha1);
hashcpy(root_tree.val_sha1, sha1);
load_subtree(t, &root_tree, t->root, 0);
}
-struct load_notes_cb_data {
- int counter;
- struct notes_tree **trees;
-};
-
-static int load_one_display_note_ref(struct string_list_item *item,
- void *cb_data)
-{
- struct load_notes_cb_data *c = cb_data;
- struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
- init_notes(t, item->string, combine_notes_ignore, 0);
- c->trees[c->counter++] = t;
- return 0;
-}
-
struct notes_tree **load_notes_trees(struct string_list *refs)
{
+ struct string_list_item *item;
+ int counter = 0;
struct notes_tree **trees;
- struct load_notes_cb_data cb_data;
trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
- cb_data.counter = 0;
- cb_data.trees = trees;
- for_each_string_list(refs, load_one_display_note_ref, &cb_data);
- trees[cb_data.counter] = NULL;
+ for_each_string_list_item(item, refs) {
+ struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+ init_notes(t, item->string, combine_notes_ignore, 0);
+ trees[counter++] = t;
+ }
+ trees[counter] = NULL;
return trees;
}
@@ -982,7 +1053,8 @@ void init_display_notes(struct display_notes_opt *opt)
assert(!display_notes_trees);
- if (!opt || !opt->suppress_default_notes) {
+ if (!opt || opt->use_default_notes > 0 ||
+ (opt->use_default_notes == -1 && !opt->extra_notes_refs.nr)) {
string_list_append(&display_notes_refs, default_notes_ref());
display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
if (display_ref_env) {
@@ -995,16 +1067,18 @@ void init_display_notes(struct display_notes_opt *opt)
git_config(notes_display_config, &load_config_refs);
- if (opt && opt->extra_notes_refs)
- for_each_string_list(opt->extra_notes_refs,
- string_list_add_refs_from_list,
- &display_notes_refs);
+ if (opt) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, &opt->extra_notes_refs)
+ string_list_add_refs_by_glob(&display_notes_refs,
+ item->string);
+ }
display_notes_trees = load_notes_trees(&display_notes_refs);
string_list_clear(&display_notes_refs, 0);
}
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes)
{
struct leaf_node *l;
@@ -1018,20 +1092,23 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
hashcpy(l->key_sha1, object_sha1);
hashcpy(l->val_sha1, note_sha1);
- note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
+ return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
}
-void remove_note(struct notes_tree *t, const unsigned char *object_sha1)
+int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
{
struct leaf_node l;
if (!t)
t = &default_notes_tree;
assert(t->initialized);
- t->dirty = 1;
hashcpy(l.key_sha1, object_sha1);
hashclr(l.val_sha1);
note_tree_remove(t, t->root, 0, &l);
+ if (is_null_sha1(l.val_sha1)) // no note was removed
+ return 1;
+ t->dirty = 1;
+ return 0;
}
const unsigned char *get_note(struct notes_tree *t,
@@ -1194,7 +1271,7 @@ void format_display_notes(const unsigned char *object_sha1,
int copy_note(struct notes_tree *t,
const unsigned char *from_obj, const unsigned char *to_obj,
- int force, combine_notes_fn combine_fn)
+ int force, combine_notes_fn combine_notes)
{
const unsigned char *note = get_note(t, from_obj);
const unsigned char *existing_note = get_note(t, to_obj);
@@ -1203,9 +1280,19 @@ int copy_note(struct notes_tree *t,
return 1;
if (note)
- add_note(t, to_obj, note, combine_fn);
+ return add_note(t, to_obj, note, combine_notes);
else if (existing_note)
- add_note(t, to_obj, null_sha1, combine_fn);
+ return add_note(t, to_obj, null_sha1, combine_notes);
return 0;
}
+
+void expand_notes_ref(struct strbuf *sb)
+{
+ if (!prefixcmp(sb->buf, "refs/notes/"))
+ return; /* we're happy */
+ else if (!prefixcmp(sb->buf, "notes/"))
+ strbuf_insert(sb, 0, "refs/", 5);
+ else
+ strbuf_insert(sb, 0, "refs/notes/", 11);
+}
diff --git a/notes.h b/notes.h
index 65fc3a6..c716694 100644
--- a/notes.h
+++ b/notes.h
@@ -1,6 +1,8 @@
#ifndef NOTES_H
#define NOTES_H
+#include "string-list.h"
+
/*
* Function type for combining two notes annotating the same object.
*
@@ -12,7 +14,10 @@
* resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return
* value indicates failure.
*
- * The two given SHA1s must both be non-NULL and different from each other.
+ * The two given SHA1s shall both be non-NULL and different from each other.
+ * Either of them (but not both) may be == null_sha1, which indicates an
+ * empty/non-existent note. If the resulting SHA1 (cur_sha1) is == null_sha1,
+ * the note will be removed from the notes tree.
*
* The default combine_notes function (you get this when passing NULL) is
* combine_notes_concatenate(), which appends the contents of the new note to
@@ -24,6 +29,7 @@ typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *ne
int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
+int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1);
/*
* Notes tree object
@@ -44,6 +50,20 @@ extern struct notes_tree {
} default_notes_tree;
/*
+ * Return the default notes ref.
+ *
+ * The default notes ref is the notes ref that is used when notes_ref == NULL
+ * is passed to init_notes().
+ *
+ * This the first of the following to be defined:
+ * 1. The '--ref' option to 'git notes', if given
+ * 2. The $GIT_NOTES_REF environment variable, if set
+ * 3. The value of the core.notesRef config variable, if set
+ * 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/notes/commits")
+ */
+const char *default_notes_ref(void);
+
+/*
* Flags controlling behaviour of notes tree initialization
*
* Default behaviour is to initialize the notes tree from the tree object
@@ -76,11 +96,24 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
/*
* Add the given note object to the given notes_tree structure
*
+ * If there already exists a note for the given object_sha1, the given
+ * combine_notes function is invoked to break the tie. If not given (i.e.
+ * combine_notes == NULL), the default combine_notes function for the given
+ * notes_tree is used.
+ *
+ * Passing note_sha1 == null_sha1 indicates the addition of an
+ * empty/non-existent note. This is a (potentially expensive) no-op unless
+ * there already exists a note for the given object_sha1, AND combining that
+ * note with the empty note (using the given combine_notes function) results
+ * in a new/changed note.
+ *
+ * Returns zero on success; non-zero means combine_notes failed.
+ *
* IMPORTANT: The changes made by add_note() to the given notes_tree structure
* are not persistent until a subsequent call to write_notes_tree() returns
* zero.
*/
-void add_note(struct notes_tree *t, const unsigned char *object_sha1,
+int add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes);
/*
@@ -89,8 +122,10 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
* IMPORTANT: The changes made by remove_note() to the given notes_tree
* structure are not persistent until a subsequent call to write_notes_tree()
* returns zero.
+ *
+ * Return 0 if a note was removed; 1 if there was no note to remove.
*/
-void remove_note(struct notes_tree *t, const unsigned char *object_sha1);
+int remove_note(struct notes_tree *t, const unsigned char *object_sha1);
/*
* Get the note object SHA1 containing the note data for the given object
@@ -103,11 +138,18 @@ const unsigned char *get_note(struct notes_tree *t,
/*
* Copy a note from one object to another in the given notes_tree.
*
- * Fails if the to_obj already has a note unless 'force' is true.
+ * Returns 1 if the to_obj already has a note and 'force' is false. Otherwise,
+ * returns non-zero if 'force' is true, but the given combine_notes function
+ * failed to combine from_obj's note with to_obj's existing note.
+ * Returns zero on success.
+ *
+ * IMPORTANT: The changes made by copy_note() to the given notes_tree structure
+ * are not persistent until a subsequent call to write_notes_tree() returns
+ * zero.
*/
int copy_note(struct notes_tree *t,
const unsigned char *from_obj, const unsigned char *to_obj,
- int force, combine_notes_fn combine_fn);
+ int force, combine_notes_fn combine_notes);
/*
* Flags controlling behaviour of for_each_note()
@@ -149,6 +191,7 @@ int copy_note(struct notes_tree *t,
* notes tree) from within the callback:
* - add_note()
* - remove_note()
+ * - copy_note()
* - free_notes()
*/
typedef int each_note_fn(const unsigned char *object_sha1,
@@ -215,8 +258,8 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
struct string_list;
struct display_notes_opt {
- unsigned int suppress_default_notes:1;
- struct string_list *extra_notes_refs;
+ int use_default_notes;
+ struct string_list extra_notes_refs;
};
/*
@@ -266,4 +309,7 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
void string_list_add_refs_from_colon_sep(struct string_list *list,
const char *globs);
+/* Expand inplace a note ref like "foo" or "notes/foo" into "refs/notes/foo" */
+void expand_notes_ref(struct strbuf *sb);
+
#endif
diff --git a/object.c b/object.c
index 277b3dd..31976b5 100644
--- a/object.c
+++ b/object.c
@@ -188,8 +188,8 @@ struct object *parse_object(const unsigned char *sha1)
unsigned long size;
enum object_type type;
int eaten;
- const unsigned char *repl;
- void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+ const unsigned char *repl = lookup_replace_object(sha1);
+ void *buffer = read_sha1_file(sha1, &type, &size);
if (buffer) {
struct object *obj;
@@ -199,7 +199,7 @@ struct object *parse_object(const unsigned char *sha1)
return NULL;
}
- obj = parse_object_buffer(repl, type, size, buffer, &eaten);
+ obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
if (!eaten)
free(buffer);
return obj;
@@ -211,10 +211,10 @@ struct object_list *object_list_insert(struct object *item,
struct object_list **list_p)
{
struct object_list *new_list = xmalloc(sizeof(struct object_list));
- new_list->item = item;
- new_list->next = *list_p;
- *list_p = new_list;
- return new_list;
+ new_list->item = item;
+ new_list->next = *list_p;
+ *list_p = new_list;
+ return new_list;
}
int object_list_contains(struct object_list *list, struct object *obj)
diff --git a/object.h b/object.h
index 82877c8..b6618d9 100644
--- a/object.h
+++ b/object.h
@@ -6,11 +6,6 @@ struct object_list {
struct object_list *next;
};
-struct object_refs {
- unsigned count;
- struct object *ref[FLEX_ARRAY]; /* more */
-};
-
struct object_array {
unsigned int nr;
unsigned int alloc;
@@ -21,6 +16,8 @@ struct object_array {
} *objects;
};
+#define OBJECT_ARRAY_INIT { 0, 0, NULL }
+
#define TYPE_BITS 3
#define FLAG_BITS 27
diff --git a/pack-check.c b/pack-check.c
index 395fb95..0c19b6e 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -2,8 +2,7 @@
#include "pack.h"
#include "pack-revindex.h"
-struct idx_entry
-{
+struct idx_entry {
off_t offset;
const unsigned char *sha1;
unsigned int nr;
@@ -24,10 +23,10 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
off_t offset, off_t len, unsigned int nr)
{
const uint32_t *index_crc;
- uint32_t data_crc = crc32(0, Z_NULL, 0);
+ uint32_t data_crc = crc32(0, NULL, 0);
do {
- unsigned int avail;
+ unsigned long avail;
void *data = use_pack(p, w_curs, offset, &avail);
if (avail > len)
avail = len;
@@ -62,7 +61,7 @@ static int verify_packfile(struct packed_git *p,
git_SHA1_Init(&ctx);
do {
- unsigned int remaining;
+ unsigned long remaining;
unsigned char *in = use_pack(p, w_curs, offset, &remaining);
offset += remaining;
if (!pack_sig_ofs)
@@ -77,7 +76,7 @@ static int verify_packfile(struct packed_git *p,
err = error("%s SHA1 checksum mismatch",
p->pack_name);
if (hashcmp(index_base + index_size - 40, pack_sig))
- err = error("%s SHA1 does not match its inddex",
+ err = error("%s SHA1 does not match its index",
p->pack_name);
unuse_pack(w_curs);
diff --git a/pack-refs.c b/pack-refs.c
index 7f43f8a..1290570 100644
--- a/pack-refs.c
+++ b/pack-refs.c
@@ -60,6 +60,37 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
return 0;
}
+/*
+ * Remove empty parents, but spare refs/ and immediate subdirs.
+ * Note: munges *name.
+ */
+static void try_remove_empty_parents(char *name)
+{
+ char *p, *q;
+ int i;
+ p = name;
+ for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
+ while (*p && *p != '/')
+ p++;
+ /* tolerate duplicate slashes; see check_ref_format() */
+ while (*p == '/')
+ p++;
+ }
+ for (q = p; *q; q++)
+ ;
+ while (1) {
+ while (q > p && *q != '/')
+ q--;
+ while (q > p && *(q-1) == '/')
+ q--;
+ if (q == p)
+ break;
+ *q = '\0';
+ if (rmdir(git_path("%s", name)))
+ break;
+ }
+}
+
/* make sure nobody touched the ref, and unlink */
static void prune_ref(struct ref_to_prune *r)
{
@@ -68,6 +99,7 @@ static void prune_ref(struct ref_to_prune *r)
if (lock) {
unlink_or_warn(git_path("%s", r->name));
unlock_ref(lock);
+ try_remove_empty_parents(r->name);
}
}
diff --git a/pack-write.c b/pack-write.c
index a905ca4..9cd3bfb 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -2,8 +2,12 @@
#include "pack.h"
#include "csum-file.h"
-uint32_t pack_idx_default_version = 2;
-uint32_t pack_idx_off32_limit = 0x7fffffff;
+void reset_pack_idx_option(struct pack_idx_option *opts)
+{
+ memset(opts, 0, sizeof(*opts));
+ opts->version = 2;
+ opts->off32_limit = 0x7fffffff;
+}
static int sha1_compare(const void *_a, const void *_b)
{
@@ -12,13 +16,35 @@ static int sha1_compare(const void *_a, const void *_b)
return hashcmp(a->sha1, b->sha1);
}
+static int cmp_uint32(const void *a_, const void *b_)
+{
+ uint32_t a = *((uint32_t *)a_);
+ uint32_t b = *((uint32_t *)b_);
+
+ return (a < b) ? -1 : (a != b);
+}
+
+static int need_large_offset(off_t offset, const struct pack_idx_option *opts)
+{
+ uint32_t ofsval;
+
+ if ((offset >> 31) || (opts->off32_limit < offset))
+ return 1;
+ if (!opts->anomaly_nr)
+ return 0;
+ ofsval = offset;
+ return !!bsearch(&ofsval, opts->anomaly, opts->anomaly_nr,
+ sizeof(ofsval), cmp_uint32);
+}
+
/*
* On entry *sha1 contains the pack content SHA1 hash, on exit it is
* the SHA1 hash of sorted object names. The objects array passed in
* will be sorted by SHA1 on exit.
*/
const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
- int nr_objects, unsigned char *sha1)
+ int nr_objects, const struct pack_idx_option *opts,
+ unsigned char *sha1)
{
struct sha1file *f;
struct pack_idx_entry **sorted_by_sha, **list, **last;
@@ -42,20 +68,25 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
else
sorted_by_sha = list = last = NULL;
- if (!index_name) {
- static char tmpfile[PATH_MAX];
- fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
- index_name = xstrdup(tmpfile);
+ if (opts->flags & WRITE_IDX_VERIFY) {
+ assert(index_name);
+ f = sha1fd_check(index_name);
} else {
- unlink(index_name);
- fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ if (!index_name) {
+ static char tmpfile[PATH_MAX];
+ fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
+ index_name = xstrdup(tmpfile);
+ } else {
+ unlink(index_name);
+ fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ }
+ if (fd < 0)
+ die_errno("unable to create '%s'", index_name);
+ f = sha1fd(fd, index_name);
}
- if (fd < 0)
- die_errno("unable to create '%s'", index_name);
- f = sha1fd(fd, index_name);
/* if last object's offset is >= 2^31 we should use index V2 */
- index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version;
+ index_version = need_large_offset(last_obj_offset, opts) ? 2 : opts->version;
/* index versions 2 and above need a header */
if (index_version >= 2) {
@@ -115,8 +146,11 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
list = sorted_by_sha;
for (i = 0; i < nr_objects; i++) {
struct pack_idx_entry *obj = *list++;
- uint32_t offset = (obj->offset <= pack_idx_off32_limit) ?
- obj->offset : (0x80000000 | nr_large_offset++);
+ uint32_t offset;
+
+ offset = (need_large_offset(obj->offset, opts)
+ ? (0x80000000 | nr_large_offset++)
+ : obj->offset);
offset = htonl(offset);
sha1write(f, &offset, 4);
}
@@ -126,18 +160,20 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
while (nr_large_offset) {
struct pack_idx_entry *obj = *list++;
uint64_t offset = obj->offset;
- if (offset > pack_idx_off32_limit) {
- uint32_t split[2];
- split[0] = htonl(offset >> 32);
- split[1] = htonl(offset & 0xffffffff);
- sha1write(f, split, 8);
- nr_large_offset--;
- }
+ uint32_t split[2];
+
+ if (!need_large_offset(offset, opts))
+ continue;
+ split[0] = htonl(offset >> 32);
+ split[1] = htonl(offset & 0xffffffff);
+ sha1write(f, split, 8);
+ nr_large_offset--;
}
}
sha1write(f, sha1, 20);
- sha1close(f, NULL, CSUM_FSYNC);
+ sha1close(f, NULL, ((opts->flags & WRITE_IDX_VERIFY)
+ ? CSUM_CLOSE : CSUM_FSYNC));
git_SHA1_Final(sha1, &ctx);
return index_name;
}
diff --git a/pack.h b/pack.h
index bb27576..722a54e 100644
--- a/pack.h
+++ b/pack.h
@@ -34,9 +34,24 @@ struct pack_header {
*/
#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */
-/* These may be overridden by command-line parameters */
-extern uint32_t pack_idx_default_version;
-extern uint32_t pack_idx_off32_limit;
+struct pack_idx_option {
+ unsigned flags;
+ /* flag bits */
+#define WRITE_IDX_VERIFY 01
+
+ uint32_t version;
+ uint32_t off32_limit;
+
+ /*
+ * List of offsets that would fit within off32_limit but
+ * need to be written out as 64-bit entity for byte-for-byte
+ * verification.
+ */
+ int anomaly_alloc, anomaly_nr;
+ uint32_t *anomaly;
+};
+
+extern void reset_pack_idx_option(struct pack_idx_option *);
/*
* Packed object index header
@@ -55,7 +70,7 @@ struct pack_idx_entry {
off_t offset;
};
-extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1);
extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
extern int verify_pack_index(struct packed_git *);
extern int verify_pack(struct packed_git *);
diff --git a/parse-options.c b/parse-options.c
index 0fa79bc..879ea82 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -3,6 +3,7 @@
#include "cache.h"
#include "commit.h"
#include "color.h"
+#include "string-list.h"
static int parse_options_usage(struct parse_opt_ctx_t *ctx,
const char * const *usagestr,
@@ -11,6 +12,13 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx,
#define OPT_SHORT 1
#define OPT_UNSET 2
+static int optbug(const struct option *opt, const char *reason)
+{
+ if (opt->long_name)
+ return error("BUG: option '%s' %s", opt->long_name, reason);
+ return error("BUG: switch '%c' %s", opt->short_name, reason);
+}
+
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
@@ -55,25 +63,13 @@ static int get_value(struct parse_opt_ctx_t *p,
return opterror(opt, "takes no value", flags);
if (unset && (opt->flags & PARSE_OPT_NONEG))
return opterror(opt, "isn't available", flags);
-
- if (!(flags & OPT_SHORT) && p->opt) {
- switch (opt->type) {
- case OPTION_CALLBACK:
- if (!(opt->flags & PARSE_OPT_NOARG))
- break;
- /* FALLTHROUGH */
- case OPTION_BOOLEAN:
- case OPTION_BIT:
- case OPTION_NEGBIT:
- case OPTION_SET_INT:
- case OPTION_SET_PTR:
- return opterror(opt, "takes no value", flags);
- default:
- break;
- }
- }
+ if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
+ return opterror(opt, "takes no value", flags);
switch (opt->type) {
+ case OPTION_LOWLEVEL_CALLBACK:
+ return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+
case OPTION_BIT:
if (unset)
*(int *)opt->value &= ~opt->defval;
@@ -281,13 +277,6 @@ static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
for (; options->type != OPTION_END; options++) {
if (!(options->flags & PARSE_OPT_NODASH))
continue;
- if ((options->flags & PARSE_OPT_OPTARG) ||
- !(options->flags & PARSE_OPT_NOARG))
- die("BUG: dashless options don't support arguments");
- if (!(options->flags & PARSE_OPT_NONEG))
- die("BUG: dashless options don't support negation");
- if (options->long_name)
- die("BUG: dashless options can't be long");
if (options->short_name == arg[0] && arg[1] == '\0')
return get_value(p, options, OPT_SHORT);
}
@@ -320,25 +309,37 @@ static void parse_options_check(const struct option *opts)
for (; opts->type != OPTION_END; opts++) {
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
- (opts->flags & PARSE_OPT_OPTARG)) {
- if (opts->long_name) {
- error("`--%s` uses incompatible flags "
- "LASTARG_DEFAULT and OPTARG", opts->long_name);
- } else {
- error("`-%c` uses incompatible flags "
- "LASTARG_DEFAULT and OPTARG", opts->short_name);
- }
- err |= 1;
+ (opts->flags & PARSE_OPT_OPTARG))
+ err |= optbug(opts, "uses incompatible flags "
+ "LASTARG_DEFAULT and OPTARG");
+ if (opts->flags & PARSE_OPT_NODASH &&
+ ((opts->flags & PARSE_OPT_OPTARG) ||
+ !(opts->flags & PARSE_OPT_NOARG) ||
+ !(opts->flags & PARSE_OPT_NONEG) ||
+ opts->long_name))
+ err |= optbug(opts, "uses feature "
+ "not supported for dashless options");
+ switch (opts->type) {
+ case OPTION_BOOLEAN:
+ case OPTION_BIT:
+ case OPTION_NEGBIT:
+ case OPTION_SET_INT:
+ case OPTION_SET_PTR:
+ case OPTION_NUMBER:
+ if ((opts->flags & PARSE_OPT_OPTARG) ||
+ !(opts->flags & PARSE_OPT_NOARG))
+ err |= optbug(opts, "should not accept an argument");
+ default:
+ ; /* ok. (usually accepts an argument) */
}
}
-
if (err)
- exit(129);
+ exit(128);
}
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
- int flags)
+ const struct option *options, int flags)
{
memset(ctx, 0, sizeof(*ctx));
ctx->argc = argc - 1;
@@ -350,6 +351,7 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION))
die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+ parse_options_check(options);
}
static int usage_with_options_internal(struct parse_opt_ctx_t *,
@@ -362,8 +364,6 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
{
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
- parse_options_check(options);
-
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
@@ -374,7 +374,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
- break;
+ return PARSE_OPT_NON_OPTION;
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
@@ -452,10 +452,11 @@ int parse_options(int argc, const char **argv, const char *prefix,
{
struct parse_opt_ctx_t ctx;
- parse_options_start(&ctx, argc, argv, prefix, flags);
+ parse_options_start(&ctx, argc, argv, prefix, options, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
exit(129);
+ case PARSE_OPT_NON_OPTION:
case PARSE_OPT_DONE:
break;
default: /* PARSE_OPT_UNKNOWN */
@@ -541,7 +542,8 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
if (opts->type == OPTION_NUMBER)
pos += fprintf(outfile, "-NUM");
- if (!(opts->flags & PARSE_OPT_NOARG))
+ if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
+ !(opts->flags & PARSE_OPT_NOARG))
pos += usage_argh(opts, outfile);
if (pos <= USAGE_OPTS_WIDTH)
@@ -560,14 +562,14 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
return PARSE_OPT_HELP;
}
-void usage_with_options(const char * const *usagestr,
+void NORETURN usage_with_options(const char * const *usagestr,
const struct option *opts)
{
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
exit(129);
}
-void usage_msg_opt(const char *msg,
+void NORETURN usage_msg_opt(const char *msg,
const char * const *usagestr,
const struct option *options)
{
@@ -686,3 +688,19 @@ int parse_options_concat(struct option *dst, size_t dst_size, struct option *src
}
return -1;
}
+
+int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
+{
+ struct string_list *v = opt->value;
+
+ if (unset) {
+ string_list_clear(v, 0);
+ return 0;
+ }
+
+ if (!arg)
+ return -1;
+
+ string_list_append(v, xstrdup(arg));
+ return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index 7435cdb..05eb09b 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -17,6 +17,7 @@ enum parse_opt_type {
OPTION_STRING,
OPTION_INTEGER,
OPTION_CALLBACK,
+ OPTION_LOWLEVEL_CALLBACK,
OPTION_FILENAME
};
@@ -43,6 +44,10 @@ enum parse_opt_option_flags {
struct option;
typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
+struct parse_opt_ctx_t;
+typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
+ const struct option *opt, int unset);
+
/*
* `type`::
* holds the type of the option, you must have an OPTION_END last in your
@@ -69,7 +74,7 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
* `flags`::
* mask of parse_opt_option_flags.
* PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)
- * PARSE_OPT_NOARG: says that this option takes no argument
+ * PARSE_OPT_NOARG: says that this option does not take an argument
* PARSE_OPT_NONEG: says that this option cannot be negated
* PARSE_OPT_HIDDEN: this option is skipped in the default usage, and
* shown only in the full usage.
@@ -87,7 +92,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
* useful for users of OPTION_NEGBIT.
*
* `callback`::
- * pointer to the callback to use for OPTION_CALLBACK.
+ * pointer to the callback to use for OPTION_CALLBACK or
+ * OPTION_LOWLEVEL_CALLBACK.
*
* `defval`::
* default value to fill (*->value) with for PARSE_OPT_OPTARG.
@@ -124,6 +130,9 @@ struct option {
(h), PARSE_OPT_NOARG, NULL, (p) }
#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) }
#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) }
+#define OPT_STRING_LIST(s, l, v, a, h) \
+ { OPTION_CALLBACK, (s), (l), (v), (a), \
+ (h), 0, &parse_opt_string_list }
#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \
(h), PARSE_OPT_NOARG, &parse_opt_tertiary }
#define OPT_DATE(s, l, v, h) \
@@ -135,7 +144,7 @@ struct option {
{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \
- "FILE", (h) }
+ "file", (h) }
#define OPT_COLOR_FLAG(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
parse_opt_color_flag_cb, (intptr_t)"always" }
@@ -161,6 +170,7 @@ extern NORETURN void usage_msg_opt(const char *msg,
enum {
PARSE_OPT_HELP = -1,
PARSE_OPT_DONE,
+ PARSE_OPT_NON_OPTION,
PARSE_OPT_UNKNOWN
};
@@ -180,7 +190,7 @@ struct parse_opt_ctx_t {
extern void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
- int flags);
+ const struct option *options, int flags);
extern int parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
@@ -197,15 +207,17 @@ extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
extern int parse_opt_tertiary(const struct option *, const char *, int);
+extern int parse_opt_string_list(const struct option *, const char *, int);
-#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
-#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
+#define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h))
+#define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h))
#define OPT__VERBOSITY(var) \
{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
-#define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run")
+#define OPT__DRY_RUN(var, h) OPT_BOOLEAN('n', "dry-run", (var), (h))
+#define OPT__FORCE(var, h) OPT_BOOLEAN('f', "force", (var), (h))
#define OPT__ABBREV(var) \
{ OPTION_CALLBACK, 0, "abbrev", (var), "n", \
"use <n> digits to display SHA-1s", \
diff --git a/patch-delta.c b/patch-delta.c
index d218faa..56e0a5e 100644
--- a/patch-delta.c
+++ b/patch-delta.c
@@ -48,7 +48,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
if (cmd & 0x20) cp_size |= (*data++ << 8);
if (cmd & 0x40) cp_size |= (*data++ << 16);
if (cp_size == 0) cp_size = 0x10000;
- if (cp_off + cp_size < cp_size ||
+ if (unsigned_add_overflows(cp_off, cp_size) ||
cp_off + cp_size > src_size ||
cp_size > size)
break;
diff --git a/path.c b/path.c
index b4c8d91..4d73cc9 100644
--- a/path.c
+++ b/path.c
@@ -122,118 +122,43 @@ char *git_path(const char *fmt, ...)
return cleanup_path(pathname);
}
-
-/* git_mkstemp() - create tmp file honoring TMPDIR variable */
-int git_mkstemp(char *path, size_t len, const char *template)
-{
- const char *tmp;
- size_t n;
-
- tmp = getenv("TMPDIR");
- if (!tmp)
- tmp = "/tmp";
- n = snprintf(path, len, "%s/%s", tmp, template);
- if (len <= n) {
- errno = ENAMETOOLONG;
- return -1;
- }
- return mkstemp(path);
-}
-
-/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
-int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
-{
- const char *tmp;
- size_t n;
-
- tmp = getenv("TMPDIR");
- if (!tmp)
- tmp = "/tmp";
- n = snprintf(path, len, "%s/%s", tmp, template);
- if (len <= n) {
- errno = ENAMETOOLONG;
- return -1;
- }
- return mkstemps(path, suffix_len);
-}
-
-/* Adapted from libiberty's mkstemp.c. */
-
-#undef TMP_MAX
-#define TMP_MAX 16384
-
-int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
+char *git_path_submodule(const char *path, const char *fmt, ...)
{
- static const char letters[] =
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789";
- static const int num_letters = 62;
- uint64_t value;
- struct timeval tv;
- char *template;
- size_t len;
- int fd, count;
+ char *pathname = get_pathname();
+ struct strbuf buf = STRBUF_INIT;
+ const char *git_dir;
+ va_list args;
+ unsigned len;
- len = strlen(pattern);
+ len = strlen(path);
+ if (len > PATH_MAX-100)
+ return bad_path;
- if (len < 6 + suffix_len) {
- errno = EINVAL;
- return -1;
- }
+ strbuf_addstr(&buf, path);
+ if (len && path[len-1] != '/')
+ strbuf_addch(&buf, '/');
+ strbuf_addstr(&buf, ".git");
- if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
- errno = EINVAL;
- return -1;
+ git_dir = read_gitfile_gently(buf.buf);
+ if (git_dir) {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, git_dir);
}
+ strbuf_addch(&buf, '/');
- /*
- * Replace pattern's XXXXXX characters with randomness.
- * Try TMP_MAX different filenames.
- */
- gettimeofday(&tv, NULL);
- value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
- template = &pattern[len - 6 - suffix_len];
- for (count = 0; count < TMP_MAX; ++count) {
- uint64_t v = value;
- /* Fill in the random bits. */
- template[0] = letters[v % num_letters]; v /= num_letters;
- template[1] = letters[v % num_letters]; v /= num_letters;
- template[2] = letters[v % num_letters]; v /= num_letters;
- template[3] = letters[v % num_letters]; v /= num_letters;
- template[4] = letters[v % num_letters]; v /= num_letters;
- template[5] = letters[v % num_letters]; v /= num_letters;
-
- fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
- if (fd > 0)
- return fd;
- /*
- * Fatal error (EPERM, ENOSPC etc).
- * It doesn't make sense to loop.
- */
- if (errno != EEXIST)
- break;
- /*
- * This is a random value. It is only necessary that
- * the next TMP_MAX values generated by adding 7777 to
- * VALUE are different with (module 2^32).
- */
- value += 7777;
- }
- /* We return the null string if we can't find a unique file name. */
- pattern[0] = '\0';
- return -1;
-}
+ if (buf.len >= PATH_MAX)
+ return bad_path;
+ memcpy(pathname, buf.buf, buf.len + 1);
-int git_mkstemp_mode(char *pattern, int mode)
-{
- /* mkstemp is just mkstemps with no suffix */
- return git_mkstemps_mode(pattern, 0, mode);
-}
+ strbuf_release(&buf);
+ len = strlen(pathname);
-int gitmkstemps(char *pattern, int suffix_len)
-{
- return git_mkstemps_mode(pattern, suffix_len, 0600);
+ va_start(args, fmt);
+ len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
}
int validate_headref(const char *path)
@@ -316,6 +241,8 @@ char *expand_user_path(const char *path)
size_t username_len = first_slash - username;
if (username_len == 0) {
const char *home = getenv("HOME");
+ if (!home)
+ goto return_null;
strbuf_add(&user_path, home, strlen(home));
} else {
struct passwd *pw = getpw_str(username, username_len);
@@ -470,7 +397,7 @@ int set_shared_perm(const char *path, int mode)
return 0;
}
-const char *make_relative_path(const char *abs, const char *base)
+const char *relative_path(const char *abs, const char *base)
{
static char buf[PATH_MAX + 1];
int i = 0, j = 0;
diff --git a/perl/Git.pm b/perl/Git.pm
index 6cb0dd1..a86ab70 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -7,6 +7,7 @@ Git - Perl interface to the Git version control system
package Git;
+use 5.008;
use strict;
@@ -98,7 +99,7 @@ increase notwithstanding).
use Carp qw(carp croak); # but croak is bad - throw instead
use Error qw(:try);
-use Cwd qw(abs_path);
+use Cwd qw(abs_path cwd);
use IPC::Open2 qw(open2);
use Fcntl qw(SEEK_SET SEEK_CUR);
}
@@ -395,7 +396,16 @@ See C<command_close_bidi_pipe()> for details.
sub command_bidi_pipe {
my ($pid, $in, $out);
+ my ($self) = _maybe_self(@_);
+ local %ENV = %ENV;
+ my $cwd_save = undef;
+ if ($self) {
+ shift;
+ $cwd_save = cwd();
+ _setup_git_cmd_env($self);
+ }
$pid = open2($in, $out, 'git', @_);
+ chdir($cwd_save) if $cwd_save;
return ($pid, $in, $out, join(' ', @_));
}
@@ -842,7 +852,7 @@ sub _open_hash_and_insert_object_if_needed {
($self->{hash_object_pid}, $self->{hash_object_in},
$self->{hash_object_out}, $self->{hash_object_ctx}) =
- command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
+ $self->command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
}
sub _close_hash_and_insert_object {
@@ -931,7 +941,7 @@ sub _open_cat_blob_if_needed {
($self->{cat_blob_pid}, $self->{cat_blob_in},
$self->{cat_blob_out}, $self->{cat_blob_ctx}) =
- command_bidi_pipe(qw(cat-file --batch));
+ $self->command_bidi_pipe(qw(cat-file --batch));
}
sub _close_cat_blob {
@@ -1278,6 +1288,14 @@ sub _command_common_pipe {
# for the given repository and execute the git command.
sub _cmd_exec {
my ($self, @args) = @_;
+ _setup_git_cmd_env($self);
+ _execv_git_cmd(@args);
+ die qq[exec "@args" failed: $!];
+}
+
+# set up the appropriate state for git command
+sub _setup_git_cmd_env {
+ my $self = shift;
if ($self) {
$self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
$self->repo_path() and $self->wc_path()
@@ -1285,8 +1303,6 @@ sub _cmd_exec {
$self->wc_path() and chdir($self->wc_path());
$self->wc_subdir() and chdir($self->wc_subdir());
}
- _execv_git_cmd(@args);
- die qq[exec "@args" failed: $!];
}
# Execute the given Git command ($_[0]) with arguments ($_[1..])
diff --git a/perl/Makefile b/perl/Makefile
index 4ab21d6..a2ffb64 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -38,7 +38,7 @@ $(makfile): ../GIT-CFLAGS Makefile
echo ' echo $(instdir_SQ)' >> $@
else
$(makfile): Makefile.PL ../GIT-CFLAGS
- $(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+ $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
endif
# this is just added comfort for calling make directly in perl dir
diff --git a/pkt-line.c b/pkt-line.c
index 295ba2b..5a04984 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -1,6 +1,51 @@
#include "cache.h"
#include "pkt-line.h"
+static const char *packet_trace_prefix = "git";
+static const char trace_key[] = "GIT_TRACE_PACKET";
+
+void packet_trace_identity(const char *prog)
+{
+ packet_trace_prefix = xstrdup(prog);
+}
+
+static void packet_trace(const char *buf, unsigned int len, int write)
+{
+ int i;
+ struct strbuf out;
+
+ if (!trace_want(trace_key))
+ return;
+
+ /* +32 is just a guess for header + quoting */
+ strbuf_init(&out, len+32);
+
+ strbuf_addf(&out, "packet: %12s%c ",
+ packet_trace_prefix, write ? '>' : '<');
+
+ if ((len >= 4 && !prefixcmp(buf, "PACK")) ||
+ (len >= 5 && !prefixcmp(buf+1, "PACK"))) {
+ strbuf_addstr(&out, "PACK ...");
+ unsetenv(trace_key);
+ }
+ else {
+ /* XXX we should really handle printable utf8 */
+ for (i = 0; i < len; i++) {
+ /* suppress newlines */
+ if (buf[i] == '\n')
+ continue;
+ if (buf[i] >= 0x20 && buf[i] <= 0x7e)
+ strbuf_addch(&out, buf[i]);
+ else
+ strbuf_addf(&out, "\\%o", buf[i]);
+ }
+ }
+
+ strbuf_addch(&out, '\n');
+ trace_strbuf(trace_key, &out);
+ strbuf_release(&out);
+}
+
/*
* Write a packetized stream, where each line is preceded by
* its length (including the header) as a 4-byte hex number.
@@ -39,11 +84,13 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n)
*/
void packet_flush(int fd)
{
+ packet_trace("0000", 4, 1);
safe_write(fd, "0000", 4);
}
void packet_buf_flush(struct strbuf *buf)
{
+ packet_trace("0000", 4, 1);
strbuf_add(buf, "0000", 4);
}
@@ -62,6 +109,7 @@ static unsigned format_packet(const char *fmt, va_list args)
buffer[1] = hex(n >> 8);
buffer[2] = hex(n >> 4);
buffer[3] = hex(n);
+ packet_trace(buffer+4, n-4, 1);
return n;
}
@@ -130,13 +178,16 @@ int packet_read_line(int fd, char *buffer, unsigned size)
len = packet_length(linelen);
if (len < 0)
die("protocol error: bad line length character: %.4s", linelen);
- if (!len)
+ if (!len) {
+ packet_trace("0000", 4, 0);
return 0;
+ }
len -= 4;
if (len >= size)
die("protocol error: bad line length %d", len);
safe_read(fd, buffer, len);
buffer[len] = 0;
+ packet_trace(buffer, len, 0);
return len;
}
@@ -153,6 +204,7 @@ int packet_get_line(struct strbuf *out,
if (!len) {
*src_buf += 4;
*src_len -= 4;
+ packet_trace("0000", 4, 0);
return 0;
}
if (*src_len < len)
@@ -165,5 +217,6 @@ int packet_get_line(struct strbuf *out,
strbuf_add(out, *src_buf, len);
*src_buf += len;
*src_len -= len;
+ packet_trace(out->buf, out->len, 0);
return len;
}
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..a242a86
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+/git.pot
diff --git a/preload-index.c b/preload-index.c
index e3d0bda..49cb08d 100644
--- a/preload-index.c
+++ b/preload-index.c
@@ -35,7 +35,9 @@ static void *preload_thread(void *_data)
struct index_state *index = p->index;
struct cache_entry **cep = index->cache + p->offset;
struct cache_def cache;
+ struct pathspec pathspec;
+ init_pathspec(&pathspec, p->pathspec);
memset(&cache, 0, sizeof(cache));
nr = p->nr;
if (nr + p->offset > index->cache_nr)
@@ -51,7 +53,7 @@ static void *preload_thread(void *_data)
continue;
if (ce_uptodate(ce))
continue;
- if (!ce_path_match(ce, p->pathspec))
+ if (!ce_path_match(ce, &pathspec))
continue;
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
continue;
@@ -61,6 +63,7 @@ static void *preload_thread(void *_data)
continue;
ce_mark_uptodate(ce);
} while (--nr > 0);
+ free_pathspec(&pathspec);
return NULL;
}
diff --git a/pretty.c b/pretty.c
index f85444b..f45eb54 100644
--- a/pretty.c
+++ b/pretty.c
@@ -208,6 +208,58 @@ int has_non_ascii(const char *s)
return 0;
}
+static int is_rfc822_special(char ch)
+{
+ switch (ch) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '[':
+ case ']':
+ case ':':
+ case ';':
+ case '@':
+ case ',':
+ case '.':
+ case '"':
+ case '\\':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int has_rfc822_specials(const char *s, int len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ if (is_rfc822_special(s[i]))
+ return 1;
+ return 0;
+}
+
+static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
+{
+ int i;
+
+ /* just a guess, we may have to also backslash-quote */
+ strbuf_grow(out, len + 2);
+
+ strbuf_addch(out, '"');
+ for (i = 0; i < len; i++) {
+ switch (s[i]) {
+ case '"':
+ case '\\':
+ strbuf_addch(out, '\\');
+ /* fall through */
+ default:
+ strbuf_addch(out, s[i]);
+ }
+ }
+ strbuf_addch(out, '"');
+}
+
static int is_rfc2047_special(char ch)
{
return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
@@ -216,49 +268,66 @@ static int is_rfc2047_special(char ch)
static void add_rfc2047(struct strbuf *sb, const char *line, int len,
const char *encoding)
{
- int i, last;
+ static const int max_length = 78; /* per rfc2822 */
+ int i;
+ int line_len;
+
+ /* How many bytes are already used on the current line? */
+ for (i = sb->len - 1; i >= 0; i--)
+ if (sb->buf[i] == '\n')
+ break;
+ line_len = sb->len - (i+1);
for (i = 0; i < len; i++) {
int ch = line[i];
- if (non_ascii(ch))
+ if (non_ascii(ch) || ch == '\n')
goto needquote;
if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
goto needquote;
}
- strbuf_add(sb, line, len);
+ strbuf_add_wrapped_bytes(sb, line, len, 0, 1, max_length - line_len);
return;
needquote:
strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
strbuf_addf(sb, "=?%s?q?", encoding);
- for (i = last = 0; i < len; i++) {
+ line_len += strlen(encoding) + 5; /* 5 for =??q? */
+ for (i = 0; i < len; i++) {
unsigned ch = line[i] & 0xFF;
+
+ if (line_len >= max_length - 2) {
+ strbuf_addf(sb, "?=\n =?%s?q?", encoding);
+ line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
+ }
+
/*
* We encode ' ' using '=20' even though rfc2047
* allows using '_' for readability. Unfortunately,
* many programs do not understand this and just
* leave the underscore in place.
*/
- if (is_rfc2047_special(ch) || ch == ' ') {
- strbuf_add(sb, line + last, i - last);
+ if (is_rfc2047_special(ch) || ch == ' ' || ch == '\n') {
strbuf_addf(sb, "=%02X", ch);
- last = i + 1;
+ line_len += 3;
+ }
+ else {
+ strbuf_addch(sb, ch);
+ line_len++;
}
}
- strbuf_add(sb, line + last, len - last);
strbuf_addstr(sb, "?=");
}
-void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
- const char *line, enum date_mode dmode,
- const char *encoding)
+void pp_user_info(const struct pretty_print_context *pp,
+ const char *what, struct strbuf *sb,
+ const char *line, const char *encoding)
{
char *date;
int namelen;
unsigned long time;
int tz;
- if (fmt == CMIT_FMT_ONELINE)
+ if (pp->fmt == CMIT_FMT_ONELINE)
return;
date = strchr(line, '>');
if (!date)
@@ -267,32 +336,48 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
time = strtoul(date, &date, 10);
tz = strtol(date, NULL, 10);
- if (fmt == CMIT_FMT_EMAIL) {
+ if (pp->fmt == CMIT_FMT_EMAIL) {
char *name_tail = strchr(line, '<');
int display_name_length;
+ int final_line;
if (!name_tail)
return;
while (line < name_tail && isspace(name_tail[-1]))
name_tail--;
display_name_length = name_tail - line;
strbuf_addstr(sb, "From: ");
- add_rfc2047(sb, line, display_name_length, encoding);
+ if (!has_rfc822_specials(line, display_name_length)) {
+ add_rfc2047(sb, line, display_name_length, encoding);
+ } else {
+ struct strbuf quoted = STRBUF_INIT;
+ add_rfc822_quoted(&quoted, line, display_name_length);
+ add_rfc2047(sb, quoted.buf, quoted.len, encoding);
+ strbuf_release(&quoted);
+ }
+ for (final_line = 0; final_line < sb->len; final_line++)
+ if (sb->buf[sb->len - final_line - 1] == '\n')
+ break;
+ if (namelen - display_name_length + final_line > 78) {
+ strbuf_addch(sb, '\n');
+ if (!isspace(name_tail[0]))
+ strbuf_addch(sb, ' ');
+ }
strbuf_add(sb, name_tail, namelen - display_name_length);
strbuf_addch(sb, '\n');
} else {
strbuf_addf(sb, "%s: %.*s%.*s\n", what,
- (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+ (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
" ", namelen, line);
}
- switch (fmt) {
+ switch (pp->fmt) {
case CMIT_FMT_MEDIUM:
- strbuf_addf(sb, "Date: %s\n", show_date(time, tz, dmode));
+ strbuf_addf(sb, "Date: %s\n", show_date(time, tz, pp->date_mode));
break;
case CMIT_FMT_EMAIL:
strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
break;
case CMIT_FMT_FULLER:
- strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
+ strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode));
break;
default:
/* notin' */
@@ -323,12 +408,12 @@ static const char *skip_empty_lines(const char *msg)
return msg;
}
-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
- const struct commit *commit, int abbrev)
+static void add_merge_info(const struct pretty_print_context *pp,
+ struct strbuf *sb, const struct commit *commit)
{
struct commit_list *parent = commit->parents;
- if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+ if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
!parent || !parent->next)
return;
@@ -337,8 +422,8 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
while (parent) {
struct commit *p = parent->item;
const char *hex = NULL;
- if (abbrev)
- hex = find_unique_abbrev(p->object.sha1, abbrev);
+ if (pp->abbrev)
+ hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
if (!hex)
hex = sha1_to_hex(p->object.sha1);
parent = parent->next;
@@ -403,8 +488,8 @@ static char *replace_encoding_header(char *buf, const char *encoding)
return strbuf_detach(&tmp, NULL);
}
-static char *logmsg_reencode(const struct commit *commit,
- const char *output_encoding)
+char *logmsg_reencode(const struct commit *commit,
+ const char *output_encoding)
{
static const char *utf8 = "UTF-8";
const char *use_encoding;
@@ -555,6 +640,7 @@ struct format_commit_context {
const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
+ char *message;
size_t width, indent1, indent2;
/* These offsets are relative to the start of the commit message. */
@@ -591,7 +677,7 @@ static int add_again(struct strbuf *sb, struct chunk *chunk)
static void parse_commit_header(struct format_commit_context *context)
{
- const char *msg = context->commit->buffer;
+ const char *msg = context->message;
int i;
for (i = 0; msg[i]; i++) {
@@ -677,8 +763,8 @@ const char *format_subject(struct strbuf *sb, const char *msg,
static void parse_commit_message(struct format_commit_context *c)
{
- const char *msg = c->commit->buffer + c->message_off;
- const char *start = c->commit->buffer;
+ const char *msg = c->message + c->message_off;
+ const char *start = c->message;
msg = skip_empty_lines(msg);
c->subject_off = msg - start;
@@ -741,7 +827,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
{
struct format_commit_context *c = context;
const struct commit *commit = c->commit;
- const char *msg = commit->buffer;
+ const char *msg = c->message;
struct commit_list *p;
int h1, h2;
@@ -858,11 +944,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
c->abbrev_parent_hashes.off;
return 1;
case 'm': /* left/right/bottom */
- strbuf_addch(sb, (commit->object.flags & BOUNDARY)
- ? '-'
- : (commit->object.flags & SYMMETRIC_LEFT)
- ? '<'
- : '>');
+ strbuf_addstr(sb, get_revision_mark(NULL, commit));
return 1;
case 'd':
format_decoration(sb, commit);
@@ -886,8 +968,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
case 'N':
if (c->pretty_ctx->show_notes) {
format_display_notes(commit->object.sha1, sb,
- git_log_output_encoding ? git_log_output_encoding
- : git_commit_encoding, 0);
+ get_log_output_encoding(), 0);
return 1;
}
return 0;
@@ -1003,7 +1084,7 @@ void userformat_find_requirements(const char *fmt, struct userformat_want *w)
return;
fmt = user_format;
}
- strbuf_expand(&dummy, user_format, userformat_want_item, w);
+ strbuf_expand(&dummy, fmt, userformat_want_item, w);
strbuf_release(&dummy);
}
@@ -1012,18 +1093,30 @@ void format_commit_message(const struct commit *commit,
const struct pretty_print_context *pretty_ctx)
{
struct format_commit_context context;
+ static const char utf8[] = "UTF-8";
+ const char *enc;
+ const char *output_enc = pretty_ctx->output_encoding;
memset(&context, 0, sizeof(context));
context.commit = commit;
context.pretty_ctx = pretty_ctx;
context.wrap_start = sb->len;
+ context.message = commit->buffer;
+ if (output_enc) {
+ enc = get_header(commit, "encoding");
+ enc = enc ? enc : utf8;
+ if (strcmp(enc, output_enc))
+ context.message = logmsg_reencode(commit, output_enc);
+ }
+
strbuf_expand(sb, format, format_commit_item, &context);
rewrap_message_tail(sb, &context, 0, 0, 0);
+
+ if (context.message != commit->buffer)
+ free(context.message);
}
-static void pp_header(enum cmit_fmt fmt,
- int abbrev,
- enum date_mode dmode,
+static void pp_header(const struct pretty_print_context *pp,
const char *encoding,
const struct commit *commit,
const char **msg_p,
@@ -1043,7 +1136,7 @@ static void pp_header(enum cmit_fmt fmt,
/* End of header */
return;
- if (fmt == CMIT_FMT_RAW) {
+ if (pp->fmt == CMIT_FMT_RAW) {
strbuf_add(sb, line, linelen);
continue;
}
@@ -1063,7 +1156,7 @@ static void pp_header(enum cmit_fmt fmt,
;
/* with enough slop */
strbuf_grow(sb, num * 50 + 20);
- add_merge_info(fmt, sb, commit, abbrev);
+ add_merge_info(pp, sb, commit);
parents_shown = 1;
}
@@ -1074,33 +1167,31 @@ static void pp_header(enum cmit_fmt fmt,
*/
if (!memcmp(line, "author ", 7)) {
strbuf_grow(sb, linelen + 80);
- pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+ pp_user_info(pp, "Author", sb, line + 7, encoding);
}
if (!memcmp(line, "committer ", 10) &&
- (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+ (pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
strbuf_grow(sb, linelen + 80);
- pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+ pp_user_info(pp, "Commit", sb, line + 10, encoding);
}
}
}
-void pp_title_line(enum cmit_fmt fmt,
+void pp_title_line(const struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
- const char *subject,
- const char *after_subject,
const char *encoding,
int need_8bit_cte)
{
- const char *line_separator = (fmt == CMIT_FMT_EMAIL) ? "\n " : " ";
struct strbuf title;
strbuf_init(&title, 80);
- *msg_p = format_subject(&title, *msg_p, line_separator);
+ *msg_p = format_subject(&title, *msg_p,
+ pp->preserve_subject ? "\n" : " ");
strbuf_grow(sb, title.len + 1024);
- if (subject) {
- strbuf_addstr(sb, subject);
+ if (pp->subject) {
+ strbuf_addstr(sb, pp->subject);
add_rfc2047(sb, title.buf, title.len, encoding);
} else {
strbuf_addbuf(sb, &title);
@@ -1114,16 +1205,16 @@ void pp_title_line(enum cmit_fmt fmt,
"Content-Transfer-Encoding: 8bit\n";
strbuf_addf(sb, header_fmt, encoding);
}
- if (after_subject) {
- strbuf_addstr(sb, after_subject);
+ if (pp->after_subject) {
+ strbuf_addstr(sb, pp->after_subject);
}
- if (fmt == CMIT_FMT_EMAIL) {
+ if (pp->fmt == CMIT_FMT_EMAIL) {
strbuf_addch(sb, '\n');
}
strbuf_release(&title);
}
-void pp_remainder(enum cmit_fmt fmt,
+void pp_remainder(const struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
int indent)
@@ -1140,7 +1231,7 @@ void pp_remainder(enum cmit_fmt fmt,
if (is_empty_line(line, &linelen)) {
if (first)
continue;
- if (fmt == CMIT_FMT_SHORT)
+ if (pp->fmt == CMIT_FMT_SHORT)
break;
}
first = 0;
@@ -1159,29 +1250,25 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding
{
const char *encoding;
- encoding = (git_log_output_encoding
- ? git_log_output_encoding
- : git_commit_encoding);
- if (!encoding)
- encoding = "UTF-8";
+ encoding = get_log_output_encoding();
if (encoding_p)
*encoding_p = encoding;
return logmsg_reencode(commit, encoding);
}
-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb,
- const struct pretty_print_context *context)
+void pretty_print_commit(const struct pretty_print_context *pp,
+ const struct commit *commit,
+ struct strbuf *sb)
{
unsigned long beginning_of_body;
int indent = 4;
const char *msg = commit->buffer;
char *reencoded;
const char *encoding;
- int need_8bit_cte = context->need_8bit_cte;
+ int need_8bit_cte = pp->need_8bit_cte;
- if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb, context);
+ if (pp->fmt == CMIT_FMT_USERFORMAT) {
+ format_commit_message(commit, user_format, sb, pp);
return;
}
@@ -1190,14 +1277,14 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
msg = reencoded;
}
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+ if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
indent = 0;
/*
* We need to check and emit Content-type: to mark it
* as 8-bit if we haven't done so.
*/
- if (fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
+ if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
int i, ch, in_body;
for (in_body = i = 0; (ch = msg[i]); i++) {
@@ -1216,9 +1303,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
}
}
- pp_header(fmt, context->abbrev, context->date_mode, encoding,
- commit, &msg, sb);
- if (fmt != CMIT_FMT_ONELINE && !context->subject) {
+ pp_header(pp, encoding, commit, &msg, sb);
+ if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
strbuf_addch(sb, '\n');
}
@@ -1226,17 +1312,16 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
msg = skip_empty_lines(msg);
/* These formats treat the title line specially. */
- if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- pp_title_line(fmt, &msg, sb, context->subject,
- context->after_subject, encoding, need_8bit_cte);
+ if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+ pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
beginning_of_body = sb->len;
- if (fmt != CMIT_FMT_ONELINE)
- pp_remainder(fmt, &msg, sb, indent);
+ if (pp->fmt != CMIT_FMT_ONELINE)
+ pp_remainder(pp, &msg, sb, indent);
strbuf_rtrim(sb);
/* Make sure there is an EOLN for the non-oneline case */
- if (fmt != CMIT_FMT_ONELINE)
+ if (pp->fmt != CMIT_FMT_ONELINE)
strbuf_addch(sb, '\n');
/*
@@ -1244,12 +1329,20 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
* format. Make sure we did not strip the blank line
* between the header and the body.
*/
- if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
+ if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
strbuf_addch(sb, '\n');
- if (context->show_notes)
+ if (pp->show_notes)
format_display_notes(commit->object.sha1, sb, encoding,
NOTES_SHOW_HEADER | NOTES_INDENT);
free(reencoded);
}
+
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+ struct strbuf *sb)
+{
+ struct pretty_print_context pp = {0};
+ pp.fmt = fmt;
+ pretty_print_commit(&pp, commit, sb);
+}
diff --git a/quote.h b/quote.h
index 38003bf..024e21d 100644
--- a/quote.h
+++ b/quote.h
@@ -1,8 +1,7 @@
#ifndef QUOTE_H
#define QUOTE_H
-#include <stddef.h>
-#include <stdio.h>
+struct strbuf;
/* Help to copy the thing properly quoted for the shell safety.
* any single quote is replaced with '\'', any exclamation point
diff --git a/reachable.c b/reachable.c
index b515fa2..3fc6b1d 100644
--- a/reachable.c
+++ b/reachable.c
@@ -70,16 +70,11 @@ static void process_tree(struct tree *tree,
static void process_tag(struct tag *tag, struct object_array *p, const char *name)
{
struct object *obj = &tag->object;
- struct name_path me;
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
- me.up = NULL;
- me.elem = "tag:/";
- me.elem_len = 5;
-
if (parse_tag(tag) < 0)
die("bad tag object %s", sha1_to_hex(obj->sha1));
if (tag->tagged)
@@ -90,7 +85,7 @@ static void walk_commit_list(struct rev_info *revs)
{
int i;
struct commit *commit;
- struct object_array objects = { 0, 0, NULL };
+ struct object_array objects = OBJECT_ARRAY_INIT;
/* Walk all commits, process their trees */
while ((commit = get_revision(revs)) != NULL)
diff --git a/read-cache.c b/read-cache.c
index f1f789b..46a9e60 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -92,7 +92,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st)
if (fd >= 0) {
unsigned char sha1[20];
- if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name))
+ if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0))
match = hashcmp(sha1, ce->sha1);
/* index_fd() closed the file descriptor already */
}
@@ -608,6 +608,29 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
ce->ce_mode = ce_mode_from_stat(ent, st_mode);
}
+ /* When core.ignorecase=true, determine if a directory of the same name but differing
+ * case already exists within the Git repository. If it does, ensure the directory
+ * case of the file being added to the repository matches (is folded into) the existing
+ * entry's directory case.
+ */
+ if (ignore_case) {
+ const char *startPtr = ce->name;
+ const char *ptr = startPtr;
+ while (*ptr) {
+ while (*ptr && *ptr != '/')
+ ++ptr;
+ if (*ptr == '/') {
+ struct cache_entry *foundce;
+ ++ptr;
+ foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
+ if (foundce) {
+ memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
+ startPtr = ptr;
+ }
+ }
+ }
+ }
+
alias = index_name_exists(istate, ce->name, ce_namelen(ce), ignore_case);
if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
/* Nothing changed, really */
@@ -618,7 +641,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
return 0;
}
if (!intent_only) {
- if (index_path(ce->sha1, path, st, 1))
+ if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT))
return error("unable to index file %s", path);
} else
record_intent_to_add(ce);
@@ -683,30 +706,9 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
}
-int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec)
{
- const char *match, *name;
- int len;
-
- if (!pathspec)
- return 1;
-
- len = ce_namelen(ce);
- name = ce->name;
- while ((match = *pathspec++) != NULL) {
- int matchlen = strlen(match);
- if (matchlen > len)
- continue;
- if (memcmp(name, match, matchlen))
- continue;
- if (matchlen && name[matchlen-1] == '/')
- return 1;
- if (name[matchlen] == '/' || !name[matchlen])
- return 1;
- if (!matchlen)
- return 1;
- }
- return 0;
+ return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL);
}
/*
@@ -724,11 +726,12 @@ static int verify_dotfile(const char *rest)
* has already been discarded, we now test
* the rest.
*/
- switch (*rest) {
+
/* "." is not allowed */
- case '\0': case '/':
+ if (*rest == '\0' || is_dir_sep(*rest))
return 0;
+ switch (*rest) {
/*
* ".git" followed by NUL or slash is bad. This
* shares the path end test with the ".." case.
@@ -741,7 +744,7 @@ static int verify_dotfile(const char *rest)
rest += 2;
/* fallthrough */
case '.':
- if (rest[1] == '\0' || rest[1] == '/')
+ if (rest[1] == '\0' || is_dir_sep(rest[1]))
return 0;
}
return 1;
@@ -751,23 +754,19 @@ int verify_path(const char *path)
{
char c;
+ if (has_dos_drive_prefix(path))
+ return 0;
+
goto inside;
for (;;) {
if (!c)
return 1;
- if (c == '/') {
+ if (is_dir_sep(c)) {
inside:
c = *path++;
- switch (c) {
- default:
- continue;
- case '/': case '\0':
- break;
- case '.':
- if (verify_dotfile(path))
- continue;
- }
- return 0;
+ if ((c == '.' && !verify_dotfile(path)) ||
+ is_dir_sep(c) || c == '\0')
+ return 0;
}
c = *path++;
}
@@ -1081,7 +1080,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
}
static void show_file(const char * fmt, const char * name, int in_porcelain,
- int * first, char *header_msg)
+ int * first, const char *header_msg)
{
if (in_porcelain && *first && header_msg) {
printf("%s\n", header_msg);
@@ -1091,7 +1090,7 @@ static void show_file(const char * fmt, const char * name, int in_porcelain,
}
int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
- char *seen, char *header_msg)
+ char *seen, const char *header_msg)
{
int i;
int has_errors = 0;
@@ -1516,6 +1515,7 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
int size = ondisk_ce_size(ce);
struct ondisk_cache_entry *ondisk = xcalloc(1, size);
char *name;
+ int result;
ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
@@ -1539,7 +1539,34 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
name = ondisk->name;
memcpy(name, ce->name, ce_namelen(ce));
- return ce_write(c, fd, ondisk, size);
+ result = ce_write(c, fd, ondisk, size);
+ free(ondisk);
+ return result;
+}
+
+static int has_racy_timestamp(struct index_state *istate)
+{
+ int entries = istate->cache_nr;
+ int i;
+
+ for (i = 0; i < entries; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (is_racy_timestamp(istate, ce))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Opportunisticly update the index but do not complain if we can't
+ */
+void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
+{
+ if ((istate->cache_changed || has_racy_timestamp(istate)) &&
+ !write_index(istate, lockfile->fd))
+ commit_locked_index(lockfile);
+ else
+ rollback_lock_file(lockfile);
}
int write_index(struct index_state *istate, int newfd)
diff --git a/reflog-walk.c b/reflog-walk.c
index 4879615..5d81d39 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -239,7 +239,6 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
commit->parents = xcalloc(sizeof(struct commit_list), 1);
commit->parents->item = commit_info->commit;
- commit->object.flags &= ~(ADDED | SEEN | SHOWN);
}
void get_reflog_selector(struct strbuf *sb,
diff --git a/refs.c b/refs.c
index 6f486ae..3a8789d 100644
--- a/refs.c
+++ b/refs.c
@@ -157,7 +157,7 @@ static struct cached_refs {
char did_packed;
struct ref_list *loose;
struct ref_list *packed;
-} cached_refs;
+} cached_refs, submodule_refs;
static struct ref_list *current_ref;
static struct ref_list *extra_refs;
@@ -229,23 +229,45 @@ void clear_extra_refs(void)
extra_refs = NULL;
}
-static struct ref_list *get_packed_refs(void)
+static struct ref_list *get_packed_refs(const char *submodule)
{
- if (!cached_refs.did_packed) {
- FILE *f = fopen(git_path("packed-refs"), "r");
- cached_refs.packed = NULL;
+ const char *packed_refs_file;
+ struct cached_refs *refs;
+
+ if (submodule) {
+ packed_refs_file = git_path_submodule(submodule, "packed-refs");
+ refs = &submodule_refs;
+ free_ref_list(refs->packed);
+ } else {
+ packed_refs_file = git_path("packed-refs");
+ refs = &cached_refs;
+ }
+
+ if (!refs->did_packed || submodule) {
+ FILE *f = fopen(packed_refs_file, "r");
+ refs->packed = NULL;
if (f) {
- read_packed_refs(f, &cached_refs);
+ read_packed_refs(f, refs);
fclose(f);
}
- cached_refs.did_packed = 1;
+ refs->did_packed = 1;
}
- return cached_refs.packed;
+ return refs->packed;
}
-static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+static struct ref_list *get_ref_dir(const char *submodule, const char *base,
+ struct ref_list *list)
{
- DIR *dir = opendir(git_path("%s", base));
+ DIR *dir;
+ const char *path;
+
+ if (submodule)
+ path = git_path_submodule(submodule, "%s", base);
+ else
+ path = git_path("%s", base);
+
+
+ dir = opendir(path);
if (dir) {
struct dirent *de;
@@ -261,6 +283,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
struct stat st;
int flag;
int namelen;
+ const char *refdir;
if (de->d_name[0] == '.')
continue;
@@ -270,16 +293,27 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
if (has_extension(de->d_name, ".lock"))
continue;
memcpy(ref + baselen, de->d_name, namelen+1);
- if (stat(git_path("%s", ref), &st) < 0)
+ refdir = submodule
+ ? git_path_submodule(submodule, "%s", ref)
+ : git_path("%s", ref);
+ if (stat(refdir, &st) < 0)
continue;
if (S_ISDIR(st.st_mode)) {
- list = get_ref_dir(ref, list);
+ list = get_ref_dir(submodule, ref, list);
continue;
}
- if (!resolve_ref(ref, sha1, 1, &flag)) {
+ if (submodule) {
hashclr(sha1);
- flag |= REF_BROKEN;
- }
+ flag = 0;
+ if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
+ hashclr(sha1);
+ flag |= REF_BROKEN;
+ }
+ } else
+ if (!resolve_ref(ref, sha1, 1, &flag)) {
+ hashclr(sha1);
+ flag |= REF_BROKEN;
+ }
list = add_ref(ref, sha1, flag, list, NULL);
}
free(ref);
@@ -322,10 +356,16 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
for_each_rawref(warn_if_dangling_symref, &data);
}
-static struct ref_list *get_loose_refs(void)
+static struct ref_list *get_loose_refs(const char *submodule)
{
+ if (submodule) {
+ free_ref_list(submodule_refs.loose);
+ submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
+ return submodule_refs.loose;
+ }
+
if (!cached_refs.did_loose) {
- cached_refs.loose = get_ref_dir("refs", NULL);
+ cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
cached_refs.did_loose = 1;
}
return cached_refs.loose;
@@ -459,7 +499,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
git_snpath(path, sizeof(path), "%s", ref);
/* Special case: non-existing file. */
if (lstat(path, &st) < 0) {
- struct ref_list *list = get_packed_refs();
+ struct ref_list *list = get_packed_refs(NULL);
while (list) {
if (!strcmp(ref, list->name)) {
hashcpy(sha1, list->sha1);
@@ -588,7 +628,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
return -1;
if ((flag & REF_ISPACKED)) {
- struct ref_list *list = get_packed_refs();
+ struct ref_list *list = get_packed_refs(NULL);
while (list) {
if (!strcmp(list->name, ref)) {
@@ -615,12 +655,12 @@ fallback:
return -1;
}
-static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data)
+static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
+ int trim, int flags, void *cb_data)
{
int retval = 0;
- struct ref_list *packed = get_packed_refs();
- struct ref_list *loose = get_loose_refs();
+ struct ref_list *packed = get_packed_refs(submodule);
+ struct ref_list *loose = get_loose_refs(submodule);
struct ref_list *extra;
@@ -657,24 +697,54 @@ end_each:
return retval;
}
-int head_ref(each_ref_fn fn, void *cb_data)
+
+static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
int flag;
+ if (submodule) {
+ if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0)
+ return fn("HEAD", sha1, 0, cb_data);
+
+ return 0;
+ }
+
if (resolve_ref("HEAD", sha1, 1, &flag))
return fn("HEAD", sha1, flag, cb_data);
+
return 0;
}
+int head_ref(each_ref_fn fn, void *cb_data)
+{
+ return do_head_ref(NULL, fn, cb_data);
+}
+
+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+ return do_head_ref(submodule, fn, cb_data);
+}
+
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0, 0, cb_data);
+ return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data);
+}
+
+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data);
}
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+}
+
+int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+ each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
@@ -682,19 +752,34 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data)
return for_each_ref_in("refs/tags/", fn, cb_data);
}
+int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+ return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
+}
+
int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
return for_each_ref_in("refs/heads/", fn, cb_data);
}
+int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+ return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
+}
+
int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
return for_each_ref_in("refs/remotes/", fn, cb_data);
}
+int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+ return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
+}
+
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
+ return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
}
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
@@ -734,7 +819,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0,
+ return do_for_each_ref(NULL, "refs/", fn, 0,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
@@ -958,7 +1043,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(ref, NULL, get_packed_refs(), 0)) {
+ !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -1021,7 +1106,7 @@ static int repack_without_ref(const char *refname)
int fd;
int found = 0;
- packed_ref_list = get_packed_refs();
+ packed_ref_list = get_packed_refs(NULL);
for (list = packed_ref_list; list; list = list->next) {
if (!strcmp(refname, list->name)) {
found = 1;
@@ -1090,6 +1175,15 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
return ret;
}
+/*
+ * People using contrib's git-new-workdir have .git/logs/refs ->
+ * /some/other/path/.git/logs/refs, and that may live on another device.
+ *
+ * IOW, to avoid cross device rename errors, the temporary renamed log must
+ * live into logs/refs.
+ */
+#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log"
+
int rename_ref(const char *oldref, const char *newref, const char *logmsg)
{
static const char renamed_ref[] = "RENAMED-REF";
@@ -1110,10 +1204,10 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
if (!symref)
return error("refname %s not found", oldref);
- if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+ if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
return 1;
- if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+ if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
return 1;
lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
@@ -1123,8 +1217,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
if (write_ref_sha1(lock, orig_sha1, logmsg))
return error("unable to save current sha1 in %s", renamed_ref);
- if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
- return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+ if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+ return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
oldref, strerror(errno));
if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
@@ -1150,7 +1244,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
}
retry:
- if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+ if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
if (errno==EISDIR || errno==ENOTDIR) {
/*
* rename(a, b) when b is an existing
@@ -1163,7 +1257,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
}
goto retry;
} else {
- error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+ error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
newref, strerror(errno));
goto rollback;
}
@@ -1203,8 +1297,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
error("unable to restore logfile %s from %s: %s",
oldref, newref, strerror(errno));
if (!logmoved && log &&
- rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
- error("unable to restore logfile %s from tmp-renamed-log: %s",
+ rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+ error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
oldref, strerror(errno));
return 1;
@@ -1357,7 +1451,7 @@ int write_ref_sha1(struct ref_lock *lock,
}
o = parse_object(sha1);
if (!o) {
- error("Trying to write ref %s with nonexistant object %s",
+ error("Trying to write ref %s with nonexistent object %s",
lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock);
return -1;
@@ -1732,6 +1826,12 @@ int update_ref(const char *action, const char *refname,
return 0;
}
+int ref_exists(char *refname)
+{
+ unsigned char sha1[20];
+ return !!resolve_ref(refname, sha1, 1, NULL);
+}
+
struct ref *find_ref_by_name(const struct ref *list, const char *name)
{
for ( ; list; list = list->next)
diff --git a/refs.h b/refs.h
index 762ce50..5de06e5 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,14 @@ extern int for_each_replace_ref(each_ref_fn, void *);
extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
+extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+ each_ref_fn fn, void *cb_data);
+extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
+
static inline const char *has_glob_specials(const char *pattern)
{
return strpbrk(pattern, "?*[");
@@ -46,6 +54,7 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
*/
extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
extern void clear_extra_refs(void);
+extern int ref_exists(char *);
extern int peel_ref(const char *, unsigned char *);
diff --git a/remote-curl.c b/remote-curl.c
index 24fbb9a..69831e9 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -227,6 +227,8 @@ static struct ref *parse_info_refs(struct discovery *heads)
if (data[i] == '\t')
mid = &data[i];
if (data[i] == '\n') {
+ if (mid - start != 40)
+ die("%sinfo/refs not valid: is this a git repository?", url);
data[i] = 0;
ref_name = mid + 1;
ref = xmalloc(sizeof(struct ref) +
@@ -347,7 +349,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
}
#endif
-static size_t rpc_in(const void *ptr, size_t eltsize,
+static size_t rpc_in(char *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
@@ -356,14 +358,59 @@ static size_t rpc_in(const void *ptr, size_t eltsize,
return size;
}
+static int run_slot(struct active_request_slot *slot)
+{
+ int err = 0;
+ struct slot_results results;
+
+ slot->results = &results;
+ slot->curl_result = curl_easy_perform(slot->curl);
+ finish_active_slot(slot);
+
+ if (results.curl_result != CURLE_OK) {
+ err |= error("RPC failed; result=%d, HTTP code = %ld",
+ results.curl_result, results.http_code);
+ }
+
+ return err;
+}
+
+static int probe_rpc(struct rpc_state *rpc)
+{
+ struct active_request_slot *slot;
+ struct curl_slist *headers = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ int err;
+
+ slot = get_active_slot();
+
+ headers = curl_slist_append(headers, rpc->hdr_content_type);
+ headers = curl_slist_append(headers, rpc->hdr_accept);
+
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+ curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, "0000");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
+
+ err = run_slot(slot);
+
+ curl_slist_free_all(headers);
+ strbuf_release(&buf);
+ return err;
+}
+
static int post_rpc(struct rpc_state *rpc)
{
struct active_request_slot *slot;
- struct slot_results results;
struct curl_slist *headers = NULL;
int use_gzip = rpc->gzip_request;
char *gzip_body = NULL;
- int err = 0, large_request = 0;
+ int err, large_request = 0;
/* Try to load the entire request, if we can fit it into the
* allocated buffer space we can use HTTP/1.0 and avoid the
@@ -386,8 +433,13 @@ static int post_rpc(struct rpc_state *rpc)
rpc->len += n;
}
+ if (large_request) {
+ err = probe_rpc(rpc);
+ if (err)
+ return err;
+ }
+
slot = get_active_slot();
- slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
@@ -396,12 +448,12 @@ static int post_rpc(struct rpc_state *rpc)
headers = curl_slist_append(headers, rpc->hdr_content_type);
headers = curl_slist_append(headers, rpc->hdr_accept);
+ headers = curl_slist_append(headers, "Expect:");
if (large_request) {
/* The request body is large and the size cannot be predicted.
* We must use chunked encoding to send it.
*/
- headers = curl_slist_append(headers, "Expect: 100-continue");
headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
rpc->initial_buffer = 1;
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
@@ -421,16 +473,12 @@ static int post_rpc(struct rpc_state *rpc)
* the transfer time.
*/
size_t size;
- z_stream stream;
+ git_zstream stream;
int ret;
memset(&stream, 0, sizeof(stream));
- ret = deflateInit2(&stream, Z_BEST_COMPRESSION,
- Z_DEFLATED, (15 + 16),
- 8, Z_DEFAULT_STRATEGY);
- if (ret != Z_OK)
- die("cannot deflate request; zlib init error %d", ret);
- size = deflateBound(&stream, rpc->len);
+ git_deflate_init_gzip(&stream, Z_BEST_COMPRESSION);
+ size = git_deflate_bound(&stream, rpc->len);
gzip_body = xmalloc(size);
stream.next_in = (unsigned char *)rpc->buf;
@@ -438,11 +486,11 @@ static int post_rpc(struct rpc_state *rpc)
stream.next_out = (unsigned char *)gzip_body;
stream.avail_out = size;
- ret = deflate(&stream, Z_FINISH);
+ ret = git_deflate(&stream, Z_FINISH);
if (ret != Z_STREAM_END)
die("cannot deflate request; zlib deflate error %d", ret);
- ret = deflateEnd(&stream);
+ ret = git_deflate_end_gently(&stream);
if (ret != Z_OK)
die("cannot deflate request; zlib end error %d", ret);
@@ -475,13 +523,7 @@ static int post_rpc(struct rpc_state *rpc)
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
- slot->curl_result = curl_easy_perform(slot->curl);
- finish_active_slot(slot);
-
- if (results.curl_result != CURLE_OK) {
- err |= error("RPC failed; result=%d, HTTP code = %ld",
- results.curl_result, results.http_code);
- }
+ err = run_slot(slot);
curl_slist_free_all(headers);
free(gzip_body);
@@ -528,11 +570,12 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
rpc->len = n;
err |= post_rpc(rpc);
}
- strbuf_read(&rpc->result, client.out, 0);
close(client.in);
- close(client.out);
client.in = -1;
+ strbuf_read(&rpc->result, client.out, 0);
+
+ close(client.out);
client.out = -1;
err |= finish_command(&client);
@@ -766,19 +809,21 @@ static void parse_push(struct strbuf *buf)
strbuf_reset(buf);
if (strbuf_getline(buf, stdin, '\n') == EOF)
- return;
+ goto free_specs;
if (!*buf->buf)
break;
} while (1);
if (push(nr_spec, specs))
exit(128); /* error already reported */
- for (i = 0; i < nr_spec; i++)
- free(specs[i]);
- free(specs);
printf("\n");
fflush(stdout);
+
+ free_specs:
+ for (i = 0; i < nr_spec; i++)
+ free(specs[i]);
+ free(specs);
}
int main(int argc, const char **argv)
diff --git a/remote.c b/remote.c
index afbba47..ca42a12 100644
--- a/remote.c
+++ b/remote.c
@@ -493,7 +493,7 @@ static void read_config(void)
}
/*
- * We need to make sure the tracking branches are well formed, but a
+ * We need to make sure the remote-tracking branches are well formed, but a
* wildcard refspec in "struct refspec" must have a trailing slash. We
* temporarily drop the trailing '/' while calling check_ref_format(),
* and put it back. The caller knows that a CHECK_REF_FORMAT_ONELEVEL
@@ -754,7 +754,7 @@ int for_each_remote(each_remote_fn fn, void *priv)
void ref_remove_duplicates(struct ref *ref_map)
{
- struct string_list refs = { NULL, 0, 0, 0 };
+ struct string_list refs = STRING_LIST_INIT_NODUP;
struct string_list_item *item = NULL;
struct ref *prev = NULL, *next = NULL;
for (; ref_map; prev = ref_map, ref_map = next) {
@@ -1704,7 +1704,7 @@ static int get_stale_heads_cb(const char *refname,
struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
{
struct ref *ref, *stale_refs = NULL;
- struct string_list ref_names = { NULL, 0, 0, 0 };
+ struct string_list ref_names = STRING_LIST_INIT_NODUP;
struct stale_heads_info info;
info.remote = remote;
info.ref_names = &ref_names;
diff --git a/replace_object.c b/replace_object.c
index eb59604..d0b1548 100644
--- a/replace_object.c
+++ b/replace_object.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "sha1-lookup.h"
#include "refs.h"
+#include "commit.h"
static struct replace_object {
unsigned char sha1[2][20];
@@ -84,12 +85,14 @@ static void prepare_replace_object(void)
for_each_replace_ref(register_replace_ref, NULL);
replace_object_prepared = 1;
+ if (!replace_object_nr)
+ read_replace_refs = 0;
}
/* We allow "recursive" replacement. Only within reason, though */
#define MAXREPLACEDEPTH 5
-const unsigned char *lookup_replace_object(const unsigned char *sha1)
+const unsigned char *do_lookup_replace_object(const unsigned char *sha1)
{
int pos, depth = MAXREPLACEDEPTH;
const unsigned char *cur = sha1;
diff --git a/rerere.c b/rerere.c
index d03a696..dcb525a 100644
--- a/rerere.c
+++ b/rerere.c
@@ -7,6 +7,11 @@
#include "ll-merge.h"
#include "attr.h"
+#define RESOLVED 0
+#define PUNTED 1
+#define THREE_STAGED 2
+void *RERERE_RESOLVED = &RERERE_RESOLVED;
+
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1;
@@ -42,8 +47,14 @@ static void read_rr(struct string_list *rr)
name = xstrdup(buf);
if (fgetc(in) != '\t')
die("corrupt MERGE_RR");
- for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
- ; /* do nothing */
+ for (i = 0; i < sizeof(buf); i++) {
+ int c = fgetc(in);
+ if (c < 0)
+ die("corrupt MERGE_RR");
+ buf[i] = c;
+ if (c == 0)
+ break;
+ }
if (i == sizeof(buf))
die("filename too long");
string_list_insert(rr, buf)->util = name;
@@ -319,9 +330,13 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
if (!mmfile[i].ptr && !mmfile[i].size)
mmfile[i].ptr = xstrdup("");
}
+ /*
+ * NEEDSWORK: handle conflicts from merges with
+ * merge.renormalize set, too
+ */
ll_merge(&result, path, &mmfile[0], NULL,
&mmfile[1], "ours",
- &mmfile[2], "theirs", 0);
+ &mmfile[2], "theirs", NULL);
for (i = 0; i < 3; i++)
free(mmfile[i].ptr);
@@ -341,21 +356,74 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
return hunk_no;
}
-static int find_conflict(struct string_list *conflict)
+static int check_one_conflict(int i, int *type)
{
- int i;
- if (read_cache() < 0)
- return error("Could not read index");
- for (i = 0; i+1 < active_nr; i++) {
+ struct cache_entry *e = active_cache[i];
+
+ if (!ce_stage(e)) {
+ *type = RESOLVED;
+ return i + 1;
+ }
+
+ *type = PUNTED;
+ if (ce_stage(e) == 1) {
+ if (active_nr <= ++i)
+ return i + 1;
+ }
+
+ /* Only handle regular files with both stages #2 and #3 */
+ if (i + 1 < active_nr) {
struct cache_entry *e2 = active_cache[i];
- struct cache_entry *e3 = active_cache[i+1];
+ struct cache_entry *e3 = active_cache[i + 1];
if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
- ce_same_name(e2, e3) &&
+ ce_same_name(e, e3) &&
S_ISREG(e2->ce_mode) &&
- S_ISREG(e3->ce_mode)) {
- string_list_insert(conflict, (const char *)e2->name);
- i++; /* skip over both #2 and #3 */
+ S_ISREG(e3->ce_mode))
+ *type = THREE_STAGED;
+ }
+
+ /* Skip the entries with the same name */
+ while (i < active_nr && ce_same_name(e, active_cache[i]))
+ i++;
+ return i;
+}
+
+static int find_conflict(struct string_list *conflict)
+{
+ int i;
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ for (i = 0; i < active_nr;) {
+ int conflict_type;
+ struct cache_entry *e = active_cache[i];
+ i = check_one_conflict(i, &conflict_type);
+ if (conflict_type == THREE_STAGED)
+ string_list_insert(conflict, (const char *)e->name);
+ }
+ return 0;
+}
+
+int rerere_remaining(struct string_list *merge_rr)
+{
+ int i;
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ for (i = 0; i < active_nr;) {
+ int conflict_type;
+ struct cache_entry *e = active_cache[i];
+ i = check_one_conflict(i, &conflict_type);
+ if (conflict_type == PUNTED)
+ string_list_insert(merge_rr, (const char *)e->name);
+ else if (conflict_type == RESOLVED) {
+ struct string_list_item *it;
+ it = string_list_lookup(merge_rr, (const char *)e->name);
+ if (it != NULL) {
+ free(it->util);
+ it->util = RERERE_RESOLVED;
+ }
}
}
return 0;
@@ -376,9 +444,15 @@ static int merge(const char *name, const char *path)
ret = 1;
goto out;
}
- ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0);
+ ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
if (!ret) {
- FILE *f = fopen(path, "w");
+ FILE *f;
+
+ if (utime(rerere_path(name, "postimage"), NULL) < 0)
+ warning("failed utime() on %s: %s",
+ rerere_path(name, "postimage"),
+ strerror(errno));
+ f = fopen(path, "w");
if (!f)
return error("Could not open %s: %s", path,
strerror(errno));
@@ -426,8 +500,8 @@ static int update_paths(struct string_list *update)
static int do_plain_rerere(struct string_list *rr, int fd)
{
- struct string_list conflict = { NULL, 0, 0, 1 };
- struct string_list update = { NULL, 0, 0, 1 };
+ struct string_list conflict = STRING_LIST_INIT_DUP;
+ struct string_list update = STRING_LIST_INIT_DUP;
int i;
find_conflict(&conflict);
@@ -522,8 +596,7 @@ static int is_rerere_enabled(void)
if (rerere_enabled < 0)
return rr_cache_exists;
- if (!rr_cache_exists &&
- (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
+ if (!rr_cache_exists && mkdir_in_gitdir(rr_cache))
die("Could not create directory %s", rr_cache);
return 1;
}
@@ -547,7 +620,7 @@ int setup_rerere(struct string_list *merge_rr, int flags)
int rerere(int flags)
{
- struct string_list merge_rr = { NULL, 0, 0, 1 };
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
int fd;
fd = setup_rerere(&merge_rr, flags);
@@ -585,8 +658,8 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
int rerere_forget(const char **pathspec)
{
int i, fd;
- struct string_list conflict = { NULL, 0, 0, 1 };
- struct string_list merge_rr = { NULL, 0, 0, 1 };
+ struct string_list conflict = STRING_LIST_INIT_DUP;
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
if (read_cache() < 0)
return error("Could not read index");
@@ -604,3 +677,88 @@ int rerere_forget(const char **pathspec)
}
return write_rr(&merge_rr, fd);
}
+
+static time_t rerere_created_at(const char *name)
+{
+ struct stat st;
+ return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static time_t rerere_last_used_at(const char *name)
+{
+ struct stat st;
+ return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static void unlink_rr_item(const char *name)
+{
+ unlink(rerere_path(name, "thisimage"));
+ unlink(rerere_path(name, "preimage"));
+ unlink(rerere_path(name, "postimage"));
+ rmdir(git_path("rr-cache/%s", name));
+}
+
+struct rerere_gc_config_cb {
+ int cutoff_noresolve;
+ int cutoff_resolve;
+};
+
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+ struct rerere_gc_config_cb *cf = cb;
+
+ if (!strcmp(var, "gc.rerereresolved"))
+ cf->cutoff_resolve = git_config_int(var, value);
+ else if (!strcmp(var, "gc.rerereunresolved"))
+ cf->cutoff_noresolve = git_config_int(var, value);
+ else
+ return git_default_config(var, value, cb);
+ return 0;
+}
+
+void rerere_gc(struct string_list *rr)
+{
+ struct string_list to_remove = STRING_LIST_INIT_DUP;
+ DIR *dir;
+ struct dirent *e;
+ int i, cutoff;
+ time_t now = time(NULL), then;
+ struct rerere_gc_config_cb cf = { 15, 60 };
+
+ git_config(git_rerere_gc_config, &cf);
+ dir = opendir(git_path("rr-cache"));
+ if (!dir)
+ die_errno("unable to open rr-cache directory");
+ while ((e = readdir(dir))) {
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+
+ then = rerere_last_used_at(e->d_name);
+ if (then) {
+ cutoff = cf.cutoff_resolve;
+ } else {
+ then = rerere_created_at(e->d_name);
+ if (!then)
+ continue;
+ cutoff = cf.cutoff_noresolve;
+ }
+ if (then < now - cutoff * 86400)
+ string_list_append(&to_remove, e->d_name);
+ }
+ closedir(dir);
+ for (i = 0; i < to_remove.nr; i++)
+ unlink_rr_item(to_remove.items[i].string);
+ string_list_clear(&to_remove, 0);
+}
+
+void rerere_clear(struct string_list *merge_rr)
+{
+ int i;
+
+ for (i = 0; i < merge_rr->nr; i++) {
+ const char *name = (const char *)merge_rr->items[i].util;
+ if (!has_rerere_resolution(name))
+ unlink_rr_item(name);
+ }
+ unlink_or_warn(git_path("MERGE_RR"));
+}
diff --git a/rerere.h b/rerere.h
index eaa9004..fcd8bc1 100644
--- a/rerere.h
+++ b/rerere.h
@@ -6,11 +6,21 @@
#define RERERE_AUTOUPDATE 01
#define RERERE_NOAUTOUPDATE 02
+/*
+ * Marks paths that have been hand-resolved and added to the
+ * index. Set in the util field of such paths after calling
+ * rerere_remaining.
+ */
+extern void *RERERE_RESOLVED;
+
extern int setup_rerere(struct string_list *, int);
extern int rerere(int);
extern const char *rerere_path(const char *hex, const char *file);
extern int has_rerere_resolution(const char *hex);
extern int rerere_forget(const char **);
+extern int rerere_remaining(struct string_list *);
+extern void rerere_clear(struct string_list *);
+extern void rerere_gc(struct string_list *);
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
"update the index with reused conflict resolution if possible")
diff --git a/resolve-undo.c b/resolve-undo.c
index 174ebec..72b4612 100644
--- a/resolve-undo.c
+++ b/resolve-undo.c
@@ -28,29 +28,25 @@ void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
ui->mode[stage - 1] = ce->ce_mode;
}
-static int write_one(struct string_list_item *item, void *cbdata)
+void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
{
- struct strbuf *sb = cbdata;
- struct resolve_undo_info *ui = item->util;
- int i;
+ struct string_list_item *item;
+ for_each_string_list_item(item, resolve_undo) {
+ struct resolve_undo_info *ui = item->util;
+ int i;
- if (!ui)
- return 0;
- strbuf_addstr(sb, item->string);
- strbuf_addch(sb, 0);
- for (i = 0; i < 3; i++)
- strbuf_addf(sb, "%o%c", ui->mode[i], 0);
- for (i = 0; i < 3; i++) {
- if (!ui->mode[i])
+ if (!ui)
continue;
- strbuf_add(sb, ui->sha1[i], 20);
+ strbuf_addstr(sb, item->string);
+ strbuf_addch(sb, 0);
+ for (i = 0; i < 3; i++)
+ strbuf_addf(sb, "%o%c", ui->mode[i], 0);
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ strbuf_add(sb, ui->sha1[i], 20);
+ }
}
- return 0;
-}
-
-void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
-{
- for_each_string_list(resolve_undo, write_one, sb);
}
struct string_list *resolve_undo_read(const char *data, unsigned long size)
diff --git a/revision.c b/revision.c
index 7e82efd..c46cfaa 100644
--- a/revision.c
+++ b/revision.c
@@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit)
static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
{
+ if (!obj)
+ return;
if (revs->no_walk && (obj->flags & UNINTERESTING))
revs->no_walk = 0;
if (revs->reflog_info && obj->type == OBJ_COMMIT) {
@@ -174,8 +176,11 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
struct object *object;
object = parse_object(sha1);
- if (!object)
+ if (!object) {
+ if (revs->ignore_missing)
+ return object;
die("bad object %s", name);
+ }
object->flags |= flags;
return object;
}
@@ -323,7 +328,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
* tagged commit by specifying both --simplify-by-decoration
* and pathspec.
*/
- if (!revs->prune_data)
+ if (!revs->prune_data.nr)
return REV_TREE_SAME;
}
@@ -444,15 +449,15 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREESAME;
}
-static void insert_by_date_cached(struct commit *p, struct commit_list **head,
+static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
struct commit_list *cached_base, struct commit_list **cache)
{
struct commit_list *new_entry;
if (cached_base && p->date < cached_base->item->date)
- new_entry = insert_by_date(p, &cached_base->next);
+ new_entry = commit_list_insert_by_date(p, &cached_base->next);
else
- new_entry = insert_by_date(p, head);
+ new_entry = commit_list_insert_by_date(p, head);
if (cache && (!*cache || p->date < (*cache)->item->date))
*cache = new_entry;
@@ -494,7 +499,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
if (p->object.flags & SEEN)
continue;
p->object.flags |= SEEN;
- insert_by_date_cached(p, list, cached_base, cache_ptr);
+ commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
}
return 0;
}
@@ -521,7 +526,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
p->object.flags |= left_flag;
if (!(p->object.flags & SEEN)) {
p->object.flags |= SEEN;
- insert_by_date_cached(p, list, cached_base, cache_ptr);
+ commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
}
if (revs->first_parent_only)
break;
@@ -535,6 +540,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
int left_count = 0, right_count = 0;
int left_first;
struct patch_ids ids;
+ unsigned cherry_flag;
/* First count the commits on the left and on the right */
for (p = list; p; p = p->next) {
@@ -553,11 +559,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
left_first = left_count < right_count;
init_patch_ids(&ids);
- if (revs->diffopt.nr_paths) {
- ids.diffopts.nr_paths = revs->diffopt.nr_paths;
- ids.diffopts.paths = revs->diffopt.paths;
- ids.diffopts.pathlens = revs->diffopt.pathlens;
- }
+ ids.diffopts.pathspec = revs->diffopt.pathspec;
/* Compute patch-ids for one side */
for (p = list; p; p = p->next) {
@@ -576,6 +578,9 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
commit->util = add_commit_patch_id(commit, &ids);
}
+ /* either cherry_mark or cherry_pick are true */
+ cherry_flag = revs->cherry_mark ? PATCHSAME : SHOWN;
+
/* Check the other side */
for (p = list; p; p = p->next) {
struct commit *commit = p->item;
@@ -598,7 +603,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
if (!id)
continue;
id->seen = 1;
- commit->object.flags |= SHOWN;
+ commit->object.flags |= cherry_flag;
}
/* Now check the original side for seen ones */
@@ -610,7 +615,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
if (!ent)
continue;
if (ent->seen)
- commit->object.flags |= SHOWN;
+ commit->object.flags |= cherry_flag;
commit->util = NULL;
}
@@ -733,6 +738,23 @@ static struct commit_list *collect_bottom_commits(struct commit_list *list)
return bottom;
}
+/* Assumes either left_only or right_only is set */
+static void limit_left_right(struct commit_list *list, struct rev_info *revs)
+{
+ struct commit_list *p;
+
+ for (p = list; p; p = p->next) {
+ struct commit *commit = p->item;
+
+ if (revs->right_only) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
+ commit->object.flags |= SHOWN;
+ } else /* revs->left_only is set */
+ if (!(commit->object.flags & SYMMETRIC_LEFT))
+ commit->object.flags |= SHOWN;
+ }
+}
+
static int limit_list(struct rev_info *revs)
{
int slop = SLOP;
@@ -785,9 +807,12 @@ static int limit_list(struct rev_info *revs)
show(revs, newlist);
show_early_output = NULL;
}
- if (revs->cherry_pick)
+ if (revs->cherry_pick || revs->cherry_mark)
cherry_pick_list(newlist, revs);
+ if (revs->left_only || revs->right_only)
+ limit_left_right(newlist, revs);
+
if (bottom) {
limit_to_ancestry(bottom, newlist);
free_commit_list(bottom);
@@ -820,12 +845,12 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
cb->all_flags = flags;
}
-static void handle_refs(struct rev_info *revs, unsigned flags,
- int (*for_each)(each_ref_fn, void *))
+static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,
+ int (*for_each)(const char *, each_ref_fn, void *))
{
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, flags);
- for_each(handle_one_ref, &cb);
+ for_each(submodule, handle_one_ref, &cb);
}
static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
@@ -886,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
return 0;
while (1) {
it = get_reference(revs, arg, sha1, 0);
+ if (!it && revs->ignore_missing)
+ return 0;
if (it->type != OBJ_TAG)
break;
if (!((struct tag*)it)->tagged)
@@ -921,6 +948,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->min_age = -1;
revs->skip_count = -1;
revs->max_count = -1;
+ revs->max_parents = -1;
revs->commit_format = CMIT_FMT_DEFAULT;
@@ -934,6 +962,8 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->diffopt.prefix = prefix;
revs->diffopt.prefix_length = strlen(prefix);
}
+
+ revs->notes_opt.use_default_notes = -1;
}
static void add_pending_commit_list(struct rev_info *revs,
@@ -973,7 +1003,7 @@ static void prepare_show_merge(struct rev_info *revs)
struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
- if (ce_path_match(ce, revs->prune_data)) {
+ if (ce_path_match(ce, &revs->prune_data)) {
prune_num++;
prune = xrealloc(prune, sizeof(*prune) * prune_num);
prune[prune_num-2] = ce->name;
@@ -983,7 +1013,8 @@ static void prepare_show_merge(struct rev_info *revs)
ce_same_name(ce, active_cache[i+1]))
i++;
}
- revs->prune_data = prune;
+ free_pathspec(&revs->prune_data);
+ init_pathspec(&revs->prune_data, prune);
revs->limited = 1;
}
@@ -1020,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
a = lookup_commit_reference(from_sha1);
b = lookup_commit_reference(sha1);
if (!a || !b) {
+ if (revs->ignore_missing)
+ return 0;
die(symmetric ?
"Invalid symmetric difference expression %s...%s" :
"Invalid revision range %s..%s",
@@ -1066,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
arg++;
}
if (get_sha1_with_mode(arg, sha1, &mode))
- return -1;
+ return revs->ignore_missing ? 0 : -1;
if (!cant_be_filename)
verify_non_filename(revs->prefix, arg);
object = get_reference(revs, arg, sha1, flags ^ local_flags);
@@ -1074,35 +1107,34 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
return 0;
}
-static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data)
-{
- const char **prune = *prune_data;
- int prune_nr;
- int prune_alloc;
+struct cmdline_pathspec {
+ int alloc;
+ int nr;
+ const char **path;
+};
- /* count existing ones */
- if (!prune)
- prune_nr = 0;
- else
- for (prune_nr = 0; prune[prune_nr]; prune_nr++)
- ;
- prune_alloc = prune_nr; /* not really, but we do not know */
+static void append_prune_data(struct cmdline_pathspec *prune, const char **av)
+{
+ while (*av) {
+ ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+ prune->path[prune->nr++] = *(av++);
+ }
+}
+static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb,
+ struct cmdline_pathspec *prune)
+{
while (strbuf_getwholeline(sb, stdin, '\n') != EOF) {
int len = sb->len;
if (len && sb->buf[len - 1] == '\n')
sb->buf[--len] = '\0';
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr++] = xstrdup(sb->buf);
+ ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+ prune->path[prune->nr++] = xstrdup(sb->buf);
}
- if (prune) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr] = NULL;
- }
- *prune_data = prune;
}
-static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune)
+static void read_revisions_from_stdin(struct rev_info *revs,
+ struct cmdline_pathspec *prune)
{
struct strbuf sb;
int seen_dashdash = 0;
@@ -1148,23 +1180,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
int *unkc, const char **unkv)
{
const char *arg = argv[0];
+ const char *optarg;
+ int argcount;
/* pseudo revision arguments */
if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
!strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
!strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
!strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
- !strcmp(arg, "--bisect"))
+ !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
+ !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
+ !prefixcmp(arg, "--remotes="))
{
unkv[(*unkc)++] = arg;
return 1;
}
- if (!prefixcmp(arg, "--max-count=")) {
- revs->max_count = atoi(arg + 12);
+ if ((argcount = parse_long_opt("max-count", argv, &optarg))) {
+ revs->max_count = atoi(optarg);
revs->no_walk = 0;
- } else if (!prefixcmp(arg, "--skip=")) {
- revs->skip_count = atoi(arg + 7);
+ return argcount;
+ } else if ((argcount = parse_long_opt("skip", argv, &optarg))) {
+ revs->skip_count = atoi(optarg);
+ return argcount;
} else if ((*arg == '-') && isdigit(arg[1])) {
/* accept -<digit>, like traditional "head" */
revs->max_count = atoi(arg + 1);
@@ -1178,18 +1216,24 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!prefixcmp(arg, "-n")) {
revs->max_count = atoi(arg + 2);
revs->no_walk = 0;
- } else if (!prefixcmp(arg, "--max-age=")) {
- revs->max_age = atoi(arg + 10);
- } else if (!prefixcmp(arg, "--since=")) {
- revs->max_age = approxidate(arg + 8);
- } else if (!prefixcmp(arg, "--after=")) {
- revs->max_age = approxidate(arg + 8);
- } else if (!prefixcmp(arg, "--min-age=")) {
- revs->min_age = atoi(arg + 10);
- } else if (!prefixcmp(arg, "--before=")) {
- revs->min_age = approxidate(arg + 9);
- } else if (!prefixcmp(arg, "--until=")) {
- revs->min_age = approxidate(arg + 8);
+ } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) {
+ revs->max_age = atoi(optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("since", argv, &optarg))) {
+ revs->max_age = approxidate(optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("after", argv, &optarg))) {
+ revs->max_age = approxidate(optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("min-age", argv, &optarg))) {
+ revs->min_age = atoi(optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("before", argv, &optarg))) {
+ revs->min_age = approxidate(optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("until", argv, &optarg))) {
+ revs->min_age = approxidate(optarg);
+ return argcount;
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
} else if (!strcmp(arg, "--ancestry-path")) {
@@ -1246,16 +1290,47 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
} else if (!strcmp(arg, "--merges")) {
- revs->merges_only = 1;
+ revs->min_parents = 2;
} else if (!strcmp(arg, "--no-merges")) {
- revs->no_merges = 1;
+ revs->max_parents = 1;
+ } else if (!prefixcmp(arg, "--min-parents=")) {
+ revs->min_parents = atoi(arg+14);
+ } else if (!prefixcmp(arg, "--no-min-parents")) {
+ revs->min_parents = 0;
+ } else if (!prefixcmp(arg, "--max-parents=")) {
+ revs->max_parents = atoi(arg+14);
+ } else if (!prefixcmp(arg, "--no-max-parents")) {
+ revs->max_parents = -1;
} else if (!strcmp(arg, "--boundary")) {
revs->boundary = 1;
} else if (!strcmp(arg, "--left-right")) {
revs->left_right = 1;
+ } else if (!strcmp(arg, "--left-only")) {
+ if (revs->right_only)
+ die("--left-only is incompatible with --right-only"
+ " or --cherry");
+ revs->left_only = 1;
+ } else if (!strcmp(arg, "--right-only")) {
+ if (revs->left_only)
+ die("--right-only is incompatible with --left-only");
+ revs->right_only = 1;
+ } else if (!strcmp(arg, "--cherry")) {
+ if (revs->left_only)
+ die("--cherry is incompatible with --left-only");
+ revs->cherry_mark = 1;
+ revs->right_only = 1;
+ revs->max_parents = 1;
+ revs->limited = 1;
} else if (!strcmp(arg, "--count")) {
revs->count = 1;
+ } else if (!strcmp(arg, "--cherry-mark")) {
+ if (revs->cherry_pick)
+ die("--cherry-mark is incompatible with --cherry-pick");
+ revs->cherry_mark = 1;
+ revs->limited = 1; /* needs limit_list() */
} else if (!strcmp(arg, "--cherry-pick")) {
+ if (revs->cherry_mark)
+ die("--cherry-pick is incompatible with --cherry-mark");
revs->cherry_pick = 1;
revs->limited = 1;
} else if (!strcmp(arg, "--objects")) {
@@ -1295,35 +1370,46 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->pretty_given = 1;
get_commit_format(arg+8, revs);
} else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
+ /*
+ * Detached form ("--pretty X" as opposed to "--pretty=X")
+ * not allowed, since the argument is optional.
+ */
revs->verbose_header = 1;
revs->pretty_given = 1;
get_commit_format(arg+9, revs);
- } else if (!strcmp(arg, "--show-notes")) {
+ } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) {
revs->show_notes = 1;
revs->show_notes_given = 1;
- } else if (!prefixcmp(arg, "--show-notes=")) {
+ revs->notes_opt.use_default_notes = 1;
+ } else if (!prefixcmp(arg, "--show-notes=") ||
+ !prefixcmp(arg, "--notes=")) {
struct strbuf buf = STRBUF_INIT;
revs->show_notes = 1;
revs->show_notes_given = 1;
- if (!revs->notes_opt.extra_notes_refs)
- revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
- if (!prefixcmp(arg+13, "refs/"))
- /* happy */;
- else if (!prefixcmp(arg+13, "notes/"))
- strbuf_addstr(&buf, "refs/");
+ if (!prefixcmp(arg, "--show-notes")) {
+ if (revs->notes_opt.use_default_notes < 0)
+ revs->notes_opt.use_default_notes = 1;
+ strbuf_addstr(&buf, arg+13);
+ }
else
- strbuf_addstr(&buf, "refs/notes/");
- strbuf_addstr(&buf, arg+13);
- string_list_append(revs->notes_opt.extra_notes_refs,
+ strbuf_addstr(&buf, arg+8);
+ expand_notes_ref(&buf);
+ string_list_append(&revs->notes_opt.extra_notes_refs,
strbuf_detach(&buf, NULL));
} else if (!strcmp(arg, "--no-notes")) {
revs->show_notes = 0;
revs->show_notes_given = 1;
+ revs->notes_opt.use_default_notes = -1;
+ /* we have been strdup'ing ourselves, so trick
+ * string_list into free()ing strings */
+ revs->notes_opt.extra_notes_refs.strdup_strings = 1;
+ string_list_clear(&revs->notes_opt.extra_notes_refs, 0);
+ revs->notes_opt.extra_notes_refs.strdup_strings = 0;
} else if (!strcmp(arg, "--standard-notes")) {
revs->show_notes_given = 1;
- revs->notes_opt.suppress_default_notes = 0;
+ revs->notes_opt.use_default_notes = 1;
} else if (!strcmp(arg, "--no-standard-notes")) {
- revs->notes_opt.suppress_default_notes = 1;
+ revs->notes_opt.use_default_notes = 0;
} else if (!strcmp(arg, "--oneline")) {
revs->verbose_header = 1;
get_commit_format("oneline", revs);
@@ -1351,6 +1437,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->abbrev = 40;
} else if (!strcmp(arg, "--abbrev-commit")) {
revs->abbrev_commit = 1;
+ revs->abbrev_commit_given = 1;
+ } else if (!strcmp(arg, "--no-abbrev-commit")) {
+ revs->abbrev_commit = 0;
} else if (!strcmp(arg, "--full-diff")) {
revs->diff = 1;
revs->full_diff = 1;
@@ -1359,21 +1448,25 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--relative-date")) {
revs->date_mode = DATE_RELATIVE;
revs->date_mode_explicit = 1;
- } else if (!strncmp(arg, "--date=", 7)) {
- revs->date_mode = parse_date_format(arg + 7);
+ } else if ((argcount = parse_long_opt("date", argv, &optarg))) {
+ revs->date_mode = parse_date_format(optarg);
revs->date_mode_explicit = 1;
+ return argcount;
} else if (!strcmp(arg, "--log-size")) {
revs->show_log_size = 1;
}
/*
* Grepping the commit log
*/
- else if (!prefixcmp(arg, "--author=")) {
- add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9);
- } else if (!prefixcmp(arg, "--committer=")) {
- add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12);
- } else if (!prefixcmp(arg, "--grep=")) {
- add_message_grep(revs, arg+7);
+ else if ((argcount = parse_long_opt("author", argv, &optarg))) {
+ add_header_grep(revs, GREP_HEADER_AUTHOR, optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("committer", argv, &optarg))) {
+ add_header_grep(revs, GREP_HEADER_COMMITTER, optarg);
+ return argcount;
+ } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
+ add_message_grep(revs, optarg);
+ return argcount;
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
revs->grep_filter.regflags |= REG_EXTENDED;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
@@ -1382,17 +1475,19 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->grep_filter.fixed = 1;
} else if (!strcmp(arg, "--all-match")) {
revs->grep_filter.all_match = 1;
- } else if (!prefixcmp(arg, "--encoding=")) {
- arg += 11;
- if (strcmp(arg, "none"))
- git_log_output_encoding = xstrdup(arg);
+ } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
+ if (strcmp(optarg, "none"))
+ git_log_output_encoding = xstrdup(optarg);
else
git_log_output_encoding = "";
+ return argcount;
} else if (!strcmp(arg, "--reverse")) {
revs->reverse ^= 1;
} else if (!strcmp(arg, "--children")) {
revs->children.name = "children";
revs->limited = 1;
+ } else if (!strcmp(arg, "--ignore-missing")) {
+ revs->ignore_missing = 1;
} else {
int opts = diff_opt_parse(&revs->diffopt, argv, argc);
if (!opts)
@@ -1417,42 +1512,77 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
ctx->argc -= n;
}
-static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data)
+static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
- return for_each_ref_in("refs/bisect/bad", fn, cb_data);
+ return for_each_ref_in_submodule(submodule, "refs/bisect/bad", fn, cb_data);
}
-static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)
+static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
- return for_each_ref_in("refs/bisect/good", fn, cb_data);
+ return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data);
}
-static void append_prune_data(const char ***prune_data, const char **av)
+static int handle_revision_pseudo_opt(const char *submodule,
+ struct rev_info *revs,
+ int argc, const char **argv, int *flags)
{
- const char **prune = *prune_data;
- int prune_nr;
- int prune_alloc;
+ const char *arg = argv[0];
+ const char *optarg;
+ int argcount;
- if (!prune) {
- *prune_data = av;
- return;
+ /*
+ * NOTE!
+ *
+ * Commands like "git shortlog" will not accept the options below
+ * unless parse_revision_opt queues them (as opposed to erroring
+ * out).
+ *
+ * When implementing your new pseudo-option, remember to
+ * register it in the list at the top of handle_revision_opt.
+ */
+ if (!strcmp(arg, "--all")) {
+ handle_refs(submodule, revs, *flags, for_each_ref_submodule);
+ handle_refs(submodule, revs, *flags, head_ref_submodule);
+ } else if (!strcmp(arg, "--branches")) {
+ handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
+ } else if (!strcmp(arg, "--bisect")) {
+ handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
+ handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
+ revs->bisect = 1;
+ } else if (!strcmp(arg, "--tags")) {
+ handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
+ } else if (!strcmp(arg, "--remotes")) {
+ handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule);
+ } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref(handle_one_ref, optarg, &cb);
+ return argcount;
+ } else if (!prefixcmp(arg, "--branches=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
+ } else if (!prefixcmp(arg, "--tags=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
+ } else if (!prefixcmp(arg, "--remotes=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
+ } else if (!strcmp(arg, "--reflog")) {
+ handle_reflog(revs, *flags);
+ } else if (!strcmp(arg, "--not")) {
+ *flags ^= UNINTERESTING;
+ } else if (!strcmp(arg, "--no-walk")) {
+ revs->no_walk = 1;
+ } else if (!strcmp(arg, "--do-walk")) {
+ revs->no_walk = 0;
+ } else {
+ return 0;
}
- /* count existing ones */
- for (prune_nr = 0; prune[prune_nr]; prune_nr++)
- ;
- prune_alloc = prune_nr; /* not really, but we do not know */
-
- while (*av) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr++] = *av;
- av++;
- }
- if (prune) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr] = NULL;
- }
- *prune_data = prune;
+ return 1;
}
/*
@@ -1465,7 +1595,12 @@ static void append_prune_data(const char ***prune_data, const char **av)
int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
{
int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
- const char **prune_data = NULL;
+ struct cmdline_pathspec prune_data;
+ const char *submodule = NULL;
+
+ memset(&prune_data, 0, sizeof(prune_data));
+ if (opt)
+ submodule = opt->submodule;
/* First, search for "--" */
seen_dashdash = 0;
@@ -1476,7 +1611,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
argv[i] = NULL;
argc = i;
if (argv[i + 1])
- prune_data = argv + i + 1;
+ append_prune_data(&prune_data, argv + i + 1);
seen_dashdash = 1;
break;
}
@@ -1489,69 +1624,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (*arg == '-') {
int opts;
- if (!strcmp(arg, "--all")) {
- handle_refs(revs, flags, for_each_ref);
- handle_refs(revs, flags, head_ref);
- continue;
- }
- if (!strcmp(arg, "--branches")) {
- handle_refs(revs, flags, for_each_branch_ref);
- continue;
- }
- if (!strcmp(arg, "--bisect")) {
- handle_refs(revs, flags, for_each_bad_bisect_ref);
- handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
- revs->bisect = 1;
- continue;
- }
- if (!strcmp(arg, "--tags")) {
- handle_refs(revs, flags, for_each_tag_ref);
- continue;
- }
- if (!strcmp(arg, "--remotes")) {
- handle_refs(revs, flags, for_each_remote_ref);
- continue;
- }
- if (!prefixcmp(arg, "--glob=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref(handle_one_ref, arg + 7, &cb);
- continue;
- }
- if (!prefixcmp(arg, "--branches=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
- continue;
- }
- if (!prefixcmp(arg, "--tags=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
- continue;
- }
- if (!prefixcmp(arg, "--remotes=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
- continue;
- }
- if (!strcmp(arg, "--reflog")) {
- handle_reflog(revs, flags);
- continue;
- }
- if (!strcmp(arg, "--not")) {
- flags ^= UNINTERESTING;
- continue;
- }
- if (!strcmp(arg, "--no-walk")) {
- revs->no_walk = 1;
- continue;
- }
- if (!strcmp(arg, "--do-walk")) {
- revs->no_walk = 0;
+ opts = handle_revision_pseudo_opt(submodule,
+ revs, argc - i, argv + i,
+ &flags);
+ if (opts > 0) {
+ i += opts - 1;
continue;
}
+
if (!strcmp(arg, "--stdin")) {
if (revs->disable_stdin) {
argv[left++] = arg;
@@ -1594,8 +1674,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
got_rev_arg = 1;
}
- if (prune_data)
- revs->prune_data = get_pathspec(revs->prefix, prune_data);
+ if (prune_data.nr) {
+ /*
+ * If we need to introduce the magic "a lone ':' means no
+ * pathspec whatsoever", here is the place to do so.
+ *
+ * if (prune_data.nr == 1 && !strcmp(prune_data[0], ":")) {
+ * prune_data.nr = 0;
+ * prune_data.alloc = 0;
+ * free(prune_data.path);
+ * prune_data.path = NULL;
+ * } else {
+ * terminate prune_data.alloc with NULL and
+ * call init_pathspec() to set revs->prune_data here.
+ * }
+ */
+ ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
+ prune_data.path[prune_data.nr++] = NULL;
+ init_pathspec(&revs->prune_data,
+ get_pathspec(revs->prefix, prune_data.path));
+ }
if (revs->def == NULL)
revs->def = opt ? opt->def : NULL;
@@ -1626,13 +1724,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (revs->topo_order)
revs->limited = 1;
- if (revs->prune_data) {
- diff_tree_setup_paths(revs->prune_data, &revs->pruning);
+ if (revs->prune_data.nr) {
+ diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
/* Can't prune commits with rename following: the paths change.. */
if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
revs->prune = 1;
if (!revs->full_diff)
- diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+ diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
}
if (revs->combine_merges)
revs->ignore_merges = 0;
@@ -1866,7 +1964,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (commit) {
if (!(commit->object.flags & SEEN)) {
commit->object.flags |= SEEN;
- insert_by_date(commit, &revs->commits);
+ commit_list_insert_by_date(commit, &revs->commits);
}
}
e++;
@@ -1960,10 +2058,15 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
return commit_ignore;
if (revs->min_age != -1 && (commit->date > revs->min_age))
return commit_ignore;
- if (revs->no_merges && commit->parents && commit->parents->next)
- return commit_ignore;
- if (revs->merges_only && !(commit->parents && commit->parents->next))
- return commit_ignore;
+ if (revs->min_parents || (revs->max_parents >= 0)) {
+ int n = 0;
+ struct commit_list *p;
+ for (p = commit->parents; p; p = p->next)
+ n++;
+ if ((n < revs->min_parents) ||
+ ((revs->max_parents >= 0) && (n > revs->max_parents)))
+ return commit_ignore;
+ }
if (!commit_match(commit, revs))
return commit_ignore;
if (revs->prune && revs->dense) {
@@ -2005,8 +2108,10 @@ static struct commit *get_revision_1(struct rev_info *revs)
revs->commits = entry->next;
free(entry);
- if (revs->reflog_info)
+ if (revs->reflog_info) {
fake_reflog_parent(revs->reflog_info, commit);
+ commit->object.flags &= ~(ADDED | SEEN | SHOWN);
+ }
/*
* If we haven't done the list limiting, we need to look at
@@ -2208,3 +2313,32 @@ struct commit *get_revision(struct rev_info *revs)
graph_update(revs->graph, c);
return c;
}
+
+char *get_revision_mark(const struct rev_info *revs, const struct commit *commit)
+{
+ if (commit->object.flags & BOUNDARY)
+ return "-";
+ else if (commit->object.flags & UNINTERESTING)
+ return "^";
+ else if (commit->object.flags & PATCHSAME)
+ return "=";
+ else if (!revs || revs->left_right) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
+ return "<";
+ else
+ return ">";
+ } else if (revs->graph)
+ return "*";
+ else if (revs->cherry_mark)
+ return "+";
+ return "";
+}
+
+void put_revision_mark(const struct rev_info *revs, const struct commit *commit)
+{
+ char *mark = get_revision_mark(revs, commit);
+ if (!strlen(mark))
+ return;
+ fputs(mark, stdout);
+ putchar(' ');
+}
diff --git a/revision.h b/revision.h
index 36fdf22..3d64ada 100644
--- a/revision.h
+++ b/revision.h
@@ -14,7 +14,8 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
-#define ALL_REV_FLAGS ((1u<<9)-1)
+#define PATCHSAME (1u<<9)
+#define ALL_REV_FLAGS ((1u<<10)-1)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
@@ -34,14 +35,13 @@ struct rev_info {
/* Basic information */
const char *prefix;
const char *def;
- void *prune_data;
- unsigned int early_output;
+ struct pathspec prune_data;
+ unsigned int early_output:1,
+ ignore_missing:1;
/* Traversal flags */
unsigned int dense:1,
prune:1,
- no_merges:1,
- merges_only:1,
no_walk:1,
show_all:1,
remove_empty_trees:1,
@@ -59,6 +59,8 @@ struct rev_info {
boundary:2,
count:1,
left_right:1,
+ left_only:1,
+ right_only:1,
rewrite_parents:1,
print_parents:1,
show_source:1,
@@ -66,6 +68,7 @@ struct rev_info {
reverse:1,
reverse_output_stage:1,
cherry_pick:1,
+ cherry_mark:1,
bisect:1,
ancestry_path:1,
first_parent_only:1;
@@ -88,9 +91,11 @@ struct rev_info {
show_notes_given:1,
pretty_given:1,
abbrev_commit:1,
+ abbrev_commit_given:1,
use_terminator:1,
missing_newline:1,
- date_mode_explicit:1;
+ date_mode_explicit:1,
+ preserve_subject:1;
unsigned int disable_stdin:1;
enum date_mode date_mode;
@@ -122,6 +127,8 @@ struct rev_info {
int max_count;
unsigned long max_age;
unsigned long min_age;
+ int min_parents;
+ int max_parents;
/* diff info for patches and for paths limiting */
struct diff_options diffopt;
@@ -137,6 +144,7 @@ struct rev_info {
/* commit counts */
int count_left;
int count_right;
+ int count_same;
};
#define REV_TREE_SAME 0
@@ -151,6 +159,7 @@ extern volatile show_early_output_fn_t show_early_output;
struct setup_revision_opt {
const char *def;
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
+ const char *submodule;
};
extern void init_revisions(struct rev_info *revs, const char *prefix);
@@ -162,6 +171,8 @@ extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,
extern int prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
+extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
+extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit);
extern void mark_parents_uninteresting(struct commit *commit);
extern void mark_tree_uninteresting(struct tree *tree);
diff --git a/run-command.c b/run-command.c
index 2a1041e..70e8a24 100644
--- a/run-command.c
+++ b/run-command.c
@@ -67,21 +67,24 @@ static int child_notifier = -1;
static void notify_parent(void)
{
- ssize_t unused;
- unused = write(child_notifier, "", 1);
+ /*
+ * execvp failed. If possible, we'd like to let start_command
+ * know, so failures like ENOENT can be handled right away; but
+ * otherwise, finish_command will still report the error.
+ */
+ xwrite(child_notifier, "", 1);
}
static NORETURN void die_child(const char *err, va_list params)
{
char msg[4096];
- ssize_t unused;
int len = vsnprintf(msg, sizeof(msg), err, params);
if (len > sizeof(msg))
len = sizeof(msg);
- unused = write(child_err, "fatal: ", 7);
- unused = write(child_err, msg, len);
- unused = write(child_err, "\n", 1);
+ write_in_full(child_err, "fatal: ", 7);
+ write_in_full(child_err, msg, len);
+ write_in_full(child_err, "\n", 1);
exit(128);
}
#endif
@@ -194,6 +197,7 @@ fail_pipe:
}
trace_argv_printf(cmd->argv, "trace: run_command:");
+ fflush(NULL);
#ifndef WIN32
{
@@ -201,7 +205,6 @@ fail_pipe:
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
- fflush(NULL);
cmd->pid = fork();
if (!cmd->pid) {
/*
diff --git a/send-pack.h b/send-pack.h
index 60b4ba6..05d7ab1 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -5,6 +5,7 @@ struct send_pack_args {
unsigned verbose:1,
quiet:1,
porcelain:1,
+ progress:1,
send_mirror:1,
force_update:1,
use_thin_pack:1,
diff --git a/server-info.c b/server-info.c
index 4098ca2..9ec744e 100644
--- a/server-info.c
+++ b/server-info.c
@@ -113,11 +113,8 @@ static int read_pack_info_file(const char *infofile)
goto out_stale;
break;
case 'D': /* we used to emit D but that was misguided. */
- goto out_stale;
- break;
case 'T': /* we used to emit T but nobody uses it. */
goto out_stale;
- break;
default:
error("unrecognized: %s", line);
break;
diff --git a/setup.c b/setup.c
index 7e04602..5ea5502 100644
--- a/setup.c
+++ b/setup.c
@@ -4,13 +4,16 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-const char *prefix_path(const char *prefix, int len, const char *path)
+char *prefix_path(const char *prefix, int len, const char *path)
{
const char *orig = path;
- char *sanitized = xmalloc(len + strlen(path) + 1);
- if (is_absolute_path(orig))
- strcpy(sanitized, path);
- else {
+ char *sanitized;
+ if (is_absolute_path(orig)) {
+ const char *temp = real_path(path);
+ sanitized = xmalloc(len + strlen(temp) + 1);
+ strcpy(sanitized, temp);
+ } else {
+ sanitized = xmalloc(len + strlen(path) + 1);
if (len)
memcpy(sanitized, prefix, len);
strcpy(sanitized + len, path);
@@ -46,7 +49,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
{
static char path[PATH_MAX];
#ifndef WIN32
- if (!pfx || !*pfx || is_absolute_path(arg))
+ if (!pfx_len || is_absolute_path(arg))
return arg;
memcpy(path, pfx, pfx_len);
strcpy(path + pfx_len, arg);
@@ -55,7 +58,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
/* don't add prefix to absolute paths, but still replace '\' by '/' */
if (is_absolute_path(arg))
pfx_len = 0;
- else
+ else if (pfx_len)
memcpy(path, pfx, pfx_len);
strcpy(path + pfx_len, arg);
for (p = path + pfx_len; *p; p++)
@@ -82,8 +85,17 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)
{
unsigned char sha1[20];
unsigned mode;
- /* try a detailed diagnostic ... */
- get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
+
+ /*
+ * Saying "'(icase)foo' does not exist in the index" when the
+ * user gave us ":(icase)foo" is just stupid. A magic pathspec
+ * begins with a colon and is followed by a non-alnum; do not
+ * let get_sha1_with_mode_1(only_to_die=1) to even trigger.
+ */
+ if (!(arg[0] == ':' && !isalnum(arg[1])))
+ /* try a detailed diagnostic ... */
+ get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);
+
/* ... or fall back the most general message. */
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
"Use '--' to separate paths from revisions", arg);
@@ -123,6 +135,105 @@ void verify_non_filename(const char *prefix, const char *arg)
"Use '--' to separate filenames from revisions", arg);
}
+/*
+ * Magic pathspec
+ *
+ * NEEDSWORK: These need to be moved to dir.h or even to a new
+ * pathspec.h when we restructure get_pathspec() users to use the
+ * "struct pathspec" interface.
+ *
+ * Possible future magic semantics include stuff like:
+ *
+ * { PATHSPEC_NOGLOB, '!', "noglob" },
+ * { PATHSPEC_ICASE, '\0', "icase" },
+ * { PATHSPEC_RECURSIVE, '*', "recursive" },
+ * { PATHSPEC_REGEXP, '\0', "regexp" },
+ *
+ */
+#define PATHSPEC_FROMTOP (1<<0)
+
+static struct pathspec_magic {
+ unsigned bit;
+ char mnemonic; /* this cannot be ':'! */
+ const char *name;
+} pathspec_magic[] = {
+ { PATHSPEC_FROMTOP, '/', "top" },
+};
+
+/*
+ * Take an element of a pathspec and check for magic signatures.
+ * Append the result to the prefix.
+ *
+ * For now, we only parse the syntax and throw out anything other than
+ * "top" magic.
+ *
+ * NEEDSWORK: This needs to be rewritten when we start migrating
+ * get_pathspec() users to use the "struct pathspec" interface. For
+ * example, a pathspec element may be marked as case-insensitive, but
+ * the prefix part must always match literally, and a single stupid
+ * string cannot express such a case.
+ */
+static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
+{
+ unsigned magic = 0;
+ const char *copyfrom = elt;
+ int i;
+
+ if (elt[0] != ':') {
+ ; /* nothing to do */
+ } else if (elt[1] == '(') {
+ /* longhand */
+ const char *nextat;
+ for (copyfrom = elt + 2;
+ *copyfrom && *copyfrom != ')';
+ copyfrom = nextat) {
+ size_t len = strcspn(copyfrom, ",)");
+ if (copyfrom[len] == ')')
+ nextat = copyfrom + len;
+ else
+ nextat = copyfrom + len + 1;
+ if (!len)
+ continue;
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+ if (strlen(pathspec_magic[i].name) == len &&
+ !strncmp(pathspec_magic[i].name, copyfrom, len)) {
+ magic |= pathspec_magic[i].bit;
+ break;
+ }
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die("Invalid pathspec magic '%.*s' in '%s'",
+ (int) len, copyfrom, elt);
+ }
+ if (*copyfrom == ')')
+ copyfrom++;
+ } else {
+ /* shorthand */
+ for (copyfrom = elt + 1;
+ *copyfrom && *copyfrom != ':';
+ copyfrom++) {
+ char ch = *copyfrom;
+
+ if (!is_pathspec_magic(ch))
+ break;
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+ if (pathspec_magic[i].mnemonic == ch) {
+ magic |= pathspec_magic[i].bit;
+ break;
+ }
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die("Unimplemented pathspec magic '%c' in '%s'",
+ ch, elt);
+ }
+ if (*copyfrom == ':')
+ copyfrom++;
+ }
+
+ if (magic & PATHSPEC_FROMTOP)
+ return xstrdup(copyfrom);
+ else
+ return prefix_path(prefix, prefixlen, copyfrom);
+}
+
const char **get_pathspec(const char *prefix, const char **pathspec)
{
const char *entry = *pathspec;
@@ -144,8 +255,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
dst = pathspec;
prefixlen = prefix ? strlen(prefix) : 0;
while (*src) {
- const char *p = prefix_path(prefix, prefixlen, *src);
- *(dst++) = p;
+ *(dst++) = prefix_pathspec(prefix, prefixlen, *src);
src++;
}
*dst = NULL;
@@ -170,6 +280,8 @@ static int is_git_directory(const char *suspect)
char path[PATH_MAX];
size_t len = strlen(suspect);
+ if (PATH_MAX <= len + strlen("/objects"))
+ die("Too long path: %.*s", 60, suspect);
strcpy(path, suspect);
if (getenv(DB_ENVIRONMENT)) {
if (access(getenv(DB_ENVIRONMENT), X_OK))
@@ -206,24 +318,6 @@ int is_inside_work_tree(void)
return inside_work_tree;
}
-/*
- * set_work_tree() is only ever called if you set GIT_DIR explicitly.
- * The old behaviour (which we retain here) is to set the work tree root
- * to the cwd, unless overridden by the config, the command line, or
- * GIT_WORK_TREE.
- */
-static const char *set_work_tree(const char *dir)
-{
- char buffer[PATH_MAX + 1];
-
- if (!getcwd(buffer, sizeof(buffer)))
- die ("Could not get the current working directory");
- git_work_tree_cfg = xstrdup(buffer);
- inside_work_tree = 1;
-
- return NULL;
-}
-
void setup_work_tree(void)
{
const char *work_tree, *git_dir;
@@ -234,16 +328,36 @@ void setup_work_tree(void)
work_tree = get_git_work_tree();
git_dir = get_git_dir();
if (!is_absolute_path(git_dir))
- git_dir = make_absolute_path(git_dir);
+ git_dir = real_path(get_git_dir());
if (!work_tree || chdir(work_tree))
die("This operation must be run in a work tree");
- set_git_dir(make_relative_path(git_dir, work_tree));
+
+ /*
+ * Make sure subsequent git processes find correct worktree
+ * if $GIT_WORK_TREE is set relative
+ */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT))
+ setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
+
+ set_git_dir(relative_path(git_dir, work_tree));
initialized = 1;
}
-static int check_repository_format_gently(int *nongit_ok)
+static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
{
- git_config(check_repository_format_version, NULL);
+ char repo_config[PATH_MAX+1];
+
+ /*
+ * git_config() can't be used here because it calls git_pathdup()
+ * to get $GIT_CONFIG/config. That call will make setup_git_env()
+ * set git_dir to ".git".
+ *
+ * We are in gitdir setup, no git dir has been found useable yet.
+ * Use a gentler version of git_config() to check if this repo
+ * is a good one.
+ */
+ snprintf(repo_config, PATH_MAX, "%s/config", gitdir);
+ git_config_early(check_repository_format_version, NULL, repo_config);
if (GIT_REPO_VERSION < repository_format_version) {
if (!nongit_ok)
die ("Expected git repo version <= %d, found %d",
@@ -268,7 +382,7 @@ const char *read_gitfile_gently(const char *path)
const char *slash;
struct stat st;
int fd;
- size_t len;
+ ssize_t len;
if (stat(path, &st))
return NULL;
@@ -305,26 +419,207 @@ const char *read_gitfile_gently(const char *path)
if (!is_git_directory(dir))
die("Not a git repository: %s", dir);
- path = make_absolute_path(dir);
+ path = real_path(dir);
free(buf);
return path;
}
+static const char *setup_explicit_git_dir(const char *gitdirenv,
+ char *cwd, int len,
+ int *nongit_ok)
+{
+ const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ const char *worktree;
+ char *gitfile;
+ int offset;
+
+ if (PATH_MAX - 40 < strlen(gitdirenv))
+ die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+
+ gitfile = (char*)read_gitfile_gently(gitdirenv);
+ if (gitfile) {
+ gitfile = xstrdup(gitfile);
+ gitdirenv = gitfile;
+ }
+
+ if (!is_git_directory(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ free(gitfile);
+ return NULL;
+ }
+ die("Not a git repository: '%s'", gitdirenv);
+ }
+
+ if (check_repository_format_gently(gitdirenv, nongit_ok)) {
+ free(gitfile);
+ return NULL;
+ }
+
+ /* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */
+ if (work_tree_env)
+ set_git_work_tree(work_tree_env);
+ else if (is_bare_repository_cfg > 0) {
+ if (git_work_tree_cfg) /* #22.2, #30 */
+ die("core.bare and core.worktree do not make sense");
+
+ /* #18, #26 */
+ set_git_dir(gitdirenv);
+ free(gitfile);
+ return NULL;
+ }
+ else if (git_work_tree_cfg) { /* #6, #14 */
+ if (is_absolute_path(git_work_tree_cfg))
+ set_git_work_tree(git_work_tree_cfg);
+ else {
+ char core_worktree[PATH_MAX];
+ if (chdir(gitdirenv))
+ die_errno("Could not chdir to '%s'", gitdirenv);
+ if (chdir(git_work_tree_cfg))
+ die_errno("Could not chdir to '%s'", git_work_tree_cfg);
+ if (!getcwd(core_worktree, PATH_MAX))
+ die_errno("Could not get directory '%s'", git_work_tree_cfg);
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ set_git_work_tree(core_worktree);
+ }
+ }
+ else /* #2, #10 */
+ set_git_work_tree(".");
+
+ /* set_git_work_tree() must have been called by now */
+ worktree = get_git_work_tree();
+
+ /* both get_git_work_tree() and cwd are already normalized */
+ if (!strcmp(cwd, worktree)) { /* cwd == worktree */
+ set_git_dir(gitdirenv);
+ free(gitfile);
+ return NULL;
+ }
+
+ offset = dir_inside_of(cwd, worktree);
+ if (offset >= 0) { /* cwd inside worktree? */
+ set_git_dir(real_path(gitdirenv));
+ if (chdir(worktree))
+ die_errno("Could not chdir to '%s'", worktree);
+ cwd[len++] = '/';
+ cwd[len] = '\0';
+ free(gitfile);
+ return cwd + offset;
+ }
+
+ /* cwd outside worktree */
+ set_git_dir(gitdirenv);
+ free(gitfile);
+ return NULL;
+}
+
+static const char *setup_discovered_git_dir(const char *gitdir,
+ char *cwd, int offset, int len,
+ int *nongit_ok)
+{
+ if (check_repository_format_gently(gitdir, nongit_ok))
+ return NULL;
+
+ /* --work-tree is set without --git-dir; use discovered one */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
+ if (offset != len && !is_absolute_path(gitdir))
+ gitdir = xstrdup(real_path(gitdir));
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+ }
+
+ /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
+ if (is_bare_repository_cfg > 0) {
+ set_git_dir(offset == len ? gitdir : real_path(gitdir));
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ return NULL;
+ }
+
+ /* #0, #1, #5, #8, #9, #12, #13 */
+ set_git_work_tree(".");
+ if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
+ set_git_dir(gitdir);
+ inside_git_dir = 0;
+ inside_work_tree = 1;
+ if (offset == len)
+ return NULL;
+
+ /* Make "offset" point to past the '/', and add a '/' at the end */
+ offset++;
+ cwd[len++] = '/';
+ cwd[len] = 0;
+ return cwd + offset;
+}
+
+/* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */
+static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongit_ok)
+{
+ int root_len;
+
+ if (check_repository_format_gently(".", nongit_ok))
+ return NULL;
+
+ /* --work-tree is set without --git-dir; use discovered one */
+ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
+ const char *gitdir;
+
+ gitdir = offset == len ? "." : xmemdupz(cwd, offset);
+ if (chdir(cwd))
+ die_errno("Could not come back to cwd");
+ return setup_explicit_git_dir(gitdir, cwd, len, nongit_ok);
+ }
+
+ inside_git_dir = 1;
+ inside_work_tree = 0;
+ if (offset != len) {
+ if (chdir(cwd))
+ die_errno("Cannot come back to cwd");
+ root_len = offset_1st_component(cwd);
+ cwd[offset > root_len ? offset : root_len] = '\0';
+ set_git_dir(cwd);
+ }
+ else
+ set_git_dir(".");
+ return NULL;
+}
+
+static const char *setup_nongit(const char *cwd, int *nongit_ok)
+{
+ if (!nongit_ok)
+ die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
+ if (chdir(cwd))
+ die_errno("Cannot come back to cwd");
+ *nongit_ok = 1;
+ return NULL;
+}
+
+static dev_t get_device_or_die(const char *path, const char *prefix)
+{
+ struct stat buf;
+ if (stat(path, &buf))
+ die_errno("failed to stat '%s%s%s'",
+ prefix ? prefix : "",
+ prefix ? "/" : "", path);
+ return buf.st_dev;
+}
+
/*
* We cannot decide in this function whether we are in the work tree or
* not, since the config can only be read _after_ this function was called.
*/
-const char *setup_git_directory_gently(int *nongit_ok)
+static const char *setup_git_directory_gently_1(int *nongit_ok)
{
- const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
static char cwd[PATH_MAX+1];
- const char *gitdirenv;
- const char *gitfile_dir;
- int len, offset, ceil_offset, root_len;
- int current_device = 0, one_filesystem = 1;
- struct stat buf;
+ const char *gitdirenv, *ret;
+ char *gitfile;
+ int len, offset, ceil_offset;
+ dev_t current_device = 0;
+ int one_filesystem = 1;
/*
* Let's assume that we are in a git repository.
@@ -334,47 +629,18 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (nongit_ok)
*nongit_ok = 0;
+ if (!getcwd(cwd, sizeof(cwd)-1))
+ die_errno("Unable to read current working directory");
+ offset = len = strlen(cwd);
+
/*
* If GIT_DIR is set explicitly, we're not going
* to do any discovery, but we still do repository
* validation.
*/
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
- if (gitdirenv) {
- if (PATH_MAX - 40 < strlen(gitdirenv))
- die("'$%s' too big", GIT_DIR_ENVIRONMENT);
- if (is_git_directory(gitdirenv)) {
- static char buffer[1024 + 1];
- const char *retval;
-
- if (!work_tree_env) {
- retval = set_work_tree(gitdirenv);
- /* config may override worktree */
- if (check_repository_format_gently(nongit_ok))
- return NULL;
- return retval;
- }
- if (check_repository_format_gently(nongit_ok))
- return NULL;
- retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
- get_git_work_tree());
- if (!retval || !*retval)
- return NULL;
- set_git_dir(make_absolute_path(gitdirenv));
- if (chdir(work_tree_env) < 0)
- die_errno ("Could not chdir to '%s'", work_tree_env);
- strcat(buffer, "/");
- return retval;
- }
- if (nongit_ok) {
- *nongit_ok = 1;
- return NULL;
- }
- die("Not a git repository: '%s'", gitdirenv);
- }
-
- if (!getcwd(cwd, sizeof(cwd)-1))
- die_errno("Unable to read current working directory");
+ if (gitdirenv)
+ return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);
ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
@@ -391,51 +657,36 @@ const char *setup_git_directory_gently(int *nongit_ok)
* - ../../.git/
* etc.
*/
- offset = len = strlen(cwd);
one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
- if (one_filesystem) {
- if (stat(".", &buf))
- die_errno("failed to stat '.'");
- current_device = buf.st_dev;
- }
+ if (one_filesystem)
+ current_device = get_device_or_die(".", NULL);
for (;;) {
- gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
- if (gitfile_dir) {
- if (set_git_dir(gitfile_dir))
- die("Repository setup failed");
- break;
+ gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+ if (gitfile)
+ gitdirenv = gitfile = xstrdup(gitfile);
+ else {
+ if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
+ gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
}
- if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
- break;
- if (is_git_directory(".")) {
- inside_git_dir = 1;
- if (!work_tree_env)
- inside_work_tree = 0;
- if (offset != len) {
- root_len = offset_1st_component(cwd);
- cwd[offset > root_len ? offset : root_len] = '\0';
- set_git_dir(cwd);
- } else
- set_git_dir(".");
- check_repository_format_gently(nongit_ok);
- return NULL;
+
+ if (gitdirenv) {
+ ret = setup_discovered_git_dir(gitdirenv,
+ cwd, offset, len,
+ nongit_ok);
+ free(gitfile);
+ return ret;
}
+ free(gitfile);
+
+ if (is_git_directory("."))
+ return setup_bare_git_dir(cwd, offset, len, nongit_ok);
+
while (--offset > ceil_offset && cwd[offset] != '/');
- if (offset <= ceil_offset) {
- if (nongit_ok) {
- if (chdir(cwd))
- die_errno("Cannot come back to cwd");
- *nongit_ok = 1;
- return NULL;
- }
- die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
- }
+ if (offset <= ceil_offset)
+ return setup_nongit(cwd, nongit_ok);
if (one_filesystem) {
- if (stat("..", &buf)) {
- cwd[offset] = '\0';
- die_errno("failed to stat '%s/..'", cwd);
- }
- if (buf.st_dev != current_device) {
+ dev_t parent_device = get_device_or_die("..", cwd);
+ if (parent_device != current_device) {
if (nongit_ok) {
if (chdir(cwd))
die_errno("Cannot come back to cwd");
@@ -452,22 +703,23 @@ const char *setup_git_directory_gently(int *nongit_ok)
die_errno("Cannot change to '%s/..'", cwd);
}
}
+}
- inside_git_dir = 0;
- if (!work_tree_env)
- inside_work_tree = 1;
- root_len = offset_1st_component(cwd);
- git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
- if (check_repository_format_gently(nongit_ok))
- return NULL;
- if (offset == len)
- return NULL;
+const char *setup_git_directory_gently(int *nongit_ok)
+{
+ const char *prefix;
- /* Make "offset" point to past the '/', and add a '/' at the end */
- offset++;
- cwd[len++] = '/';
- cwd[len] = 0;
- return cwd + offset;
+ prefix = setup_git_directory_gently_1(nongit_ok);
+ if (prefix)
+ setenv("GIT_PREFIX", prefix, 1);
+ else
+ setenv("GIT_PREFIX", "", 1);
+
+ if (startup_info) {
+ startup_info->have_repository = !nongit_ok || !*nongit_ok;
+ startup_info->prefix = prefix;
+ }
+ return prefix;
}
int git_config_perm(const char *var, const char *value)
@@ -543,7 +795,7 @@ int check_repository_format_version(const char *var, const char *value, void *cb
int check_repository_format(void)
{
- return check_repository_format_gently(NULL);
+ return check_repository_format_gently(get_git_dir(), NULL);
}
/*
@@ -554,19 +806,5 @@ int check_repository_format(void)
*/
const char *setup_git_directory(void)
{
- const char *retval = setup_git_directory_gently(NULL);
-
- /* If the work tree is not the default one, recompute prefix */
- if (inside_work_tree < 0) {
- static char buffer[PATH_MAX + 1];
- char *rel;
- if (retval && chdir(retval))
- die_errno ("Could not jump back into original cwd");
- rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
- if (rel && *rel && chdir(get_git_work_tree()))
- die_errno ("Could not jump to working directory");
- return rel && *rel ? strcat(rel, "/") : NULL;
- }
-
- return retval;
+ return setup_git_directory_gently(NULL);
}
diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c
new file mode 100644
index 0000000..5ddd688
--- /dev/null
+++ b/sh-i18n--envsubst.c
@@ -0,0 +1,444 @@
+/*
+ * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1)
+ *
+ * Copyright (C) 2010 Ævar Arnfjörð Bjarmason
+ *
+ * This is a modified version of
+ * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git
+ * repository. It has been stripped down to only implement the
+ * envsubst(1) features that we need in the git-sh-i18n fallbacks.
+ *
+ * The "Close standard error" part in main() is from
+ * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for
+ * both files are reproduced immediately below.
+ */
+
+#include "git-compat-util.h"
+
+/* Substitution of environment variables in shell format strings.
+ Copyright (C) 2003-2007 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2003.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+/* closeout.c - close standard output and standard error
+ Copyright (C) 1998-2007 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ 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, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* If true, substitution shall be performed on all variables. */
+static unsigned short int all_variables;
+
+/* Forward declaration of local functions. */
+static void print_variables (const char *string);
+static void note_variables (const char *string);
+static void subst_from_stdin (void);
+
+int
+main (int argc, char *argv[])
+{
+ /* Default values for command line options. */
+ /* unsigned short int show_variables = 0; */
+
+ switch (argc)
+ {
+ case 1:
+ error ("we won't substitute all variables on stdin for you");
+ break;
+ /*
+ all_variables = 1;
+ subst_from_stdin ();
+ */
+ case 2:
+ /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */
+ all_variables = 0;
+ note_variables (argv[1]);
+ subst_from_stdin ();
+ break;
+ case 3:
+ /* git sh-i18n--envsubst --variables '$foo and $bar' */
+ if (strcmp(argv[1], "--variables"))
+ error ("first argument must be --variables when two are given");
+ /* show_variables = 1; */
+ print_variables (argv[2]);
+ break;
+ default:
+ error ("too many arguments");
+ break;
+ }
+
+ /* Close standard error. This is simpler than fwriteerror_no_ebadf, because
+ upon failure we don't need an errno - all we can do at this point is to
+ set an exit status. */
+ errno = 0;
+ if (ferror (stderr) || fflush (stderr))
+ {
+ fclose (stderr);
+ exit (EXIT_FAILURE);
+ }
+ if (fclose (stderr) && errno != EBADF)
+ exit (EXIT_FAILURE);
+
+ exit (EXIT_SUCCESS);
+}
+
+/* Parse the string and invoke the callback each time a $VARIABLE or
+ ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
+ of ASCII alphanumeric/underscore characters, starting with an ASCII
+ alphabetic/underscore character.
+ We allow only ASCII characters, to avoid dependencies w.r.t. the current
+ encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
+ encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
+ SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
+ encodings. */
+static void
+find_variables (const char *string,
+ void (*callback) (const char *var_ptr, size_t var_len))
+{
+ for (; *string != '\0';)
+ if (*string++ == '$')
+ {
+ const char *variable_start;
+ const char *variable_end;
+ unsigned short int valid;
+ char c;
+
+ if (*string == '{')
+ string++;
+
+ variable_start = string;
+ c = *string;
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
+ {
+ do
+ c = *++string;
+ while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9') || c == '_');
+ variable_end = string;
+
+ if (variable_start[-1] == '{')
+ {
+ if (*string == '}')
+ {
+ string++;
+ valid = 1;
+ }
+ else
+ valid = 0;
+ }
+ else
+ valid = 1;
+
+ if (valid)
+ callback (variable_start, variable_end - variable_start);
+ }
+ }
+}
+
+
+/* Print a variable to stdout, followed by a newline. */
+static void
+print_variable (const char *var_ptr, size_t var_len)
+{
+ fwrite (var_ptr, var_len, 1, stdout);
+ putchar ('\n');
+}
+
+/* Print the variables contained in STRING to stdout, each one followed by a
+ newline. */
+static void
+print_variables (const char *string)
+{
+ find_variables (string, &print_variable);
+}
+
+
+/* Type describing list of immutable strings,
+ implemented using a dynamic array. */
+typedef struct string_list_ty string_list_ty;
+struct string_list_ty
+{
+ const char **item;
+ size_t nitems;
+ size_t nitems_max;
+};
+
+/* Initialize an empty list of strings. */
+static inline void
+string_list_init (string_list_ty *slp)
+{
+ slp->item = NULL;
+ slp->nitems = 0;
+ slp->nitems_max = 0;
+}
+
+/* Append a single string to the end of a list of strings. */
+static inline void
+string_list_append (string_list_ty *slp, const char *s)
+{
+ /* Grow the list. */
+ if (slp->nitems >= slp->nitems_max)
+ {
+ size_t nbytes;
+
+ slp->nitems_max = slp->nitems_max * 2 + 4;
+ nbytes = slp->nitems_max * sizeof (slp->item[0]);
+ slp->item = (const char **) xrealloc (slp->item, nbytes);
+ }
+
+ /* Add the string to the end of the list. */
+ slp->item[slp->nitems++] = s;
+}
+
+/* Compare two strings given by reference. */
+static int
+cmp_string (const void *pstr1, const void *pstr2)
+{
+ const char *str1 = *(const char **)pstr1;
+ const char *str2 = *(const char **)pstr2;
+
+ return strcmp (str1, str2);
+}
+
+/* Sort a list of strings. */
+static inline void
+string_list_sort (string_list_ty *slp)
+{
+ if (slp->nitems > 0)
+ qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
+}
+
+/* Test whether a string list contains a given string. */
+static inline int
+string_list_member (const string_list_ty *slp, const char *s)
+{
+ size_t j;
+
+ for (j = 0; j < slp->nitems; ++j)
+ if (strcmp (slp->item[j], s) == 0)
+ return 1;
+ return 0;
+}
+
+/* Test whether a sorted string list contains a given string. */
+static int
+sorted_string_list_member (const string_list_ty *slp, const char *s)
+{
+ size_t j1, j2;
+
+ j1 = 0;
+ j2 = slp->nitems;
+ if (j2 > 0)
+ {
+ /* Binary search. */
+ while (j2 - j1 > 1)
+ {
+ /* Here we know that if s is in the list, it is at an index j
+ with j1 <= j < j2. */
+ size_t j = (j1 + j2) >> 1;
+ int result = strcmp (slp->item[j], s);
+
+ if (result > 0)
+ j2 = j;
+ else if (result == 0)
+ return 1;
+ else
+ j1 = j + 1;
+ }
+ if (j2 > j1)
+ if (strcmp (slp->item[j1], s) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+
+/* Set of variables on which to perform substitution.
+ Used only if !all_variables. */
+static string_list_ty variables_set;
+
+/* Adds a variable to variables_set. */
+static void
+note_variable (const char *var_ptr, size_t var_len)
+{
+ char *string = xmalloc (var_len + 1);
+ memcpy (string, var_ptr, var_len);
+ string[var_len] = '\0';
+
+ string_list_append (&variables_set, string);
+}
+
+/* Stores the variables occurring in the string in variables_set. */
+static void
+note_variables (const char *string)
+{
+ string_list_init (&variables_set);
+ find_variables (string, &note_variable);
+ string_list_sort (&variables_set);
+}
+
+
+static int
+do_getc (void)
+{
+ int c = getc (stdin);
+
+ if (c == EOF)
+ {
+ if (ferror (stdin))
+ error ("error while reading standard input");
+ }
+
+ return c;
+}
+
+static inline void
+do_ungetc (int c)
+{
+ if (c != EOF)
+ ungetc (c, stdin);
+}
+
+/* Copies stdin to stdout, performing substitutions. */
+static void
+subst_from_stdin (void)
+{
+ static char *buffer;
+ static size_t bufmax;
+ static size_t buflen;
+ int c;
+
+ for (;;)
+ {
+ c = do_getc ();
+ if (c == EOF)
+ break;
+ /* Look for $VARIABLE or ${VARIABLE}. */
+ if (c == '$')
+ {
+ unsigned short int opening_brace = 0;
+ unsigned short int closing_brace = 0;
+
+ c = do_getc ();
+ if (c == '{')
+ {
+ opening_brace = 1;
+ c = do_getc ();
+ }
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
+ {
+ unsigned short int valid;
+
+ /* Accumulate the VARIABLE in buffer. */
+ buflen = 0;
+ do
+ {
+ if (buflen >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[buflen++] = c;
+
+ c = do_getc ();
+ }
+ while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9') || c == '_');
+
+ if (opening_brace)
+ {
+ if (c == '}')
+ {
+ closing_brace = 1;
+ valid = 1;
+ }
+ else
+ {
+ valid = 0;
+ do_ungetc (c);
+ }
+ }
+ else
+ {
+ valid = 1;
+ do_ungetc (c);
+ }
+
+ if (valid)
+ {
+ /* Terminate the variable in the buffer. */
+ if (buflen >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[buflen] = '\0';
+
+ /* Test whether the variable shall be substituted. */
+ if (!all_variables
+ && !sorted_string_list_member (&variables_set, buffer))
+ valid = 0;
+ }
+
+ if (valid)
+ {
+ /* Substitute the variable's value from the environment. */
+ const char *env_value = getenv (buffer);
+
+ if (env_value != NULL)
+ fputs (env_value, stdout);
+ }
+ else
+ {
+ /* Perform no substitution at all. Since the buffered input
+ contains no other '$' than at the start, we can just
+ output all the buffered contents. */
+ putchar ('$');
+ if (opening_brace)
+ putchar ('{');
+ fwrite (buffer, buflen, 1, stdout);
+ if (closing_brace)
+ putchar ('}');
+ }
+ }
+ else
+ {
+ do_ungetc (c);
+ putchar ('$');
+ if (opening_brace)
+ putchar ('{');
+ }
+ }
+ else
+ putchar (c);
+ }
+}
diff --git a/sha1-array.c b/sha1-array.c
new file mode 100644
index 0000000..b2f47f9
--- /dev/null
+++ b/sha1-array.c
@@ -0,0 +1,59 @@
+#include "cache.h"
+#include "sha1-array.h"
+#include "sha1-lookup.h"
+
+void sha1_array_append(struct sha1_array *array, const unsigned char *sha1)
+{
+ ALLOC_GROW(array->sha1, array->nr + 1, array->alloc);
+ hashcpy(array->sha1[array->nr++], sha1);
+ array->sorted = 0;
+}
+
+static int void_hashcmp(const void *a, const void *b)
+{
+ return hashcmp(a, b);
+}
+
+void sha1_array_sort(struct sha1_array *array)
+{
+ qsort(array->sha1, array->nr, sizeof(*array->sha1), void_hashcmp);
+ array->sorted = 1;
+}
+
+static const unsigned char *sha1_access(size_t index, void *table)
+{
+ unsigned char (*array)[20] = table;
+ return array[index];
+}
+
+int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1)
+{
+ if (!array->sorted)
+ sha1_array_sort(array);
+ return sha1_pos(sha1, array->sha1, array->nr, sha1_access);
+}
+
+void sha1_array_clear(struct sha1_array *array)
+{
+ free(array->sha1);
+ array->sha1 = NULL;
+ array->nr = 0;
+ array->alloc = 0;
+ array->sorted = 0;
+}
+
+void sha1_array_for_each_unique(struct sha1_array *array,
+ for_each_sha1_fn fn,
+ void *data)
+{
+ int i;
+
+ if (!array->sorted)
+ sha1_array_sort(array);
+
+ for (i = 0; i < array->nr; i++) {
+ if (i > 0 && !hashcmp(array->sha1[i], array->sha1[i-1]))
+ continue;
+ fn(array->sha1[i], data);
+ }
+}
diff --git a/sha1-array.h b/sha1-array.h
new file mode 100644
index 0000000..4499b5d
--- /dev/null
+++ b/sha1-array.h
@@ -0,0 +1,24 @@
+#ifndef SHA1_ARRAY_H
+#define SHA1_ARRAY_H
+
+struct sha1_array {
+ unsigned char (*sha1)[20];
+ int nr;
+ int alloc;
+ int sorted;
+};
+
+#define SHA1_ARRAY_INIT { NULL, 0, 0, 0 }
+
+void sha1_array_append(struct sha1_array *array, const unsigned char *sha1);
+void sha1_array_sort(struct sha1_array *array);
+int sha1_array_lookup(struct sha1_array *array, const unsigned char *sha1);
+void sha1_array_clear(struct sha1_array *array);
+
+typedef void (*for_each_sha1_fn)(const unsigned char sha1[20],
+ void *data);
+void sha1_array_for_each_unique(struct sha1_array *array,
+ for_each_sha1_fn fn,
+ void *data);
+
+#endif /* SHA1_ARRAY_H */
diff --git a/sha1_file.c b/sha1_file.c
index e42ef96..89d7e5e 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -11,8 +11,10 @@
#include "pack.h"
#include "blob.h"
#include "commit.h"
+#include "run-command.h"
#include "tag.h"
#include "tree.h"
+#include "tree-walk.h"
#include "refs.h"
#include "pack-revindex.h"
#include "sha1-lookup.h"
@@ -25,16 +27,75 @@
#endif
#endif
-#ifdef NO_C99_FORMAT
-#define SZ_FMT "lu"
-static unsigned long sz_fmt(size_t s) { return (unsigned long)s; }
-#else
-#define SZ_FMT "zu"
-static size_t sz_fmt(size_t s) { return s; }
-#endif
+#define SZ_FMT PRIuMAX
+static inline uintmax_t sz_fmt(size_t s) { return s; }
const unsigned char null_sha1[20];
+/*
+ * This is meant to hold a *small* number of objects that you would
+ * want read_sha1_file() to be able to return, but yet you do not want
+ * to write them into the object store (e.g. a browse-only
+ * application).
+ */
+static struct cached_object {
+ unsigned char sha1[20];
+ enum object_type type;
+ void *buf;
+ unsigned long size;
+} *cached_objects;
+static int cached_object_nr, cached_object_alloc;
+
+static struct cached_object empty_tree = {
+ EMPTY_TREE_SHA1_BIN_LITERAL,
+ OBJ_TREE,
+ "",
+ 0
+};
+
+static struct cached_object *find_cached_object(const unsigned char *sha1)
+{
+ int i;
+ struct cached_object *co = cached_objects;
+
+ for (i = 0; i < cached_object_nr; i++, co++) {
+ if (!hashcmp(co->sha1, sha1))
+ return co;
+ }
+ if (!hashcmp(sha1, empty_tree.sha1))
+ return &empty_tree;
+ return NULL;
+}
+
+int mkdir_in_gitdir(const char *path)
+{
+ if (mkdir(path, 0777)) {
+ int saved_errno = errno;
+ struct stat st;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (errno != EEXIST)
+ return -1;
+ /*
+ * Are we looking at a path in a symlinked worktree
+ * whose original repository does not yet have it?
+ * e.g. .git/rr-cache pointing at its original
+ * repository in which the user hasn't performed any
+ * conflict resolution yet?
+ */
+ if (lstat(path, &st) || !S_ISLNK(st.st_mode) ||
+ strbuf_readlink(&sb, path, st.st_size) ||
+ !is_absolute_path(sb.buf) ||
+ mkdir(sb.buf, 0777)) {
+ strbuf_release(&sb);
+ errno = saved_errno;
+ return -1;
+ }
+ strbuf_release(&sb);
+ }
+ return adjust_shared_perm(path);
+}
+
int safe_create_leading_directories(char *path)
{
char *pos = path + offset_1st_component(path);
@@ -165,6 +226,7 @@ struct alternate_object_database *alt_odb_list;
static struct alternate_object_database **alt_odb_tail;
static void read_info_alternates(const char * alternates, int depth);
+static int git_open_noatime(const char *name);
/*
* Prepare alternate object database registry.
@@ -298,7 +360,7 @@ static void read_info_alternates(const char * relative_base, int depth)
int fd;
sprintf(path, "%s/%s", relative_base, alt_file_name);
- fd = open(path, O_RDONLY);
+ fd = git_open_noatime(path);
if (fd < 0)
return;
if (fstat(fd, &st) || (st.st_size == 0)) {
@@ -380,6 +442,8 @@ static unsigned int pack_used_ctr;
static unsigned int pack_mmap_calls;
static unsigned int peak_pack_open_windows;
static unsigned int pack_open_windows;
+static unsigned int pack_open_fds;
+static unsigned int pack_max_fds;
static size_t peak_pack_mapped;
static size_t pack_mapped;
struct packed_git *packed_git;
@@ -411,7 +475,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
struct pack_idx_header *hdr;
size_t idx_size;
uint32_t version, nr, i, *index;
- int fd = open(path, O_RDONLY);
+ int fd = git_open_noatime(path);
struct stat st;
if (fd < 0)
@@ -557,8 +621,10 @@ static int unuse_one_window(struct packed_git *current, int keep_fd)
lru_l->next = lru_w->next;
else {
lru_p->windows = lru_w->next;
- if (!lru_p->windows && lru_p->pack_fd != keep_fd) {
+ if (!lru_p->windows && lru_p->pack_fd != -1
+ && lru_p->pack_fd != keep_fd) {
close(lru_p->pack_fd);
+ pack_open_fds--;
lru_p->pack_fd = -1;
}
}
@@ -576,6 +642,21 @@ void release_pack_memory(size_t need, int fd)
; /* nothing */
}
+void *xmmap(void *start, size_t length,
+ int prot, int flags, int fd, off_t offset)
+{
+ void *ret = mmap(start, length, prot, flags, fd, offset);
+ if (ret == MAP_FAILED) {
+ if (!length)
+ return NULL;
+ release_pack_memory(length, fd);
+ ret = mmap(start, length, prot, flags, fd, offset);
+ if (ret == MAP_FAILED)
+ die_errno("Out of memory? mmap failed");
+ }
+ return ret;
+}
+
void close_pack_windows(struct packed_git *p)
{
while (p->windows) {
@@ -628,8 +709,10 @@ void free_pack_by_name(const char *pack_name)
if (strcmp(pack_name, p->pack_name) == 0) {
clear_delta_base_cache();
close_pack_windows(p);
- if (p->pack_fd != -1)
+ if (p->pack_fd != -1) {
close(p->pack_fd);
+ pack_open_fds--;
+ }
close_pack_index(p);
free(p->bad_object_sha1);
*pp = p->next;
@@ -655,11 +738,29 @@ static int open_packed_git_1(struct packed_git *p)
if (!p->index_data && open_pack_index(p))
return error("packfile %s index unavailable", p->pack_name);
- p->pack_fd = open(p->pack_name, O_RDONLY);
- while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
- p->pack_fd = open(p->pack_name, O_RDONLY);
+ if (!pack_max_fds) {
+ struct rlimit lim;
+ unsigned int max_fds;
+
+ if (getrlimit(RLIMIT_NOFILE, &lim))
+ die_errno("cannot get RLIMIT_NOFILE");
+
+ max_fds = lim.rlim_cur;
+
+ /* Save 3 for stdin/stdout/stderr, 22 for work */
+ if (25 < max_fds)
+ pack_max_fds = max_fds - 25;
+ else
+ pack_max_fds = 1;
+ }
+
+ while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1))
+ ; /* nothing */
+
+ p->pack_fd = git_open_noatime(p->pack_name);
if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
return -1;
+ pack_open_fds++;
/* If we created the struct before we had the pack we lack size. */
if (!p->pack_size) {
@@ -711,6 +812,7 @@ static int open_packed_git(struct packed_git *p)
return 0;
if (p->pack_fd != -1) {
close(p->pack_fd);
+ pack_open_fds--;
p->pack_fd = -1;
}
return -1;
@@ -732,18 +834,17 @@ static int in_window(struct pack_window *win, off_t offset)
unsigned char *use_pack(struct packed_git *p,
struct pack_window **w_cursor,
off_t offset,
- unsigned int *left)
+ unsigned long *left)
{
struct pack_window *win = *w_cursor;
- if (p->pack_fd == -1 && open_packed_git(p))
- die("packfile %s cannot be accessed", p->pack_name);
-
/* Since packfiles end in a hash of their content and it's
* pointless to ask for an offset into the middle of that
* hash, and the in_window function above wouldn't match
* don't allow an offset too close to the end of the file.
*/
+ if (!p->pack_size && p->pack_fd == -1 && open_packed_git(p))
+ die("packfile %s cannot be accessed", p->pack_name);
if (offset > (p->pack_size - 20))
die("offset beyond end of packfile (truncated pack?)");
@@ -757,6 +858,10 @@ unsigned char *use_pack(struct packed_git *p,
if (!win) {
size_t window_align = packed_git_window_size / 2;
off_t len;
+
+ if (p->pack_fd == -1 && open_packed_git(p))
+ die("packfile %s cannot be accessed", p->pack_name);
+
win = xcalloc(1, sizeof(*win));
win->offset = (offset / window_align) * window_align;
len = p->pack_size - win->offset;
@@ -774,6 +879,12 @@ unsigned char *use_pack(struct packed_git *p,
die("packfile %s cannot be mapped: %s",
p->pack_name,
strerror(errno));
+ if (!win->offset && win->len == p->pack_size
+ && !p->do_not_close) {
+ close(p->pack_fd);
+ pack_open_fds--;
+ p->pack_fd = -1;
+ }
pack_mmap_calls++;
pack_open_windows++;
if (pack_mapped > peak_pack_mapped)
@@ -803,11 +914,22 @@ static struct packed_git *alloc_packed_git(int extra)
return p;
}
+static void try_to_free_pack_memory(size_t size)
+{
+ release_pack_memory(size, -1);
+}
+
struct packed_git *add_packed_git(const char *path, int path_len, int local)
{
+ static int have_set_try_to_free_routine;
struct stat st;
struct packed_git *p = alloc_packed_git(path_len + 2);
+ if (!have_set_try_to_free_routine) {
+ have_set_try_to_free_routine = 1;
+ set_try_to_free_routine(try_to_free_pack_memory);
+ }
+
/*
* Make sure a corresponding .pack file exists and that
* the index looks sane.
@@ -857,6 +979,9 @@ struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path)
void install_packed_git(struct packed_git *pack)
{
+ if (pack->pack_fd != -1)
+ pack_open_fds++;
+
pack->next = packed_git;
packed_git = pack;
}
@@ -874,8 +999,6 @@ static void prepare_packed_git_one(char *objdir, int local)
sprintf(path, "%s/pack", objdir);
len = strlen(path);
dir = opendir(path);
- while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
- dir = opendir(path);
if (!dir) {
if (errno != ENOENT)
error("unable to open object pack directory: %s: %s",
@@ -1003,7 +1126,7 @@ static void mark_bad_packed_object(struct packed_git *p,
p->num_bad_objects++;
}
-static int has_packed_and_bad(const unsigned char *sha1)
+static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
{
struct packed_git *p;
unsigned i;
@@ -1011,8 +1134,8 @@ static int has_packed_and_bad(const unsigned char *sha1)
for (p = packed_git; p; p = p->next)
for (i = 0; i < p->num_bad_objects; i++)
if (!hashcmp(sha1, p->bad_object_sha1 + 20 * i))
- return 1;
- return 0;
+ return p;
+ return NULL;
}
int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
@@ -1025,15 +1148,20 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz
static int git_open_noatime(const char *name)
{
static int sha1_file_open_flag = O_NOATIME;
- int fd = open(name, O_RDONLY | sha1_file_open_flag);
- /* Might the failure be due to O_NOATIME? */
- if (fd < 0 && errno != ENOENT && sha1_file_open_flag) {
- fd = open(name, O_RDONLY);
+ for (;;) {
+ int fd = open(name, O_RDONLY | sha1_file_open_flag);
if (fd >= 0)
+ return fd;
+
+ /* Might the failure be due to O_NOATIME? */
+ if (errno != ENOENT && sha1_file_open_flag) {
sha1_file_open_flag = 0;
+ continue;
+ }
+
+ return -1;
}
- return fd;
}
static int open_sha1_file(const unsigned char *sha1)
@@ -1058,7 +1186,7 @@ static int open_sha1_file(const unsigned char *sha1)
return -1;
}
-static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
{
void *map;
int fd;
@@ -1077,20 +1205,29 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
return map;
}
-static int legacy_loose_object(unsigned char *map)
+/*
+ * There used to be a second loose object header format which
+ * was meant to mimic the in-pack format, allowing for direct
+ * copy of the object data. This format turned up not to be
+ * really worth it and we no longer write loose objects in that
+ * format.
+ */
+static int experimental_loose_object(unsigned char *map)
{
unsigned int word;
/*
* Is it a zlib-compressed buffer? If so, the first byte
* must be 0x78 (15-bit window size, deflated), and the
- * first 16-bit word is evenly divisible by 31
+ * first 16-bit word is evenly divisible by 31. If so,
+ * we are looking at the official format, not the experimental
+ * one.
*/
word = (map[0] << 8) + map[1];
if (map[0] == 0x78 && !(word % 31))
- return 1;
- else
return 0;
+ else
+ return 1;
}
unsigned long unpack_object_header_buffer(const unsigned char *buf,
@@ -1117,7 +1254,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
return used;
}
-static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
{
unsigned long size, used;
static const char valid_loose_object_type[8] = {
@@ -1134,37 +1271,32 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
stream->next_out = buffer;
stream->avail_out = bufsiz;
- if (legacy_loose_object(map)) {
- git_inflate_init(stream);
- return git_inflate(stream, 0);
- }
-
+ if (experimental_loose_object(map)) {
+ /*
+ * The old experimental format we no longer produce;
+ * we can still read it.
+ */
+ used = unpack_object_header_buffer(map, mapsize, &type, &size);
+ if (!used || !valid_loose_object_type[type])
+ return -1;
+ map += used;
+ mapsize -= used;
- /*
- * There used to be a second loose object header format which
- * was meant to mimic the in-pack format, allowing for direct
- * copy of the object data. This format turned up not to be
- * really worth it and we don't write it any longer. But we
- * can still read it.
- */
- used = unpack_object_header_buffer(map, mapsize, &type, &size);
- if (!used || !valid_loose_object_type[type])
- return -1;
- map += used;
- mapsize -= used;
+ /* Set up the stream for the rest.. */
+ stream->next_in = map;
+ stream->avail_in = mapsize;
+ git_inflate_init(stream);
- /* Set up the stream for the rest.. */
- stream->next_in = map;
- stream->avail_in = mapsize;
+ /* And generate the fake traditional header */
+ stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
+ typename(type), size);
+ return 0;
+ }
git_inflate_init(stream);
-
- /* And generate the fake traditional header */
- stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
- typename(type), size);
- return 0;
+ return git_inflate(stream, 0);
}
-static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
+static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
{
int bytes = strlen(buffer) + 1;
unsigned char *buf = xmallocz(size);
@@ -1180,7 +1312,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
/*
* The above condition must be (bytes <= size), not
* (bytes < size). In other words, even though we
- * expect no more output and set avail_out to zer0,
+ * expect no more output and set avail_out to zero,
* the input zlib stream may have bytes that express
* "this concludes the stream", and we *do* want to
* eat that input.
@@ -1214,7 +1346,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-static int parse_sha1_header(const char *hdr, unsigned long *sizep)
+int parse_sha1_header(const char *hdr, unsigned long *sizep)
{
char type[10];
int i;
@@ -1263,7 +1395,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep)
static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
{
int ret;
- z_stream stream;
+ git_zstream stream;
char hdr[8192];
ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
@@ -1279,7 +1411,7 @@ unsigned long get_size_from_delta(struct packed_git *p,
{
const unsigned char *data;
unsigned char delta_head[20], *in;
- z_stream stream;
+ git_zstream stream;
int st;
memset(&stream, 0, sizeof(stream));
@@ -1353,7 +1485,7 @@ static off_t get_delta_base(struct packed_git *p,
/* forward declaration for a mutually recursive function */
static int packed_object_info(struct packed_git *p, off_t offset,
- unsigned long *sizep);
+ unsigned long *sizep, int *rtype);
static int packed_delta_info(struct packed_git *p,
struct pack_window **w_curs,
@@ -1367,7 +1499,7 @@ static int packed_delta_info(struct packed_git *p,
base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
if (!base_offset)
return OBJ_BAD;
- type = packed_object_info(p, base_offset, NULL);
+ type = packed_object_info(p, base_offset, NULL, NULL);
if (type <= OBJ_NONE) {
struct revindex_entry *revidx;
const unsigned char *base_sha1;
@@ -1395,18 +1527,18 @@ static int packed_delta_info(struct packed_git *p,
return type;
}
-static int unpack_object_header(struct packed_git *p,
- struct pack_window **w_curs,
- off_t *curpos,
- unsigned long *sizep)
+int unpack_object_header(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t *curpos,
+ unsigned long *sizep)
{
unsigned char *base;
- unsigned int left;
+ unsigned long left;
unsigned long used;
enum object_type type;
/* use_pack() assures us we have [base, base + 20) available
- * as a range that we can look at at. (Its actually the hash
+ * as a range that we can look at. (Its actually the hash
* size that is assured.) With our object header encoding
* the maximum deflated object size is 2^137, which is just
* insane, so we know won't exceed what we have been given.
@@ -1421,63 +1553,8 @@ static int unpack_object_header(struct packed_git *p,
return type;
}
-const char *packed_object_info_detail(struct packed_git *p,
- off_t obj_offset,
- unsigned long *size,
- unsigned long *store_size,
- unsigned int *delta_chain_length,
- unsigned char *base_sha1)
-{
- struct pack_window *w_curs = NULL;
- off_t curpos;
- unsigned long dummy;
- unsigned char *next_sha1;
- enum object_type type;
- struct revindex_entry *revidx;
-
- *delta_chain_length = 0;
- curpos = obj_offset;
- type = unpack_object_header(p, &w_curs, &curpos, size);
-
- revidx = find_pack_revindex(p, obj_offset);
- *store_size = revidx[1].offset - obj_offset;
-
- for (;;) {
- switch (type) {
- default:
- die("pack %s contains unknown object type %d",
- p->pack_name, type);
- case OBJ_COMMIT:
- case OBJ_TREE:
- case OBJ_BLOB:
- case OBJ_TAG:
- unuse_pack(&w_curs);
- return typename(type);
- case OBJ_OFS_DELTA:
- obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
- if (!obj_offset)
- die("pack %s contains bad delta base reference of type %s",
- p->pack_name, typename(type));
- if (*delta_chain_length == 0) {
- revidx = find_pack_revindex(p, obj_offset);
- hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr));
- }
- break;
- case OBJ_REF_DELTA:
- next_sha1 = use_pack(p, &w_curs, curpos, NULL);
- if (*delta_chain_length == 0)
- hashcpy(base_sha1, next_sha1);
- obj_offset = find_pack_entry_one(next_sha1, p);
- break;
- }
- (*delta_chain_length)++;
- curpos = obj_offset;
- type = unpack_object_header(p, &w_curs, &curpos, &dummy);
- }
-}
-
static int packed_object_info(struct packed_git *p, off_t obj_offset,
- unsigned long *sizep)
+ unsigned long *sizep, int *rtype)
{
struct pack_window *w_curs = NULL;
unsigned long size;
@@ -1485,6 +1562,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset,
enum object_type type;
type = unpack_object_header(p, &w_curs, &curpos, &size);
+ if (rtype)
+ *rtype = type; /* representation type */
switch (type) {
case OBJ_OFS_DELTA:
@@ -1514,7 +1593,7 @@ static void *unpack_compressed_entry(struct packed_git *p,
unsigned long size)
{
int st;
- z_stream stream;
+ git_zstream stream;
unsigned char *buffer, *in;
buffer = xmallocz(size);
@@ -1567,6 +1646,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
return hash % MAX_DELTA_CACHE;
}
+static int in_delta_base_cache(struct packed_git *p, off_t base_offset)
+{
+ unsigned long hash = pack_entry_hash(p, base_offset);
+ struct delta_base_cache_entry *ent = delta_base_cache + hash;
+ return (ent->data && ent->p == p && ent->base_offset == base_offset);
+}
+
static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
unsigned long *base_size, enum object_type *type, int keep_cache)
{
@@ -1857,6 +1943,27 @@ off_t find_pack_entry_one(const unsigned char *sha1,
return 0;
}
+static int is_pack_valid(struct packed_git *p)
+{
+ /* An already open pack is known to be valid. */
+ if (p->pack_fd != -1)
+ return 1;
+
+ /* If the pack has one window completely covering the
+ * file size, the pack is known to be valid even if
+ * the descriptor is not currently open.
+ */
+ if (p->windows) {
+ struct pack_window *w = p->windows;
+
+ if (!w->offset && w->len == p->pack_size)
+ return 1;
+ }
+
+ /* Force the pack to open to prove its valid. */
+ return !open_packed_git(p);
+}
+
static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
{
static struct packed_git *last_found = (void *)1;
@@ -1886,7 +1993,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
* it may have been deleted since the index
* was loaded!
*/
- if (p->pack_fd == -1 && open_packed_git(p)) {
+ if (!is_pack_valid(p)) {
error("packfile %s cannot be accessed", p->pack_name);
goto next;
}
@@ -1926,7 +2033,7 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
int status;
unsigned long mapsize, size;
void *map;
- z_stream stream;
+ git_zstream stream;
char hdr[32];
map = map_sha1_file(sha1, &mapsize);
@@ -1944,16 +2051,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size
return status;
}
-int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+/* returns enum object_type or negative */
+int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi)
{
+ struct cached_object *co;
struct pack_entry e;
- int status;
+ int status, rtype;
+
+ co = find_cached_object(sha1);
+ if (co) {
+ if (oi->sizep)
+ *(oi->sizep) = co->size;
+ oi->whence = OI_CACHED;
+ return co->type;
+ }
if (!find_pack_entry(sha1, &e)) {
/* Most likely it's a loose object. */
- status = sha1_loose_object_info(sha1, sizep);
- if (status >= 0)
+ status = sha1_loose_object_info(sha1, oi->sizep);
+ if (status >= 0) {
+ oi->whence = OI_LOOSE;
return status;
+ }
/* Not a loose object; someone else may have just packed it. */
reprepare_packed_git();
@@ -1961,15 +2080,31 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
return status;
}
- status = packed_object_info(e.p, e.offset, sizep);
+ status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
if (status < 0) {
mark_bad_packed_object(e.p, sha1);
- status = sha1_object_info(sha1, sizep);
+ status = sha1_object_info_extended(sha1, oi);
+ } else if (in_delta_base_cache(e.p, e.offset)) {
+ oi->whence = OI_DBCACHED;
+ } else {
+ oi->whence = OI_PACKED;
+ oi->u.packed.offset = e.offset;
+ oi->u.packed.pack = e.p;
+ oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA ||
+ rtype == OBJ_OFS_DELTA);
}
return status;
}
+int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
+{
+ struct object_info oi;
+
+ oi.sizep = sizep;
+ return sha1_object_info_extended(sha1, &oi);
+}
+
static void *read_packed_sha1(const unsigned char *sha1,
enum object_type *type, unsigned long *size)
{
@@ -1994,41 +2129,6 @@ static void *read_packed_sha1(const unsigned char *sha1,
return data;
}
-/*
- * This is meant to hold a *small* number of objects that you would
- * want read_sha1_file() to be able to return, but yet you do not want
- * to write them into the object store (e.g. a browse-only
- * application).
- */
-static struct cached_object {
- unsigned char sha1[20];
- enum object_type type;
- void *buf;
- unsigned long size;
-} *cached_objects;
-static int cached_object_nr, cached_object_alloc;
-
-static struct cached_object empty_tree = {
- EMPTY_TREE_SHA1_BIN,
- OBJ_TREE,
- "",
- 0
-};
-
-static struct cached_object *find_cached_object(const unsigned char *sha1)
-{
- int i;
- struct cached_object *co = cached_objects;
-
- for (i = 0; i < cached_object_nr; i++, co++) {
- if (!hashcmp(co->sha1, sha1))
- return co;
- }
- if (!hashcmp(sha1, empty_tree.sha1))
- return &empty_tree;
- return NULL;
-}
-
int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
unsigned char *sha1)
{
@@ -2079,27 +2179,46 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,
return read_packed_sha1(sha1, type, size);
}
-void *read_sha1_file_repl(const unsigned char *sha1,
- enum object_type *type,
- unsigned long *size,
- const unsigned char **replacement)
+/*
+ * This function dies on corrupt objects; the callers who want to
+ * deal with them should arrange to call read_object() and give error
+ * messages themselves.
+ */
+void *read_sha1_file_extended(const unsigned char *sha1,
+ enum object_type *type,
+ unsigned long *size,
+ unsigned flag)
{
- const unsigned char *repl = lookup_replace_object(sha1);
- void *data = read_object(repl, type, size);
+ void *data;
+ char *path;
+ const struct packed_git *p;
+ const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE)
+ ? lookup_replace_object(sha1) : sha1;
+
+ errno = 0;
+ data = read_object(repl, type, size);
+ if (data)
+ return data;
+
+ if (errno && errno != ENOENT)
+ die_errno("failed to read object %s", sha1_to_hex(sha1));
/* die if we replaced an object with one that does not exist */
- if (!data && repl != sha1)
+ if (repl != sha1)
die("replacement %s not found for %s",
sha1_to_hex(repl), sha1_to_hex(sha1));
- /* legacy behavior is to die on corrupted objects */
- if (!data && (has_loose_object(repl) || has_packed_and_bad(repl)))
- die("object %s is corrupted", sha1_to_hex(repl));
+ if (has_loose_object(repl)) {
+ path = sha1_file_name(sha1);
+ die("loose object %s (stored in %s) is corrupt",
+ sha1_to_hex(repl), path);
+ }
- if (replacement)
- *replacement = repl;
+ if ((p = has_packed_and_bad(repl)) != NULL)
+ die("packed object %s (stored in %s) is corrupt",
+ sha1_to_hex(repl), p->pack_name);
- return data;
+ return NULL;
}
void *read_object_with_reference(const unsigned char *sha1,
@@ -2283,7 +2402,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
{
int fd, ret;
unsigned char compressed[4096];
- z_stream stream;
+ git_zstream stream;
git_SHA_CTX c;
unsigned char parano_sha1[20];
char *filename;
@@ -2291,8 +2410,6 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
filename = sha1_file_name(sha1);
fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
- while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
- fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
if (fd < 0) {
if (errno == EACCES)
return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
@@ -2302,7 +2419,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
/* Set it up */
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
+ git_deflate_init(&stream, zlib_compression_level);
stream.next_out = compressed;
stream.avail_out = sizeof(compressed);
git_SHA1_Init(&c);
@@ -2310,8 +2427,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
/* First header.. */
stream.next_in = (unsigned char *)hdr;
stream.avail_in = hdrlen;
- while (deflate(&stream, 0) == Z_OK)
- /* nothing */;
+ while (git_deflate(&stream, 0) == Z_OK)
+ ; /* nothing */
git_SHA1_Update(&c, hdr, hdrlen);
/* Then the data itself.. */
@@ -2319,7 +2436,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
stream.avail_in = len;
do {
unsigned char *in0 = stream.next_in;
- ret = deflate(&stream, Z_FINISH);
+ ret = git_deflate(&stream, Z_FINISH);
git_SHA1_Update(&c, in0, stream.next_in - in0);
if (write_buffer(fd, compressed, stream.next_out - compressed) < 0)
die("unable to write sha1 file");
@@ -2329,7 +2446,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
if (ret != Z_STREAM_END)
die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
- ret = deflateEnd(&stream);
+ ret = git_deflate_end_gently(&stream);
if (ret != Z_OK)
die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
git_SHA1_Final(parano_sha1, &c);
@@ -2411,10 +2528,40 @@ int has_sha1_file(const unsigned char *sha1)
return has_loose_object(sha1);
}
+static void check_tree(const void *buf, size_t size)
+{
+ struct tree_desc desc;
+ struct name_entry entry;
+
+ init_tree_desc(&desc, buf, size);
+ while (tree_entry(&desc, &entry))
+ /* do nothing
+ * tree_entry() will die() on malformed entries */
+ ;
+}
+
+static void check_commit(const void *buf, size_t size)
+{
+ struct commit c;
+ memset(&c, 0, sizeof(c));
+ if (parse_commit_buffer(&c, buf, size))
+ die("corrupt commit");
+}
+
+static void check_tag(const void *buf, size_t size)
+{
+ struct tag t;
+ memset(&t, 0, sizeof(t));
+ if (parse_tag_buffer(&t, buf, size))
+ die("corrupt tag");
+}
+
static int index_mem(unsigned char *sha1, void *buf, size_t size,
- int write_object, enum object_type type, const char *path)
+ enum object_type type,
+ const char *path, unsigned flags)
{
int ret, re_allocated = 0;
+ int write_object = flags & HASH_WRITE_OBJECT;
if (!type)
type = OBJ_BLOB;
@@ -2430,6 +2577,14 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
re_allocated = 1;
}
}
+ if (flags & HASH_FORMAT_CHECK) {
+ if (type == OBJ_TREE)
+ check_tree(buf, size);
+ if (type == OBJ_COMMIT)
+ check_commit(buf, size);
+ if (type == OBJ_TAG)
+ check_tag(buf, size);
+ }
if (write_object)
ret = write_sha1_file(buf, size, typename(type), sha1);
@@ -2440,42 +2595,141 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
return ret;
}
+static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
+ const char *path, unsigned flags)
+{
+ struct strbuf sbuf = STRBUF_INIT;
+ int ret;
+
+ if (strbuf_read(&sbuf, fd, 4096) >= 0)
+ ret = index_mem(sha1, sbuf.buf, sbuf.len, type, path, flags);
+ else
+ ret = -1;
+ strbuf_release(&sbuf);
+ return ret;
+}
+
#define SMALL_FILE_SIZE (32*1024)
-int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
- enum object_type type, const char *path)
+static int index_core(unsigned char *sha1, int fd, size_t size,
+ enum object_type type, const char *path,
+ unsigned flags)
{
int ret;
- size_t size = xsize_t(st->st_size);
- if (!S_ISREG(st->st_mode)) {
- struct strbuf sbuf = STRBUF_INIT;
- if (strbuf_read(&sbuf, fd, 4096) >= 0)
- ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
- type, path);
- else
- ret = -1;
- strbuf_release(&sbuf);
- } else if (!size) {
- ret = index_mem(sha1, NULL, size, write_object, type, path);
+ if (!size) {
+ ret = index_mem(sha1, NULL, size, type, path, flags);
} else if (size <= SMALL_FILE_SIZE) {
char *buf = xmalloc(size);
if (size == read_in_full(fd, buf, size))
- ret = index_mem(sha1, buf, size, write_object, type,
- path);
+ ret = index_mem(sha1, buf, size, type, path, flags);
else
ret = error("short read %s", strerror(errno));
free(buf);
} else {
void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- ret = index_mem(sha1, buf, size, write_object, type, path);
+ ret = index_mem(sha1, buf, size, type, path, flags);
munmap(buf, size);
}
+ return ret;
+}
+
+/*
+ * This creates one packfile per large blob, because the caller
+ * immediately wants the result sha1, and fast-import can report the
+ * object name via marks mechanism only by closing the created
+ * packfile.
+ *
+ * This also bypasses the usual "convert-to-git" dance, and that is on
+ * purpose. We could write a streaming version of the converting
+ * functions and insert that before feeding the data to fast-import
+ * (or equivalent in-core API described above), but the primary
+ * motivation for trying to stream from the working tree file and to
+ * avoid mmaping it in core is to deal with large binary blobs, and
+ * by definition they do _not_ want to get any conversion.
+ */
+static int index_stream(unsigned char *sha1, int fd, size_t size,
+ enum object_type type, const char *path,
+ unsigned flags)
+{
+ struct child_process fast_import;
+ char export_marks[512];
+ const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
+ char tmpfile[512];
+ char fast_import_cmd[512];
+ char buf[512];
+ int len, tmpfd;
+
+ strcpy(tmpfile, git_path("hashstream_XXXXXX"));
+ tmpfd = git_mkstemp_mode(tmpfile, 0600);
+ if (tmpfd < 0)
+ die_errno("cannot create tempfile: %s", tmpfile);
+ if (close(tmpfd))
+ die_errno("cannot close tempfile: %s", tmpfile);
+ sprintf(export_marks, "--export-marks=%s", tmpfile);
+
+ memset(&fast_import, 0, sizeof(fast_import));
+ fast_import.in = -1;
+ fast_import.argv = argv;
+ fast_import.git_cmd = 1;
+ if (start_command(&fast_import))
+ die_errno("index-stream: git fast-import failed");
+
+ len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
+ (unsigned long) size);
+ write_or_whine(fast_import.in, fast_import_cmd, len,
+ "index-stream: feeding fast-import");
+ while (size) {
+ char buf[10240];
+ size_t sz = size < sizeof(buf) ? size : sizeof(buf);
+ ssize_t actual;
+
+ actual = read_in_full(fd, buf, sz);
+ if (actual < 0)
+ die_errno("index-stream: reading input");
+ if (write_in_full(fast_import.in, buf, actual) != actual)
+ die_errno("index-stream: feeding fast-import");
+ size -= actual;
+ }
+ if (close(fast_import.in))
+ die_errno("index-stream: closing fast-import");
+ if (finish_command(&fast_import))
+ die_errno("index-stream: finishing fast-import");
+
+ tmpfd = open(tmpfile, O_RDONLY);
+ if (tmpfd < 0)
+ die_errno("index-stream: cannot open fast-import mark");
+ len = read(tmpfd, buf, sizeof(buf));
+ if (len < 0)
+ die_errno("index-stream: reading fast-import mark");
+ if (close(tmpfd) < 0)
+ die_errno("index-stream: closing fast-import mark");
+ if (unlink(tmpfile))
+ die_errno("index-stream: unlinking fast-import mark");
+ if (len != 44 ||
+ memcmp(":1 ", buf, 3) ||
+ get_sha1_hex(buf + 3, sha1))
+ die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
+ return 0;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st,
+ enum object_type type, const char *path, unsigned flags)
+{
+ int ret;
+ size_t size = xsize_t(st->st_size);
+
+ if (!S_ISREG(st->st_mode))
+ ret = index_pipe(sha1, fd, type, path, flags);
+ else if (size <= big_file_threshold || type != OBJ_BLOB)
+ ret = index_core(sha1, fd, size, type, path, flags);
+ else
+ ret = index_stream(sha1, fd, size, type, path, flags);
close(fd);
return ret;
}
-int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags)
{
int fd;
struct strbuf sb = STRBUF_INIT;
@@ -2486,7 +2740,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
if (fd < 0)
return error("open(\"%s\"): %s", path,
strerror(errno));
- if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0)
+ if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0)
return error("%s: failed to insert into database",
path);
break;
@@ -2496,7 +2750,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
return error("readlink(\"%s\"): %s", path,
errstr);
}
- if (!write_object)
+ if (!(flags & HASH_WRITE_OBJECT))
hash_sha1_file(sb.buf, sb.len, blob_type, sha1);
else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1))
return error("%s: failed to insert into database",
diff --git a/sha1_name.c b/sha1_name.c
index 4f2af8d..ff5992a 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -7,6 +7,8 @@
#include "refs.h"
#include "remote.h"
+static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
+
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
struct alternate_object_database *alt;
@@ -342,7 +344,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1);
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
- static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+ static const char *warn_msg = "refname '%.*s' is ambiguous.";
char *real_ref = NULL;
int refs_found = 0;
int at, reflog_len;
@@ -390,7 +392,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
return -1;
if (warn_ambiguous_refs && refs_found > 1)
- fprintf(stderr, warning, len, str);
+ warning(warn_msg, len, str);
if (reflog_len) {
int nth, i;
@@ -426,14 +428,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
&co_time, &co_tz, &co_cnt)) {
if (at_time)
- fprintf(stderr,
- "warning: Log for '%.*s' only goes "
- "back to %s.\n", len, str,
+ warning("Log for '%.*s' only goes "
+ "back to %s.", len, str,
show_date(co_time, co_tz, DATE_RFC2822));
- else
- fprintf(stderr,
- "warning: Log for '%.*s' only has "
- "%d entries.\n", len, str, co_cnt);
+ else {
+ free(real_ref);
+ die("Log for '%.*s' only has %d entries.",
+ len, str, co_cnt);
+ }
}
}
@@ -560,6 +562,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
expected_type = OBJ_BLOB;
else if (sp[0] == '}')
expected_type = OBJ_NONE;
+ else if (sp[0] == '/')
+ expected_type = OBJ_COMMIT;
else
return -1;
@@ -574,19 +578,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
if (!o || (!o->parsed && !parse_object(o->sha1)))
return -1;
hashcpy(sha1, o->sha1);
+ return 0;
}
- else {
+
+ /*
+ * At this point, the syntax look correct, so
+ * if we do not get the needed object, we should
+ * barf.
+ */
+ o = peel_to_type(name, len, o, expected_type);
+ if (!o)
+ return -1;
+
+ hashcpy(sha1, o->sha1);
+ if (sp[0] == '/') {
+ /* "$commit^{/foo}" */
+ char *prefix;
+ int ret;
+ struct commit_list *list = NULL;
+
/*
- * At this point, the syntax look correct, so
- * if we do not get the needed object, we should
- * barf.
+ * $commit^{/}. Some regex implementation may reject.
+ * We don't need regex anyway. '' pattern always matches.
*/
- o = peel_to_type(name, len, o, expected_type);
- if (o) {
- hashcpy(sha1, o->sha1);
+ if (sp[1] == '}')
return 0;
- }
- return -1;
+
+ prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
+ commit_list_insert((struct commit *)o, &list);
+ ret = get_sha1_oneline(prefix, sha1, list);
+ free(prefix);
+ return ret;
}
return 0;
}
@@ -659,6 +681,16 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
return get_short_sha1(name, len, sha1, 0);
}
+/*
+ * This interprets names like ':/Initial revision of "git"' by searching
+ * through history and returning the first commit whose message starts
+ * the given regular expression.
+ *
+ * For future extension, ':/!' is reserved. If you want to match a message
+ * beginning with a '!', you have to repeat the exclamation mark.
+ */
+#define ONELINE_SEEN (1u<<20)
+
static int handle_one_ref(const char *path,
const unsigned char *sha1, int flag, void *cb_data)
{
@@ -673,25 +705,15 @@ static int handle_one_ref(const char *path,
}
if (object->type != OBJ_COMMIT)
return 0;
- insert_by_date((struct commit *)object, list);
+ commit_list_insert_by_date((struct commit *)object, list);
return 0;
}
-/*
- * This interprets names like ':/Initial revision of "git"' by searching
- * through history and returning the first commit whose message matches
- * the given regular expression.
- *
- * For future extension, ':/!' is reserved. If you want to match a message
- * beginning with a '!', you have to repeat the exclamation mark.
- */
-
-#define ONELINE_SEEN (1u<<20)
-static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
+ struct commit_list *list)
{
- struct commit_list *list = NULL, *backup = NULL, *l;
- int retval = -1;
- char *temp_commit_buffer = NULL;
+ struct commit_list *backup = NULL, *l;
+ int found = 0;
regex_t regex;
if (prefix[0] == '!') {
@@ -703,41 +725,45 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
if (regcomp(&regex, prefix, REG_EXTENDED))
die("Invalid search pattern: %s", prefix);
- for_each_ref(handle_one_ref, &list);
- for (l = list; l; l = l->next)
+ for (l = list; l; l = l->next) {
+ l->item->object.flags |= ONELINE_SEEN;
commit_list_insert(l->item, &backup);
+ }
while (list) {
- char *p;
+ char *p, *to_free = NULL;
struct commit *commit;
enum object_type type;
unsigned long size;
+ int matches;
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
if (!parse_object(commit->object.sha1))
continue;
- free(temp_commit_buffer);
if (commit->buffer)
p = commit->buffer;
else {
p = read_sha1_file(commit->object.sha1, &type, &size);
if (!p)
continue;
- temp_commit_buffer = p;
+ to_free = p;
}
- if (!(p = strstr(p, "\n\n")))
- continue;
- if (!regexec(&regex, p + 2, 0, NULL, 0)) {
+
+ p = strstr(p, "\n\n");
+ matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
+ free(to_free);
+
+ if (matches) {
hashcpy(sha1, commit->object.sha1);
- retval = 0;
+ found = 1;
break;
}
}
regfree(&regex);
- free(temp_commit_buffer);
free_commit_list(list);
for (l = backup; l; l = l->next)
clear_commit_marks(l->item, ONELINE_SEEN);
- return retval;
+ free_commit_list(backup);
+ return found ? 0 : -1;
}
struct grab_nth_branch_switch_cbdata {
@@ -933,6 +959,24 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
return len;
}
+int strbuf_branchname(struct strbuf *sb, const char *name)
+{
+ int len = strlen(name);
+ if (interpret_branch_name(name, sb) == len)
+ return 0;
+ strbuf_add(sb, name, len);
+ return len;
+}
+
+int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
+{
+ strbuf_branchname(sb, name);
+ if (name[0] == '-')
+ return CHECK_REF_FORMAT_ERROR;
+ strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+ return check_ref_format(sb->buf);
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
@@ -968,11 +1012,13 @@ static void diagnose_invalid_sha1_path(const char *prefix,
if (!get_tree_entry(tree_sha1, fullname,
sha1, &mode)) {
die("Path '%s' exists, but not '%s'.\n"
- "Did you mean '%s:%s'?",
+ "Did you mean '%s:%s' aka '%s:./%s'?",
fullname,
filename,
object_name,
- fullname);
+ fullname,
+ object_name,
+ filename);
}
die("Path '%s' does not exist in '%s'",
filename, object_name);
@@ -1021,9 +1067,10 @@ static void diagnose_invalid_index_path(int stage,
if (ce_namelen(ce) == fullnamelen &&
!memcmp(ce->name, fullname, fullnamelen))
die("Path '%s' is in the index, but not '%s'.\n"
- "Did you mean ':%d:%s'?",
+ "Did you mean ':%d:%s' aka ':%d:./%s'?",
fullname, filename,
- ce_stage(ce), fullname);
+ ce_stage(ce), fullname,
+ ce_stage(ce), filename);
}
if (!lstat(filename, &st))
@@ -1036,18 +1083,36 @@ static void diagnose_invalid_index_path(int stage,
}
-int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
+ int only_to_die, const char *prefix)
{
struct object_context oc;
int ret;
- ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix);
+ ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix);
*mode = oc.mode;
return ret;
}
+static char *resolve_relative_path(const char *rel)
+{
+ if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
+ return NULL;
+
+ if (!startup_info)
+ die("BUG: startup_info struct is not initialized.");
+
+ if (!is_inside_work_tree())
+ die("relative path syntax can't be used outside working tree.");
+
+ /* die() inside prefix_path() if resolved path is outside worktree */
+ return prefix_path(startup_info->prefix,
+ startup_info->prefix ? strlen(startup_info->prefix) : 0,
+ rel);
+}
+
int get_sha1_with_context_1(const char *name, unsigned char *sha1,
struct object_context *oc,
- int gently, const char *prefix)
+ int only_to_die, const char *prefix)
{
int ret, bracket_depth;
int namelen = strlen(name);
@@ -1059,15 +1124,21 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (!ret)
return ret;
/* sha1:path --> object name of path in ent sha1
- * :path -> object name of path in index
+ * :path -> object name of absolute path in index
+ * :./path -> object name of path relative to cwd in index
* :[0-3]:path -> object name of path in index at stage
+ * :/foo -> recent commit matching foo
*/
if (name[0] == ':') {
int stage = 0;
struct cache_entry *ce;
+ char *new_path = NULL;
int pos;
- if (namelen > 2 && name[1] == '/')
- return get_sha1_oneline(name + 2, sha1);
+ if (!only_to_die && namelen > 2 && name[1] == '/') {
+ struct commit_list *list = NULL;
+ for_each_ref(handle_one_ref, &list);
+ return get_sha1_oneline(name + 2, sha1, list);
+ }
if (namelen < 3 ||
name[2] != ':' ||
name[1] < '0' || '3' < name[1])
@@ -1076,7 +1147,13 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
stage = name[1] - '0';
cp = name + 3;
}
- namelen = namelen - (cp - name);
+ new_path = resolve_relative_path(cp);
+ if (!new_path) {
+ namelen = namelen - (cp - name);
+ } else {
+ cp = new_path;
+ namelen = strlen(cp);
+ }
strncpy(oc->path, cp,
sizeof(oc->path));
@@ -1094,12 +1171,15 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
break;
if (ce_stage(ce) == stage) {
hashcpy(sha1, ce->sha1);
+ oc->mode = ce->ce_mode;
+ free(new_path);
return 0;
}
pos++;
}
- if (!gently)
+ if (only_to_die && name[1] && name[1] != '/')
diagnose_invalid_index_path(stage, prefix, cp);
+ free(new_path);
return -1;
}
for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -1113,15 +1193,20 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
if (*cp == ':') {
unsigned char tree_sha1[20];
char *object_name = NULL;
- if (!gently) {
+ if (only_to_die) {
object_name = xmalloc(cp-name+1);
strncpy(object_name, name, cp-name);
object_name[cp-name] = '\0';
}
if (!get_sha1_1(name, cp-name, tree_sha1)) {
const char *filename = cp+1;
+ char *new_filename = NULL;
+
+ new_filename = resolve_relative_path(filename);
+ if (new_filename)
+ filename = new_filename;
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
- if (!gently) {
+ if (only_to_die) {
diagnose_invalid_sha1_path(prefix, filename,
tree_sha1, object_name);
free(object_name);
@@ -1131,9 +1216,10 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
sizeof(oc->path));
oc->path[sizeof(oc->path)-1] = '\0';
+ free(new_filename);
return ret;
} else {
- if (!gently)
+ if (only_to_die)
die("Invalid object name '%s'.", object_name);
}
}
diff --git a/shallow.c b/shallow.c
index 4d90eda..a0363de 100644
--- a/shallow.c
+++ b/shallow.c
@@ -47,7 +47,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
{
int i = 0, cur_depth = 0;
struct commit_list *result = NULL;
- struct object_array stack = {0, 0, NULL};
+ struct object_array stack = OBJECT_ARRAY_INIT;
struct commit *commit = NULL;
while (commit || i < heads->nr || stack.nr) {
diff --git a/shell.c b/shell.c
index e4864e0..abb8622 100644
--- a/shell.c
+++ b/shell.c
@@ -2,6 +2,10 @@
#include "quote.h"
#include "exec_cmd.h"
#include "strbuf.h"
+#include "run-command.h"
+
+#define COMMAND_DIR "git-shell-commands"
+#define HELP_COMMAND COMMAND_DIR "/help"
static int do_generic_cmd(const char *me, char *arg)
{
@@ -33,6 +37,86 @@ static int do_cvs_cmd(const char *me, char *arg)
return execv_git_cmd(cvsserver_argv);
}
+static int is_valid_cmd_name(const char *cmd)
+{
+ /* Test command contains no . or / characters */
+ return cmd[strcspn(cmd, "./")] == '\0';
+}
+
+static char *make_cmd(const char *prog)
+{
+ char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
+ strcpy(prefix, COMMAND_DIR);
+ strcat(prefix, "/");
+ strcat(prefix, prog);
+ return prefix;
+}
+
+static void cd_to_homedir(void)
+{
+ const char *home = getenv("HOME");
+ if (!home)
+ die("could not determine user's home directory; HOME is unset");
+ if (chdir(home) == -1)
+ die("could not chdir to user's home directory");
+}
+
+static void run_shell(void)
+{
+ int done = 0;
+ static const char *help_argv[] = { HELP_COMMAND, NULL };
+ /* Print help if enabled */
+ run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
+
+ do {
+ struct strbuf line = STRBUF_INIT;
+ const char *prog;
+ char *full_cmd;
+ char *rawargs;
+ char *split_args;
+ const char **argv;
+ int code;
+ int count;
+
+ fprintf(stderr, "git> ");
+ if (strbuf_getline(&line, stdin, '\n') == EOF) {
+ fprintf(stderr, "\n");
+ strbuf_release(&line);
+ break;
+ }
+ strbuf_trim(&line);
+ rawargs = strbuf_detach(&line, NULL);
+ split_args = xstrdup(rawargs);
+ count = split_cmdline(split_args, &argv);
+ if (count < 0) {
+ fprintf(stderr, "invalid command format '%s': %s\n", rawargs,
+ split_cmdline_strerror(count));
+ free(split_args);
+ free(rawargs);
+ continue;
+ }
+
+ prog = argv[0];
+ if (!strcmp(prog, "")) {
+ } else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
+ !strcmp(prog, "exit") || !strcmp(prog, "bye")) {
+ done = 1;
+ } else if (is_valid_cmd_name(prog)) {
+ full_cmd = make_cmd(prog);
+ argv[0] = full_cmd;
+ code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
+ if (code == -1 && errno == ENOENT) {
+ fprintf(stderr, "unrecognized command '%s'\n", prog);
+ }
+ free(full_cmd);
+ } else {
+ fprintf(stderr, "invalid command format '%s'\n", prog);
+ }
+
+ free(argv);
+ free(rawargs);
+ } while (!done);
+}
static struct commands {
const char *name;
@@ -48,8 +132,12 @@ static struct commands {
int main(int argc, char **argv)
{
char *prog;
+ const char **user_argv;
struct commands *cmd;
int devnull_fd;
+ int count;
+
+ git_extract_argv0_path(argv[0]);
/*
* Always open file descriptors 0/1/2 to avoid clobbering files
@@ -66,17 +154,28 @@ int main(int argc, char **argv)
/*
* Special hack to pretend to be a CVS server
*/
- if (argc == 2 && !strcmp(argv[1], "cvs server"))
+ if (argc == 2 && !strcmp(argv[1], "cvs server")) {
argv--;
+ } else if (argc == 1) {
+ /* Allow the user to run an interactive shell */
+ cd_to_homedir();
+ if (access(COMMAND_DIR, R_OK | X_OK) == -1) {
+ die("Interactive git shell is not enabled.\n"
+ "hint: ~/" COMMAND_DIR " should exist "
+ "and have read and execute access.");
+ }
+ run_shell();
+ exit(0);
+ } else if (argc != 3 || strcmp(argv[1], "-c")) {
+ /*
+ * We do not accept any other modes except "-c" followed by
+ * "cmd arg", where "cmd" is a very limited subset of git
+ * commands or a command in the COMMAND_DIR
+ */
+ die("Run with no arguments or with -c cmd");
+ }
- /*
- * We do not accept anything but "-c" followed by "cmd arg",
- * where "cmd" is a very limited subset of git commands.
- */
- else if (argc != 3 || strcmp(argv[1], "-c"))
- die("What do you think I am? A shell?");
-
- prog = argv[2];
+ prog = xstrdup(argv[2]);
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
/* Accept "git foo" as if the caller said "git-foo". */
prog[3] = '-';
@@ -99,5 +198,21 @@ int main(int argc, char **argv)
}
exit(cmd->exec(cmd->name, arg));
}
- die("unrecognized command '%s'", prog);
+
+ cd_to_homedir();
+ count = split_cmdline(prog, &user_argv);
+ if (count >= 0) {
+ if (is_valid_cmd_name(user_argv[0])) {
+ prog = make_cmd(user_argv[0]);
+ user_argv[0] = prog;
+ execv(user_argv[0], (char *const *) user_argv);
+ }
+ free(prog);
+ free(user_argv);
+ die("unrecognized command '%s'", argv[2]);
+ } else {
+ free(prog);
+ die("invalid command format '%s': %s", argv[2],
+ split_cmdline_strerror(count));
+ }
}
diff --git a/strbuf.c b/strbuf.c
index bc3a080..1a7df12 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -30,8 +30,10 @@ void strbuf_init(struct strbuf *sb, size_t hint)
{
sb->alloc = sb->len = 0;
sb->buf = strbuf_slopbuf;
- if (hint)
+ if (hint) {
strbuf_grow(sb, hint);
+ sb->buf[0] = '\0';
+ }
}
void strbuf_release(struct strbuf *sb)
@@ -63,7 +65,8 @@ void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
void strbuf_grow(struct strbuf *sb, size_t extra)
{
- if (sb->len + extra + 1 <= sb->len)
+ if (unsigned_add_overflows(extra, 1) ||
+ unsigned_add_overflows(sb->len, extra + 1))
die("you want to use way too much memory");
if (!sb->alloc)
sb->buf = NULL;
@@ -100,24 +103,27 @@ void strbuf_ltrim(struct strbuf *sb)
sb->buf[sb->len] = '\0';
}
-struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+struct strbuf **strbuf_split_buf(const char *str, size_t slen, int delim, int max)
{
int alloc = 2, pos = 0;
- char *n, *p;
+ const char *n, *p;
struct strbuf **ret;
struct strbuf *t;
ret = xcalloc(alloc, sizeof(struct strbuf *));
- p = n = sb->buf;
- while (n < sb->buf + sb->len) {
+ p = n = str;
+ while (n < str + slen) {
int len;
- n = memchr(n, delim, sb->len - (n - sb->buf));
+ if (max <= 0 || pos + 1 < max)
+ n = memchr(n, delim, slen - (n - str));
+ else
+ n = NULL;
if (pos + 1 >= alloc) {
alloc = alloc * 2;
ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
}
if (!n)
- n = sb->buf + sb->len - 1;
+ n = str + slen - 1;
len = n - p + 1;
t = xmalloc(sizeof(struct strbuf));
strbuf_init(t, len);
@@ -152,7 +158,7 @@ int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
const void *data, size_t dlen)
{
- if (pos + len < pos)
+ if (unsigned_add_overflows(pos, len))
die("you want to use way too much memory");
if (pos > sb->len)
die("`pos' is too far after the end of the buffer");
@@ -194,24 +200,29 @@ void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
{
- int len;
va_list ap;
+ va_start(ap, fmt);
+ strbuf_vaddf(sb, fmt, ap);
+ va_end(ap);
+}
+
+void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)
+{
+ int len;
+ va_list cp;
if (!strbuf_avail(sb))
strbuf_grow(sb, 64);
- va_start(ap, fmt);
- len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
- va_end(ap);
+ va_copy(cp, ap);
+ len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, cp);
+ va_end(cp);
if (len < 0)
- die("your vsnprintf is broken");
+ die("BUG: your vsnprintf is broken (returned %d)", len);
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
- va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
- va_end(ap);
- if (len > strbuf_avail(sb)) {
- die("this should not happen, your snprintf is broken");
- }
+ if (len > strbuf_avail(sb))
+ die("BUG: your vsnprintf is broken (insatiable)");
}
strbuf_setlen(sb, sb->len + len);
}
@@ -386,19 +397,3 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
return len;
}
-
-int strbuf_branchname(struct strbuf *sb, const char *name)
-{
- int len = strlen(name);
- if (interpret_branch_name(name, sb) == len)
- return 0;
- strbuf_add(sb, name, len);
- return len;
-}
-
-int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
-{
- strbuf_branchname(sb, name);
- strbuf_splice(sb, 0, 0, "refs/heads/", 11);
- return check_ref_format(sb->buf);
-}
diff --git a/strbuf.h b/strbuf.h
index fac2dbc..46a33f8 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -1,44 +1,7 @@
#ifndef STRBUF_H
#define STRBUF_H
-/*
- * Strbuf's can be use in many ways: as a byte array, or to store arbitrary
- * long, overflow safe strings.
- *
- * Strbufs has some invariants that are very important to keep in mind:
- *
- * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
- * build complex strings/buffers whose final size isn't easily known.
- *
- * It is NOT legal to copy the ->buf pointer away.
- * `strbuf_detach' is the operation that detaches a buffer from its shell
- * while keeping the shell valid wrt its invariants.
- *
- * 2. the ->buf member is a byte array that has at least ->len + 1 bytes
- * allocated. The extra byte is used to store a '\0', allowing the ->buf
- * member to be a valid C-string. Every strbuf function ensure this
- * invariant is preserved.
- *
- * Note that it is OK to "play" with the buffer directly if you work it
- * that way:
- *
- * strbuf_grow(sb, SOME_SIZE);
- * ... Here, the memory array starting at sb->buf, and of length
- * ... strbuf_avail(sb) is all yours, and you are sure that
- * ... strbuf_avail(sb) is at least SOME_SIZE.
- * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
- *
- * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
- *
- * Doing so is safe, though if it has to be done in many places, adding the
- * missing API to the strbuf module is the way to go.
- *
- * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
- * even if it's true in the current implementation. Alloc is somehow a
- * "private" member that should not be messed with.
- */
-
-#include <assert.h>
+/* See Documentation/technical/api-strbuf.txt */
extern char strbuf_slopbuf[];
struct strbuf {
@@ -68,9 +31,8 @@ static inline size_t strbuf_avail(const struct strbuf *sb) {
extern void strbuf_grow(struct strbuf *, size_t);
static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
- if (!sb->alloc)
- strbuf_grow(sb, 0);
- assert(len < sb->alloc);
+ if (len > (sb->alloc ? sb->alloc - 1 : 0))
+ die("BUG: strbuf_setlen() beyond buffer");
sb->len = len;
sb->buf[len] = '\0';
}
@@ -82,7 +44,22 @@ extern void strbuf_rtrim(struct strbuf *);
extern void strbuf_ltrim(struct strbuf *);
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
-extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
+extern struct strbuf **strbuf_split_buf(const char *, size_t,
+ int delim, int max);
+static inline struct strbuf **strbuf_split_str(const char *str,
+ int delim, int max)
+{
+ return strbuf_split_buf(str, strlen(str), delim, max);
+}
+static inline struct strbuf **strbuf_split_max(const struct strbuf *sb,
+ int delim, int max)
+{
+ return strbuf_split_buf(sb->buf, sb->len, delim, max);
+}
+static inline struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+{
+ return strbuf_split_max(sb, delim, 0);
+}
extern void strbuf_list_free(struct strbuf **);
/*----- add data in your buffer -----*/
@@ -120,6 +97,8 @@ extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *
__attribute__((format (printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+__attribute__((format (printf,2,0)))
+extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
/* XXX: if read fails, any partial read is undone */
diff --git a/streaming.c b/streaming.c
new file mode 100644
index 0000000..25c9a20
--- /dev/null
+++ b/streaming.c
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "cache.h"
+#include "streaming.h"
+
+enum input_source {
+ stream_error = -1,
+ incore = 0,
+ loose = 1,
+ pack_non_delta = 2
+};
+
+typedef int (*open_istream_fn)(struct git_istream *,
+ struct object_info *,
+ const unsigned char *,
+ enum object_type *);
+typedef int (*close_istream_fn)(struct git_istream *);
+typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
+
+struct stream_vtbl {
+ close_istream_fn close;
+ read_istream_fn read;
+};
+
+#define open_method_decl(name) \
+ int open_istream_ ##name \
+ (struct git_istream *st, struct object_info *oi, \
+ const unsigned char *sha1, \
+ enum object_type *type)
+
+#define close_method_decl(name) \
+ int close_istream_ ##name \
+ (struct git_istream *st)
+
+#define read_method_decl(name) \
+ ssize_t read_istream_ ##name \
+ (struct git_istream *st, char *buf, size_t sz)
+
+/* forward declaration */
+static open_method_decl(incore);
+static open_method_decl(loose);
+static open_method_decl(pack_non_delta);
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+ struct stream_filter *filter);
+
+
+static open_istream_fn open_istream_tbl[] = {
+ open_istream_incore,
+ open_istream_loose,
+ open_istream_pack_non_delta,
+};
+
+#define FILTER_BUFFER (1024*16)
+
+struct filtered_istream {
+ struct git_istream *upstream;
+ struct stream_filter *filter;
+ char ibuf[FILTER_BUFFER];
+ char obuf[FILTER_BUFFER];
+ int i_end, i_ptr;
+ int o_end, o_ptr;
+ int input_finished;
+};
+
+struct git_istream {
+ const struct stream_vtbl *vtbl;
+ unsigned long size; /* inflated size of full object */
+ git_zstream z;
+ enum { z_unused, z_used, z_done, z_error } z_state;
+
+ union {
+ struct {
+ char *buf; /* from read_object() */
+ unsigned long read_ptr;
+ } incore;
+
+ struct {
+ void *mapped;
+ unsigned long mapsize;
+ char hdr[32];
+ int hdr_avail;
+ int hdr_used;
+ } loose;
+
+ struct {
+ struct packed_git *pack;
+ off_t pos;
+ } in_pack;
+
+ struct filtered_istream filtered;
+ } u;
+};
+
+int close_istream(struct git_istream *st)
+{
+ return st->vtbl->close(st);
+}
+
+ssize_t read_istream(struct git_istream *st, char *buf, size_t sz)
+{
+ return st->vtbl->read(st, buf, sz);
+}
+
+static enum input_source istream_source(const unsigned char *sha1,
+ enum object_type *type,
+ struct object_info *oi)
+{
+ unsigned long size;
+ int status;
+
+ oi->sizep = &size;
+ status = sha1_object_info_extended(sha1, oi);
+ if (status < 0)
+ return stream_error;
+ *type = status;
+
+ switch (oi->whence) {
+ case OI_LOOSE:
+ return loose;
+ case OI_PACKED:
+ if (!oi->u.packed.is_delta && big_file_threshold <= size)
+ return pack_non_delta;
+ /* fallthru */
+ default:
+ return incore;
+ }
+}
+
+struct git_istream *open_istream(const unsigned char *sha1,
+ enum object_type *type,
+ unsigned long *size,
+ struct stream_filter *filter)
+{
+ struct git_istream *st;
+ struct object_info oi;
+ const unsigned char *real = lookup_replace_object(sha1);
+ enum input_source src = istream_source(real, type, &oi);
+
+ if (src < 0)
+ return NULL;
+
+ st = xmalloc(sizeof(*st));
+ if (open_istream_tbl[src](st, &oi, real, type)) {
+ if (open_istream_incore(st, &oi, real, type)) {
+ free(st);
+ return NULL;
+ }
+ }
+ if (st && filter) {
+ /* Add "&& !is_null_stream_filter(filter)" for performance */
+ struct git_istream *nst = attach_stream_filter(st, filter);
+ if (!nst)
+ close_istream(st);
+ st = nst;
+ }
+
+ *size = st->size;
+ return st;
+}
+
+
+/*****************************************************************
+ *
+ * Common helpers
+ *
+ *****************************************************************/
+
+static void close_deflated_stream(struct git_istream *st)
+{
+ if (st->z_state == z_used)
+ git_inflate_end(&st->z);
+}
+
+
+/*****************************************************************
+ *
+ * Filtered stream
+ *
+ *****************************************************************/
+
+static close_method_decl(filtered)
+{
+ free_stream_filter(st->u.filtered.filter);
+ return close_istream(st->u.filtered.upstream);
+}
+
+static read_method_decl(filtered)
+{
+ struct filtered_istream *fs = &(st->u.filtered);
+ size_t filled = 0;
+
+ while (sz) {
+ /* do we already have filtered output? */
+ if (fs->o_ptr < fs->o_end) {
+ size_t to_move = fs->o_end - fs->o_ptr;
+ if (sz < to_move)
+ to_move = sz;
+ memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
+ fs->o_ptr += to_move;
+ sz -= to_move;
+ filled += to_move;
+ continue;
+ }
+ fs->o_end = fs->o_ptr = 0;
+
+ /* do we have anything to feed the filter with? */
+ if (fs->i_ptr < fs->i_end) {
+ size_t to_feed = fs->i_end - fs->i_ptr;
+ size_t to_receive = FILTER_BUFFER;
+ if (stream_filter(fs->filter,
+ fs->ibuf + fs->i_ptr, &to_feed,
+ fs->obuf, &to_receive))
+ return -1;
+ fs->i_ptr = fs->i_end - to_feed;
+ fs->o_end = FILTER_BUFFER - to_receive;
+ continue;
+ }
+
+ /* tell the filter to drain upon no more input */
+ if (fs->input_finished) {
+ size_t to_receive = FILTER_BUFFER;
+ if (stream_filter(fs->filter,
+ NULL, NULL,
+ fs->obuf, &to_receive))
+ return -1;
+ fs->o_end = FILTER_BUFFER - to_receive;
+ if (!fs->o_end)
+ break;
+ continue;
+ }
+ fs->i_end = fs->i_ptr = 0;
+
+ /* refill the input from the upstream */
+ if (!fs->input_finished) {
+ fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
+ if (fs->i_end < 0)
+ break;
+ if (fs->i_end)
+ continue;
+ }
+ fs->input_finished = 1;
+ }
+ return filled;
+}
+
+static struct stream_vtbl filtered_vtbl = {
+ close_istream_filtered,
+ read_istream_filtered,
+};
+
+static struct git_istream *attach_stream_filter(struct git_istream *st,
+ struct stream_filter *filter)
+{
+ struct git_istream *ifs = xmalloc(sizeof(*ifs));
+ struct filtered_istream *fs = &(ifs->u.filtered);
+
+ ifs->vtbl = &filtered_vtbl;
+ fs->upstream = st;
+ fs->filter = filter;
+ fs->i_end = fs->i_ptr = 0;
+ fs->o_end = fs->o_ptr = 0;
+ fs->input_finished = 0;
+ ifs->size = -1; /* unknown */
+ return ifs;
+}
+
+/*****************************************************************
+ *
+ * Loose object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(loose)
+{
+ size_t total_read = 0;
+
+ switch (st->z_state) {
+ case z_done:
+ return 0;
+ case z_error:
+ return -1;
+ default:
+ break;
+ }
+
+ if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
+ size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
+ if (sz < to_copy)
+ to_copy = sz;
+ memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
+ st->u.loose.hdr_used += to_copy;
+ total_read += to_copy;
+ }
+
+ while (total_read < sz) {
+ int status;
+
+ st->z.next_out = (unsigned char *)buf + total_read;
+ st->z.avail_out = sz - total_read;
+ status = git_inflate(&st->z, Z_FINISH);
+
+ total_read = st->z.next_out - (unsigned char *)buf;
+
+ if (status == Z_STREAM_END) {
+ git_inflate_end(&st->z);
+ st->z_state = z_done;
+ break;
+ }
+ if (status != Z_OK && status != Z_BUF_ERROR) {
+ git_inflate_end(&st->z);
+ st->z_state = z_error;
+ return -1;
+ }
+ }
+ return total_read;
+}
+
+static close_method_decl(loose)
+{
+ close_deflated_stream(st);
+ munmap(st->u.loose.mapped, st->u.loose.mapsize);
+ return 0;
+}
+
+static struct stream_vtbl loose_vtbl = {
+ close_istream_loose,
+ read_istream_loose,
+};
+
+static open_method_decl(loose)
+{
+ st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize);
+ if (!st->u.loose.mapped)
+ return -1;
+ if (unpack_sha1_header(&st->z,
+ st->u.loose.mapped,
+ st->u.loose.mapsize,
+ st->u.loose.hdr,
+ sizeof(st->u.loose.hdr)) < 0) {
+ git_inflate_end(&st->z);
+ munmap(st->u.loose.mapped, st->u.loose.mapsize);
+ return -1;
+ }
+
+ parse_sha1_header(st->u.loose.hdr, &st->size);
+ st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
+ st->u.loose.hdr_avail = st->z.total_out;
+ st->z_state = z_used;
+
+ st->vtbl = &loose_vtbl;
+ return 0;
+}
+
+
+/*****************************************************************
+ *
+ * Non-delta packed object stream
+ *
+ *****************************************************************/
+
+static read_method_decl(pack_non_delta)
+{
+ size_t total_read = 0;
+
+ switch (st->z_state) {
+ case z_unused:
+ memset(&st->z, 0, sizeof(st->z));
+ git_inflate_init(&st->z);
+ st->z_state = z_used;
+ break;
+ case z_done:
+ return 0;
+ case z_error:
+ return -1;
+ case z_used:
+ break;
+ }
+
+ while (total_read < sz) {
+ int status;
+ struct pack_window *window = NULL;
+ unsigned char *mapped;
+
+ mapped = use_pack(st->u.in_pack.pack, &window,
+ st->u.in_pack.pos, &st->z.avail_in);
+
+ st->z.next_out = (unsigned char *)buf + total_read;
+ st->z.avail_out = sz - total_read;
+ st->z.next_in = mapped;
+ status = git_inflate(&st->z, Z_FINISH);
+
+ st->u.in_pack.pos += st->z.next_in - mapped;
+ total_read = st->z.next_out - (unsigned char *)buf;
+ unuse_pack(&window);
+
+ if (status == Z_STREAM_END) {
+ git_inflate_end(&st->z);
+ st->z_state = z_done;
+ break;
+ }
+ if (status != Z_OK && status != Z_BUF_ERROR) {
+ git_inflate_end(&st->z);
+ st->z_state = z_error;
+ return -1;
+ }
+ }
+ return total_read;
+}
+
+static close_method_decl(pack_non_delta)
+{
+ close_deflated_stream(st);
+ return 0;
+}
+
+static struct stream_vtbl pack_non_delta_vtbl = {
+ close_istream_pack_non_delta,
+ read_istream_pack_non_delta,
+};
+
+static open_method_decl(pack_non_delta)
+{
+ struct pack_window *window;
+ enum object_type in_pack_type;
+
+ st->u.in_pack.pack = oi->u.packed.pack;
+ st->u.in_pack.pos = oi->u.packed.offset;
+ window = NULL;
+
+ in_pack_type = unpack_object_header(st->u.in_pack.pack,
+ &window,
+ &st->u.in_pack.pos,
+ &st->size);
+ unuse_pack(&window);
+ switch (in_pack_type) {
+ default:
+ return -1; /* we do not do deltas for now */
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ break;
+ }
+ st->z_state = z_unused;
+ st->vtbl = &pack_non_delta_vtbl;
+ return 0;
+}
+
+
+/*****************************************************************
+ *
+ * In-core stream
+ *
+ *****************************************************************/
+
+static close_method_decl(incore)
+{
+ free(st->u.incore.buf);
+ return 0;
+}
+
+static read_method_decl(incore)
+{
+ size_t read_size = sz;
+ size_t remainder = st->size - st->u.incore.read_ptr;
+
+ if (remainder <= read_size)
+ read_size = remainder;
+ if (read_size) {
+ memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
+ st->u.incore.read_ptr += read_size;
+ }
+ return read_size;
+}
+
+static struct stream_vtbl incore_vtbl = {
+ close_istream_incore,
+ read_istream_incore,
+};
+
+static open_method_decl(incore)
+{
+ st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0);
+ st->u.incore.read_ptr = 0;
+ st->vtbl = &incore_vtbl;
+
+ return st->u.incore.buf ? 0 : -1;
+}
diff --git a/streaming.h b/streaming.h
new file mode 100644
index 0000000..589e857
--- /dev/null
+++ b/streaming.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef STREAMING_H
+#define STREAMING_H 1
+#include "cache.h"
+
+/* opaque */
+struct git_istream;
+
+extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *);
+extern int close_istream(struct git_istream *);
+extern ssize_t read_istream(struct git_istream *, char *, size_t);
+
+#endif /* STREAMING_H */
diff --git a/string-list.c b/string-list.c
index 9b023a2..5168118 100644
--- a/string-list.c
+++ b/string-list.c
@@ -153,6 +153,7 @@ struct string_list_item *string_list_append(struct string_list *list, const char
ALLOC_GROW(list->items, list->nr + 1, list->alloc);
list->items[list->nr].string =
list->strdup_strings ? xstrdup(string) : (char *)string;
+ list->items[list->nr].util = NULL;
return list->items + list->nr++;
}
diff --git a/string-list.h b/string-list.h
index 680d600..bda6983 100644
--- a/string-list.h
+++ b/string-list.h
@@ -5,13 +5,15 @@ struct string_list_item {
char *string;
void *util;
};
-struct string_list
-{
+struct string_list {
struct string_list_item *items;
unsigned int nr, alloc;
unsigned int strdup_strings:1;
};
+#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 }
+#define STRING_LIST_INIT_DUP { NULL, 0, 0, 1 }
+
void print_string_list(const struct string_list *p, const char *text);
void string_list_clear(struct string_list *list, int free_util);
@@ -20,10 +22,12 @@ void string_list_clear(struct string_list *list, int free_util);
typedef void (*string_list_clear_func_t)(void *p, const char *str);
void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
-/* Use this function to iterate over each item */
+/* Use this function or the macro below to iterate over each item */
typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
int for_each_string_list(struct string_list *list,
string_list_each_func_t, void *cb_data);
+#define for_each_string_list_item(item,list) \
+ for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
/* Use these functions only on sorted lists: */
int string_list_has_string(const struct string_list *list, const char *string);
diff --git a/submodule.c b/submodule.c
index 61cb6e2..1ba9646 100644
--- a/submodule.c
+++ b/submodule.c
@@ -6,6 +6,23 @@
#include "revision.h"
#include "run-command.h"
#include "diffcore.h"
+#include "refs.h"
+#include "string-list.h"
+
+static struct string_list config_name_for_path;
+static struct string_list config_fetch_recurse_submodules_for_name;
+static struct string_list config_ignore_for_name;
+static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
+static struct string_list changed_submodule_paths;
+/*
+ * The following flag is set if the .gitmodules file is unmerged. We then
+ * disable recursion for all submodules where .git/config doesn't have a
+ * matching config entry because we can't guess what might be configured in
+ * .gitmodules unless the user resolves the conflict. When a command line
+ * option is given (which always overrides configuration) this flag will be
+ * ignored.
+ */
+static int gitmodules_is_unmerged;
static int add_submodule_odb(const char *path)
{
@@ -46,30 +63,199 @@ done:
return ret;
}
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+ const char *path)
+{
+ struct string_list_item *path_option, *ignore_option;
+ path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+ if (path_option) {
+ ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
+ if (ignore_option)
+ handle_ignore_submodules_arg(diffopt, ignore_option->util);
+ else if (gitmodules_is_unmerged)
+ DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
+ }
+}
+
+int submodule_config(const char *var, const char *value, void *cb)
+{
+ if (!prefixcmp(var, "submodule."))
+ return parse_submodule_config_option(var, value);
+ else if (!strcmp(var, "fetch.recursesubmodules")) {
+ config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value);
+ return 0;
+ }
+ return 0;
+}
+
+void gitmodules_config(void)
+{
+ const char *work_tree = get_git_work_tree();
+ if (work_tree) {
+ struct strbuf gitmodules_path = STRBUF_INIT;
+ int pos;
+ strbuf_addstr(&gitmodules_path, work_tree);
+ strbuf_addstr(&gitmodules_path, "/.gitmodules");
+ if (read_cache() < 0)
+ die("index file corrupt");
+ pos = cache_name_pos(".gitmodules", 11);
+ if (pos < 0) { /* .gitmodules not found or isn't merged */
+ pos = -1 - pos;
+ if (active_nr > pos) { /* there is a .gitmodules */
+ const struct cache_entry *ce = active_cache[pos];
+ if (ce_namelen(ce) == 11 &&
+ !memcmp(ce->name, ".gitmodules", 11))
+ gitmodules_is_unmerged = 1;
+ }
+ }
+
+ if (!gitmodules_is_unmerged)
+ git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+ strbuf_release(&gitmodules_path);
+ }
+}
+
+int parse_submodule_config_option(const char *var, const char *value)
+{
+ int len;
+ struct string_list_item *config;
+ struct strbuf submodname = STRBUF_INIT;
+
+ var += 10; /* Skip "submodule." */
+
+ len = strlen(var);
+ if ((len > 5) && !strcmp(var + len - 5, ".path")) {
+ strbuf_add(&submodname, var, len - 5);
+ config = unsorted_string_list_lookup(&config_name_for_path, value);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_name_for_path, xstrdup(value));
+ config->util = strbuf_detach(&submodname, NULL);
+ strbuf_release(&submodname);
+ } else if ((len > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
+ strbuf_add(&submodname, var, len - 23);
+ config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+ if (!config)
+ config = string_list_append(&config_fetch_recurse_submodules_for_name,
+ strbuf_detach(&submodname, NULL));
+ config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value);
+ strbuf_release(&submodname);
+ } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
+ if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
+ strcmp(value, "all") && strcmp(value, "none")) {
+ warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
+ return 0;
+ }
+
+ strbuf_add(&submodname, var, len - 7);
+ config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
+ if (config)
+ free(config->util);
+ else
+ config = string_list_append(&config_ignore_for_name,
+ strbuf_detach(&submodname, NULL));
+ strbuf_release(&submodname);
+ config->util = xstrdup(value);
+ return 0;
+ }
+ return 0;
+}
+
void handle_ignore_submodules_arg(struct diff_options *diffopt,
const char *arg)
{
+ DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
+ DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
+
if (!strcmp(arg, "all"))
DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
else if (!strcmp(arg, "untracked"))
DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
else if (!strcmp(arg, "dirty"))
DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
- else
+ else if (strcmp(arg, "none"))
die("bad --ignore-submodules argument: %s", arg);
}
+static int prepare_submodule_summary(struct rev_info *rev, const char *path,
+ struct commit *left, struct commit *right,
+ int *fast_forward, int *fast_backward)
+{
+ struct commit_list *merge_bases, *list;
+
+ init_revisions(rev, NULL);
+ setup_revisions(0, NULL, rev, NULL);
+ rev->left_right = 1;
+ rev->first_parent_only = 1;
+ left->object.flags |= SYMMETRIC_LEFT;
+ add_pending_object(rev, &left->object, path);
+ add_pending_object(rev, &right->object, path);
+ merge_bases = get_merge_bases(left, right, 1);
+ if (merge_bases) {
+ if (merge_bases->item == left)
+ *fast_forward = 1;
+ else if (merge_bases->item == right)
+ *fast_backward = 1;
+ }
+ for (list = merge_bases; list; list = list->next) {
+ list->item->object.flags |= UNINTERESTING;
+ add_pending_object(rev, &list->item->object,
+ sha1_to_hex(list->item->object.sha1));
+ }
+ return prepare_revision_walk(rev);
+}
+
+static void print_submodule_summary(struct rev_info *rev, FILE *f,
+ const char *del, const char *add, const char *reset)
+{
+ static const char format[] = " %m %s";
+ struct strbuf sb = STRBUF_INIT;
+ struct commit *commit;
+
+ while ((commit = get_revision(rev))) {
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = rev->date_mode;
+ strbuf_setlen(&sb, 0);
+ if (commit->object.flags & SYMMETRIC_LEFT) {
+ if (del)
+ strbuf_addstr(&sb, del);
+ }
+ else if (add)
+ strbuf_addstr(&sb, add);
+ format_commit_message(commit, format, &sb, &ctx);
+ if (reset)
+ strbuf_addstr(&sb, reset);
+ strbuf_addch(&sb, '\n');
+ fprintf(f, "%s", sb.buf);
+ }
+ strbuf_release(&sb);
+}
+
+int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)
+{
+ switch (git_config_maybe_bool(opt, arg)) {
+ case 1:
+ return RECURSE_SUBMODULES_ON;
+ case 0:
+ return RECURSE_SUBMODULES_OFF;
+ default:
+ if (!strcmp(arg, "on-demand"))
+ return RECURSE_SUBMODULES_ON_DEMAND;
+ die("bad %s argument: %s", opt, arg);
+ }
+}
+
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
const char *del, const char *add, const char *reset)
{
struct rev_info rev;
- struct commit *commit, *left = left, *right = right;
- struct commit_list *merge_bases, *list;
+ struct commit *left = left, *right = right;
const char *message = NULL;
struct strbuf sb = STRBUF_INIT;
- static const char *format = " %m %s";
int fast_forward = 0, fast_backward = 0;
if (is_null_sha1(two))
@@ -82,29 +268,10 @@ void show_submodule_summary(FILE *f, const char *path,
!(right = lookup_commit_reference(two)))
message = "(commits not present)";
- if (!message) {
- init_revisions(&rev, NULL);
- setup_revisions(0, NULL, &rev, NULL);
- rev.left_right = 1;
- rev.first_parent_only = 1;
- left->object.flags |= SYMMETRIC_LEFT;
- add_pending_object(&rev, &left->object, path);
- add_pending_object(&rev, &right->object, path);
- merge_bases = get_merge_bases(left, right, 1);
- if (merge_bases) {
- if (merge_bases->item == left)
- fast_forward = 1;
- else if (merge_bases->item == right)
- fast_backward = 1;
- }
- for (list = merge_bases; list; list = list->next) {
- list->item->object.flags |= UNINTERESTING;
- add_pending_object(&rev, &list->item->object,
- sha1_to_hex(list->item->object.sha1));
- }
- if (prepare_revision_walk(&rev))
- message = "(revision walker failed)";
- }
+ if (!message &&
+ prepare_submodule_summary(&rev, path, left, right,
+ &fast_forward, &fast_backward))
+ message = "(revision walker failed)";
if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
fprintf(f, "Submodule %s contains untracked content\n", path);
@@ -128,28 +295,212 @@ void show_submodule_summary(FILE *f, const char *path,
fwrite(sb.buf, sb.len, 1, f);
if (!message) {
- while ((commit = get_revision(&rev))) {
- struct pretty_print_context ctx = {0};
- ctx.date_mode = rev.date_mode;
- strbuf_setlen(&sb, 0);
- if (commit->object.flags & SYMMETRIC_LEFT) {
- if (del)
- strbuf_addstr(&sb, del);
- }
- else if (add)
- strbuf_addstr(&sb, add);
- format_commit_message(commit, format, &sb, &ctx);
- if (reset)
- strbuf_addstr(&sb, reset);
- strbuf_addch(&sb, '\n');
- fprintf(f, "%s", sb.buf);
- }
+ print_submodule_summary(&rev, f, del, add, reset);
clear_commit_marks(left, ~0);
clear_commit_marks(right, ~0);
}
+
strbuf_release(&sb);
}
+void set_config_fetch_recurse_submodules(int value)
+{
+ config_fetch_recurse_submodules = value;
+}
+
+static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
+{
+ int is_present = 0;
+ if (!add_submodule_odb(path) && lookup_commit_reference(sha1)) {
+ /* Even if the submodule is checked out and the commit is
+ * present, make sure it is reachable from a ref. */
+ struct child_process cp;
+ const char *argv[] = {"rev-list", "-n", "1", NULL, "--not", "--all", NULL};
+ struct strbuf buf = STRBUF_INIT;
+
+ argv[3] = sha1_to_hex(sha1);
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.out = -1;
+ cp.dir = path;
+ if (!run_command(&cp) && !strbuf_read(&buf, cp.out, 1024))
+ is_present = 1;
+
+ close(cp.out);
+ strbuf_release(&buf);
+ }
+ return is_present;
+}
+
+static void submodule_collect_changed_cb(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!S_ISGITLINK(p->two->mode))
+ continue;
+
+ if (S_ISGITLINK(p->one->mode)) {
+ /* NEEDSWORK: We should honor the name configured in
+ * the .gitmodules file of the commit we are examining
+ * here to be able to correctly follow submodules
+ * being moved around. */
+ struct string_list_item *path;
+ path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path);
+ if (!path && !is_submodule_commit_present(p->two->path, p->two->sha1))
+ string_list_append(&changed_submodule_paths, xstrdup(p->two->path));
+ } else {
+ /* Submodule is new or was moved here */
+ /* NEEDSWORK: When the .git directories of submodules
+ * live inside the superprojects .git directory some
+ * day we should fetch new submodules directly into
+ * that location too when config or options request
+ * that so they can be checked out from there. */
+ continue;
+ }
+ }
+}
+
+void check_for_new_submodule_commits(unsigned char new_sha1[20])
+{
+ struct rev_info rev;
+ struct commit *commit;
+ const char *argv[] = {NULL, NULL, "--not", "--all", NULL};
+ int argc = ARRAY_SIZE(argv) - 1;
+
+ init_revisions(&rev, NULL);
+ argv[1] = xstrdup(sha1_to_hex(new_sha1));
+ setup_revisions(argc, argv, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ /*
+ * Collect all submodules (whether checked out or not) for which new
+ * commits have been recorded upstream in "changed_submodule_paths".
+ */
+ while ((commit = get_revision(&rev))) {
+ struct commit_list *parent = commit->parents;
+ while (parent) {
+ struct diff_options diff_opts;
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.output_format |= DIFF_FORMAT_CALLBACK;
+ diff_opts.format_callback = submodule_collect_changed_cb;
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(parent->item->object.sha1, commit->object.sha1, "", &diff_opts);
+ diffcore_std(&diff_opts);
+ diff_flush(&diff_opts);
+ parent = parent->next;
+ }
+ }
+ free((char *)argv[1]);
+}
+
+int fetch_populated_submodules(int num_options, const char **options,
+ const char *prefix, int command_line_option,
+ int quiet)
+{
+ int i, result = 0, argc = 0, default_argc;
+ struct child_process cp;
+ const char **argv;
+ struct string_list_item *name_for_path;
+ const char *work_tree = get_git_work_tree();
+ if (!work_tree)
+ goto out;
+
+ if (!the_index.initialized)
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ /* 6: "fetch" (options) --recurse-submodules-default default "--submodule-prefix" prefix NULL */
+ argv = xcalloc(num_options + 6, sizeof(const char *));
+ argv[argc++] = "fetch";
+ for (i = 0; i < num_options; i++)
+ argv[argc++] = options[i];
+ argv[argc++] = "--recurse-submodules-default";
+ default_argc = argc++;
+ argv[argc++] = "--submodule-prefix";
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = local_repo_env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+
+ for (i = 0; i < active_nr; i++) {
+ struct strbuf submodule_path = STRBUF_INIT;
+ struct strbuf submodule_git_dir = STRBUF_INIT;
+ struct strbuf submodule_prefix = STRBUF_INIT;
+ struct cache_entry *ce = active_cache[i];
+ const char *git_dir, *name, *default_argv;
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ name = ce->name;
+ name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name);
+ if (name_for_path)
+ name = name_for_path->util;
+
+ default_argv = "yes";
+ if (command_line_option == RECURSE_SUBMODULES_DEFAULT) {
+ struct string_list_item *fetch_recurse_submodules_option;
+ fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name);
+ if (fetch_recurse_submodules_option) {
+ if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF)
+ continue;
+ if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+ } else {
+ if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) ||
+ gitmodules_is_unmerged)
+ continue;
+ if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+ }
+ } else if (command_line_option == RECURSE_SUBMODULES_ON_DEMAND) {
+ if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
+ continue;
+ default_argv = "on-demand";
+ }
+
+ strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
+ strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
+ strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
+ git_dir = read_gitfile_gently(submodule_git_dir.buf);
+ if (!git_dir)
+ git_dir = submodule_git_dir.buf;
+ if (is_directory(git_dir)) {
+ if (!quiet)
+ printf("Fetching submodule %s%s\n", prefix, ce->name);
+ cp.dir = submodule_path.buf;
+ argv[default_argc] = default_argv;
+ argv[argc] = submodule_prefix.buf;
+ if (run_command(&cp))
+ result = 1;
+ }
+ strbuf_release(&submodule_path);
+ strbuf_release(&submodule_git_dir);
+ strbuf_release(&submodule_prefix);
+ }
+ free(argv);
+out:
+ string_list_clear(&changed_submodule_paths, 1);
+ return result;
+}
+
unsigned is_submodule_modified(const char *path, int ignore_untracked)
{
ssize_t len;
@@ -218,3 +569,163 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
strbuf_release(&buf);
return dirty_submodule;
}
+
+static int find_first_merges(struct object_array *result, const char *path,
+ struct commit *a, struct commit *b)
+{
+ int i, j;
+ struct object_array merges;
+ struct commit *commit;
+ int contains_another;
+
+ char merged_revision[42];
+ const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+ "--all", merged_revision, NULL };
+ struct rev_info revs;
+ struct setup_revision_opt rev_opts;
+
+ memset(&merges, 0, sizeof(merges));
+ memset(result, 0, sizeof(struct object_array));
+ memset(&rev_opts, 0, sizeof(rev_opts));
+
+ /* get all revisions that merge commit a */
+ snprintf(merged_revision, sizeof(merged_revision), "^%s",
+ sha1_to_hex(a->object.sha1));
+ init_revisions(&revs, NULL);
+ rev_opts.submodule = path;
+ setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts);
+
+ /* save all revisions from the above list that contain b */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct object *o = &(commit->object);
+ if (in_merge_bases(b, &commit, 1))
+ add_object_array(o, NULL, &merges);
+ }
+
+ /* Now we've got all merges that contain a and b. Prune all
+ * merges that contain another found merge and save them in
+ * result.
+ */
+ for (i = 0; i < merges.nr; i++) {
+ struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+ contains_another = 0;
+ for (j = 0; j < merges.nr; j++) {
+ struct commit *m2 = (struct commit *) merges.objects[j].item;
+ if (i != j && in_merge_bases(m2, &m1, 1)) {
+ contains_another = 1;
+ break;
+ }
+ }
+
+ if (!contains_another)
+ add_object_array(merges.objects[i].item,
+ merges.objects[i].name, result);
+ }
+
+ free(merges.objects);
+ return result->nr;
+}
+
+static void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+ fprintf(stderr, "%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+#define MERGE_WARNING(path, msg) \
+ warning("Failed to merge submodule %s (%s)", path, msg);
+
+int merge_submodule(unsigned char result[20], const char *path,
+ const unsigned char base[20], const unsigned char a[20],
+ const unsigned char b[20])
+{
+ struct commit *commit_base, *commit_a, *commit_b;
+ int parent_count;
+ struct object_array merges;
+
+ int i;
+
+ /* store a in result in case we fail */
+ hashcpy(result, a);
+
+ /* we can not handle deletion conflicts */
+ if (is_null_sha1(base))
+ return 0;
+ if (is_null_sha1(a))
+ return 0;
+ if (is_null_sha1(b))
+ return 0;
+
+ if (add_submodule_odb(path)) {
+ MERGE_WARNING(path, "not checked out");
+ return 0;
+ }
+
+ if (!(commit_base = lookup_commit_reference(base)) ||
+ !(commit_a = lookup_commit_reference(a)) ||
+ !(commit_b = lookup_commit_reference(b))) {
+ MERGE_WARNING(path, "commits not present");
+ return 0;
+ }
+
+ /* check whether both changes are forward */
+ if (!in_merge_bases(commit_base, &commit_a, 1) ||
+ !in_merge_bases(commit_base, &commit_b, 1)) {
+ MERGE_WARNING(path, "commits don't follow merge-base");
+ return 0;
+ }
+
+ /* Case #1: a is contained in b or vice versa */
+ if (in_merge_bases(commit_a, &commit_b, 1)) {
+ hashcpy(result, b);
+ return 1;
+ }
+ if (in_merge_bases(commit_b, &commit_a, 1)) {
+ hashcpy(result, a);
+ return 1;
+ }
+
+ /*
+ * Case #2: There are one or more merges that contain a and b in
+ * the submodule. If there is only one, then present it as a
+ * suggestion to the user, but leave it marked unmerged so the
+ * user needs to confirm the resolution.
+ */
+
+ /* find commit which merges them */
+ parent_count = find_first_merges(&merges, path, commit_a, commit_b);
+ switch (parent_count) {
+ case 0:
+ MERGE_WARNING(path, "merge following commits not found");
+ break;
+
+ case 1:
+ MERGE_WARNING(path, "not fast-forward");
+ fprintf(stderr, "Found a possible merge resolution "
+ "for the submodule:\n");
+ print_commit((struct commit *) merges.objects[0].item);
+ fprintf(stderr,
+ "If this is correct simply add it to the index "
+ "for example\n"
+ "by using:\n\n"
+ " git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+ "which will accept this suggestion.\n",
+ sha1_to_hex(merges.objects[0].item->sha1), path);
+ break;
+
+ default:
+ MERGE_WARNING(path, "multiple merges found");
+ for (i = 0; i < merges.nr; i++)
+ print_commit((struct commit *) merges.objects[i].item);
+ }
+
+ free(merges.objects);
+ return 0;
+}
diff --git a/submodule.h b/submodule.h
index 6fd3bb4..5350b0d 100644
--- a/submodule.h
+++ b/submodule.h
@@ -3,11 +3,31 @@
struct diff_options;
+enum {
+ RECURSE_SUBMODULES_ON_DEMAND = -1,
+ RECURSE_SUBMODULES_OFF = 0,
+ RECURSE_SUBMODULES_DEFAULT = 1,
+ RECURSE_SUBMODULES_ON = 2
+};
+
+void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
+ const char *path);
+int submodule_config(const char *var, const char *value, void *cb);
+void gitmodules_config();
+int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
+int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
unsigned dirty_submodule,
const char *del, const char *add, const char *reset);
+void set_config_fetch_recurse_submodules(int value);
+void check_for_new_submodule_commits(unsigned char new_sha1[20]);
+int fetch_populated_submodules(int num_options, const char **options,
+ const char *prefix, int command_line_option,
+ int quiet);
unsigned is_submodule_modified(const char *path, int ignore_untracked);
+int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
+ const unsigned char a[20], const unsigned char b[20]);
#endif
diff --git a/symlinks.c b/symlinks.c
index 8860120..034943b 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -64,11 +64,13 @@ static inline void reset_lstat_cache(struct cache_def *cache)
* of the prefix, where the cache should use the stat() function
* instead of the lstat() function to test each path component.
*/
-static int lstat_cache(struct cache_def *cache, const char *name, int len,
- int track_flags, int prefix_len_stat_func)
+static int lstat_cache_matchlen(struct cache_def *cache,
+ const char *name, int len,
+ int *ret_flags, int track_flags,
+ int prefix_len_stat_func)
{
int match_len, last_slash, last_slash_dir, previous_slash;
- int match_flags, ret_flags, save_flags, max_len, ret;
+ int save_flags, max_len, ret;
struct stat st;
if (cache->track_flags != track_flags ||
@@ -90,13 +92,13 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
match_len = last_slash =
longest_path_match(name, len, cache->path, cache->len,
&previous_slash);
- match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK);
+ *ret_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK);
if (!(track_flags & FL_FULLPATH) && match_len == len)
match_len = last_slash = previous_slash;
- if (match_flags && match_len == cache->len)
- return match_flags;
+ if (*ret_flags && match_len == cache->len)
+ return match_len;
/*
* If we now have match_len > 0, we would know that
* the matched part will always be a directory.
@@ -105,16 +107,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
* a substring of the cache on a path component basis,
* we can return immediately.
*/
- match_flags = track_flags & FL_DIR;
- if (match_flags && len == match_len)
- return match_flags;
+ *ret_flags = track_flags & FL_DIR;
+ if (*ret_flags && len == match_len)
+ return match_len;
}
/*
* Okay, no match from the cache so far, so now we have to
* check the rest of the path components.
*/
- ret_flags = FL_DIR;
+ *ret_flags = FL_DIR;
last_slash_dir = last_slash;
max_len = len < PATH_MAX ? len : PATH_MAX;
while (match_len < max_len) {
@@ -133,16 +135,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
ret = lstat(cache->path, &st);
if (ret) {
- ret_flags = FL_LSTATERR;
+ *ret_flags = FL_LSTATERR;
if (errno == ENOENT)
- ret_flags |= FL_NOENT;
+ *ret_flags |= FL_NOENT;
} else if (S_ISDIR(st.st_mode)) {
last_slash_dir = last_slash;
continue;
} else if (S_ISLNK(st.st_mode)) {
- ret_flags = FL_SYMLINK;
+ *ret_flags = FL_SYMLINK;
} else {
- ret_flags = FL_ERR;
+ *ret_flags = FL_ERR;
}
break;
}
@@ -152,7 +154,7 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
* path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached
* for the moment!
*/
- save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
+ save_flags = *ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {
cache->path[last_slash] = '\0';
cache->len = last_slash;
@@ -176,7 +178,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
} else {
reset_lstat_cache(cache);
}
- return ret_flags;
+ return match_len;
+}
+
+static int lstat_cache(struct cache_def *cache, const char *name, int len,
+ int track_flags, int prefix_len_stat_func)
+{
+ int flags;
+ (void)lstat_cache_matchlen(cache, name, len, &flags, track_flags,
+ prefix_len_stat_func);
+ return flags;
}
#define USE_ONLY_LSTAT 0
@@ -198,15 +209,26 @@ int has_symlink_leading_path(const char *name, int len)
}
/*
- * Return non-zero if path 'name' has a leading symlink component or
+ * Return zero if path 'name' has a leading symlink component or
* if some leading path component does not exists.
+ *
+ * Return -1 if leading path exists and is a directory.
+ *
+ * Return path length if leading path exists and is neither a
+ * directory nor a symlink.
*/
-int has_symlink_or_noent_leading_path(const char *name, int len)
+int check_leading_path(const char *name, int len)
{
struct cache_def *cache = &default_cache; /* FIXME */
- return lstat_cache(cache, name, len,
- FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
- (FL_SYMLINK|FL_NOENT);
+ int flags;
+ int match_len = lstat_cache_matchlen(cache, name, len, &flags,
+ FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT);
+ if (flags & FL_NOENT)
+ return 0;
+ else if (flags & FL_DIR)
+ return -1;
+ else
+ return match_len;
}
/*
diff --git a/t/.gitignore b/t/.gitignore
index 7dcbb23..4e731dc 100644
--- a/t/.gitignore
+++ b/t/.gitignore
@@ -1,2 +1,3 @@
/trash directory*
/test-results
+/.prove
diff --git a/t/Makefile b/t/Makefile
index f9de24b..9046ec9 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -8,18 +8,28 @@
#GIT_TEST_OPTS=--verbose --debug
SHELL_PATH ?= $(SHELL)
+PERL_PATH ?= /usr/bin/perl
TAR ?= $(TAR)
RM ?= rm -f
+PROVE ?= prove
+DEFAULT_TEST_TARGET ?= test
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
+TGITWEB = $(wildcard t95[0-9][0-9]-*.sh)
-all: pre-clean
+all: $(DEFAULT_TEST_TARGET)
+
+test: pre-clean $(TEST_LINT)
$(MAKE) aggregate-results-and-cleanup
+prove: pre-clean $(TEST_LINT)
+ @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+ $(MAKE) clean
+
$(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
@@ -28,15 +38,27 @@ pre-clean:
clean:
$(RM) -r 'trash directory'.* test-results
- $(RM) t????/cvsroot/CVSROOT/?*
$(RM) -r valgrind/bin
+ $(RM) .prove
+
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+ @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+ test -z "$$dups" || { \
+ echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+ @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+ test -z "$$bad" || { \
+ echo >&2 "non-executable tests:" $$bad; exit 1; }
aggregate-results-and-cleanup: $(T)
$(MAKE) aggregate-results
$(MAKE) clean
aggregate-results:
- for f in test-results/t*-*; do \
+ for f in test-results/t*-*.counts; do \
echo "$$f"; \
done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
@@ -45,7 +67,48 @@ full-svn-test:
$(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
$(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
+gitweb-test:
+ $(MAKE) $(TGITWEB)
+
valgrind:
- GIT_TEST_OPTS=--valgrind $(MAKE)
+ $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
+
+# Smoke testing targets
+-include ../GIT-VERSION-FILE
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo unknown')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo unknown')
+
+test-results:
+ mkdir -p test-results
+
+test-results/git-smoke.tar.gz: test-results
+ $(PERL_PATH) ./harness \
+ --archive="test-results/git-smoke.tar.gz" \
+ $(T)
+
+smoke: test-results/git-smoke.tar.gz
+
+SMOKE_UPLOAD_FLAGS =
+ifdef SMOKE_USERNAME
+ SMOKE_UPLOAD_FLAGS += -F username="$(SMOKE_USERNAME)" -F password="$(SMOKE_PASSWORD)"
+endif
+ifdef SMOKE_COMMENT
+ SMOKE_UPLOAD_FLAGS += -F comments="$(SMOKE_COMMENT)"
+endif
+ifdef SMOKE_TAGS
+ SMOKE_UPLOAD_FLAGS += -F tags="$(SMOKE_TAGS)"
+endif
+
+smoke_report: smoke
+ curl \
+ -H "Expect: " \
+ -F project=Git \
+ -F architecture="$(uname_M)" \
+ -F platform="$(uname_S)" \
+ -F revision="$(GIT_VERSION)" \
+ -F report_file=@test-results/git-smoke.tar.gz \
+ $(SMOKE_UPLOAD_FLAGS) \
+ http://smoke.git.nix.is/app/projects/process_add_report/1 \
+ | grep -v ^Redirecting
-.PHONY: pre-clean $(T) aggregate-results clean valgrind
+.PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
diff --git a/t/README b/t/README
index 0e4e8d8..c85abaf 100644
--- a/t/README
+++ b/t/README
@@ -18,25 +18,54 @@ The easiest way to run tests is to say "make". This runs all
the tests.
*** t0000-basic.sh ***
- * ok 1: .git/objects should be empty after git-init in an empty repo.
- * ok 2: .git/objects should have 256 subdirectories.
- * ok 3: git-update-index without --add should fail adding.
+ ok 1 - .git/objects should be empty after git init in an empty repo.
+ ok 2 - .git/objects should have 3 subdirectories.
+ ok 3 - success is reported like this
...
- * ok 23: no diff after checkout and git-update-index --refresh.
- * passed all 23 test(s)
- *** t0100-environment-names.sh ***
- * ok 1: using old names should issue warnings.
- * ok 2: using old names but having new names should not issue warnings.
- ...
-
-Or you can run each test individually from command line, like
-this:
-
- $ sh ./t3001-ls-files-killed.sh
- * ok 1: git-update-index --add to add various paths.
- * ok 2: git-ls-files -k to show killed files.
- * ok 3: validate git-ls-files -k output.
- * passed all 3 test(s)
+ ok 43 - very long name in the index handled sanely
+ # fixed 1 known breakage(s)
+ # still have 1 known breakage(s)
+ # passed all remaining 42 test(s)
+ 1..43
+ *** t0001-init.sh ***
+ ok 1 - plain
+ ok 2 - plain with GIT_WORK_TREE
+ ok 3 - plain bare
+
+Since the tests all output TAP (see http://testanything.org) they can
+be run with any TAP harness. Here's an example of parallel testing
+powered by a recent version of prove(1):
+
+ $ prove --timer --jobs 15 ./t[0-9]*.sh
+ [19:17:33] ./t0005-signals.sh ................................... ok 36 ms
+ [19:17:33] ./t0022-crlf-rename.sh ............................... ok 69 ms
+ [19:17:33] ./t0024-crlf-archive.sh .............................. ok 154 ms
+ [19:17:33] ./t0004-unwritable.sh ................................ ok 289 ms
+ [19:17:33] ./t0002-gitfile.sh ................................... ok 480 ms
+ ===( 102;0 25/? 6/? 5/? 16/? 1/? 4/? 2/? 1/? 3/? 1... )===
+
+prove and other harnesses come with a lot of useful options. The
+--state option in particular is very useful:
+
+ # Repeat until no more failures
+ $ prove -j 15 --state=failed,save ./t[0-9]*.sh
+
+You can give DEFAULT_TEST_TARGET=prove on the make command (or define it
+in config.mak) to cause "make test" to run tests under prove.
+GIT_PROVE_OPTS can be used to pass additional options, e.g.
+
+ $ make DEFAULT_TEST_TARGET=prove GIT_PROVE_OPTS='--timer --jobs 16' test
+
+You can also run each test individually from command line, like this:
+
+ $ sh ./t3010-ls-files-killed-modified.sh
+ ok 1 - git update-index --add to add various paths.
+ ok 2 - git ls-files -k to show killed files.
+ ok 3 - validate git ls-files -k output.
+ ok 4 - git ls-files -m to show modified files.
+ ok 5 - validate git ls-files -m output.
+ # passed all 5 test(s)
+ 1..5
You can pass --verbose (or -v), --debug (or -d), and --immediate
(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
@@ -50,6 +79,10 @@ appropriately before running "make".
--debug::
This may help the person who is developing a new test.
It causes the command defined with test_debug to run.
+ The "trash" directory (used to store all temporary data
+ during testing) is not deleted even if there are no
+ failed tests so that you can inspect its contents after
+ the test finished.
--immediate::
This causes the test to immediately exit upon the first
@@ -69,6 +102,13 @@ appropriately before running "make".
not see any output, this option implies --verbose. For
convenience, it also implies --tee.
+ Note that valgrind is run with the option --leak-check=no,
+ as the git process is short-lived and some errors are not
+ interesting. In order to run a single command under the same
+ conditions manually, you should set GIT_VALGRIND to point to
+ the 't/valgrind/' directory and use the commands under
+ 't/valgrind/bin/'.
+
--tee::
In addition to printing the test output to the terminal,
write it to files named 't/test-results/$TEST_NAME.out'.
@@ -161,7 +201,7 @@ we are testing.
If you create files under t/ directory (i.e. here) that is not
the top-level test script, never name the file to match the above
pattern. The Makefile here considers all such files as the
-top-level test script and tries to run all of them. A care is
+top-level test script and tries to run all of them. Care is
especially needed if you are creating a common test library
file, similar to test-lib.sh, because such a library file may
not be suitable for standalone execution.
@@ -198,15 +238,130 @@ This test harness library does the following things:
- If the script is invoked with command line argument --help
(or -h), it shows the test_description and exits.
- - Creates an empty test directory with an empty .git/objects
- database and chdir(2) into it. This directory is 't/trash directory'
- if you must know, but I do not think you care.
+ - Creates an empty test directory with an empty .git/objects database
+ and chdir(2) into it. This directory is 't/trash
+ directory.$test_name_without_dotsh', with t/ subject to change by
+ the --root option documented above.
- Defines standard test helper functions for your scripts to
use. These functions are designed to make all scripts behave
consistently when command line arguments --verbose (or -v),
--debug (or -d), and --immediate (or -i) is given.
+Do's, don'ts & things to keep in mind
+-------------------------------------
+
+Here are a few examples of things you probably should and shouldn't do
+when writing tests.
+
+Do:
+
+ - Put all code inside test_expect_success and other assertions.
+
+ Even code that isn't a test per se, but merely some setup code
+ should be inside a test assertion.
+
+ - Chain your test assertions
+
+ Write test code like this:
+
+ git merge foo &&
+ git push bar &&
+ test ...
+
+ Instead of:
+
+ git merge hla
+ git push gh
+ test ...
+
+ That way all of the commands in your tests will succeed or fail. If
+ you must ignore the return value of something, consider using a
+ helper function (e.g. use sane_unset instead of unset, in order
+ to avoid unportable return value for unsetting a variable that was
+ already unset), or prepending the command with test_might_fail or
+ test_must_fail.
+
+ - Check the test coverage for your tests. See the "Test coverage"
+ below.
+
+ Don't blindly follow test coverage metrics; if a new function you added
+ doesn't have any coverage, then you're probably doing something wrong,
+ but having 100% coverage doesn't necessarily mean that you tested
+ everything.
+
+ Tests that are likely to smoke out future regressions are better
+ than tests that just inflate the coverage metrics.
+
+ - When a test checks for an absolute path that a git command generated,
+ construct the expected value using $(pwd) rather than $PWD,
+ $TEST_DIRECTORY, or $TRASH_DIRECTORY. It makes a difference on
+ Windows, where the shell (MSYS bash) mangles absolute path names.
+ For details, see the commit message of 4114156ae9.
+
+Don't:
+
+ - exit() within a <script> part.
+
+ The harness will catch this as a programming error of the test.
+ Use test_done instead if you need to stop the tests early (see
+ "Skipping tests" below).
+
+ - Break the TAP output
+
+ The raw output from your test may be interpreted by a TAP harness. TAP
+ harnesses will ignore everything they don't know about, but don't step
+ on their toes in these areas:
+
+ - Don't print lines like "$x..$y" where $x and $y are integers.
+
+ - Don't print lines that begin with "ok" or "not ok".
+
+ TAP harnesses expect a line that begins with either "ok" and "not
+ ok" to signal a test passed or failed (and our harness already
+ produces such lines), so your script shouldn't emit such lines to
+ their output.
+
+ You can glean some further possible issues from the TAP grammar
+ (see http://search.cpan.org/perldoc?TAP::Parser::Grammar#TAP_Grammar)
+ but the best indication is to just run the tests with prove(1),
+ it'll complain if anything is amiss.
+
+Keep in mind:
+
+ - Inside <script> part, the standard output and standard error
+ streams are discarded, and the test harness only reports "ok" or
+ "not ok" to the end user running the tests. Under --verbose, they
+ are shown to help debugging the tests.
+
+
+Skipping tests
+--------------
+
+If you need to skip tests you should do so by using the three-arg form
+of the test_* functions (see the "Test harness library" section
+below), e.g.:
+
+ test_expect_success PERL 'I need Perl' "
+ '$PERL_PATH' -e 'hlagh() if unf_unf()'
+ "
+
+The advantage of skipping tests like this is that platforms that don't
+have the PERL and other optional dependencies get an indication of how
+many tests they're missing.
+
+If the test code is too hairy for that (i.e. does a lot of setup work
+outside test assertions) you can also skip all remaining tests by
+setting skip_all and immediately call test_done:
+
+ if ! test_have_prereq PERL
+ then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+ fi
+
+The string you give to skip_all will be used as an explanation for why
+the test was skipped.
End with test_done
------------------
@@ -222,9 +377,9 @@ Test harness library
There are a handful helper functions defined in the test harness
library for your script to use.
- - test_expect_success <message> <script>
+ - test_expect_success [<prereq>] <message> <script>
- This takes two strings as parameter, and evaluates the
+ Usually takes two strings as parameters, and evaluates the
<script>. If it yields success, test is considered
successful. <message> should state what it is testing.
@@ -234,7 +389,20 @@ library for your script to use.
'git-write-tree should be able to write an empty tree.' \
'tree=$(git-write-tree)'
- - test_expect_failure <message> <script>
+ If you supply three parameters the first will be taken to be a
+ prerequisite; see the test_set_prereq and test_have_prereq
+ documentation below:
+
+ test_expect_success TTY 'git --paginate rev-list uses a pager' \
+ ' ... '
+
+ You can also supply a comma-separated list of prerequisites, in the
+ rare case where your test depends on more than one:
+
+ test_expect_success PERL,PYTHON 'yo dawg' \
+ ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" '
+
+ - test_expect_failure [<prereq>] <message> <script>
This is NOT the opposite of test_expect_success, but is used
to mark a test that demonstrates a known breakage. Unlike
@@ -243,6 +411,9 @@ library for your script to use.
success and "still broken" on failure. Failures from these
tests won't cause -i (immediate) to stop.
+ Like test_expect_success this function can optionally use a three
+ argument invocation with a prerequisite as the first argument.
+
- test_debug <script>
This takes a single argument, <script>, and evaluates it only
@@ -259,7 +430,7 @@ library for your script to use.
- test_tick
Make commit and tag names consistent by setting the author and
- committer times to defined stated. Subsequent calls will
+ committer times to defined state. Subsequent calls will
advance the times by a fixed amount.
- test_commit <message> [<filename> [<contents>]]
@@ -275,6 +446,153 @@ library for your script to use.
Merges the given rev using the given message. Like test_commit,
creates a tag and calls test_tick before committing.
+ - test_set_prereq <prereq>
+
+ Set a test prerequisite to be used later with test_have_prereq. The
+ test-lib will set some prerequisites for you, see the
+ "Prerequisites" section below for a full list of these.
+
+ Others you can set yourself and use later with either
+ test_have_prereq directly, or the three argument invocation of
+ test_expect_success and test_expect_failure.
+
+ - test_have_prereq <prereq>
+
+ Check if we have a prerequisite previously set with
+ test_set_prereq. The most common use of this directly is to skip
+ all the tests if we don't have some essential prerequisite:
+
+ if ! test_have_prereq PERL
+ then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+ fi
+
+ - test_external [<prereq>] <message> <external> <script>
+
+ Execute a <script> with an <external> interpreter (like perl). This
+ was added for tests like t9700-perl-git.sh which do most of their
+ work in an external test script.
+
+ test_external \
+ 'GitwebCache::*FileCache*' \
+ "$PERL_PATH" "$TEST_DIRECTORY"/t9503/test_cache_interface.pl
+
+ If the test is outputting its own TAP you should set the
+ test_external_has_tap variable somewhere before calling the first
+ test_external* function. See t9700-perl-git.sh for an example.
+
+ # The external test will outputs its own plan
+ test_external_has_tap=1
+
+ - test_external_without_stderr [<prereq>] <message> <external> <script>
+
+ Like test_external but fail if there's any output on stderr,
+ instead of checking the exit code.
+
+ test_external_without_stderr \
+ 'Perl API' \
+ "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
+
+ - test_expect_code <exit-code> <command>
+
+ Run a command and ensure that it exits with the given exit code.
+ For example:
+
+ test_expect_success 'Merge with d/f conflicts' '
+ test_expect_code 1 git merge "merge msg" B master
+ '
+
+ - test_must_fail <git-command>
+
+ Run a git command and ensure it fails in a controlled way. Use
+ this instead of "! <git-command>". When git-command dies due to a
+ segfault, test_must_fail diagnoses it as an error; "! <git-command>"
+ treats it as just another expected failure, which would let such a
+ bug go unnoticed.
+
+ - test_might_fail <git-command>
+
+ Similar to test_must_fail, but tolerate success, too. Use this
+ instead of "<git-command> || :" to catch failures due to segv.
+
+ - test_cmp <expected> <actual>
+
+ Check whether the content of the <actual> file matches the
+ <expected> file. This behaves like "cmp" but produces more
+ helpful output when the test is run with "-v" option.
+
+ - test_line_count (= | -lt | -ge | ...) <length> <file>
+
+ Check whether a file has the length it is expected to.
+
+ - test_path_is_file <path> [<diagnosis>]
+ test_path_is_dir <path> [<diagnosis>]
+ test_path_is_missing <path> [<diagnosis>]
+
+ Check if the named path is a file, if the named path is a
+ directory, or if the named path does not exist, respectively,
+ and fail otherwise, showing the <diagnosis> text.
+
+ - test_when_finished <script>
+
+ Prepend <script> to a list of commands to run to clean up
+ at the end of the current test. If some clean-up command
+ fails, the test will not pass.
+
+ Example:
+
+ test_expect_success 'branch pointing to non-commit' '
+ git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
+ test_when_finished "git update-ref -d refs/heads/invalid" &&
+ ...
+ '
+
+Prerequisites
+-------------
+
+These are the prerequisites that the test library predefines with
+test_have_prereq.
+
+See the prereq argument to the test_* functions in the "Test harness
+library" section above and the "test_have_prereq" function for how to
+use these, and "test_set_prereq" for how to define your own.
+
+ - PERL & PYTHON
+
+ Git wasn't compiled with NO_PERL=YesPlease or
+ NO_PYTHON=YesPlease. Wrap any tests that need Perl or Python in
+ these.
+
+ - POSIXPERM
+
+ The filesystem supports POSIX style permission bits.
+
+ - BSLASHPSPEC
+
+ Backslashes in pathspec are not directory separators. This is not
+ set on Windows. See 6fd1106a for details.
+
+ - EXECKEEPSPID
+
+ The process retains the same pid across exec(2). See fb9a2bea for
+ details.
+
+ - SYMLINKS
+
+ The filesystem we're on supports symbolic links. E.g. a FAT
+ filesystem doesn't support these. See 704a3143 for details.
+
+ - SANITY
+
+ Test is not run by root user, and an attempt to write to an
+ unwritable file is expected to fail correctly.
+
+ - LIBPCRE
+
+ Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests
+ that use git-grep --perl-regexp or git-grep -P in these.
+
Tips for Writing Tests
----------------------
@@ -301,3 +619,115 @@ the purpose of t0000-basic.sh, which is to isolate that level of
validation in one place. Your test also ends up needing
updating when such a change to the internal happens, so do _not_
do it and leave the low level of validation to t0000-basic.sh.
+
+Test coverage
+-------------
+
+You can use the coverage tests to find code paths that are not being
+used or properly exercised yet.
+
+To do that, run the coverage target at the top-level (not in the t/
+directory):
+
+ make coverage
+
+That'll compile Git with GCC's coverage arguments, and generate a test
+report with gcov after the tests finish. Running the coverage tests
+can take a while, since running the tests in parallel is incompatible
+with GCC's coverage mode.
+
+After the tests have run you can generate a list of untested
+functions:
+
+ make coverage-untested-functions
+
+You can also generate a detailed per-file HTML report using the
+Devel::Cover module. To install it do:
+
+ # On Debian or Ubuntu:
+ sudo aptitude install libdevel-cover-perl
+
+ # From the CPAN with cpanminus
+ curl -L http://cpanmin.us | perl - --sudo --self-upgrade
+ cpanm --sudo Devel::Cover
+
+Then, at the top-level:
+
+ make cover_db_html
+
+That'll generate a detailed cover report in the "cover_db_html"
+directory, which you can then copy to a webserver, or inspect locally
+in a browser.
+
+Smoke testing
+-------------
+
+The Git test suite has support for smoke testing. Smoke testing is
+when you submit the results of a test run to a central server for
+analysis and aggregation.
+
+Running a smoke tester is an easy and valuable way of contributing to
+Git development, particularly if you have access to an uncommon OS on
+obscure hardware.
+
+After building Git you can generate a smoke report like this in the
+"t" directory:
+
+ make clean smoke
+
+You can also pass arguments via the environment. This should make it
+faster:
+
+ GIT_TEST_OPTS='--root=/dev/shm' TEST_JOBS=10 make clean smoke
+
+The "smoke" target will run the Git test suite with Perl's
+"TAP::Harness" module, and package up the results in a .tar.gz archive
+with "TAP::Harness::Archive". The former is included with Perl v5.10.1
+or later, but you'll need to install the latter from the CPAN. See the
+"Test coverage" section above for how you might do that.
+
+Once the "smoke" target finishes you'll see a message like this:
+
+ TAP Archive created at <path to git>/t/test-results/git-smoke.tar.gz
+
+To upload the smoke report you need to have curl(1) installed, then
+do:
+
+ make smoke_report
+
+To upload the report anonymously. Hopefully that'll return something
+like "Reported #7 added.".
+
+If you're going to be uploading reports frequently please request a
+user account by E-Mailing gitsmoke@v.nix.is. Once you have a username
+and password you'll be able to do:
+
+ SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> make smoke_report
+
+You can also add an additional comment to attach to the report, and/or
+a comma separated list of tags:
+
+ SMOKE_USERNAME=<username> SMOKE_PASSWORD=<password> \
+ SMOKE_COMMENT=<comment> SMOKE_TAGS=<tags> \
+ make smoke_report
+
+Once the report is uploaded it'll be made available at
+http://smoke.git.nix.is, here's an overview of Recent Smoke Reports
+for Git:
+
+ http://smoke.git.nix.is/app/projects/smoke_reports/1
+
+The reports will also be mirrored to GitHub every few hours:
+
+ http://github.com/gitsmoke/smoke-reports
+
+The Smolder SQLite database is also mirrored and made available for
+download:
+
+ http://github.com/gitsmoke/smoke-database
+
+Note that the database includes hashed (with crypt()) user passwords
+and E-Mail addresses. Don't use a valuable password for the smoke
+service if you have an account, or an E-Mail address you don't want to
+be publicly known. The user accounts are just meant to be convenient
+labels, they're not meant to be secure.
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 396b965..c56a77d 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -1,5 +1,5 @@
# This file isn't used as a test script directly, instead it is
-# sourced from t8001-annotate.sh and t8001-blame.sh.
+# sourced from t8001-annotate.sh and t8002-blame.sh.
check_count () {
head=
@@ -8,27 +8,27 @@ check_count () {
$PROG file $head >.result || return 1
cat .result | perl -e '
my %expect = (@ARGV);
- my %count = ();
+ my %count = map { $_ => 0 } keys %expect;
while (<STDIN>) {
if (/^[0-9a-f]+\t\(([^\t]+)\t/) {
my $author = $1;
for ($author) { s/^\s*//; s/\s*$//; }
- if (exists $expect{$author}) {
- $count{$author}++;
- }
+ $count{$author}++;
}
}
my $bad = 0;
while (my ($author, $count) = each %count) {
my $ok;
- if ($expect{$author} != $count) {
+ my $value = 0;
+ $value = $expect{$author} if defined $expect{$author};
+ if ($value != $count) {
$bad = 1;
$ok = "bad";
}
else {
$ok = "good";
}
- print STDERR "Author $author (expected $expect{$author}, attributed $count) $ok\n";
+ print STDERR "Author $author (expected $value, attributed $count) $ok\n";
}
exit($bad);
' "$@"
@@ -38,8 +38,8 @@ test_expect_success \
'prepare reference tree' \
'echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
- git add file
- GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+ git add file &&
+ GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" git commit -a -m "Initial."'
test_expect_success \
'check all lines blamed on A' \
@@ -49,7 +49,7 @@ test_expect_success \
'Setup new lines blamed on B' \
'echo "2A quick brown fox jumps over the" >>file &&
echo "lazy dog" >> file &&
- GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+ GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" git commit -a -m "Second."'
test_expect_success \
'Two lines blamed on A, two on B' \
@@ -60,7 +60,7 @@ test_expect_success \
'git checkout -b branch1 master &&
echo "3A slow green fox jumps into the" >> file &&
echo "well." >> file &&
- GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+ GIT_AUTHOR_NAME="B1" GIT_AUTHOR_EMAIL="B1@test.git" git commit -a -m "Branch1-1"'
test_expect_success \
'Two lines blamed on A, two on B, two on B1' \
@@ -71,7 +71,7 @@ test_expect_success \
'git checkout -b branch2 master &&
sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
mv file.new file &&
- GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+ GIT_AUTHOR_NAME="B2" GIT_AUTHOR_EMAIL="B2@test.git" git commit -a -m "Branch2-1"'
test_expect_success \
'Two lines blamed on A, one on B, one on B2' \
@@ -105,7 +105,7 @@ test_expect_success \
test_expect_success \
'an incomplete line added' \
'echo "incomplete" | tr -d "\\012" >>file &&
- GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+ GIT_AUTHOR_NAME="C" GIT_AUTHOR_EMAIL="C@test.git" git commit -a -m "Incomplete"'
test_expect_success \
'With incomplete lines.' \
@@ -119,8 +119,19 @@ test_expect_success \
echo
} | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file &&
echo "incomplete" | tr -d "\\012" >>file &&
- GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+ GIT_AUTHOR_NAME="D" GIT_AUTHOR_EMAIL="D@test.git" git commit -a -m "edit"'
test_expect_success \
'some edit' \
'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1'
+
+test_expect_success \
+ 'an obfuscated email added' \
+ 'echo "No robots allowed" > file.new &&
+ cat file >> file.new &&
+ mv file.new file &&
+ GIT_AUTHOR_NAME="E" GIT_AUTHOR_EMAIL="E at test dot git" git commit -a -m "norobots"'
+
+test_expect_success \
+ 'obfuscated email parsed' \
+ 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1'
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
index b70b891..292753f 100644
--- a/t/gitweb-lib.sh
+++ b/t/gitweb-lib.sh
@@ -19,9 +19,9 @@ our \$site_name = '[localhost]';
our \$site_header = '';
our \$site_footer = '';
our \$home_text = 'indextext.html';
-our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css');
-our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png';
-our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png';
+our @stylesheets = ('file:///$GIT_BUILD_DIR/gitweb/static/gitweb.css');
+our \$logo = 'file:///$GIT_BUILD_DIR/gitweb/static/git-logo.png';
+our \$favicon = 'file:///$GIT_BUILD_DIR/gitweb/static/git-favicon.png';
our \$projects_list = '';
our \$export_ok = '';
our \$strict_export = '';
@@ -32,17 +32,34 @@ EOF
cat >.git/description <<EOF
$0 test repository
EOF
+
+ # You can set the GITWEB_TEST_INSTALLED environment variable to
+ # the gitwebdir (the directory where gitweb is installed / deployed to)
+ # of an existing gitweb instalation to test that installation,
+ # or simply to pathname of installed gitweb script.
+ if test -n "$GITWEB_TEST_INSTALLED" ; then
+ if test -d $GITWEB_TEST_INSTALLED; then
+ SCRIPT_NAME="$GITWEB_TEST_INSTALLED/gitweb.cgi"
+ else
+ SCRIPT_NAME="$GITWEB_TEST_INSTALLED"
+ fi
+ test -f "$SCRIPT_NAME" ||
+ error "Cannot find gitweb at $GITWEB_TEST_INSTALLED."
+ say "# Testing $SCRIPT_NAME"
+ else # normal case, use source version of gitweb
+ SCRIPT_NAME="$GIT_BUILD_DIR/gitweb/gitweb.perl"
+ fi
+ export SCRIPT_NAME
}
gitweb_run () {
GATEWAY_INTERFACE='CGI/1.1'
HTTP_ACCEPT='*/*'
REQUEST_METHOD='GET'
- SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
QUERY_STRING=""$1""
PATH_INFO=""$2""
export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
- SCRIPT_NAME QUERY_STRING PATH_INFO
+ QUERY_STRING PATH_INFO
GITWEB_CONFIG=$(pwd)/gitweb_config.perl
export GITWEB_CONFIG
@@ -65,7 +82,12 @@ gitweb_run () {
}
close O;
' gitweb.output &&
- if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi
+ if grep '^[[]' gitweb.log >/dev/null 2>&1; then
+ test_debug 'cat gitweb.log >&2' &&
+ false
+ else
+ true
+ fi
# gitweb.log is left for debugging
# gitweb.output is used to parse HTTP output
@@ -76,13 +98,18 @@ gitweb_run () {
. ./test-lib.sh
if ! test_have_prereq PERL; then
- say 'skipping gitweb tests, perl not available'
+ skip_all='skipping gitweb tests, perl not available'
test_done
fi
-perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
- say 'skipping gitweb tests, perl version is too old'
- test_done
+perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || {
+ skip_all='skipping gitweb tests, perl version is too old'
+ test_done
+}
+
+perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || {
+ skip_all='skipping gitweb tests, CGI module unusable'
+ test_done
}
gitweb_init
diff --git a/t/harness b/t/harness
new file mode 100755
index 0000000..f5c02f4
--- /dev/null
+++ b/t/harness
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Getopt::Long ();
+use TAP::Harness::Archive;
+
+Getopt::Long::Parser->new(
+ config => [ qw/ pass_through / ],
+)->getoptions(
+ 'jobs:1' => \(my $jobs = $ENV{TEST_JOBS}),
+ 'archive=s' => \my $archive,
+) or die "$0: Couldn't getoptions()";
+
+TAP::Harness::Archive->new({
+ jobs => $jobs,
+ archive => $archive,
+ ($ENV{GIT_TEST_OPTS}
+ ? (test_args => [ split /\s+/, $ENV{GIT_TEST_OPTS} ])
+ : ()),
+ extra_properties => {},
+})->runtests(@ARGV);
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 4b3b793..44263ad 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -3,13 +3,10 @@
. ./test-lib.sh
unset CVS_SERVER
-# for clean cvsps cache
-HOME=$(pwd)
-export HOME
if ! type cvs >/dev/null 2>&1
then
- say 'skipping cvsimport tests, cvs not found'
+ skip_all='skipping cvsimport tests, cvs not found'
test_done
fi
@@ -21,15 +18,21 @@ case "$cvsps_version" in
2.1 | 2.2*)
;;
'')
- say 'skipping cvsimport tests, cvsps not found'
+ skip_all='skipping cvsimport tests, cvsps not found'
test_done
;;
*)
- say 'skipping cvsimport tests, unsupported cvsps version'
+ skip_all='skipping cvsimport tests, unsupported cvsps version'
test_done
;;
esac
+setup_cvs_test_repository () {
+ CVSROOT="$(pwd)/.cvsroot" &&
+ cp -r "$TEST_DIRECTORY/$1/cvsroot" "$CVSROOT" &&
+ export CVSROOT
+}
+
test_cvs_co () {
# Usage: test_cvs_co BRANCH_NAME
rm -rf module-cvs-"$1"
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 0f7f35c..199f22c 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -5,23 +5,22 @@ git_svn_id=git""-svn-id
if test -n "$NO_SVN_TESTS"
then
- say 'skipping git svn tests, NO_SVN_TESTS defined'
+ skip_all='skipping git svn tests, NO_SVN_TESTS defined'
test_done
fi
if ! test_have_prereq PERL; then
- say 'skipping git svn tests, perl not available'
+ skip_all='skipping git svn tests, perl not available'
test_done
fi
GIT_DIR=$PWD/.git
GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn
SVN_TREE=$GIT_SVN_DIR/svn-tree
-PERL=${PERL:-perl}
svn >/dev/null 2>&1
if test $? -ne 1
then
- say 'skipping git svn tests, svn not found'
+ skip_all='skipping git svn tests, svn not found'
test_done
fi
@@ -30,7 +29,7 @@ export svnrepo
svnconf=$PWD/svnconf
export svnconf
-$PERL -w -e "
+"$PERL_PATH" -w -e "
use SVN::Core;
use SVN::Repos;
\$SVN::Core::VERSION gt '1.1.0' or exit(42);
@@ -40,13 +39,12 @@ x=$?
if test $x -ne 0
then
if test $x -eq 42; then
- err='Perl SVN libraries must be >= 1.1.0'
+ skip_all='Perl SVN libraries must be >= 1.1.0'
elif test $x -eq 41; then
- err='svnadmin failed to create fsfs repository'
+ skip_all='svnadmin failed to create fsfs repository'
else
- err='Perl SVN libraries not found or unusable, skipping test'
+ skip_all='Perl SVN libraries not found or unusable'
fi
- say "$err"
test_done
fi
@@ -70,41 +68,46 @@ svn_cmd () {
svn "$orig_svncmd" --config-dir "$svnconf" "$@"
}
-for d in \
- "$SVN_HTTPD_PATH" \
- /usr/sbin/apache2 \
- /usr/sbin/httpd \
-; do
- if test -f "$d"
+prepare_httpd () {
+ for d in \
+ "$SVN_HTTPD_PATH" \
+ /usr/sbin/apache2 \
+ /usr/sbin/httpd \
+ ; do
+ if test -f "$d"
+ then
+ SVN_HTTPD_PATH="$d"
+ break
+ fi
+ done
+ if test -z "$SVN_HTTPD_PATH"
then
- SVN_HTTPD_PATH="$d"
- break
+ echo >&2 '*** error: Apache not found'
+ return 1
fi
-done
-for d in \
- "$SVN_HTTPD_MODULE_PATH" \
- /usr/lib/apache2/modules \
- /usr/libexec/apache2 \
-; do
- if test -d "$d"
+ for d in \
+ "$SVN_HTTPD_MODULE_PATH" \
+ /usr/lib/apache2/modules \
+ /usr/libexec/apache2 \
+ ; do
+ if test -d "$d"
+ then
+ SVN_HTTPD_MODULE_PATH="$d"
+ break
+ fi
+ done
+ if test -z "$SVN_HTTPD_MODULE_PATH"
then
- SVN_HTTPD_MODULE_PATH="$d"
- break
+ echo >&2 '*** error: Apache module dir not found'
+ return 1
fi
-done
-
-start_httpd () {
- repo_base_path="$1"
- if test -z "$SVN_HTTPD_PORT"
+ if test ! -f "$SVN_HTTPD_MODULE_PATH/mod_dav_svn.so"
then
- echo >&2 'SVN_HTTPD_PORT is not defined!'
- return
- fi
- if test -z "$repo_base_path"
- then
- repo_base_path=svn
+ echo >&2 '*** error: Apache module "mod_dav_svn" not found'
+ return 1
fi
+ repo_base_path="${1-svn}"
mkdir "$GIT_DIR"/logs
cat > "$GIT_DIR/httpd.conf" <<EOF
@@ -121,17 +124,29 @@ LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
SVNPath "$rawsvnrepo"
</Location>
EOF
+}
+
+start_httpd () {
+ if test -z "$SVN_HTTPD_PORT"
+ then
+ echo >&2 'SVN_HTTPD_PORT is not defined!'
+ return
+ fi
+
+ prepare_httpd "$1" || return 1
+
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
svnrepo="http://127.0.0.1:$SVN_HTTPD_PORT/$repo_base_path"
}
stop_httpd () {
test -z "$SVN_HTTPD_PORT" && return
+ test ! -f "$GIT_DIR/httpd.conf" && return
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
}
convert_to_rev_db () {
- $PERL -w -- - "$@" <<\EOF
+ "$PERL_PATH" -w -- - "$@" <<\EOF
use strict;
@ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>";
open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]";
@@ -159,7 +174,7 @@ EOF
require_svnserve () {
if test -z "$SVNSERVE_PORT"
then
- say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
+ skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)'
test_done
fi
}
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index da4b8d5..b8996a3 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -5,8 +5,7 @@
if test -z "$GIT_TEST_HTTPD"
then
- say "skipping test, network testing disabled by default"
- say "(define GIT_TEST_HTTPD to enable)"
+ skip_all="Network testing disabled (define GIT_TEST_HTTPD to enable)"
test_done
fi
@@ -46,7 +45,7 @@ HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
if ! test -x "$LIB_HTTPD_PATH"
then
- say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
+ skip_all="skipping test, no web server found at '$LIB_HTTPD_PATH'"
test_done
fi
@@ -59,12 +58,12 @@ then
then
if ! test $HTTPD_VERSION -ge 2
then
- say "skipping test, at least Apache version 2 is required"
+ skip_all="skipping test, at least Apache version 2 is required"
test_done
fi
if ! test -d "$DEFAULT_HTTPD_MODULE_PATH"
then
- say "Apache module directory not found. Skipping tests."
+ skip_all="Apache module directory not found. Skipping tests."
test_done
fi
@@ -76,12 +75,14 @@ fi
prepare_httpd() {
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
+ cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
if test -n "$LIB_HTTPD_SSL"
then
HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
+ AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
-config "$TEST_PATH/ssl.cnf" \
@@ -93,6 +94,7 @@ prepare_httpd() {
HTTPD_PARA="$HTTPD_PARA -DSSL"
else
HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
+ AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
fi
if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
@@ -119,7 +121,7 @@ start_httpd() {
>&3 2>&4
if test $? -ne 0
then
- say "skipping test, web server setup failed"
+ skip_all="skipping test, web server setup failed"
trap 'die' EXIT
test_done
fi
@@ -146,7 +148,7 @@ test_http_push_nonff() {
echo "changed" > path2 &&
git commit -a -m path2 --amend &&
- !(git push -v origin >output 2>&1) &&
+ test_must_fail git push -v origin >output 2>&1 &&
(cd "$REMOTE_REPO" &&
test $HEAD = $(git rev-parse --verify HEAD))
'
@@ -156,7 +158,6 @@ test_http_push_nonff() {
'
test_expect_success 'non-fast-forward push shows help message' '
- grep "To prevent you from losing history, non-fast-forward updates were rejected" \
- output
+ test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output
'
}
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 4961505..0a4cdfa 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -17,8 +17,33 @@ ErrorLog error.log
<IfModule !mod_env.c>
LoadModule env_module modules/mod_env.so
</IfModule>
+<IfModule !mod_rewrite.c>
+ LoadModule rewrite_module modules/mod_rewrite.so
+</IFModule>
+<IfModule !mod_version.c>
+ LoadModule version_module modules/mod_version.so
+</IfModule>
+
+<IfVersion < 2.1>
+<IfModule !mod_auth.c>
+ LoadModule auth_module modules/mod_auth.so
+</IfModule>
+</IfVersion>
+
+<IfVersion >= 2.1>
+<IfModule !mod_auth_basic.c>
+ LoadModule auth_basic_module modules/mod_auth_basic.so
+</IfModule>
+<IfModule !mod_authn_file.c>
+ LoadModule authn_file_module modules/mod_authn_file.so
+</IfModule>
+<IfModule !mod_authz_user.c>
+ LoadModule authz_user_module modules/mod_authz_user.so
+</IfModule>
+</IfVersion>
Alias /dumb/ www/
+Alias /auth/ www/auth/
<Location /smart/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
@@ -36,6 +61,10 @@ ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
Options ExecCGI
</Files>
+RewriteEngine on
+RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301]
+RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302]
+
<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
@@ -48,6 +77,13 @@ SSLMutex file:ssl_mutex
SSLEngine On
</IfDefine>
+<Location /auth/>
+ AuthType Basic
+ AuthName "git-auth"
+ AuthUserFile passwd
+ Require valid-user
+</Location>
+
<IfDefine DAV>
LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so
diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd
new file mode 100644
index 0000000..f2fbcad
--- /dev/null
+++ b/t/lib-httpd/passwd
@@ -0,0 +1 @@
+user@host:nKpa8pZUHx/ic
diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh
index ce36f34..06c3c91 100644
--- a/t/lib-patch-mode.sh
+++ b/t/lib-patch-mode.sh
@@ -2,11 +2,6 @@
. ./test-lib.sh
-if ! test_have_prereq PERL; then
- say 'skipping --patch tests, perl not available'
- test_done
-fi
-
set_state () {
echo "$3" > "$1" &&
git add "$1" &&
diff --git a/t/lib-prereq-FILEMODE.sh b/t/lib-prereq-FILEMODE.sh
new file mode 100644
index 0000000..bce5a4c
--- /dev/null
+++ b/t/lib-prereq-FILEMODE.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+if test "$(git config --bool core.filemode)" = false
+then
+ say 'filemode disabled on the filesystem'
+else
+ test_set_prereq FILEMODE
+fi
diff --git a/t/lib-read-tree.sh b/t/lib-read-tree.sh
new file mode 100644
index 0000000..abc2c6f
--- /dev/null
+++ b/t/lib-read-tree.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Helper functions to check if read-tree would succeed/fail as expected with
+# and without the dry-run option. They also test that the dry-run does not
+# write the index and that together with -u it doesn't touch the work tree.
+#
+read_tree_must_succeed () {
+ git ls-files -s >pre-dry-run &&
+ git read-tree -n "$@" &&
+ git ls-files -s >post-dry-run &&
+ test_cmp pre-dry-run post-dry-run &&
+ git read-tree "$@"
+}
+
+read_tree_must_fail () {
+ git ls-files -s >pre-dry-run &&
+ test_must_fail git read-tree -n "$@" &&
+ git ls-files -s >post-dry-run &&
+ test_cmp pre-dry-run post-dry-run &&
+ test_must_fail git read-tree "$@"
+}
+
+read_tree_u_must_succeed () {
+ git ls-files -s >pre-dry-run &&
+ git diff-files -p >pre-dry-run-wt &&
+ git read-tree -n "$@" &&
+ git ls-files -s >post-dry-run &&
+ git diff-files -p >post-dry-run-wt &&
+ test_cmp pre-dry-run post-dry-run &&
+ test_cmp pre-dry-run-wt post-dry-run-wt &&
+ git read-tree "$@"
+}
+
+read_tree_u_must_fail () {
+ git ls-files -s >pre-dry-run &&
+ git diff-files -p >pre-dry-run-wt &&
+ test_must_fail git read-tree -n "$@" &&
+ git ls-files -s >post-dry-run &&
+ git diff-files -p >post-dry-run-wt &&
+ test_cmp pre-dry-run post-dry-run &&
+ test_cmp pre-dry-run-wt post-dry-run-wt &&
+ test_must_fail git read-tree "$@"
+}
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 6aefe27..6ccf797 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -47,6 +47,8 @@ for line in $FAKE_LINES; do
case $line in
squash|fixup|edit|reword)
action="$line";;
+ exec*)
+ echo "$line" | sed 's/_/ /g' >> "$1";;
"#")
echo '# comment' >> "$1";;
">")
diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh
new file mode 100644
index 0000000..58d911d
--- /dev/null
+++ b/t/lib-terminal.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_expect_success PERL 'set up terminal for tests' '
+ # Reading from the pty master seems to get stuck _sometimes_
+ # on Mac OS X 10.5.0, using Perl 5.10.0 or 5.8.9.
+ #
+ # Reproduction recipe: run
+ #
+ # i=0
+ # while ./test-terminal.perl echo hi $i
+ # do
+ # : $((i = $i + 1))
+ # done
+ #
+ # After 2000 iterations or so it hangs.
+ # https://rt.cpan.org/Ticket/Display.html?id=65692
+ #
+ if test "$(uname -s)" = Darwin
+ then
+ :
+ elif
+ "$PERL_PATH" "$TEST_DIRECTORY"/test-terminal.perl \
+ sh -c "test -t 1 && test -t 2"
+ then
+ test_set_prereq TTY &&
+ test_terminal () {
+ if ! test_declared_prereq TTY
+ then
+ echo >&4 "test_terminal: need to declare TTY prerequisite"
+ return 127
+ fi
+ "$PERL_PATH" "$TEST_DIRECTORY"/test-terminal.perl "$@"
+ }
+ fi
+'
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index f2c7336..f4e8f43 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -54,9 +54,40 @@ test_expect_success 'success is reported like this' '
test_expect_failure 'pretend we have a known breakage' '
false
'
+
+test_expect_success 'pretend we have fixed a known breakage (run in sub test-lib)' "
+ mkdir passing-todo &&
+ (cd passing-todo &&
+ cat >passing-todo.sh <<EOF &&
+#!$SHELL_PATH
+
+test_description='A passing TODO test
+
+This is run in a sub test-lib so that we do not get incorrect passing
+metrics
+'
+
+# Point to the t/test-lib.sh, which isn't in ../ as usual
+TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+. \"\$TEST_DIRECTORY\"/test-lib.sh
+
test_expect_failure 'pretend we have fixed a known breakage' '
:
'
+
+test_done
+EOF
+ chmod +x passing-todo.sh &&
+ ./passing-todo.sh >out 2>err &&
+ ! test -s err &&
+sed -e 's/^> //' >expect <<EOF &&
+> ok 1 - pretend we have fixed a known breakage # TODO known breakage
+> # fixed 1 known breakage(s)
+> # passed all 1 test(s)
+> 1..1
+EOF
+ test_cmp expect out)
+"
test_set_prereq HAVEIT
haveit=no
test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
@@ -73,27 +104,83 @@ then
exit 1
fi
+test_set_prereq HAVETHIS
+haveit=no
+test_expect_success HAVETHIS,HAVEIT 'test runs if prerequisites are satisfied' '
+ test_have_prereq HAVEIT &&
+ test_have_prereq HAVETHIS &&
+ haveit=yes
+'
+donthaveit=yes
+test_expect_success HAVEIT,DONTHAVEIT 'unmet prerequisites causes test to be skipped' '
+ donthaveit=no
+'
+donthaveiteither=yes
+test_expect_success DONTHAVEIT,HAVEIT 'unmet prerequisites causes test to be skipped' '
+ donthaveiteither=no
+'
+if test $haveit$donthaveit$donthaveiteither != yesyesyes
+then
+ say "bug in test framework: multiple prerequisite tags do not work reliably"
+ exit 1
+fi
+
clean=no
test_expect_success 'tests clean up after themselves' '
test_when_finished clean=yes
'
-cleaner=no
-test_expect_code 1 'tests clean up even after a failure' '
- test_when_finished cleaner=yes &&
- (exit 1)
-'
-
-if test $clean$cleaner != yesyes
+if test $clean != yes
then
- say "bug in test framework: cleanup commands do not work reliably"
+ say "bug in test framework: basic cleanup command does not work reliably"
exit 1
fi
-test_expect_code 2 'failure to clean up causes the test to fail' '
- test_when_finished "(exit 2)"
+test_expect_success 'tests clean up even on failures' "
+ mkdir failing-cleanup &&
+ (cd failing-cleanup &&
+ cat >failing-cleanup.sh <<EOF &&
+#!$SHELL_PATH
+
+test_description='Failing tests with cleanup commands'
+
+# Point to the t/test-lib.sh, which isn't in ../ as usual
+TEST_DIRECTORY=\"$TEST_DIRECTORY\"
+. \"\$TEST_DIRECTORY\"/test-lib.sh
+
+test_expect_success 'tests clean up even after a failure' '
+ touch clean-after-failure &&
+ test_when_finished rm clean-after-failure &&
+ (exit 1)
+'
+
+test_expect_success 'failure to clean up causes the test to fail' '
+ test_when_finished \"(exit 2)\"
'
+test_done
+EOF
+ chmod +x failing-cleanup.sh &&
+ test_must_fail ./failing-cleanup.sh >out 2>err &&
+ ! test -s err &&
+ ! test -f \"trash directory.failing-cleanup/clean-after-failure\" &&
+sed -e 's/Z$//' -e 's/^> //' >expect <<\EOF &&
+> not ok - 1 tests clean up even after a failure
+> # Z
+> # touch clean-after-failure &&
+> # test_when_finished rm clean-after-failure &&
+> # (exit 1)
+> # Z
+> not ok - 2 failure to clean up causes the test to fail
+> # Z
+> # test_when_finished \"(exit 2)\"
+> # Z
+> # failed 2 among 2 test(s)
+> 1..2
+EOF
+ test_cmp expect out)
+"
+
################################################################
# Basics of the basics
@@ -348,7 +435,7 @@ test_expect_success 'update-index D/F conflict' '
test $numpath0 = 1
'
-test_expect_success SYMLINKS 'absolute path works as expected' '
+test_expect_success SYMLINKS 'real path works as expected' '
mkdir first &&
ln -s ../.git first/.git &&
mkdir second &&
@@ -356,14 +443,14 @@ test_expect_success SYMLINKS 'absolute path works as expected' '
mkdir third &&
dir="$(cd .git; pwd -P)" &&
dir2=third/../second/other/.git &&
- test "$dir" = "$(test-path-utils make_absolute_path $dir2)" &&
+ test "$dir" = "$(test-path-utils real_path $dir2)" &&
file="$dir"/index &&
- test "$file" = "$(test-path-utils make_absolute_path $dir2/index)" &&
+ test "$file" = "$(test-path-utils real_path $dir2/index)" &&
basename=blub &&
- test "$dir/$basename" = "$(cd .git && test-path-utils make_absolute_path "$basename")" &&
+ test "$dir/$basename" = "$(cd .git && test-path-utils real_path "$basename")" &&
ln -s ../first/file .git/syml &&
sym="$(cd first; pwd -P)"/file &&
- test "$sym" = "$(test-path-utils make_absolute_path "$dir2/syml")"
+ test "$sym" = "$(test-path-utils real_path "$dir2/syml")"
'
test_expect_success 'very long name in the index handled sanely' '
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 7c0a698..ad66410 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -25,7 +25,7 @@ check_config () {
test_expect_success 'plain' '
(
- unset GIT_DIR GIT_WORK_TREE
+ sane_unset GIT_DIR GIT_WORK_TREE &&
mkdir plain &&
cd plain &&
git init
@@ -33,9 +33,65 @@ test_expect_success 'plain' '
check_config plain/.git false unset
'
+test_expect_success 'plain nested in bare' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init --bare bare-ancestor.git &&
+ cd bare-ancestor.git &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git init
+ ) &&
+ check_config bare-ancestor.git/plain-nested/.git false unset
+'
+
+test_expect_success 'plain through aliased command, outside any git repo' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ HOME=$(pwd)/alias-config &&
+ export HOME &&
+ mkdir alias-config &&
+ echo "[alias] aliasedinit = init" >alias-config/.gitconfig &&
+
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+
+ mkdir plain-aliased &&
+ cd plain-aliased &&
+ git aliasedinit
+ ) &&
+ check_config plain-aliased/.git false unset
+'
+
+test_expect_failure 'plain nested through aliased command' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init plain-ancestor-aliased &&
+ cd plain-ancestor-aliased &&
+ echo "[alias] aliasedinit = init" >>.git/config &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git aliasedinit
+ ) &&
+ check_config plain-ancestor-aliased/plain-nested/.git false unset
+'
+
+test_expect_failure 'plain nested in bare through aliased command' '
+ (
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ git init --bare bare-ancestor-aliased.git &&
+ cd bare-ancestor-aliased.git &&
+ echo "[alias] aliasedinit = init" >>config &&
+ mkdir plain-nested &&
+ cd plain-nested &&
+ git aliasedinit
+ ) &&
+ check_config bare-ancestor-aliased.git/plain-nested/.git false unset
+'
+
test_expect_success 'plain with GIT_WORK_TREE' '
if (
- unset GIT_DIR
+ sane_unset GIT_DIR &&
mkdir plain-wt &&
cd plain-wt &&
GIT_WORK_TREE=$(pwd) git init
@@ -48,7 +104,7 @@ test_expect_success 'plain with GIT_WORK_TREE' '
test_expect_success 'plain bare' '
(
- unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
mkdir plain-bare-1 &&
cd plain-bare-1 &&
git --bare init
@@ -58,7 +114,7 @@ test_expect_success 'plain bare' '
test_expect_success 'plain bare with GIT_WORK_TREE' '
if (
- unset GIT_DIR GIT_CONFIG
+ sane_unset GIT_DIR GIT_CONFIG &&
mkdir plain-bare-2 &&
cd plain-bare-2 &&
GIT_WORK_TREE=$(pwd) git --bare init
@@ -72,7 +128,7 @@ test_expect_success 'plain bare with GIT_WORK_TREE' '
test_expect_success 'GIT_DIR bare' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-bare.git &&
GIT_DIR=git-dir-bare.git git init
) &&
@@ -82,7 +138,7 @@ test_expect_success 'GIT_DIR bare' '
test_expect_success 'init --bare' '
(
- unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
mkdir init-bare.git &&
cd init-bare.git &&
git init --bare
@@ -93,7 +149,7 @@ test_expect_success 'init --bare' '
test_expect_success 'GIT_DIR non-bare' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir non-bare &&
cd non-bare &&
GIT_DIR=.git git init
@@ -104,7 +160,7 @@ test_expect_success 'GIT_DIR non-bare' '
test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
(
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-wt-1.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-1.git git init
) &&
@@ -114,7 +170,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
if (
- unset GIT_CONFIG
+ sane_unset GIT_CONFIG &&
mkdir git-dir-wt-2.git &&
GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-2.git git --bare init
)
@@ -127,18 +183,18 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
test_expect_success 'reinit' '
(
- unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG
+ sane_unset GIT_CONFIG GIT_WORK_TREE GIT_CONFIG &&
mkdir again &&
cd again &&
git init >out1 2>err1 &&
git init >out2 2>err2
) &&
- grep "Initialized empty" again/out1 &&
- grep "Reinitialized existing" again/out2 &&
+ test_i18ngrep "Initialized empty" again/out1 &&
+ test_i18ngrep "Reinitialized existing" again/out2 &&
>again/empty &&
- test_cmp again/empty again/err1 &&
- test_cmp again/empty again/err2
+ test_i18ncmp again/empty again/err1 &&
+ test_i18ncmp again/empty again/err2
'
test_expect_success 'init with --template' '
@@ -171,14 +227,11 @@ test_expect_success 'init with init.templatedir set' '
mkdir templatedir-source &&
echo Content >templatedir-source/file &&
(
- HOME="`pwd`" &&
- export HOME &&
test_config="${HOME}/.gitconfig" &&
git config -f "$test_config" init.templatedir "${HOME}/templatedir-source" &&
mkdir templatedir-set &&
cd templatedir-set &&
- unset GIT_CONFIG_NOGLOBAL &&
- unset GIT_TEMPLATE_DIR &&
+ sane_unset GIT_TEMPLATE_DIR &&
NO_SET_GIT_TEMPLATE_DIR=t &&
export NO_SET_GIT_TEMPLATE_DIR &&
git init
@@ -188,10 +241,7 @@ test_expect_success 'init with init.templatedir set' '
test_expect_success 'init --bare/--shared overrides system/global config' '
(
- HOME="`pwd`" &&
- export HOME &&
test_config="$HOME"/.gitconfig &&
- unset GIT_CONFIG_NOGLOBAL &&
git config -f "$test_config" core.bare false &&
git config -f "$test_config" core.sharedRepository 0640 &&
mkdir init-bare-shared-override &&
@@ -205,10 +255,7 @@ test_expect_success 'init --bare/--shared overrides system/global config' '
test_expect_success 'init honors global core.sharedRepository' '
(
- HOME="`pwd`" &&
- export HOME &&
test_config="$HOME"/.gitconfig &&
- unset GIT_CONFIG_NOGLOBAL &&
git config -f "$test_config" core.sharedRepository 0666 &&
mkdir shared-honor-global &&
cd shared-honor-global &&
@@ -301,7 +348,7 @@ test_expect_success 'init notices EEXIST (2)' '
)
'
-test_expect_success POSIXPERM 'init notices EPERM' '
+test_expect_success POSIXPERM,SANITY 'init notices EPERM' '
rm -fr newdir &&
(
mkdir newdir &&
@@ -324,4 +371,50 @@ test_expect_success 'init prefers command line to GIT_DIR' '
! test -d otherdir/refs
'
+test_expect_success 'init with separate gitdir' '
+ rm -rf newdir &&
+ git init --separate-git-dir realgitdir newdir &&
+ echo "gitdir: `pwd`/realgitdir" >expected &&
+ test_cmp expected newdir/.git &&
+ test -d realgitdir/refs
+'
+
+test_expect_success 're-init to update git link' '
+ (
+ cd newdir &&
+ git init --separate-git-dir ../surrealgitdir
+ ) &&
+ echo "gitdir: `pwd`/surrealgitdir" >expected &&
+ test_cmp expected newdir/.git &&
+ test -d surrealgitdir/refs &&
+ ! test -d realgitdir/refs
+'
+
+test_expect_success 're-init to move gitdir' '
+ rm -rf newdir realgitdir surrealgitdir &&
+ git init newdir &&
+ (
+ cd newdir &&
+ git init --separate-git-dir ../realgitdir
+ ) &&
+ echo "gitdir: `pwd`/realgitdir" >expected &&
+ test_cmp expected newdir/.git &&
+ test -d realgitdir/refs
+'
+
+test_expect_success SYMLINKS 're-init to move gitdir symlink' '
+ rm -rf newdir realgitdir &&
+ git init newdir &&
+ (
+ cd newdir &&
+ mv .git here &&
+ ln -s here .git &&
+ git init --separate-git-dir ../realgitdir
+ ) &&
+ echo "gitdir: `pwd`/realgitdir" >expected &&
+ test_cmp expected newdir/.git &&
+ test -d realgitdir/refs &&
+ ! test -d newdir/here
+'
+
test_done
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 53bd7fc..ebbc755 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -36,6 +36,9 @@ test_expect_success 'setup' '
echo "d/* test=a/b/d/*"
echo "d/yes notest"
) >a/b/.gitattributes
+ (
+ echo "global test=global"
+ ) >"$HOME"/global-gitattributes
'
@@ -48,18 +51,28 @@ test_expect_success 'attribute test' '
attr_check a/b/g a/b/g &&
attr_check b/g unspecified &&
attr_check a/b/h a/b/h &&
- attr_check a/b/d/g "a/b/d/*"
- attr_check onoff unset
- attr_check offon set
- attr_check no unspecified
- attr_check a/b/d/no "a/b/d/*"
+ attr_check a/b/d/g "a/b/d/*" &&
+ attr_check onoff unset &&
+ attr_check offon set &&
+ attr_check no unspecified &&
+ attr_check a/b/d/no "a/b/d/*" &&
attr_check a/b/d/yes unspecified
'
+test_expect_success 'core.attributesfile' '
+ attr_check global unspecified &&
+ git config core.attributesfile "$HOME/global-gitattributes" &&
+ attr_check global global &&
+ git config core.attributesfile "~/global-gitattributes" &&
+ attr_check global global &&
+ echo "global test=precedence" >> .gitattributes &&
+ attr_check global precedence
+'
+
test_expect_success 'attribute test: read paths from stdin' '
- cat <<EOF > expect
+ cat <<EOF > expect &&
f: test: f
a/f: test: f
a/c/f: test: f
diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh
index 2342ac5..e3137d6 100755
--- a/t/t0004-unwritable.sh
+++ b/t/t0004-unwritable.sh
@@ -15,54 +15,30 @@ test_expect_success setup '
'
-test_expect_success POSIXPERM 'write-tree should notice unwritable repository' '
-
- (
- chmod a-w .git/objects .git/objects/?? &&
- test_must_fail git write-tree
- )
- status=$?
- chmod 775 .git/objects .git/objects/??
- (exit $status)
-
+test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable repository' '
+ test_when_finished "chmod 775 .git/objects .git/objects/??" &&
+ chmod a-w .git/objects .git/objects/?? &&
+ test_must_fail git write-tree
'
-test_expect_success POSIXPERM 'commit should notice unwritable repository' '
-
- (
- chmod a-w .git/objects .git/objects/?? &&
- test_must_fail git commit -m second
- )
- status=$?
- chmod 775 .git/objects .git/objects/??
- (exit $status)
-
+test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
+ test_when_finished "chmod 775 .git/objects .git/objects/??" &&
+ chmod a-w .git/objects .git/objects/?? &&
+ test_must_fail git commit -m second
'
-test_expect_success POSIXPERM 'update-index should notice unwritable repository' '
-
- (
- echo 6O >file &&
- chmod a-w .git/objects .git/objects/?? &&
- test_must_fail git update-index file
- )
- status=$?
- chmod 775 .git/objects .git/objects/??
- (exit $status)
-
+test_expect_success POSIXPERM,SANITY 'update-index should notice unwritable repository' '
+ test_when_finished "chmod 775 .git/objects .git/objects/??" &&
+ echo 6O >file &&
+ chmod a-w .git/objects .git/objects/?? &&
+ test_must_fail git update-index file
'
-test_expect_success POSIXPERM 'add should notice unwritable repository' '
-
- (
- echo b >file &&
- chmod a-w .git/objects .git/objects/?? &&
- test_must_fail git add file
- )
- status=$?
- chmod 775 .git/objects .git/objects/??
- (exit $status)
-
+test_expect_success POSIXPERM,SANITY 'add should notice unwritable repository' '
+ test_when_finished "chmod 775 .git/objects .git/objects/??" &&
+ echo b >file &&
+ chmod a-w .git/objects .git/objects/?? &&
+ test_must_fail git add file
'
test_done
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
index 09f855a..93e58c0 100755
--- a/t/t0005-signals.sh
+++ b/t/t0005-signals.sh
@@ -13,6 +13,7 @@ test_expect_success 'sigchain works' '
test-sigchain >actual
case "$?" in
143) true ;; # POSIX w/ SIGTERM=15
+ 271) true ;; # ksh w/ SIGTERM=15
3) true ;; # Windows
*) false ;;
esac &&
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index 75b02af..f87abb5 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -25,11 +25,12 @@ check_show 37500000 '1 year, 2 months ago'
check_show 55188000 '1 year, 9 months ago'
check_show 630000000 '20 years ago'
check_show 31449600 '12 months ago'
+check_show 62985600 '2 years ago'
check_parse() {
echo "$1 -> $2" >expect
- test_expect_${3:-success} "parse date ($1)" "
- test-date parse '$1' >actual &&
+ test_expect_${4:-success} "parse date ($1${3:+ TZ=$3})" "
+ TZ=${3:-$TZ} test-date parse '$1' >actual &&
test_cmp expect actual
"
}
@@ -38,6 +39,8 @@ check_parse 2008 bad
check_parse 2008-02 bad
check_parse 2008-02-14 bad
check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
check_approxidate() {
echo "$1 -> $2 +0000" >expect
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 234a94f..1a8f44c 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -439,7 +439,7 @@ test_expect_success 'checkout when deleting .gitattributes' '
git rm .gitattributes &&
echo "contentsQ" | q_to_cr > .file2 &&
git add .file2 &&
- git commit -m third
+ git commit -m third &&
git checkout master~1 &&
git checkout master &&
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 828e35b..f19e651 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -66,31 +66,91 @@ test_expect_success expanded_in_repo '
echo "\$Id:NoSpaceAtEitherEnd\$"
echo "\$Id: NoTerminatingSymbol"
echo "\$Id: Foreign Commit With Spaces \$"
- echo "\$Id: NoTerminatingSymbolAtEOF"
- } > expanded-keywords &&
+ } >expanded-keywords.0 &&
+
+ {
+ cat expanded-keywords.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expanded-keywords &&
+ cat expanded-keywords >expanded-keywords-crlf &&
+ git add expanded-keywords expanded-keywords-crlf &&
+ git commit -m "File with keywords expanded" &&
+ id=$(git rev-parse --verify :expanded-keywords) &&
{
echo "File with expanded keywords"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
- echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
+ echo "\$Id: $id \$"
echo "\$Id: NoTerminatingSymbol"
echo "\$Id: Foreign Commit With Spaces \$"
- echo "\$Id: NoTerminatingSymbolAtEOF"
- } > expected-output &&
-
- git add expanded-keywords &&
- git commit -m "File with keywords expanded" &&
+ } >expected-output.0 &&
+ {
+ cat expected-output.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expected-output &&
+ {
+ append_cr <expected-output.0 &&
+ printf "\$Id: NoTerminatingSymbolAtEOF"
+ } >expected-output-crlf &&
+ {
+ echo "expanded-keywords ident"
+ echo "expanded-keywords-crlf ident text eol=crlf"
+ } >>.gitattributes &&
- echo "expanded-keywords ident" >> .gitattributes &&
+ rm -f expanded-keywords expanded-keywords-crlf &&
- rm -f expanded-keywords &&
git checkout -- expanded-keywords &&
- cat expanded-keywords &&
- cmp expanded-keywords expected-output
+ test_cmp expanded-keywords expected-output &&
+
+ git checkout -- expanded-keywords-crlf &&
+ test_cmp expanded-keywords-crlf expected-output-crlf
+'
+
+# The use of %f in a filter definition is expanded to the path to
+# the filename being smudged or cleaned. It must be shell escaped.
+# First, set up some interesting file names and pet them in
+# .gitattributes.
+test_expect_success 'filter shell-escaped filenames' '
+ cat >argc.sh <<-EOF &&
+ #!$SHELL_PATH
+ cat >/dev/null
+ echo argc: \$# "\$@"
+ EOF
+ normal=name-no-magic &&
+ special="name with '\''sq'\'' and \$x" &&
+ echo some test text >"$normal" &&
+ echo some test text >"$special" &&
+ git add "$normal" "$special" &&
+ git commit -q -m "add files" &&
+ echo "name* filter=argc" >.gitattributes &&
+
+ # delete the files and check them out again, using a smudge filter
+ # that will count the args and echo the command-line back to us
+ git config filter.argc.smudge "sh ./argc.sh %f" &&
+ rm "$normal" "$special" &&
+ git checkout -- "$normal" "$special" &&
+
+ # make sure argc.sh counted the right number of args
+ echo "argc: 1 $normal" >expect &&
+ test_cmp expect "$normal" &&
+ echo "argc: 1 $special" >expect &&
+ test_cmp expect "$special" &&
+
+ # do the same thing, but with more args in the filter expression
+ git config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" &&
+ rm "$normal" "$special" &&
+ git checkout -- "$normal" "$special" &&
+
+ # make sure argc.sh counted the right number of args
+ echo "argc: 2 $normal --my-extra-arg" >expect &&
+ test_cmp expect "$normal" &&
+ echo "argc: 2 $special --my-extra-arg" >expect &&
+ test_cmp expect "$special" &&
+ :
'
test_done
diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh
index c7d0324..ec6c1b3 100755
--- a/t/t0024-crlf-archive.sh
+++ b/t/t0024-crlf-archive.sh
@@ -7,7 +7,7 @@ UNZIP=${UNZIP:-unzip}
test_expect_success setup '
- git config core.autocrlf true
+ git config core.autocrlf true &&
printf "CRLF line ending\r\nAnd another\r\n" > sample &&
git add sample &&
@@ -20,7 +20,7 @@ test_expect_success setup '
test_expect_success 'tar archive' '
git archive --format=tar HEAD |
- ( mkdir untarred && cd untarred && "$TAR" -xf - )
+ ( mkdir untarred && cd untarred && "$TAR" -xf - ) &&
test_cmp sample untarred/sample
diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh
index f37ac8f..fe0164b 100755
--- a/t/t0026-eol-config.sh
+++ b/t/t0026-eol-config.sh
@@ -12,7 +12,7 @@ test_expect_success setup '
git config core.autocrlf false &&
- echo "one text" > .gitattributes
+ echo "one text" > .gitattributes &&
for w in Hello world how are you; do echo $w; done >one &&
for w in I am very very fine thank you; do echo $w; done >two &&
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 2092450..007f39d 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -19,7 +19,7 @@ usage: test-parse-options <options>
--set23 set integer to 23
-t <time> get timestamp of <time>
-L, --length <str> get length of <str>
- -F, --file <FILE> set file to <FILE>
+ -F, --file <file> set file to <file>
String options
-s, --string <string>
@@ -28,6 +28,7 @@ String options
--st <st> get another string (pervert ordering)
-o <str> get another string
--default-string set string to default
+ --list <str> add str to list
Magic arguments
--quux means --quux
@@ -337,4 +338,20 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' '
test_cmp expect output
'
+cat >>expect <<'EOF'
+list: foo
+list: bar
+list: baz
+EOF
+test_expect_success '--list keeps list of strings' '
+ test-parse-options --list foo --list=bar --list=baz >output &&
+ test_cmp expect output
+'
+
+test_expect_success '--no-list resets list' '
+ test-parse-options --list=other --list=irrelevant --list=options \
+ --no-list --list=foo --list=bar --list=baz >output &&
+ test_cmp expect output
+'
+
test_done
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 41df6bc..1542cf6 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -4,22 +4,22 @@ test_description='Various filesystem issues'
. ./test-lib.sh
-auml=`printf '\xc3\xa4'`
-aumlcdiar=`printf '\x61\xcc\x88'`
+auml=$(printf '\303\244')
+aumlcdiar=$(printf '\141\314\210')
case_insensitive=
unibad=
no_symlinks=
test_expect_success 'see what we expect' '
- test_case=test_expect_success
- test_unicode=test_expect_success
+ test_case=test_expect_success &&
+ test_unicode=test_expect_success &&
mkdir junk &&
echo good >junk/CamelCase &&
echo bad >junk/camelcase &&
if test "$(cat junk/CamelCase)" != good
then
- test_case=test_expect_failure
+ test_case=test_expect_failure &&
case_insensitive=t
fi &&
rm -fr junk &&
@@ -27,7 +27,7 @@ test_expect_success 'see what we expect' '
>junk/"$auml" &&
case "$(cd junk && echo *)" in
"$aumlcdiar")
- test_unicode=test_expect_failure
+ test_unicode=test_expect_failure &&
unibad=t
;;
*) ;;
@@ -36,7 +36,7 @@ test_expect_success 'see what we expect' '
{
ln -s x y 2> /dev/null &&
test -h y 2> /dev/null ||
- no_symlinks=1
+ no_symlinks=1 &&
rm -f y
}
'
@@ -128,7 +128,7 @@ test_expect_success "setup unicode normalization tests" '
cd unicode &&
touch "$aumlcdiar" &&
git add "$aumlcdiar" &&
- git commit -m initial
+ git commit -m initial &&
git tag initial &&
git checkout -b topic &&
git mv $aumlcdiar tmp &&
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index 10b26e4..8d4938f 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -7,8 +7,31 @@ test_description='Test run command'
. ./test-lib.sh
+cat >hello-script <<-EOF
+ #!$SHELL_PATH
+ cat hello-script
+EOF
+>empty
+
test_expect_success 'start_command reports ENOENT' '
test-run-command start-command-ENOENT ./does-not-exist
'
+test_expect_success 'run_command can run a command' '
+ cat hello-script >hello.sh &&
+ chmod +x hello.sh &&
+ test-run-command run-command ./hello.sh >actual 2>err &&
+
+ test_cmp hello-script actual &&
+ test_cmp empty err
+'
+
+test_expect_success POSIXPERM 'run_command reports EACCES' '
+ cat hello-script >hello.sh &&
+ chmod -x hello.sh &&
+ test_must_fail test-run-command run-command ./hello.sh 2>err &&
+
+ grep "fatal: cannot exec.*hello.sh" err
+'
+
test_done
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
index 680d7d6..9bee8bf 100755
--- a/t/t0070-fundamental.sh
+++ b/t/t0070-fundamental.sh
@@ -12,4 +12,17 @@ test_expect_success 'character classes (isspace, isalpha etc.)' '
test-ctype
'
+test_expect_success 'mktemp to nonexistent directory prints filename' '
+ test_must_fail test-mktemp doesnotexist/testXXXXXX 2>err &&
+ grep "doesnotexist/test" err
+'
+
+test_expect_success POSIXPERM 'mktemp to unwritable directory prints filename' '
+ mkdir cannotwrite &&
+ chmod -w cannotwrite &&
+ test_when_finished "chmod +w cannotwrite" &&
+ test_must_fail test-mktemp cannotwrite/testXXXXXX 2>err &&
+ grep "cannotwrite/test" err
+'
+
test_done
diff --git a/t/t0080-vcs-svn.sh b/t/t0080-vcs-svn.sh
new file mode 100755
index 0000000..99a314b
--- /dev/null
+++ b/t/t0080-vcs-svn.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+test_description='check infrastructure for svn importer'
+
+. ./test-lib.sh
+uint32_max=4294967295
+
+test_expect_success 'obj pool: store data' '
+ cat <<-\EOF >expected &&
+ 0
+ 1
+ EOF
+
+ test-obj-pool <<-\EOF >actual &&
+ alloc one 16
+ set one 13
+ test one 13
+ reset one
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'obj pool: NULL is offset ~0' '
+ echo "$uint32_max" >expected &&
+ echo null one | test-obj-pool >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'obj pool: out-of-bounds access' '
+ cat <<-EOF >expected &&
+ 0
+ 0
+ $uint32_max
+ $uint32_max
+ 16
+ 20
+ $uint32_max
+ EOF
+
+ test-obj-pool <<-\EOF >actual &&
+ alloc one 16
+ alloc two 16
+ offset one 20
+ offset two 20
+ alloc one 5
+ offset one 20
+ free one 1
+ offset one 20
+ reset one
+ reset two
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'obj pool: high-water mark' '
+ cat <<-\EOF >expected &&
+ 0
+ 0
+ 10
+ 20
+ 20
+ 20
+ EOF
+
+ test-obj-pool <<-\EOF >actual &&
+ alloc one 10
+ committed one
+ alloc one 10
+ commit one
+ committed one
+ alloc one 10
+ free one 20
+ committed one
+ reset one
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'string pool' '
+ echo a does not equal b >expected.differ &&
+ echo a equals a >expected.match &&
+ echo equals equals equals >expected.matchmore &&
+
+ test-string-pool "a,--b" >actual.differ &&
+ test-string-pool "a,a" >actual.match &&
+ test-string-pool "equals-equals" >actual.matchmore &&
+ test_must_fail test-string-pool a,a,a &&
+ test_must_fail test-string-pool a &&
+
+ test_cmp expected.differ actual.differ &&
+ test_cmp expected.match actual.match &&
+ test_cmp expected.matchmore actual.matchmore
+'
+
+test_expect_success 'treap sort' '
+ cat <<-\EOF >unsorted &&
+ 68
+ 12
+ 13
+ 13
+ 68
+ 13
+ 13
+ 21
+ 10
+ 11
+ 12
+ 13
+ 13
+ EOF
+ sort unsorted >expected &&
+
+ test-treap <unsorted >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t0081-line-buffer.sh b/t/t0081-line-buffer.sh
new file mode 100755
index 0000000..bd83ed3
--- /dev/null
+++ b/t/t0081-line-buffer.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description="Test the svn importer's input handling routines.
+
+These tests provide some simple checks that the line_buffer API
+behaves as advertised.
+
+While at it, check that input of newlines and null bytes are handled
+correctly.
+"
+. ./test-lib.sh
+
+test_expect_success 'hello world' '
+ echo ">HELLO" >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 6
+ HELLO
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success '0-length read, send along greeting' '
+ echo ">HELLO" >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 0
+ copy 6
+ HELLO
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'read from file descriptor' '
+ rm -f input &&
+ echo hello >expect &&
+ echo hello >input &&
+ echo copy 6 |
+ test-line-buffer "&4" 4<input >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'skip, copy null byte' '
+ echo Q | q_to_nul >expect &&
+ q_to_nul <<-\EOF | test-line-buffer >actual &&
+ skip 2
+ Q
+ copy 2
+ Q
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'read null byte' '
+ echo ">QhelloQ" | q_to_nul >expect &&
+ q_to_nul <<-\EOF | test-line-buffer >actual &&
+ binary 8
+ QhelloQ
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long reads are truncated' '
+ echo ">foo" >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long copies are truncated' '
+ printf "%s\n" ">" foo >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 1
+
+ copy 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'long binary reads are truncated' '
+ echo ">foo" >expect &&
+ test-line-buffer <<-\EOF >actual &&
+ binary 5
+ foo
+ EOF
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755
index 0000000..54d98b9
--- /dev/null
+++ b/t/t0201-gettext-fallbacks.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext Shell fallbacks'
+
+. ./test-lib.sh
+. "$GIT_BUILD_DIR"/git-sh-i18n
+
+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
+ printf "test" >expect &&
+ gettext "test" >actual &&
+ test_i18ncmp expect actual &&
+ printf "test more words" >expect &&
+ gettext "test more words" >actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '
+ printf "test" >expect &&
+ eval_gettext "test" >actual &&
+ test_i18ncmp expect actual &&
+ printf "test more words" >expect &&
+ eval_gettext "test more words" >actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '
+ printf "test YesPlease" >expect &&
+ GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" >actual &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces' '
+ cmdline="git am" &&
+ export cmdline;
+ printf "When you have resolved this problem run git am --resolved." >expect &&
+ eval_gettext "When you have resolved this problem run \$cmdline --resolved." >actual
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables with spaces and quotes' '
+ cmdline="git am" &&
+ export cmdline;
+ printf "When you have resolved this problem run \"git am --resolved\"." >expect &&
+ eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\"." >actual
+ test_i18ncmp expect actual
+'
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
index 4f17172..babcdd2 100755
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -72,6 +72,7 @@ In addition:
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
. "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh
################################################################
@@ -137,7 +138,7 @@ test_expect_success \
'3-way merge with git read-tree -m, empty cache' \
"rm -fr [NDMALTS][NDMALTSF] Z &&
rm .git/index &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
# This starts out with the first head, which is the normal
@@ -146,9 +147,9 @@ test_expect_success \
'3-way merge with git read-tree -m, match H' \
"rm -fr [NDMALTS][NDMALTSF] Z &&
rm .git/index &&
- git read-tree $tree_A &&
+ read_tree_must_succeed $tree_A &&
git checkout-index -f -u -a &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
: <<\END_OF_CASE_TABLE
@@ -211,7 +212,7 @@ test_expect_success '1 - must not have an entry not in A.' "
rm -f .git/index XX &&
echo XX >XX &&
git update-index --add XX &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -219,7 +220,7 @@ test_expect_success \
"rm -f .git/index NA &&
cp .orig-B/NA NA &&
git update-index --add NA &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B"
test_expect_success \
'2 - matching B alone is OK in !O && !A && B case.' \
@@ -227,14 +228,14 @@ test_expect_success \
cp .orig-B/NA NA &&
git update-index --add NA &&
echo extra >>NA &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B"
test_expect_success \
'3 - must match A in !O && A && !B case.' \
"rm -f .git/index AN &&
cp .orig-A/AN AN &&
git update-index --add AN &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -243,7 +244,7 @@ test_expect_success \
cp .orig-A/AN AN &&
git update-index --add AN &&
echo extra >>AN &&
- git read-tree -m $tree_O $tree_A $tree_B"
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B"
test_expect_success \
'3 (fail) - must match A in !O && A && !B case.' "
@@ -251,7 +252,7 @@ test_expect_success \
cp .orig-A/AN AN &&
echo extra >>AN &&
git update-index --add AN &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -259,7 +260,7 @@ test_expect_success \
"rm -f .git/index AA &&
cp .orig-A/AA AA &&
git update-index --add AA &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -268,7 +269,7 @@ test_expect_success \
cp .orig-A/AA AA &&
git update-index --add AA &&
echo extra >>AA &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -277,7 +278,7 @@ test_expect_success \
cp .orig-A/AA AA &&
echo extra >>AA &&
git update-index --add AA &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -285,7 +286,7 @@ test_expect_success \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
git update-index --add LL &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -294,7 +295,7 @@ test_expect_success \
cp .orig-A/LL LL &&
git update-index --add LL &&
echo extra >>LL &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -303,15 +304,15 @@ test_expect_success \
cp .orig-A/LL LL &&
echo extra >>LL &&
git update-index --add LL &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
'6 - must not exist in O && !A && !B case' "
rm -f .git/index DD &&
- echo DD >DD
+ echo DD >DD &&
git update-index --add DD &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -319,7 +320,7 @@ test_expect_success \
rm -f .git/index DM &&
cp .orig-B/DM DM &&
git update-index --add DM &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -327,7 +328,7 @@ test_expect_success \
rm -f .git/index DN &&
cp .orig-B/DN DN &&
git update-index --add DN &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -335,7 +336,7 @@ test_expect_success \
"rm -f .git/index MD &&
cp .orig-A/MD MD &&
git update-index --add MD &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -344,7 +345,7 @@ test_expect_success \
cp .orig-A/MD MD &&
git update-index --add MD &&
echo extra >>MD &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -353,7 +354,7 @@ test_expect_success \
cp .orig-A/MD MD &&
echo extra >>MD &&
git update-index --add MD &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -361,7 +362,7 @@ test_expect_success \
"rm -f .git/index ND &&
cp .orig-A/ND ND &&
git update-index --add ND &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -370,7 +371,7 @@ test_expect_success \
cp .orig-A/ND ND &&
git update-index --add ND &&
echo extra >>ND &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -379,7 +380,7 @@ test_expect_success \
cp .orig-A/ND ND &&
echo extra >>ND &&
git update-index --add ND &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -387,7 +388,7 @@ test_expect_success \
"rm -f .git/index MM &&
cp .orig-A/MM MM &&
git update-index --add MM &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -396,7 +397,7 @@ test_expect_success \
cp .orig-A/MM MM &&
git update-index --add MM &&
echo extra >>MM &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -405,7 +406,7 @@ test_expect_success \
cp .orig-A/MM MM &&
echo extra >>MM &&
git update-index --add MM &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -413,7 +414,7 @@ test_expect_success \
"rm -f .git/index SS &&
cp .orig-A/SS SS &&
git update-index --add SS &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -422,7 +423,7 @@ test_expect_success \
cp .orig-A/SS SS &&
git update-index --add SS &&
echo extra >>SS &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -431,7 +432,7 @@ test_expect_success \
cp .orig-A/SS SS &&
echo extra >>SS &&
git update-index --add SS &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -439,7 +440,7 @@ test_expect_success \
"rm -f .git/index MN &&
cp .orig-A/MN MN &&
git update-index --add MN &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -448,7 +449,7 @@ test_expect_success \
cp .orig-A/MN MN &&
git update-index --add MN &&
echo extra >>MN &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -456,7 +457,7 @@ test_expect_success \
"rm -f .git/index NM &&
cp .orig-A/NM NM &&
git update-index --add NM &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -465,7 +466,7 @@ test_expect_success \
cp .orig-B/NM NM &&
git update-index --add NM &&
echo extra >>NM &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -474,7 +475,7 @@ test_expect_success \
cp .orig-A/NM NM &&
git update-index --add NM &&
echo extra >>NM &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -483,7 +484,7 @@ test_expect_success \
cp .orig-A/NM NM &&
echo extra >>NM &&
git update-index --add NM &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
test_expect_success \
@@ -491,7 +492,7 @@ test_expect_success \
"rm -f .git/index NN &&
cp .orig-A/NN NN &&
git update-index --add NN &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -500,7 +501,7 @@ test_expect_success \
cp .orig-A/NN NN &&
git update-index --add NN &&
echo extra >>NN &&
- git read-tree -m $tree_O $tree_A $tree_B &&
+ read_tree_must_succeed -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
@@ -509,7 +510,7 @@ test_expect_success \
cp .orig-A/NN NN &&
echo extra >>NN &&
git update-index --add NN &&
- test_must_fail git read-tree -m $tree_O $tree_A $tree_B
+ read_tree_must_fail -m $tree_O $tree_A $tree_B
"
# #16
@@ -522,7 +523,7 @@ test_expect_success \
echo E16 >F16 &&
git update-index F16 &&
tree1=`git write-tree` &&
- git read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+ read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 &&
git ls-files --stage'
test_done
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index 6327d20..acaab07 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -21,6 +21,7 @@ In the test, these paths are used:
yomin - not in H nor M
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
read_tree_twoway () {
git read-tree -m "$1" "$2" && git ls-files --stage
@@ -94,33 +95,33 @@ echo '+100644 X 0 yomin' >expected
test_expect_success \
'4 - carry forward local addition.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
git update-index --add yomin &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >4.out || return 1
- git diff --no-index M.out 4.out >4diff.out
+ git ls-files --stage >4.out &&
+ test_must_fail git diff --no-index M.out 4.out >4diff.out &&
compare_change 4diff.out expected &&
check_cache_at yomin clean'
test_expect_success \
'5 - carry forward local addition.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo yomin >yomin &&
git update-index --add yomin &&
echo yomin yomin >yomin &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >5.out || return 1
- git diff --no-index M.out 5.out >5diff.out
+ git ls-files --stage >5.out &&
+ test_must_fail git diff --no-index M.out 5.out >5diff.out &&
compare_change 5diff.out expected &&
check_cache_at yomin dirty'
test_expect_success \
'6 - local addition already has the same.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
git update-index --add frotz &&
read_tree_twoway $treeH $treeM &&
@@ -131,7 +132,7 @@ test_expect_success \
test_expect_success \
'7 - local addition already has the same.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo frotz >frotz &&
git update-index --add frotz &&
@@ -144,7 +145,7 @@ test_expect_success \
test_expect_success \
'8 - conflicting addition.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo frotz frotz >frotz &&
git update-index --add frotz &&
@@ -153,7 +154,7 @@ test_expect_success \
test_expect_success \
'9 - conflicting addition.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo frotz frotz >frotz &&
git update-index --add frotz &&
@@ -163,7 +164,7 @@ test_expect_success \
test_expect_success \
'10 - path removed.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo rezrov >rezrov &&
git update-index --add rezrov &&
@@ -174,7 +175,7 @@ test_expect_success \
test_expect_success \
'11 - dirty path removed.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo rezrov >rezrov &&
git update-index --add rezrov &&
@@ -184,7 +185,7 @@ test_expect_success \
test_expect_success \
'12 - unmatching local changes being removed.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo rezrov rezrov >rezrov &&
git update-index --add rezrov &&
@@ -193,7 +194,7 @@ test_expect_success \
test_expect_success \
'13 - unmatching local changes being removed.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo rezrov rezrov >rezrov &&
git update-index --add rezrov &&
@@ -208,34 +209,34 @@ EOF
test_expect_success \
'14 - unchanged in two heads.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >14.out || return 1
- git diff --no-index M.out 14.out >14diff.out
+ git ls-files --stage >14.out &&
+ test_must_fail git diff --no-index M.out 14.out >14diff.out &&
compare_change 14diff.out expected &&
check_cache_at nitfol clean'
test_expect_success \
'15 - unchanged in two heads.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
read_tree_twoway $treeH $treeM &&
- git ls-files --stage >15.out || return 1
- git diff --no-index M.out 15.out >15diff.out
+ git ls-files --stage >15.out &&
+ test_must_fail git diff --no-index M.out 15.out >15diff.out &&
compare_change 15diff.out expected &&
check_cache_at nitfol dirty'
test_expect_success \
'16 - conflicting local change.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo bozbar bozbar >bozbar &&
git update-index --add bozbar &&
@@ -244,7 +245,7 @@ test_expect_success \
test_expect_success \
'17 - conflicting local change.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
echo bozbar bozbar >bozbar &&
git update-index --add bozbar &&
@@ -254,7 +255,7 @@ test_expect_success \
test_expect_success \
'18 - local change already having a good result.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
cat bozbar-new >bozbar &&
git update-index --add bozbar &&
@@ -266,7 +267,7 @@ test_expect_success \
test_expect_success \
'19 - local change already having a good result, further modified.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
cat bozbar-new >bozbar &&
git update-index --add bozbar &&
@@ -279,7 +280,7 @@ test_expect_success \
test_expect_success \
'20 - no local change, use new tree.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
cat bozbar-old >bozbar &&
git update-index --add bozbar &&
@@ -291,7 +292,7 @@ test_expect_success \
test_expect_success \
'21 - no local change, dirty cache.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
cat bozbar-old >bozbar &&
git update-index --add bozbar &&
@@ -302,7 +303,7 @@ test_expect_success \
test_expect_success \
'22 - local change cache updated.' \
'rm -f .git/index &&
- git read-tree $treeH &&
+ read_tree_must_succeed $treeH &&
git checkout-index -u -f -q -a &&
sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
git update-index --add bozbar &&
@@ -359,7 +360,7 @@ test_expect_success \
test_expect_success \
'a/b (untracked) vs a, plus c/d case test.' \
- '! git read-tree -u -m "$treeH" "$treeM" &&
+ 'read_tree_u_must_fail -u -m "$treeH" "$treeM" &&
git ls-files --stage &&
test -f a/b'
@@ -377,7 +378,7 @@ test_expect_success \
git ls-files --stage >treeM.out &&
rm -f a &&
- mkdir a
+ mkdir a &&
: >a/b &&
git update-index --add --remove a a/b &&
treeH=`git write-tree` &&
@@ -386,8 +387,24 @@ test_expect_success \
test_expect_success \
'a/b vs a, plus c/d case test.' \
- 'git read-tree -u -m "$treeH" "$treeM" &&
+ 'read_tree_u_must_succeed -u -m "$treeH" "$treeM" &&
git ls-files --stage | tee >treeMcheck.out &&
test_cmp treeM.out treeMcheck.out'
+test_expect_success '-m references the correct modified tree' '
+ echo >file-a &&
+ echo >file-b &&
+ git add file-a file-b &&
+ git commit -a -m "test for correct modified tree" &&
+ git branch initial-mod &&
+ echo b >file-b &&
+ git commit -a -m "B" &&
+ echo a >file-a &&
+ git add file-a &&
+ git ls-tree $(git write-tree) file-a >expect &&
+ read_tree_must_succeed -m HEAD initial-mod &&
+ git ls-tree $(git write-tree) file-a >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
index 0241329..a847709 100755
--- a/t/t1002-read-tree-m-u-2way.sh
+++ b/t/t1002-read-tree-m-u-2way.sh
@@ -9,6 +9,7 @@ This is identical to t1001, but uses -u to update the work tree as well.
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
compare_change () {
sed >current \
@@ -56,8 +57,8 @@ test_expect_success \
test_expect_success \
'1, 2, 3 - no carry forward' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed --reset -u $treeH &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >1-3.out &&
cmp M.out 1-3.out &&
sum bozbar frotz nitfol >actual3.sum &&
@@ -69,11 +70,11 @@ test_expect_success \
test_expect_success \
'4 - carry forward local addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo "+100644 X 0 yomin" >expected &&
echo yomin >yomin &&
git update-index --add yomin &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >4.out || return 1
git diff -U0 --no-index M.out 4.out >4diff.out
compare_change 4diff.out expected &&
@@ -87,12 +88,12 @@ test_expect_success \
test_expect_success \
'5 - carry forward local addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
- git read-tree -m -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
+ read_tree_u_must_succeed -m -u $treeH &&
echo yomin >yomin &&
git update-index --add yomin &&
echo yomin yomin >yomin &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >5.out || return 1
git diff -U0 --no-index M.out 5.out >5diff.out
compare_change 5diff.out expected &&
@@ -107,10 +108,10 @@ test_expect_success \
test_expect_success \
'6 - local addition already has the same.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo frotz >frotz &&
git update-index --add frotz &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >6.out &&
test_cmp M.out 6.out &&
check_cache_at frotz clean &&
@@ -123,11 +124,11 @@ test_expect_success \
test_expect_success \
'7 - local addition already has the same.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo frotz >frotz &&
git update-index --add frotz &&
echo frotz frotz >frotz &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >7.out &&
test_cmp M.out 7.out &&
check_cache_at frotz dirty &&
@@ -141,27 +142,27 @@ test_expect_success \
test_expect_success \
'8 - conflicting addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo frotz frotz >frotz &&
git update-index --add frotz &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'9 - conflicting addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo frotz frotz >frotz &&
git update-index --add frotz &&
echo frotz >frotz &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'10 - path removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo rezrov >rezrov &&
git update-index --add rezrov &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >10.out &&
cmp M.out 10.out &&
sum bozbar frotz nitfol >actual10.sum &&
@@ -170,28 +171,28 @@ test_expect_success \
test_expect_success \
'11 - dirty path removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo rezrov >rezrov &&
git update-index --add rezrov &&
echo rezrov rezrov >rezrov &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'12 - unmatching local changes being removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo rezrov rezrov >rezrov &&
git update-index --add rezrov &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'13 - unmatching local changes being removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo rezrov rezrov >rezrov &&
git update-index --add rezrov &&
echo rezrov >rezrov &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
cat >expected <<EOF
-100644 X 0 nitfol
@@ -201,12 +202,12 @@ EOF
test_expect_success \
'14 - unchanged in two heads.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
- git read-tree -m -u $treeH $treeM &&
- git ls-files --stage >14.out || return 1
- git diff -U0 --no-index M.out 14.out >14diff.out
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
+ git ls-files --stage >14.out &&
+ test_must_fail git diff -U0 --no-index M.out 14.out >14diff.out &&
compare_change 14diff.out expected &&
sum bozbar frotz >actual14.sum &&
grep -v nitfol M.sum > expected14.sum &&
@@ -221,13 +222,13 @@ test_expect_success \
test_expect_success \
'15 - unchanged in two heads.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo nitfol nitfol >nitfol &&
git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
- git read-tree -m -u $treeH $treeM &&
- git ls-files --stage >15.out || return 1
- git diff -U0 --no-index M.out 15.out >15diff.out
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
+ git ls-files --stage >15.out &&
+ test_must_fail git diff -U0 --no-index M.out 15.out >15diff.out &&
compare_change 15diff.out expected &&
check_cache_at nitfol dirty &&
sum bozbar frotz >actual15.sum &&
@@ -242,27 +243,27 @@ test_expect_success \
test_expect_success \
'16 - conflicting local change.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo bozbar bozbar >bozbar &&
git update-index --add bozbar &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'17 - conflicting local change.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo bozbar bozbar >bozbar &&
git update-index --add bozbar &&
echo bozbar bozbar bozbar >bozbar &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'18 - local change already having a good result.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo gnusto >bozbar &&
git update-index --add bozbar &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >18.out &&
test_cmp M.out 18.out &&
check_cache_at bozbar clean &&
@@ -272,11 +273,11 @@ test_expect_success \
test_expect_success \
'19 - local change already having a good result, further modified.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo gnusto >bozbar &&
git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >19.out &&
test_cmp M.out 19.out &&
check_cache_at bozbar dirty &&
@@ -292,10 +293,10 @@ test_expect_success \
test_expect_success \
'20 - no local change, use new tree.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo bozbar >bozbar &&
git update-index --add bozbar &&
- git read-tree -m -u $treeH $treeM &&
+ read_tree_u_must_succeed -m -u $treeH $treeM &&
git ls-files --stage >20.out &&
test_cmp M.out 20.out &&
check_cache_at bozbar clean &&
@@ -305,16 +306,16 @@ test_expect_success \
test_expect_success \
'21 - no local change, dirty cache.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git read-tree --reset -u $treeH &&
+ read_tree_u_must_succeed --reset -u $treeH &&
echo bozbar >bozbar &&
git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
- if git read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi'
# Also make sure we did not break DF vs DF/DF case.
test_expect_success \
'DF vs DF/DF case setup.' \
- 'rm -f .git/index
+ 'rm -f .git/index &&
echo DF >DF &&
git update-index --add DF &&
treeDF=`git write-tree` &&
@@ -336,7 +337,7 @@ test_expect_success \
rm -fr DF &&
echo DF >DF &&
git update-index --add DF &&
- git read-tree -m -u $treeDF $treeDFDF &&
+ read_tree_u_must_succeed -m -u $treeDF $treeDFDF &&
git ls-files --stage >DFDFcheck.out &&
test_cmp DFDF.out DFDFcheck.out &&
check_cache_at DF/DF clean'
diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh
index f19b4a2..b3ae7d5 100755
--- a/t/t1004-read-tree-m-u-wf.sh
+++ b/t/t1004-read-tree-m-u-wf.sh
@@ -3,6 +3,7 @@
test_description='read-tree -m -u checks working tree files'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
# two-tree test
@@ -29,7 +30,7 @@ test_expect_success 'two-way not clobbering' '
echo >file2 master creates untracked file2 &&
echo >subdir/file2 master creates untracked subdir/file2 &&
- if err=`git read-tree -m -u master side 2>&1`
+ if err=`read_tree_u_must_succeed -m -u master side 2>&1`
then
echo should have complained
false
@@ -42,7 +43,7 @@ echo file2 >.gitignore
test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
- if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1`
+ if err=`read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1`
then
echo should have complained
false
@@ -53,7 +54,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
- if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+ if err=`read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
then
echo should have complained
false
@@ -64,7 +65,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
test_expect_success 'two-way clobbering a ignored file' '
- git read-tree -m -u --exclude-per-directory=.gitignore master side
+ read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore master side
'
rm -f .gitignore
@@ -84,7 +85,7 @@ test_expect_success 'three-way not complaining on an untracked path in both' '
echo >file2 file two is untracked on the master side &&
echo >subdir/file2 file two is untracked on the master side &&
- git read-tree -m -u branch-point master side
+ read_tree_u_must_succeed -m -u branch-point master side
'
test_expect_success 'three-way not clobbering a working tree file' '
@@ -94,7 +95,7 @@ test_expect_success 'three-way not clobbering a working tree file' '
git checkout master &&
echo >file3 file three created in master, untracked &&
echo >subdir/file3 file three created in master, untracked &&
- if err=`git read-tree -m -u branch-point master side 2>&1`
+ if err=`read_tree_u_must_succeed -m -u branch-point master side 2>&1`
then
echo should have complained
false
@@ -113,7 +114,7 @@ test_expect_success 'three-way not complaining on an untracked file' '
echo >file3 file three created in master, untracked &&
echo >subdir/file3 file three created in master, untracked &&
- git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side
+ read_tree_u_must_succeed -m -u --exclude-per-directory=.gitignore branch-point master side
'
test_expect_success '3-way not overwriting local changes (setup)' '
@@ -137,7 +138,7 @@ test_expect_success '3-way not overwriting local changes (our side)' '
git reset --hard &&
echo >>file1 "local changes" &&
- git read-tree -m -u branch-point side-a side-b &&
+ read_tree_u_must_succeed -m -u branch-point side-a side-b &&
grep "new line to be kept" file1 &&
grep "local changes" file1
@@ -151,7 +152,7 @@ test_expect_success '3-way not overwriting local changes (their side)' '
git reset --hard &&
echo >>file2 "local changes" &&
- test_must_fail git read-tree -m -u branch-point side-a side-b &&
+ read_tree_u_must_fail -m -u branch-point side-a side-b &&
! grep "new line to be kept" file2 &&
grep "local changes" file2
@@ -173,11 +174,11 @@ test_expect_success SYMLINKS 'funny symlink in work tree' '
git add a/b &&
git commit -m "we add a/b" &&
- git read-tree -m -u sym-a sym-a sym-b
+ read_tree_u_must_succeed -m -u sym-a sym-a sym-b
'
-test_expect_success SYMLINKS 'funny symlink in work tree, un-unlink-able' '
+test_expect_success SYMLINKS,SANITY 'funny symlink in work tree, un-unlink-able' '
rm -fr a b &&
git reset --hard &&
@@ -209,7 +210,7 @@ test_expect_success 'D/F setup' '
test_expect_success 'D/F' '
git checkout side-b &&
- git read-tree -m -u branch-point side-b side-a &&
+ read_tree_u_must_succeed -m -u branch-point side-b side-a &&
git ls-files -u >actual &&
(
a=$(git rev-parse branch-point:subdir/file2)
diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh
index 8499116..f53de79 100755
--- a/t/t1005-read-tree-reset.sh
+++ b/t/t1005-read-tree-reset.sh
@@ -3,6 +3,7 @@
test_description='read-tree -u --reset'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
# two-tree test
@@ -22,13 +23,13 @@ test_expect_success 'setup' '
'
test_expect_success 'reset should work' '
- git read-tree -u --reset HEAD^ &&
+ read_tree_u_must_succeed -u --reset HEAD^ &&
git ls-files >actual &&
test_cmp expect actual
'
test_expect_success 'reset should remove remnants from a failed merge' '
- git read-tree --reset -u HEAD &&
+ read_tree_u_must_succeed --reset -u HEAD &&
git ls-files -s >expect &&
sha1=$(git rev-parse :new) &&
(
@@ -37,13 +38,13 @@ test_expect_success 'reset should remove remnants from a failed merge' '
) | git update-index --index-info &&
>old &&
git ls-files -s &&
- git read-tree --reset -u HEAD &&
+ read_tree_u_must_succeed --reset -u HEAD &&
git ls-files -s >actual &&
! test -f old
'
test_expect_success 'Porcelain reset should remove remnants too' '
- git read-tree --reset -u HEAD &&
+ read_tree_u_must_succeed --reset -u HEAD &&
git ls-files -s >expect &&
sha1=$(git rev-parse :new) &&
(
@@ -58,7 +59,7 @@ test_expect_success 'Porcelain reset should remove remnants too' '
'
test_expect_success 'Porcelain checkout -f should remove remnants too' '
- git read-tree --reset -u HEAD &&
+ read_tree_u_must_succeed --reset -u HEAD &&
git ls-files -s >expect &&
sha1=$(git rev-parse :new) &&
(
@@ -73,7 +74,7 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' '
'
test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' '
- git read-tree --reset -u HEAD &&
+ read_tree_u_must_succeed --reset -u HEAD &&
git ls-files -s >expect &&
sha1=$(git rev-parse :new) &&
(
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index dd32432..6d52b82 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -188,4 +188,17 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do
pop_repo
done
+test_expect_success 'corrupt tree' '
+ echo abc >malformed-tree
+ test_must_fail git hash-object -t tree malformed-tree
+'
+
+test_expect_success 'corrupt commit' '
+ test_must_fail git hash-object -t commit --stdin </dev/null
+'
+
+test_expect_success 'corrupt tag' '
+ test_must_fail git hash-object -t tag --stdin </dev/null
+'
+
test_done
diff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh
index f9e0028..4c50ed9 100755
--- a/t/t1008-read-tree-overlay.sh
+++ b/t/t1008-read-tree-overlay.sh
@@ -3,6 +3,7 @@
test_description='test multi-tree read-tree without merging'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
test_expect_success setup '
echo one >a &&
@@ -21,7 +22,7 @@ test_expect_success setup '
'
test_expect_success 'multi-read' '
- git read-tree initial master side &&
+ read_tree_must_succeed initial master side &&
(echo a; echo b/c) >expect &&
git ls-files >actual &&
test_cmp expect actual
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index 62246db..018c354 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -1,45 +1,60 @@
#!/bin/sh
-test_description='sparse checkout tests'
+test_description='sparse checkout tests
+
+* (tag: removed, master) removed
+| D sub/added
+* (HEAD, tag: top) modified and added
+| M init.t
+| A sub/added
+* (tag: init) init
+ A init.t
+'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
-cat >expected <<EOF
-100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
-100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
-EOF
test_expect_success 'setup' '
+ cat >expected <<-\EOF &&
+ 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
+ 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
+ 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo
+ 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added
+ EOF
+ cat >expected.swt <<-\EOF &&
+ H init.t
+ H sub/added
+ H sub/addedtoo
+ H subsub/added
+ EOF
+
test_commit init &&
- echo modified >> init.t &&
- mkdir sub &&
- touch sub/added &&
- git add init.t sub/added &&
+ echo modified >>init.t &&
+ mkdir sub subsub &&
+ touch sub/added sub/addedtoo subsub/added &&
+ git add init.t sub/added sub/addedtoo subsub/added &&
git commit -m "modified and added" &&
git tag top &&
git rm sub/added &&
git commit -m removed &&
git tag removed &&
git checkout top &&
- git ls-files --stage > result &&
+ git ls-files --stage >result &&
test_cmp expected result
'
-cat >expected.swt <<EOF
-H init.t
-H sub/added
-EOF
test_expect_success 'read-tree without .git/info/sparse-checkout' '
- git read-tree -m -u HEAD &&
- git ls-files --stage > result &&
+ read_tree_u_must_succeed -m -u HEAD &&
+ git ls-files --stage >result &&
test_cmp expected result &&
- git ls-files -t > result &&
+ git ls-files -t >result &&
test_cmp expected.swt result
'
test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
- echo > .git/info/sparse-checkout
- git read-tree -m -u HEAD &&
- git ls-files -t > result &&
+ echo >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+ git ls-files -t >result &&
test_cmp expected.swt result &&
test -f init.t &&
test -f sub/added
@@ -47,9 +62,9 @@ test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
git config core.sparsecheckout true &&
- echo > .git/info/sparse-checkout &&
- git read-tree --no-sparse-checkout -m -u HEAD &&
- git ls-files -t > result &&
+ echo >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed --no-sparse-checkout -m -u HEAD &&
+ git ls-files -t >result &&
test_cmp expected.swt result &&
test -f init.t &&
test -f sub/added
@@ -57,94 +72,166 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-
test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
git config core.sparsecheckout true &&
- echo > .git/info/sparse-checkout &&
- test_must_fail git read-tree -m -u HEAD &&
- git ls-files --stage > result &&
+ echo >.git/info/sparse-checkout &&
+ read_tree_u_must_fail -m -u HEAD &&
+ git ls-files --stage >result &&
test_cmp expected result &&
- git ls-files -t > result &&
+ git ls-files -t >result &&
test_cmp expected.swt result &&
test -f init.t &&
test -f sub/added
'
-cat >expected.swt <<EOF
-S init.t
-H sub/added
-EOF
test_expect_success 'match directories with trailing slash' '
+ cat >expected.swt-noinit <<-\EOF &&
+ S init.t
+ H sub/added
+ H sub/addedtoo
+ S subsub/added
+ EOF
+
echo sub/ > .git/info/sparse-checkout &&
- git read-tree -m -u HEAD &&
+ read_tree_u_must_succeed -m -u HEAD &&
git ls-files -t > result &&
- test_cmp expected.swt result &&
+ test_cmp expected.swt-noinit result &&
test ! -f init.t &&
test -f sub/added
'
-cat >expected.swt <<EOF
+test_expect_success 'match directories without trailing slash' '
+ echo sub >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-noinit result &&
+ test ! -f init.t &&
+ test -f sub/added
+'
+
+test_expect_success 'match directories with negated patterns' '
+ cat >expected.swt-negation <<\EOF &&
+S init.t
+S sub/added
+H sub/addedtoo
+S subsub/added
+EOF
+
+ cat >.git/info/sparse-checkout <<\EOF &&
+sub
+!sub/added
+EOF
+ git read-tree -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-negation result &&
+ test ! -f init.t &&
+ test ! -f sub/added &&
+ test -f sub/addedtoo
+'
+
+test_expect_success 'match directories with negated patterns (2)' '
+ cat >expected.swt-negation2 <<\EOF &&
H init.t
H sub/added
+S sub/addedtoo
+H subsub/added
+EOF
+
+ cat >.git/info/sparse-checkout <<\EOF &&
+/*
+!sub
+sub/added
EOF
-test_expect_failure 'match directories without trailing slash' '
- echo init.t > .git/info/sparse-checkout &&
- echo sub >> .git/info/sparse-checkout &&
git read-tree -m -u HEAD &&
- git ls-files -t > result &&
- test_cmp expected.swt result &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-negation2 result &&
+ test -f init.t &&
+ test -f sub/added &&
+ test ! -f sub/addedtoo
+'
+
+test_expect_success 'match directory pattern' '
+ echo "s?b" >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-noinit result &&
test ! -f init.t &&
test -f sub/added
'
-cat >expected.swt <<EOF
-H init.t
-S sub/added
-EOF
test_expect_success 'checkout area changes' '
- echo init.t > .git/info/sparse-checkout &&
- git read-tree -m -u HEAD &&
- git ls-files -t > result &&
- test_cmp expected.swt result &&
+ cat >expected.swt-nosub <<-\EOF &&
+ H init.t
+ S sub/added
+ S sub/addedtoo
+ S subsub/added
+ EOF
+
+ echo init.t >.git/info/sparse-checkout &&
+ read_tree_u_must_succeed -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-nosub result &&
test -f init.t &&
test ! -f sub/added
'
test_expect_success 'read-tree updates worktree, absent case' '
- echo sub/added > .git/info/sparse-checkout &&
+ echo sub/added >.git/info/sparse-checkout &&
git checkout -f top &&
- git read-tree -m -u HEAD^ &&
+ read_tree_u_must_succeed -m -u HEAD^ &&
test ! -f init.t
'
test_expect_success 'read-tree updates worktree, dirty case' '
- echo sub/added > .git/info/sparse-checkout &&
+ echo sub/added >.git/info/sparse-checkout &&
git checkout -f top &&
- echo dirty > init.t &&
- git read-tree -m -u HEAD^ &&
+ echo dirty >init.t &&
+ read_tree_u_must_succeed -m -u HEAD^ &&
grep -q dirty init.t &&
rm init.t
'
test_expect_success 'read-tree removes worktree, dirty case' '
- echo init.t > .git/info/sparse-checkout &&
+ echo init.t >.git/info/sparse-checkout &&
git checkout -f top &&
- echo dirty > added &&
- git read-tree -m -u HEAD^ &&
+ echo dirty >added &&
+ read_tree_u_must_succeed -m -u HEAD^ &&
grep -q dirty added
'
test_expect_success 'read-tree adds to worktree, absent case' '
- echo init.t > .git/info/sparse-checkout &&
+ echo init.t >.git/info/sparse-checkout &&
git checkout -f removed &&
- git read-tree -u -m HEAD^ &&
+ read_tree_u_must_succeed -u -m HEAD^ &&
test ! -f sub/added
'
test_expect_success 'read-tree adds to worktree, dirty case' '
- echo init.t > .git/info/sparse-checkout &&
+ echo init.t >.git/info/sparse-checkout &&
git checkout -f removed &&
mkdir sub &&
- echo dirty > sub/added &&
- git read-tree -u -m HEAD^ &&
+ echo dirty >sub/added &&
+ read_tree_u_must_succeed -u -m HEAD^ &&
grep -q dirty sub/added
'
+test_expect_success 'index removal and worktree narrowing at the same time' '
+ >empty &&
+ echo init.t >.git/info/sparse-checkout &&
+ echo sub/added >>.git/info/sparse-checkout &&
+ git checkout -f top &&
+ echo init.t >.git/info/sparse-checkout &&
+ git checkout removed &&
+ git ls-files sub/added >result &&
+ test ! -f sub/added &&
+ test_cmp empty result
+'
+
+test_expect_success 'read-tree --reset removes outside worktree' '
+ >empty &&
+ echo init.t >.git/info/sparse-checkout &&
+ git checkout -f top &&
+ git reset --hard removed &&
+ git ls-files sub/added >result &&
+ test_cmp empty result
+'
+
test_done
diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh
index 9811d46..a6a04b6 100755
--- a/t/t1012-read-tree-df.sh
+++ b/t/t1012-read-tree-df.sh
@@ -3,6 +3,7 @@
test_description='read-tree D/F conflict corner cases'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
maketree () {
(
@@ -53,7 +54,7 @@ test_expect_success setup '
test_expect_success '3-way (1)' '
settree A-000 &&
- git read-tree -m -u O-000 A-000 B-000 &&
+ read_tree_u_must_succeed -m -u O-000 A-000 B-000 &&
checkindex <<-EOF
3 a/b
0 a/b-2/c/d
@@ -65,7 +66,7 @@ test_expect_success '3-way (1)' '
test_expect_success '3-way (2)' '
settree A-001 &&
- git read-tree -m -u O-000 A-001 B-000 &&
+ read_tree_u_must_succeed -m -u O-000 A-001 B-000 &&
checkindex <<-EOF
3 a/b
0 a/b-2/c/d
@@ -78,7 +79,7 @@ test_expect_success '3-way (2)' '
test_expect_success '3-way (3)' '
settree A-010 &&
- git read-tree -m -u O-010 A-010 B-010 &&
+ read_tree_u_must_succeed -m -u O-010 A-010 B-010 &&
checkindex <<-EOF
2 t
1 t-0
@@ -92,7 +93,7 @@ test_expect_success '3-way (3)' '
test_expect_success '2-way (1)' '
settree O-020 &&
- git read-tree -m -u O-020 A-020 &&
+ read_tree_u_must_succeed -m -u O-020 A-020 &&
checkindex <<-EOF
0 ds/dma/ioat/Makefile
0 ds/dma/ioat/registers.h
diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh
index 210e594..865b8ed 100755
--- a/t/t1020-subdirectory.sh
+++ b/t/t1020-subdirectory.sh
@@ -7,6 +7,7 @@ test_description='Try various core-level commands in subdirectory.
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-read-tree.sh
test_expect_success setup '
long="a b c d e f g h i j k l m n o p q r s t u v w x y z" &&
@@ -16,123 +17,178 @@ test_expect_success setup '
cp one original.one &&
cp dir/two original.two
'
-HERE=`pwd`
LF='
'
test_expect_success 'update-index and ls-files' '
- cd "$HERE" &&
git update-index --add one &&
case "`git ls-files`" in
- one) echo ok one ;;
+ one) echo pass one ;;
*) echo bad one; exit 1 ;;
esac &&
- cd dir &&
- git update-index --add two &&
+ (
+ cd dir &&
+ git update-index --add two &&
+ case "`git ls-files`" in
+ two) echo pass two ;;
+ *) echo bad two; exit 1 ;;
+ esac
+ ) &&
case "`git ls-files`" in
- two) echo ok two ;;
- *) echo bad two; exit 1 ;;
- esac &&
- cd .. &&
- case "`git ls-files`" in
- dir/two"$LF"one) echo ok both ;;
+ dir/two"$LF"one) echo pass both ;;
*) echo bad; exit 1 ;;
esac
'
test_expect_success 'cat-file' '
- cd "$HERE" &&
two=`git ls-files -s dir/two` &&
two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` &&
echo "$two" &&
git cat-file -p "$two" >actual &&
cmp dir/two actual &&
- cd dir &&
- git cat-file -p "$two" >actual &&
- cmp two actual
+ (
+ cd dir &&
+ git cat-file -p "$two" >actual &&
+ cmp two actual
+ )
'
rm -f actual dir/actual
test_expect_success 'diff-files' '
- cd "$HERE" &&
echo a >>one &&
echo d >>dir/two &&
case "`git diff-files --name-only`" in
- dir/two"$LF"one) echo ok top ;;
+ dir/two"$LF"one) echo pass top ;;
*) echo bad top; exit 1 ;;
esac &&
# diff should not omit leading paths
- cd dir &&
- case "`git diff-files --name-only`" in
- dir/two"$LF"one) echo ok subdir ;;
- *) echo bad subdir; exit 1 ;;
- esac &&
- case "`git diff-files --name-only .`" in
- dir/two) echo ok subdir limited ;;
- *) echo bad subdir limited; exit 1 ;;
- esac
+ (
+ cd dir &&
+ case "`git diff-files --name-only`" in
+ dir/two"$LF"one) echo pass subdir ;;
+ *) echo bad subdir; exit 1 ;;
+ esac &&
+ case "`git diff-files --name-only .`" in
+ dir/two) echo pass subdir limited ;;
+ *) echo bad subdir limited; exit 1 ;;
+ esac
+ )
'
test_expect_success 'write-tree' '
- cd "$HERE" &&
top=`git write-tree` &&
echo $top &&
- cd dir &&
- sub=`git write-tree` &&
- echo $sub &&
- test "z$top" = "z$sub"
+ (
+ cd dir &&
+ sub=`git write-tree` &&
+ echo $sub &&
+ test "z$top" = "z$sub"
+ )
'
test_expect_success 'checkout-index' '
- cd "$HERE" &&
git checkout-index -f -u one &&
cmp one original.one &&
- cd dir &&
- git checkout-index -f -u two &&
- cmp two ../original.two
+ (
+ cd dir &&
+ git checkout-index -f -u two &&
+ cmp two ../original.two
+ )
'
test_expect_success 'read-tree' '
- cd "$HERE" &&
rm -f one dir/two &&
tree=`git write-tree` &&
- git read-tree --reset -u "$tree" &&
+ read_tree_u_must_succeed --reset -u "$tree" &&
cmp one original.one &&
cmp dir/two original.two &&
- cd dir &&
- rm -f two &&
- git read-tree --reset -u "$tree" &&
- cmp two ../original.two &&
- cmp ../one ../original.one
+ (
+ cd dir &&
+ rm -f two &&
+ read_tree_u_must_succeed --reset -u "$tree" &&
+ cmp two ../original.two &&
+ cmp ../one ../original.one
+ )
+'
+
+test_expect_success 'alias expansion' '
+ (
+ git config alias.ss status &&
+ cd dir &&
+ git status &&
+ git ss
+ )
+'
+
+test_expect_success '!alias expansion' '
+ pwd >expect &&
+ (
+ git config alias.test !pwd &&
+ cd dir &&
+ git test >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'GIT_PREFIX for !alias' '
+ printf "dir/" >expect &&
+ (
+ git config alias.test "!sh -c \"printf \$GIT_PREFIX\"" &&
+ cd dir &&
+ git test >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'GIT_PREFIX for built-ins' '
+ # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
+ # receives the GIT_PREFIX variable.
+ printf "dir/" >expect &&
+ printf "#!/bin/sh\n" >diff &&
+ printf "printf \"\$GIT_PREFIX\"" >>diff &&
+ chmod +x diff &&
+ (
+ cd dir &&
+ printf "change" >two &&
+ env GIT_EXTERNAL_DIFF=./diff git diff >../actual
+ git checkout -- two
+ ) &&
+ test_cmp expect actual
'
test_expect_success 'no file/rev ambiguity check inside .git' '
- cd "$HERE" &&
git commit -a -m 1 &&
- cd "$HERE"/.git &&
- git show -s HEAD
+ (
+ cd .git &&
+ git show -s HEAD
+ )
'
test_expect_success 'no file/rev ambiguity check inside a bare repo' '
- cd "$HERE" &&
git clone -s --bare .git foo.git &&
- cd foo.git && GIT_DIR=. git show -s HEAD
+ (
+ cd foo.git &&
+ GIT_DIR=. git show -s HEAD
+ )
'
# This still does not work as it should...
: test_expect_success 'no file/rev ambiguity check inside a bare repo' '
- cd "$HERE" &&
git clone -s --bare .git foo.git &&
- cd foo.git && git show -s HEAD
+ (
+ cd foo.git &&
+ git show -s HEAD
+ )
'
test_expect_success SYMLINKS 'detection should not be fooled by a symlink' '
- cd "$HERE" &&
rm -fr foo.git &&
git clone -s .git another &&
ln -s another yetanother &&
- cd yetanother/.git &&
- git show -s HEAD
+ (
+ cd yetanother/.git &&
+ git show -s HEAD
+ )
'
test_done
diff --git a/t/t1021-rerere-in-workdir.sh b/t/t1021-rerere-in-workdir.sh
new file mode 100755
index 0000000..301e071
--- /dev/null
+++ b/t/t1021-rerere-in-workdir.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='rerere run in a workdir'
+. ./test-lib.sh
+
+test_expect_success SYMLINKS setup '
+ git config rerere.enabled true &&
+ >world &&
+ git add world &&
+ test_tick &&
+ git commit -m initial &&
+
+ echo hello >world &&
+ test_tick &&
+ git commit -a -m hello &&
+
+ git checkout -b side HEAD^ &&
+ echo goodbye >world &&
+ test_tick &&
+ git commit -a -m goodbye &&
+
+ git checkout master
+'
+
+test_expect_success SYMLINKS 'rerere in workdir' '
+ rm -rf .git/rr-cache &&
+ "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . work &&
+ (
+ cd work &&
+ test_must_fail git merge side &&
+ git rerere status >actual &&
+ echo world >expect &&
+ test_cmp expect actual
+ )
+'
+
+# This fails because we don't resolve relative symlink in mkdir_in_gitdir()
+# For the purpose of helping contrib/workdir/git-new-workdir users, we do not
+# have to support relative symlinks, but it might be nicer to make this work
+# with a relative symbolic link someday.
+test_expect_failure SYMLINKS 'rerere in workdir (relative)' '
+ rm -rf .git/rr-cache &&
+ "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" . krow &&
+ (
+ cd krow &&
+ rm -f .git/rr-cache &&
+ ln -s ../.git/rr-cache .git/rr-cache &&
+ test_must_fail git merge side &&
+ git rerere status >actual &&
+ echo world >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_done
diff --git a/t/t1050-large.sh b/t/t1050-large.sh
new file mode 100755
index 0000000..deba111
--- /dev/null
+++ b/t/t1050-large.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='adding and checking out large blobs'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ git config core.bigfilethreshold 200k &&
+ echo X | dd of=large bs=1k seek=2000
+'
+
+test_expect_success 'add a large file' '
+ git add large &&
+ # make sure we got a packfile and no loose objects
+ test -f .git/objects/pack/pack-*.pack &&
+ test ! -f .git/objects/??/??????????????????????????????????????
+'
+
+test_expect_success 'checkout a large file' '
+ large=$(git rev-parse :large) &&
+ git update-index --add --cacheinfo 100644 $large another &&
+ git checkout another &&
+ cmp large another ;# this must not be test_cmp
+'
+
+test_done
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index ab55eda..5e29e13 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -42,7 +42,7 @@ test_expect_success 'git diff' '
'
test_expect_success 'tree' '
- tree=$(git write-tree 2>/dev/null)
+ tree=$(git write-tree 2>/dev/null) &&
test 8988da15d077d4829fc51d8544c097def6644dbb = $tree
'
@@ -163,8 +163,11 @@ test_expect_success 'git resolve' '
git checkout mybranch &&
git merge -m "Merge upstream changes." master |
sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \
- -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output &&
- test_cmp resolve.expect resolve.output
+ -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output
+'
+
+test_expect_success 'git resolve output' '
+ test_i18ncmp resolve.expect resolve.output
'
cat > show-branch2.expect << EOF
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 64f0508..3e140c1 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -289,6 +289,14 @@ test_expect_success 'working --list' \
'git config --list > output && cmp output expect'
cat > expect << EOF
+EOF
+
+test_expect_success '--list without repo produces empty output' '
+ git --git-dir=nonexistent config --list >output &&
+ test_cmp expect output
+'
+
+cat > expect << EOF
beta.noindent sillyValue
nextsection.nonewline wow2 for me
EOF
@@ -701,25 +709,47 @@ cat >expect <<\EOF
trailingtilde = foo~
EOF
-test_expect_success 'set --path' '
+test_expect_success NOT_MINGW 'set --path' '
git config --path path.home "~/" &&
git config --path path.normal "/dev/null" &&
git config --path path.trailingtilde "foo~" &&
test_cmp expect .git/config'
+if test_have_prereq NOT_MINGW && test "${HOME+set}"
+then
+ test_set_prereq HOMEVAR
+fi
+
cat >expect <<EOF
$HOME/
/dev/null
foo~
EOF
-test_expect_success 'get --path' '
+test_expect_success HOMEVAR 'get --path' '
git config --get --path path.home > result &&
git config --get --path path.normal >> result &&
git config --get --path path.trailingtilde >> result &&
test_cmp expect result
'
+cat >expect <<\EOF
+/dev/null
+foo~
+EOF
+
+test_expect_success NOT_MINGW 'get --path copes with unset $HOME' '
+ (
+ unset HOME;
+ test_must_fail git config --get --path path.home \
+ >result 2>msg &&
+ git config --get --path path.normal >>result &&
+ git config --get --path path.trailingtilde >>result
+ ) &&
+ grep "[Ff]ailed to expand.*~/" msg &&
+ test_cmp expect result
+'
+
rm .git/config
git config quote.leading " test"
@@ -814,6 +844,27 @@ test_expect_success SYMLINKS 'symlinked configuration' '
'
+test_expect_success 'nonexistent configuration' '
+ (
+ GIT_CONFIG=doesnotexist &&
+ export GIT_CONFIG &&
+ test_must_fail git config --list &&
+ test_must_fail git config test.xyzzy
+ )
+'
+
+test_expect_success SYMLINKS 'symlink to nonexistent configuration' '
+ ln -s doesnotexist linktonada &&
+ ln -s linktonada linktolinktonada &&
+ (
+ GIT_CONFIG=linktonada &&
+ export GIT_CONFIG &&
+ test_must_fail git config --list &&
+ GIT_CONFIG=linktolinktonada &&
+ test_must_fail git config --list
+ )
+'
+
test_expect_success 'check split_cmdline return' "
git config alias.split-cmdline-fix 'echo \"' &&
test_must_fail git split-cmdline-fix &&
@@ -825,11 +876,50 @@ test_expect_success 'check split_cmdline return' "
"
test_expect_success 'git -c "key=value" support' '
- test "z$(git -c name=value config name)" = zvalue &&
test "z$(git -c core.name=value config core.name)" = zvalue &&
- test "z$(git -c CamelCase=value config camelcase)" = zvalue &&
- test "z$(git -c flag config --bool flag)" = ztrue &&
- test_must_fail git -c core.name=value config name
+ test "z$(git -c foo.CamelCase=value config foo.camelcase)" = zvalue &&
+ test "z$(git -c foo.flag config --bool foo.flag)" = ztrue &&
+ test_must_fail git -c name=value config core.name
+'
+
+test_expect_success 'key sanity-checking' '
+ test_must_fail git config foo=bar &&
+ test_must_fail git config foo=.bar &&
+ test_must_fail git config foo.ba=r &&
+ test_must_fail git config foo.1bar &&
+ test_must_fail git config foo."ba
+ z".bar &&
+ test_must_fail git config . false &&
+ test_must_fail git config .foo false &&
+ test_must_fail git config foo. false &&
+ test_must_fail git config .foo. false &&
+ git config foo.bar true &&
+ git config foo."ba =z".bar false
+'
+
+test_expect_success 'git -c works with aliases of builtins' '
+ git config alias.checkconfig "-c foo.check=bar config foo.check" &&
+ echo bar >expect &&
+ git checkconfig >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git -c does not split values on equals' '
+ echo "value with = in it" >expect &&
+ git -c core.foo="value with = in it" config core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git -c dies on bogus config' '
+ test_must_fail git -c core.bare=foo rev-parse
+'
+
+test_expect_success 'git -c complains about empty key' '
+ test_must_fail git -c "=foo" rev-parse
+'
+
+test_expect_success 'git -c complains about empty key and value' '
+ test_must_fail git -c "" rev-parse
'
test_done
diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh
index 8d305b4..0e47662 100755
--- a/t/t1302-repo-version.sh
+++ b/t/t1302-repo-version.sh
@@ -7,41 +7,64 @@ test_description='Test repository version check'
. ./test-lib.sh
-cat >test.patch <<EOF
-diff --git a/test.txt b/test.txt
-new file mode 100644
---- /dev/null
-+++ b/test.txt
-@@ -0,0 +1 @@
-+123
-EOF
+test_expect_success 'setup' '
+ cat >test.patch <<-\EOF &&
+ diff --git a/test.txt b/test.txt
+ new file mode 100644
+ --- /dev/null
+ +++ b/test.txt
+ @@ -0,0 +1 @@
+ +123
+ EOF
-test_create_repo "test"
-test_create_repo "test2"
-
-GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99 || exit 1
+ test_create_repo "test" &&
+ test_create_repo "test2" &&
+ GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99
+'
test_expect_success 'gitdir selection on normal repos' '
- (test "$(git config core.repositoryformatversion)" = 0 &&
- cd test &&
- test "$(git config core.repositoryformatversion)" = 0)'
+ echo 0 >expect &&
+ git config core.repositoryformatversion >actual &&
+ (
+ cd test &&
+ git config core.repositoryformatversion >../actual2
+ ) &&
+ test_cmp expect actual &&
+ test_cmp expect actual2
+'
-# Make sure it would stop at test2, not trash
test_expect_success 'gitdir selection on unsupported repo' '
- (cd test2 &&
- test "$(git config core.repositoryformatversion)" = 99)'
+ # Make sure it would stop at test2, not trash
+ echo 99 >expect &&
+ (
+ cd test2 &&
+ git config core.repositoryformatversion >../actual
+ ) &&
+ test_cmp expect actual
+'
test_expect_success 'gitdir not required mode' '
- (git apply --stat test.patch &&
- cd test && git apply --stat ../test.patch &&
- cd ../test2 && git apply --stat ../test.patch)'
-
-test_expect_success 'gitdir required mode on normal repos' '
- (git apply --check --index test.patch &&
- cd test && git apply --check --index ../test.patch)'
+ git apply --stat test.patch &&
+ (
+ cd test &&
+ git apply --stat ../test.patch
+ ) &&
+ (
+ cd test2 &&
+ git apply --stat ../test.patch
+ )
+'
-test_expect_success 'gitdir required mode on unsupported repo' '
- (cd test2 && test_must_fail git apply --check --index ../test.patch)
+test_expect_success 'gitdir required mode' '
+ git apply --check --index test.patch &&
+ (
+ cd test &&
+ git apply --check --index ../test.patch
+ ) &&
+ (
+ cd test2 &&
+ test_must_fail git apply --check --index ../test.patch
+ )
'
test_done
diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh
index 080117c..46103a1 100755
--- a/t/t1303-wacky-config.sh
+++ b/t/t1303-wacky-config.sh
@@ -44,7 +44,7 @@ LONG_VALUE=$(printf "x%01021dx a" 7)
test_expect_success 'do not crash on special long config line' '
setup &&
git config section.key "$LONG_VALUE" &&
- check section.key "fatal: bad config file line 2 in .git/config"
+ check section.key "$LONG_VALUE"
'
test_done
diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh
index 055ad00..b5d89a2 100755
--- a/t/t1304-default-acl.sh
+++ b/t/t1304-default-acl.sh
@@ -15,9 +15,14 @@ umask 077
# is a good candidate: exists on all unices, and it has permission
# anyway, so we don't create a security hole running the testsuite.
-if ! setfacl -m u:root:rwx .; then
- say "Skipping ACL tests: unable to use setfacl"
- test_done
+setfacl_out="$(setfacl -m u:root:rwx . 2>&1)"
+setfacl_ret=$?
+
+if test $setfacl_ret != 0
+then
+ say "Unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')"
+else
+ test_set_prereq SETFACL
fi
check_perms_and_acl () {
@@ -31,7 +36,7 @@ check_perms_and_acl () {
dirs_to_set="./ .git/ .git/objects/ .git/objects/pack/"
-test_expect_success 'Setup test repo' '
+test_expect_success SETFACL 'Setup test repo' '
setfacl -m d:u::rwx,d:g::---,d:o:---,d:m:rwx $dirs_to_set &&
setfacl -m m:rwx $dirs_to_set &&
setfacl -m u:root:rwx $dirs_to_set &&
@@ -43,12 +48,12 @@ test_expect_success 'Setup test repo' '
git commit -m "init"
'
-test_expect_success 'Objects creation does not break ACLs with restrictive umask' '
+test_expect_success SETFACL 'Objects creation does not break ACLs with restrictive umask' '
# SHA1 for empty blob
check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
'
-test_expect_success 'git gc does not break ACLs with restrictive umask' '
+test_expect_success SETFACL 'git gc does not break ACLs with restrictive umask' '
git gc &&
check_perms_and_acl .git/objects/pack/*.pack
'
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 54ba3df..4fd83a6 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -6,7 +6,7 @@
test_description='Test git update-ref and basic ref logging'
. ./test-lib.sh
-Z=0000000000000000000000000000000000000000
+Z=$_z40
test_expect_success setup '
@@ -52,9 +52,8 @@ rm -f .git/$m
test_expect_success \
"fail to create $n" \
- "touch .git/$n_dir
- git update-ref $n $A >out 2>err"'
- test $? != 0'
+ "touch .git/$n_dir &&
+ test_must_fail git update-ref $n $A >out 2>err"
rm -f .git/$n_dir out err
test_expect_success \
@@ -185,55 +184,55 @@ gd="Thu, 26 May 2005 18:33:00 -0500"
ld="Thu, 26 May 2005 18:43:00 -0500"
test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify master@{2005-05-25} >o 2>e &&
test '"$C"' = $(cat o) &&
echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
test '"$A"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
test '"$B"' = $(cat o) &&
test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
test '"$Z"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
test '"$E"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
- 'rm -f o e
+ 'rm -f o e &&
git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
test '"$D"' = $(cat o) &&
test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
@@ -247,7 +246,7 @@ test_expect_success \
git add F &&
GIT_AUTHOR_DATE="2005-05-26 23:30" \
GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
- h_TEST=$(git rev-parse --verify HEAD)
+ h_TEST=$(git rev-parse --verify HEAD) &&
echo The other day this did not work. >M &&
echo And then Bob told me how to fix it. >>M &&
echo OTHER >F &&
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
index 7fa5f5b..2c96551 100755
--- a/t/t1401-symbolic-ref.sh
+++ b/t/t1401-symbolic-ref.sh
@@ -28,7 +28,7 @@ test_expect_success 'symbolic-ref refuses non-ref for HEAD' '
reset_to_sane
test_expect_success 'symbolic-ref refuses bare sha1' '
- echo content >file && git add file && git commit -m one
+ echo content >file && git add file && git commit -m one &&
test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
'
reset_to_sane
diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh
index eb45afb..1b0f82f 100755
--- a/t/t1402-check-ref-format.sh
+++ b/t/t1402-check-ref-format.sh
@@ -32,7 +32,7 @@ test_expect_success "check-ref-format --branch @{-1}" '
T=$(git write-tree) &&
sha1=$(echo A | git commit-tree $T) &&
git update-ref refs/heads/master $sha1 &&
- git update-ref refs/remotes/origin/master $sha1
+ git update-ref refs/remotes/origin/master $sha1 &&
git checkout master &&
git checkout origin/master &&
git checkout master &&
@@ -41,6 +41,23 @@ test_expect_success "check-ref-format --branch @{-1}" '
refname2=$(git check-ref-format --branch @{-2}) &&
test "$refname2" = master'
+test_expect_success 'check-ref-format --branch from subdir' '
+ mkdir subdir &&
+
+ T=$(git write-tree) &&
+ sha1=$(echo A | git commit-tree $T) &&
+ git update-ref refs/heads/master $sha1 &&
+ git update-ref refs/remotes/origin/master $sha1 &&
+ git checkout master &&
+ git checkout origin/master &&
+ git checkout master &&
+ refname=$(
+ cd subdir &&
+ git check-ref-format --branch @{-1}
+ ) &&
+ test "$refname" = "$sha1"
+'
+
valid_ref_normalized() {
test_expect_success "ref name '$1' simplifies to '$2'" "
refname=\$(git check-ref-format --print '$1') &&
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 25046c4..252fc82 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -186,8 +186,8 @@ test_expect_success 'delete' '
test_tick &&
git commit -m tiger C &&
- HEAD_entry_count=$(git reflog | wc -l)
- master_entry_count=$(git reflog show master | wc -l)
+ HEAD_entry_count=$(git reflog | wc -l) &&
+ master_entry_count=$(git reflog show master | wc -l) &&
test $HEAD_entry_count = 5 &&
test $master_entry_count = 5 &&
@@ -199,13 +199,13 @@ test_expect_success 'delete' '
test $HEAD_entry_count = $(git reflog | wc -l) &&
! grep ox < output &&
- master_entry_count=$(wc -l < output)
+ master_entry_count=$(wc -l < output) &&
git reflog delete HEAD@{1} &&
test $(($HEAD_entry_count -1)) = $(git reflog | wc -l) &&
test $master_entry_count = $(git reflog show master | wc -l) &&
- HEAD_entry_count=$(git reflog | wc -l)
+ HEAD_entry_count=$(git reflog | wc -l) &&
git reflog delete master@{07.04.2005.15:15:00.-0700} &&
git reflog show master > output &&
diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh
index ba25ff3..caa687b 100755
--- a/t/t1411-reflog-show.sh
+++ b/t/t1411-reflog-show.sh
@@ -28,6 +28,24 @@ test_expect_success 'oneline reflog format' '
test_cmp expect actual
'
+test_expect_success 'reflog default format' '
+ git reflog -1 >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+commit e46513e
+Reflog: HEAD@{0} (C O Mitter <committer@example.com>)
+Reflog message: commit (initial): one
+Author: A U Thor <author@example.com>
+
+ one
+EOF
+test_expect_success 'override reflog default format' '
+ git reflog --format=short -1 >actual &&
+ test_cmp expect actual
+'
+
cat >expect <<'EOF'
Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter <committer@example.com>)
Reflog message: commit (initial): one
diff --git a/t/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh
new file mode 100755
index 0000000..7f519e5
--- /dev/null
+++ b/t/t1412-reflog-loop.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='reflog walk shows repeated commits again'
+. ./test-lib.sh
+
+test_expect_success 'setup commits' '
+ test_tick &&
+ echo content >file && git add file && git commit -m one &&
+ git tag one &&
+ echo content >>file && git add file && git commit -m two &&
+ git tag two
+'
+
+test_expect_success 'setup reflog with alternating commits' '
+ git checkout -b topic &&
+ git reset one &&
+ git reset two &&
+ git reset one &&
+ git reset two
+'
+
+test_expect_success 'reflog shows all entries' '
+ cat >expect <<-\EOF
+ topic@{0} two: updating HEAD
+ topic@{1} one: updating HEAD
+ topic@{2} two: updating HEAD
+ topic@{3} one: updating HEAD
+ topic@{4} branch: Created from HEAD
+ EOF
+ git log -g --format="%gd %gs" topic >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 759cf12..bb01d5a 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -1,21 +1,23 @@
#!/bin/sh
-test_description='git fsck random collection of tests'
+test_description='git fsck random collection of tests
+
+* (HEAD) B
+* (master) A
+'
. ./test-lib.sh
test_expect_success setup '
+ git config gc.auto 0 &&
git config i18n.commitencoding ISO-8859-1 &&
test_commit A fileA one &&
git config --unset i18n.commitencoding &&
git checkout HEAD^0 &&
test_commit B fileB two &&
git tag -d A B &&
- git reflog expire --expire=now --all
-'
-
-test_expect_success 'HEAD is part of refs' '
- test 0 = $(git fsck | wc -l)
+ git reflog expire --expire=now --all &&
+ >empty
'
test_expect_success 'loose objects borrowed from alternate are not missing' '
@@ -25,110 +27,132 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
git init &&
echo ../../../.git/objects >.git/objects/info/alternates &&
test_commit C fileC one &&
- git fsck >out &&
- ! grep "missing blob" out
- )
+ git fsck >../out 2>&1
+ ) &&
+ {
+ grep -v dangling out >actual ||
+ :
+ } &&
+ test_cmp empty actual
'
-test_expect_success 'valid objects appear valid' '
- { git fsck 2>out; true; } &&
- ! grep error out &&
- ! grep fatal out
+test_expect_success 'HEAD is part of refs, valid objects appear valid' '
+ git fsck >actual 2>&1 &&
+ test_cmp empty actual
'
# Corruption tests follow. Make sure to remove all traces of the
# specific corruption you test afterwards, lest a later test trip over
# it.
+test_expect_success 'setup: helpers for corruption tests' '
+ sha1_file() {
+ echo "$*" | sed "s#..#.git/objects/&/#"
+ } &&
+
+ remove_object() {
+ file=$(sha1_file "$*") &&
+ test -e "$file" &&
+ rm -f "$file"
+ }
+'
+
test_expect_success 'object with bad sha1' '
sha=$(echo blob | git hash-object -w --stdin) &&
- echo $sha &&
old=$(echo $sha | sed "s+^..+&/+") &&
new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
- sha="$(dirname $new)$(basename $new)"
+ sha="$(dirname $new)$(basename $new)" &&
mv .git/objects/$old .git/objects/$new &&
+ test_when_finished "remove_object $sha" &&
git update-index --add --cacheinfo 100644 $sha foo &&
+ test_when_finished "git read-tree -u --reset HEAD" &&
tree=$(git write-tree) &&
+ test_when_finished "remove_object $tree" &&
cmt=$(echo bogus | git commit-tree $tree) &&
+ test_when_finished "remove_object $cmt" &&
git update-ref refs/heads/bogus $cmt &&
- (git fsck 2>out; true) &&
- grep "$sha.*corrupt" out &&
- rm -f .git/objects/$new &&
- git update-ref -d refs/heads/bogus &&
- git read-tree -u --reset HEAD
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+
+ test_might_fail git fsck 2>out &&
+ cat out &&
+ grep "$sha.*corrupt" out
'
test_expect_success 'branch pointing to non-commit' '
- git rev-parse HEAD^{tree} > .git/refs/heads/invalid &&
+ git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
+ test_when_finished "git update-ref -d refs/heads/invalid" &&
git fsck 2>out &&
- grep "not a commit" out &&
- git update-ref -d refs/heads/invalid
+ cat out &&
+ grep "not a commit" out
'
-new=nothing
test_expect_success 'email without @ is okay' '
git cat-file commit HEAD >basis &&
sed "s/@/AT/" basis >okay &&
new=$(git hash-object -t commit -w --stdin <okay) &&
- echo "$new" &&
+ test_when_finished "remove_object $new" &&
git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
git fsck 2>out &&
cat out &&
- ! grep "error in commit $new" out
+ ! grep "commit $new" out
'
-git update-ref -d refs/heads/bogus
-rm -f ".git/objects/$new"
-new=nothing
test_expect_success 'email with embedded > is not okay' '
git cat-file commit HEAD >basis &&
sed "s/@[a-z]/&>/" basis >bad-email &&
new=$(git hash-object -t commit -w --stdin <bad-email) &&
- echo "$new" &&
+ test_when_finished "remove_object $new" &&
git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
git fsck 2>out &&
cat out &&
grep "error in commit $new" out
'
-git update-ref -d refs/heads/bogus
-rm -f ".git/objects/$new"
-
-cat > invalid-tag <<EOF
-object ffffffffffffffffffffffffffffffffffffffff
-type commit
-tag invalid
-tagger T A Gger <tagger@example.com> 1234567890 -0000
-
-This is an invalid tag.
-EOF
test_expect_success 'tag pointing to nonexistent' '
- tag=$(git hash-object -t tag -w --stdin < invalid-tag) &&
- echo $tag > .git/refs/tags/invalid &&
+ cat >invalid-tag <<-\EOF &&
+ object ffffffffffffffffffffffffffffffffffffffff
+ type commit
+ tag invalid
+ tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+ This is an invalid tag.
+ EOF
+
+ tag=$(git hash-object -t tag -w --stdin <invalid-tag) &&
+ test_when_finished "remove_object $tag" &&
+ echo $tag >.git/refs/tags/invalid &&
+ test_when_finished "git update-ref -d refs/tags/invalid" &&
test_must_fail git fsck --tags >out &&
cat out &&
- grep "broken link" out &&
- rm .git/refs/tags/invalid
+ grep "broken link" out
'
-cat > wrong-tag <<EOF
-object $(echo blob | git hash-object -w --stdin)
-type commit
-tag wrong
-tagger T A Gger <tagger@example.com> 1234567890 -0000
-
-This is an invalid tag.
-EOF
-
test_expect_success 'tag pointing to something else than its type' '
- tag=$(git hash-object -t tag -w --stdin < wrong-tag) &&
- echo $tag > .git/refs/tags/wrong &&
+ sha=$(echo blob | git hash-object -w --stdin) &&
+ test_when_finished "remove_object $sha" &&
+ cat >wrong-tag <<-EOF &&
+ object $sha
+ type commit
+ tag wrong
+ tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+ This is an invalid tag.
+ EOF
+
+ tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
+ test_when_finished "remove_object $tag" &&
+ echo $tag >.git/refs/tags/wrong &&
+ test_when_finished "git update-ref -d refs/tags/wrong" &&
test_must_fail git fsck --tags 2>out &&
cat out &&
- grep "error in tag.*broken links" out &&
- rm .git/refs/tags/wrong
+ grep "error in tag.*broken links" out
'
-
+test_expect_success 'cleaned up' '
+ git fsck >actual 2>&1 &&
+ test_cmp empty actual
+'
test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index bd8b607..6384983 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -3,183 +3,319 @@
test_description='test separate work tree'
. ./test-lib.sh
-test_rev_parse() {
- name=$1
- shift
-
- test_expect_success "$name: is-bare-repository" \
- "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
- shift
- [ $# -eq 0 ] && return
-
- test_expect_success "$name: is-inside-git-dir" \
- "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
- shift
- [ $# -eq 0 ] && return
-
- test_expect_success "$name: is-inside-work-tree" \
- "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
- shift
- [ $# -eq 0 ] && return
-
- test_expect_success "$name: prefix" \
- "test '$1' = \"\$(git rev-parse --show-prefix)\""
- shift
- [ $# -eq 0 ] && return
-}
-
-EMPTY_TREE=$(git write-tree)
-mkdir -p work/sub/dir || exit 1
-mkdir -p work2 || exit 1
-mv .git repo.git || exit 1
-
-say "core.worktree = relative path"
-GIT_DIR=repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-export GIT_DIR GIT_CONFIG
-unset GIT_WORK_TREE
-git config core.worktree ../work
-test_rev_parse 'outside' false false false
-cd work || exit 1
-GIT_DIR=../repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-test_rev_parse 'inside' false false true ''
-cd sub/dir || exit 1
-GIT_DIR=../../../repo.git
-GIT_CONFIG="$(pwd)"/$GIT_DIR/config
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-say "core.worktree = absolute path"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-git config core.worktree "$(pwd)/work"
-test_rev_parse 'outside' false false false
-cd work2
-test_rev_parse 'outside2' false false false
-cd ../work || exit 1
-test_rev_parse 'inside' false false true ''
-cd sub/dir || exit 1
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-say "GIT_WORK_TREE=relative path (override core.worktree)"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-git config core.worktree non-existent
-GIT_WORK_TREE=work
-export GIT_WORK_TREE
-test_rev_parse 'outside' false false false
-cd work2
-test_rev_parse 'outside' false false false
-cd ../work || exit 1
-GIT_WORK_TREE=.
-test_rev_parse 'inside' false false true ''
-cd sub/dir || exit 1
-GIT_WORK_TREE=../..
-test_rev_parse 'subdirectory' false false true sub/dir/
-cd ../../.. || exit 1
-
-mv work repo.git/work
-mv work2 repo.git/work2
-
-say "GIT_WORK_TREE=absolute path, work tree below git dir"
-GIT_DIR=$(pwd)/repo.git
-GIT_CONFIG=$GIT_DIR/config
-GIT_WORK_TREE=$(pwd)/repo.git/work
-test_rev_parse 'outside' false false false
-cd repo.git || exit 1
-test_rev_parse 'in repo.git' false true false
-cd objects || exit 1
-test_rev_parse 'in repo.git/objects' false true false
-cd ../work2 || exit 1
-test_rev_parse 'in repo.git/work2' false true false
-cd ../work || exit 1
-test_rev_parse 'in repo.git/work' false true true ''
-cd sub/dir || exit 1
-test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
-cd ../../../.. || exit 1
-
-test_expect_success 'repo finds its work tree' '
- (cd repo.git &&
- : > work/sub/dir/untracked &&
- test sub/dir/untracked = "$(git ls-files --others)")
-'
-
-test_expect_success 'repo finds its work tree from work tree, too' '
- (cd repo.git/work/sub/dir &&
- : > tracked &&
- git --git-dir=../../.. add tracked &&
- cd ../../.. &&
- test sub/dir/tracked = "$(git ls-files)")
+test_expect_success 'setup' '
+ EMPTY_TREE=$(git write-tree) &&
+ EMPTY_BLOB=$(git hash-object -t blob --stdin </dev/null) &&
+ CHANGED_BLOB=$(echo changed | git hash-object -t blob --stdin) &&
+ EMPTY_BLOB7=$(echo $EMPTY_BLOB | sed "s/\(.......\).*/\1/") &&
+ CHANGED_BLOB7=$(echo $CHANGED_BLOB | sed "s/\(.......\).*/\1/") &&
+
+ mkdir -p work/sub/dir &&
+ mkdir -p work2 &&
+ mv .git repo.git
+'
+
+test_expect_success 'setup: helper for testing rev-parse' '
+ test_rev_parse() {
+ echo $1 >expected.bare &&
+ echo $2 >expected.inside-git &&
+ echo $3 >expected.inside-worktree &&
+ if test $# -ge 4
+ then
+ echo $4 >expected.prefix
+ fi &&
+
+ git rev-parse --is-bare-repository >actual.bare &&
+ git rev-parse --is-inside-git-dir >actual.inside-git &&
+ git rev-parse --is-inside-work-tree >actual.inside-worktree &&
+ if test $# -ge 4
+ then
+ git rev-parse --show-prefix >actual.prefix
+ fi &&
+
+ test_cmp expected.bare actual.bare &&
+ test_cmp expected.inside-git actual.inside-git &&
+ test_cmp expected.inside-worktree actual.inside-worktree &&
+ if test $# -ge 4
+ then
+ # rev-parse --show-prefix should output
+ # a single newline when at the top of the work tree,
+ # but we test for that separately.
+ test -z "$4" && ! test -s actual.prefix ||
+ test_cmp expected.prefix actual.prefix
+ fi
+ }
+'
+
+test_expect_success 'setup: core.worktree = relative path' '
+ unset GIT_WORK_TREE;
+ GIT_DIR=repo.git &&
+ GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+ export GIT_DIR GIT_CONFIG &&
+ git config core.worktree ../work
+'
+
+test_expect_success 'outside' '
+ test_rev_parse false false false
+'
+
+test_expect_success 'inside work tree' '
+ (
+ cd work &&
+ GIT_DIR=../repo.git &&
+ GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+ test_rev_parse false false true ""
+ )
+'
+
+test_expect_failure 'empty prefix is actually written out' '
+ echo >expected &&
+ (
+ cd work &&
+ GIT_DIR=../repo.git &&
+ GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+ git rev-parse --show-prefix >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'subdir of work tree' '
+ (
+ cd work/sub/dir &&
+ GIT_DIR=../../../repo.git &&
+ GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
+ test_rev_parse false false true sub/dir/
+ )
+'
+
+test_expect_success 'setup: core.worktree = absolute path' '
+ unset GIT_WORK_TREE;
+ GIT_DIR=$(pwd)/repo.git &&
+ GIT_CONFIG=$GIT_DIR/config &&
+ export GIT_DIR GIT_CONFIG &&
+ git config core.worktree "$(pwd)/work"
+'
+
+test_expect_success 'outside' '
+ test_rev_parse false false false &&
+ (
+ cd work2 &&
+ test_rev_parse false false false
+ )
+'
+
+test_expect_success 'inside work tree' '
+ (
+ cd work &&
+ test_rev_parse false false true ""
+ )
+'
+
+test_expect_success 'subdir of work tree' '
+ (
+ cd work/sub/dir &&
+ test_rev_parse false false true sub/dir/
+ )
+'
+
+test_expect_success 'setup: GIT_WORK_TREE=relative (override core.worktree)' '
+ GIT_DIR=$(pwd)/repo.git &&
+ GIT_CONFIG=$GIT_DIR/config &&
+ git config core.worktree non-existent &&
+ GIT_WORK_TREE=work &&
+ export GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'outside' '
+ test_rev_parse false false false &&
+ (
+ cd work2 &&
+ test_rev_parse false false false
+ )
+'
+
+test_expect_success 'inside work tree' '
+ (
+ cd work &&
+ GIT_WORK_TREE=. &&
+ test_rev_parse false false true ""
+ )
+'
+
+test_expect_success 'subdir of work tree' '
+ (
+ cd work/sub/dir &&
+ GIT_WORK_TREE=../.. &&
+ test_rev_parse false false true sub/dir/
+ )
+'
+
+test_expect_success 'setup: GIT_WORK_TREE=absolute, below git dir' '
+ mv work repo.git/work &&
+ mv work2 repo.git/work2 &&
+ GIT_DIR=$(pwd)/repo.git &&
+ GIT_CONFIG=$GIT_DIR/config &&
+ GIT_WORK_TREE=$(pwd)/repo.git/work &&
+ export GIT_DIR GIT_CONFIG GIT_WORK_TREE
+'
+
+test_expect_success 'outside' '
+ echo outside &&
+ test_rev_parse false false false
+'
+
+test_expect_success 'in repo.git' '
+ (
+ cd repo.git &&
+ test_rev_parse false true false
+ ) &&
+ (
+ cd repo.git/objects &&
+ test_rev_parse false true false
+ ) &&
+ (
+ cd repo.git/work2 &&
+ test_rev_parse false true false
+ )
+'
+
+test_expect_success 'inside work tree' '
+ (
+ cd repo.git/work &&
+ test_rev_parse false true true ""
+ )
+'
+
+test_expect_success 'subdir of work tree' '
+ (
+ cd repo.git/work/sub/dir &&
+ test_rev_parse false true true sub/dir/
+ )
+'
+
+test_expect_success 'find work tree from repo' '
+ echo sub/dir/untracked >expected &&
+ cat <<-\EOF >repo.git/work/.gitignore &&
+ expected.*
+ actual.*
+ .gitignore
+ EOF
+ >repo.git/work/sub/dir/untracked &&
+ (
+ cd repo.git &&
+ git ls-files --others --exclude-standard >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'find work tree from work tree' '
+ echo sub/dir/tracked >expected &&
+ >repo.git/work/sub/dir/tracked &&
+ (
+ cd repo.git/work/sub/dir &&
+ git --git-dir=../../.. add tracked
+ ) &&
+ (
+ cd repo.git &&
+ git ls-files >../actual
+ ) &&
+ test_cmp expected actual
'
test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' '
- (cd repo.git/work/sub/dir &&
- GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \
+ (
+ cd repo.git/work/sub/dir &&
+ GIT_DIR=../../.. &&
+ GIT_WORK_TREE=../.. &&
+ GIT_PAGER= &&
+ export GIT_DIR GIT_WORK_TREE GIT_PAGER &&
+
git diff --exit-code tracked &&
- echo changed > tracked &&
- ! GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \
- git diff --exit-code tracked)
-'
-cat > diff-index-cached.expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 A sub/dir/tracked
-EOF
-cat > diff-index.expected <<\EOF
-:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A sub/dir/tracked
-EOF
-
-
-test_expect_success 'git diff-index' '
- GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-index $EMPTY_TREE > result &&
- test_cmp diff-index.expected result &&
- GIT_DIR=repo.git git diff-index --cached $EMPTY_TREE > result &&
- test_cmp diff-index-cached.expected result
-'
-cat >diff-files.expected <<\EOF
-:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M sub/dir/tracked
-EOF
-
-test_expect_success 'git diff-files' '
- GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-files > result &&
- test_cmp diff-files.expected result
-'
-
-cat >diff-TREE.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-new file mode 100644
-index 0000000..5ea2ed4
---- /dev/null
-+++ b/sub/dir/tracked
-@@ -0,0 +1 @@
-+changed
-EOF
-cat >diff-TREE-cached.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-new file mode 100644
-index 0000000..e69de29
-EOF
-cat >diff-FILES.expected <<\EOF
-diff --git a/sub/dir/tracked b/sub/dir/tracked
-index e69de29..5ea2ed4 100644
---- a/sub/dir/tracked
-+++ b/sub/dir/tracked
-@@ -0,0 +1 @@
-+changed
-EOF
-
-test_expect_success 'git diff' '
- GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff $EMPTY_TREE > result &&
- test_cmp diff-TREE.expected result &&
- GIT_DIR=repo.git git diff --cached $EMPTY_TREE > result &&
- test_cmp diff-TREE-cached.expected result &&
- GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff > result &&
- test_cmp diff-FILES.expected result
+ echo changed >tracked &&
+ test_must_fail git diff --exit-code tracked
+ )
+'
+
+test_expect_success 'diff-index respects work tree under .git dir' '
+ cat >diff-index-cached.expected <<-EOF &&
+ :000000 100644 $_z40 $EMPTY_BLOB A sub/dir/tracked
+ EOF
+ cat >diff-index.expected <<-EOF &&
+ :000000 100644 $_z40 $_z40 A sub/dir/tracked
+ EOF
+
+ (
+ GIT_DIR=repo.git &&
+ GIT_WORK_TREE=repo.git/work &&
+ export GIT_DIR GIT_WORK_TREE &&
+ git diff-index $EMPTY_TREE >diff-index.actual &&
+ git diff-index --cached $EMPTY_TREE >diff-index-cached.actual
+ ) &&
+ test_cmp diff-index.expected diff-index.actual &&
+ test_cmp diff-index-cached.expected diff-index-cached.actual
+'
+
+test_expect_success 'diff-files respects work tree under .git dir' '
+ cat >diff-files.expected <<-EOF &&
+ :100644 100644 $EMPTY_BLOB $_z40 M sub/dir/tracked
+ EOF
+
+ (
+ GIT_DIR=repo.git &&
+ GIT_WORK_TREE=repo.git/work &&
+ export GIT_DIR GIT_WORK_TREE &&
+ git diff-files >diff-files.actual
+ ) &&
+ test_cmp diff-files.expected diff-files.actual
+'
+
+test_expect_success 'git diff respects work tree under .git dir' '
+ cat >diff-TREE.expected <<-EOF &&
+ diff --git a/sub/dir/tracked b/sub/dir/tracked
+ new file mode 100644
+ index 0000000..$CHANGED_BLOB7
+ --- /dev/null
+ +++ b/sub/dir/tracked
+ @@ -0,0 +1 @@
+ +changed
+ EOF
+ cat >diff-TREE-cached.expected <<-EOF &&
+ diff --git a/sub/dir/tracked b/sub/dir/tracked
+ new file mode 100644
+ index 0000000..$EMPTY_BLOB7
+ EOF
+ cat >diff-FILES.expected <<-EOF &&
+ diff --git a/sub/dir/tracked b/sub/dir/tracked
+ index $EMPTY_BLOB7..$CHANGED_BLOB7 100644
+ --- a/sub/dir/tracked
+ +++ b/sub/dir/tracked
+ @@ -0,0 +1 @@
+ +changed
+ EOF
+
+ (
+ GIT_DIR=repo.git &&
+ GIT_WORK_TREE=repo.git/work &&
+ export GIT_DIR GIT_WORK_TREE &&
+ git diff $EMPTY_TREE >diff-TREE.actual &&
+ git diff --cached $EMPTY_TREE >diff-TREE-cached.actual &&
+ git diff >diff-FILES.actual
+ ) &&
+ test_cmp diff-TREE.expected diff-TREE.actual &&
+ test_cmp diff-TREE-cached.expected diff-TREE-cached.actual &&
+ test_cmp diff-FILES.expected diff-FILES.actual
'
test_expect_success 'git grep' '
- (cd repo.git/work/sub &&
- GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked)
+ echo dir/tracked >expected.grep &&
+ (
+ cd repo.git/work/sub &&
+ GIT_DIR=../.. &&
+ GIT_WORK_TREE=.. &&
+ export GIT_DIR GIT_WORK_TREE &&
+ git grep -l changed >../../../actual.grep
+ ) &&
+ test_cmp expected.grep actual.grep
'
test_expect_success 'git commit' '
@@ -191,16 +327,23 @@ test_expect_success 'git commit' '
test_expect_success 'absolute pathspec should fail gracefully' '
(
- cd repo.git || exit 1
- git config --unset core.worktree
+ cd repo.git &&
+ test_might_fail git config --unset core.worktree &&
test_must_fail git log HEAD -- /home
)
'
test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
- : > dummy_file
+ >dummy_file
echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
'
+test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
+ GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \
+ test-subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
+ echo "$(pwd)/repo.git/work" >expected &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
index 4346795..1efd7f7 100755
--- a/t/t1502-rev-parse-parseopt.sh
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -40,7 +40,7 @@ extra1 line above used to cause a segfault but no longer does
EOF
test_expect_success 'test --parseopt help output' '
- git rev-parse --parseopt -- -h > output < optionspec
+ test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec &&
test_cmp expect output
'
@@ -81,4 +81,22 @@ test_expect_success 'test --parseopt --keep-dashdash' '
test_cmp expect output
'
+cat >expect <<EOF
+set -- --foo -- '--' 'arg' '--spam=ham'
+EOF
+
+test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option with --' '
+ git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo -- arg --spam=ham <optionspec >output &&
+ test_cmp expect output
+'
+
+cat > expect <<EOF
+set -- --foo -- 'arg' '--spam=ham'
+EOF
+
+test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option without --' '
+ git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo arg --spam=ham <optionspec >output &&
+ test_cmp expect output
+'
+
test_done
diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh
index cc65394..813cc1b 100755
--- a/t/t1503-rev-parse-verify.sh
+++ b/t/t1503-rev-parse-verify.sh
@@ -104,4 +104,15 @@ test_expect_success 'use --default' '
test_must_fail git rev-parse --verify --default bar
'
+test_expect_success 'master@{n} for various n' '
+ N=$(git reflog | wc -l) &&
+ Nm1=$(($N-1)) &&
+ Np1=$(($N+1)) &&
+ git rev-parse --verify master@{0} &&
+ git rev-parse --verify master@{1} &&
+ git rev-parse --verify master@{$Nm1} &&
+ test_must_fail git rev-parse --verify master@{$N} &&
+ test_must_fail git rev-parse --verify master@{$Np1}
+'
+
test_done
diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh
index df5ad8c..cce87a5 100755
--- a/t/t1504-ceiling-dirs.sh
+++ b/t/t1504-ceiling-dirs.sh
@@ -9,8 +9,9 @@ test_prefix() {
}
test_fail() {
- test_expect_code 128 "$1: prefix" \
- "git rev-parse --show-prefix"
+ test_expect_success "$1: prefix" '
+ test_expect_code 128 git rev-parse --show-prefix
+ '
}
TRASH_ROOT="$PWD"
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index af721f9..0843a1c 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -6,6 +6,16 @@ exec </dev/null
. ./test-lib.sh
+test_did_you_mean ()
+{
+ sq="'" &&
+ cat >expected <<-EOF &&
+ fatal: Path '$2$3' $4, but not ${5:-$sq$3$sq}.
+ Did you mean '$1:$2$3'${2:+ aka $sq$1:./$3$sq}?
+ EOF
+ test_cmp expected error
+}
+
HASH_file=
test_expect_success 'set up basic repo' '
@@ -31,6 +41,67 @@ test_expect_success 'correct file objects' '
test $HASH_file = $(git rev-parse :0:file.txt) )
'
+test_expect_success 'correct relative file objects (0)' '
+ git rev-parse :file.txt >expected &&
+ git rev-parse :./file.txt >result &&
+ test_cmp expected result &&
+ git rev-parse :0:./file.txt >result &&
+ test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (1)' '
+ git rev-parse HEAD:file.txt >expected &&
+ git rev-parse HEAD:./file.txt >result &&
+ test_cmp expected result
+'
+
+test_expect_success 'correct relative file objects (2)' '
+ (
+ cd subdir &&
+ git rev-parse HEAD:../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (3)' '
+ (
+ cd subdir &&
+ git rev-parse HEAD:../subdir/../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (4)' '
+ git rev-parse HEAD:subdir/file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse HEAD:./file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (5)' '
+ git rev-parse :subdir/file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse :./file.txt >result &&
+ test_cmp ../expected result &&
+ git rev-parse :0:./file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
+test_expect_success 'correct relative file objects (6)' '
+ git rev-parse :file.txt >expected &&
+ (
+ cd subdir &&
+ git rev-parse :../file.txt >result &&
+ test_cmp ../expected result &&
+ git rev-parse :0:../file.txt >result &&
+ test_cmp ../expected result
+ )
+'
+
test_expect_success 'incorrect revision id' '
test_must_fail git rev-parse foobar:file.txt 2>error &&
grep "Invalid object name '"'"'foobar'"'"'." error &&
@@ -45,7 +116,7 @@ test_expect_success 'incorrect file in sha1:path' '
grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
(cd subdir &&
test_must_fail git rev-parse HEAD:file2.txt 2> error &&
- grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error )
+ test_did_you_mean HEAD subdir/ file2.txt exists )
'
test_expect_success 'incorrect file in :path and :N:path' '
@@ -54,16 +125,50 @@ test_expect_success 'incorrect file in :path and :N:path' '
test_must_fail git rev-parse :1:nothing.txt 2> error &&
grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
test_must_fail git rev-parse :1:file.txt 2> error &&
- grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+ test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
(cd subdir &&
test_must_fail git rev-parse :1:file.txt 2> error &&
- grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+ test_did_you_mean ":0" "" file.txt "is in the index" "at stage 1" &&
test_must_fail git rev-parse :file2.txt 2> error &&
- grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error &&
+ test_did_you_mean ":0" subdir/ file2.txt "is in the index" &&
test_must_fail git rev-parse :2:file2.txt 2> error &&
- grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) &&
+ test_did_you_mean :0 subdir/ file2.txt "is in the index") &&
test_must_fail git rev-parse :disk-only.txt 2> error &&
grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
'
+test_expect_success 'invalid @{n} reference' '
+ test_must_fail git rev-parse master@{99999} >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error &&
+ test_must_fail git rev-parse --verify master@{99999} >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error
+'
+
+test_expect_success 'relative path not found' '
+ (
+ cd subdir &&
+ test_must_fail git rev-parse HEAD:./nonexistent.txt 2>error &&
+ grep subdir/nonexistent.txt error
+ )
+'
+
+test_expect_success 'relative path outside worktree' '
+ test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "outside repository" error
+'
+
+test_expect_success 'relative path when cwd is outside worktree' '
+ test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error &&
+ test -z "$(cat output)" &&
+ grep "relative path syntax can.t be used outside working tree." error
+'
+
+test_expect_success 'relative path when startup_info is NULL' '
+ test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
+ grep "BUG: startup_info struct is not initialized." error
+'
+
test_done
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index 8c8dfda..a455551 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -85,7 +85,7 @@ test_expect_success 'merge my-side@{u} records the correct name' '
git branch -t new my-side@{u} &&
git merge -s ours new@{u} &&
git show -s --pretty=format:%s >actual &&
- echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+ echo "Merge remote-tracking branch ${sq}origin/side${sq}" >expect &&
test_cmp expect actual
)
'
diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-worktree.sh
index 5322a3b..335420f 100755
--- a/t/t1509-root-worktree.sh
+++ b/t/t1509-root-worktree.sh
@@ -99,17 +99,17 @@ test_foobar_foobar() {
}
if ! test_have_prereq POSIXPERM || ! [ -w / ]; then
- say "Dangerous test skipped. Read this test if you want to execute it"
+ skip_all="Dangerous test skipped. Read this test if you want to execute it"
test_done
fi
if [ "$IKNOWWHATIAMDOING" != "YES" ]; then
- say "You must set env var IKNOWWHATIAMDOING=YES in order to run this test"
+ skip_all="You must set env var IKNOWWHATIAMDOING=YES in order to run this test"
test_done
fi
if [ "$UID" = 0 ]; then
- say "No you can't run this with root"
+ skip_all="No you can't run this with root"
test_done
fi
@@ -134,8 +134,8 @@ cat >ls.expected <<EOF
100644 $ONE_SHA1 0 me
EOF
-export GIT_DIR="$TRASH_DIRECTORY/.git"
-export GIT_WORK_TREE=/
+GIT_DIR="$TRASH_DIRECTORY/.git" && export GIT_DIR
+GIT_WORK_TREE=/ && export GIT_WORK_TREE
test_vars 'abs gitdir, root' "$GIT_DIR" "/" ""
test_foobar_root
@@ -154,24 +154,24 @@ say "GIT_DIR relative, GIT_WORK_TREE set"
test_expect_success 'go to /' 'cd /'
-export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
-export GIT_WORK_TREE=/
+GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" && export GIT_DIR
+GIT_WORK_TREE=/ && export GIT_WORK_TREE
test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
test_foobar_root
test_expect_success 'go to /foo' 'cd /foo'
-export GIT_DIR="../$TRASH_DIRECTORY/.git"
-export GIT_WORK_TREE=/
+GIT_DIR="../$TRASH_DIRECTORY/.git" && export GIT_DIR
+GIT_WORK_TREE=/ && export GIT_WORK_TREE
test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
test_foobar_foo
test_expect_success 'go to /foo/bar' 'cd /foo/bar'
-export GIT_DIR="../../$TRASH_DIRECTORY/.git"
-export GIT_WORK_TREE=/
+GIT_DIR="../../$TRASH_DIRECTORY/.git" && export GIT_DIR
+GIT_WORK_TREE=/ && export GIT_WORK_TREE
test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
test_foobar_foobar
@@ -180,24 +180,24 @@ say "GIT_DIR relative, GIT_WORK_TREE relative"
test_expect_success 'go to /' 'cd /'
-export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
-export GIT_WORK_TREE=.
+GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git" && export GIT_DIR
+GIT_WORK_TREE=. && export GIT_WORK_TREE
test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
test_foobar_root
test_expect_success 'go to /' 'cd /foo'
-export GIT_DIR="../$TRASH_DIRECTORY/.git"
-export GIT_WORK_TREE=..
+GIT_DIR="../$TRASH_DIRECTORY/.git" && export GIT_DIR
+GIT_WORK_TREE=.. && export GIT_WORK_TREE
test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
test_foobar_foo
test_expect_success 'go to /foo/bar' 'cd /foo/bar'
-export GIT_DIR="../../$TRASH_DIRECTORY/.git"
-export GIT_WORK_TREE=../..
+GIT_DIR="../../$TRASH_DIRECTORY/.git" && export GIT_DIR
+GIT_WORK_TREE=../.. && export GIT_WORK_TREE
test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
test_foobar_foobar
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
new file mode 100755
index 0000000..ec50a9a
--- /dev/null
+++ b/t/t1510-repo-setup.sh
@@ -0,0 +1,776 @@
+#!/bin/sh
+
+test_description="Tests of cwd/prefix/worktree/gitdir setup in all cases
+
+A few rules for repo setup:
+
+1. GIT_DIR is relative to user's cwd. --git-dir is equivalent to
+ GIT_DIR.
+
+2. .git file is relative to parent directory. .git file is basically
+ symlink in disguise. The directory where .git file points to will
+ become new git_dir.
+
+3. core.worktree is relative to git_dir.
+
+4. GIT_WORK_TREE is relative to user's cwd. --work-tree is
+ equivalent to GIT_WORK_TREE.
+
+5. GIT_WORK_TREE/core.worktree was originally meant to work only if
+ GIT_DIR is set, but earlier git didn't enforce it, and some scripts
+ depend on the implementation that happened to first discover .git by
+ going up from the users $cwd and then using the specified working tree
+ that may or may not have any relation to where .git was found in. This
+ historical behaviour must be kept.
+
+6. Effective GIT_WORK_TREE overrides core.worktree and core.bare
+
+7. Effective core.worktree conflicts with core.bare
+
+8. If GIT_DIR is set but neither worktree nor bare setting is given,
+ original cwd becomes worktree.
+
+9. If .git discovery is done inside a repo, the repo becomes a bare
+ repo. .git discovery is performed if GIT_DIR is not set.
+
+10. If no worktree is available, cwd remains unchanged, prefix is
+ NULL.
+
+11. When user's cwd is outside worktree, cwd remains unchanged,
+ prefix is NULL.
+"
+. ./test-lib.sh
+
+here=$(pwd)
+
+test_repo () {
+ (
+ cd "$1" &&
+ if test -n "$2"
+ then
+ GIT_DIR="$2" &&
+ export GIT_DIR
+ fi &&
+ if test -n "$3"
+ then
+ GIT_WORK_TREE="$3" &&
+ export GIT_WORK_TREE
+ fi &&
+ rm -f trace &&
+ GIT_TRACE_SETUP="$(pwd)/trace" git symbolic-ref HEAD >/dev/null &&
+ grep '^setup: ' trace >result &&
+ test_cmp expected result
+ )
+}
+
+maybe_config () {
+ file=$1 var=$2 value=$3 &&
+ if test "$value" != unset
+ then
+ git config --file="$file" "$var" "$value"
+ fi
+}
+
+setup_repo () {
+ name=$1 worktreecfg=$2 gitfile=$3 barecfg=$4 &&
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+
+ git init "$name" &&
+ maybe_config "$name/.git/config" core.worktree "$worktreecfg" &&
+ maybe_config "$name/.git/config" core.bare "$barecfg" &&
+ mkdir -p "$name/sub/sub" &&
+
+ if test "${gitfile:+set}"
+ then
+ mv "$name/.git" "$name.git" &&
+ echo "gitdir: ../$name.git" >"$name/.git"
+ fi
+}
+
+maybe_set () {
+ var=$1 value=$2 &&
+ if test "$value" != unset
+ then
+ eval "$var=\$value" &&
+ export $var
+ fi
+}
+
+setup_env () {
+ worktreenv=$1 gitdirenv=$2 &&
+ sane_unset GIT_DIR GIT_WORK_TREE &&
+ maybe_set GIT_DIR "$gitdirenv" &&
+ maybe_set GIT_WORK_TREE "$worktreeenv"
+}
+
+expect () {
+ cat >"$1/expected" <<-EOF
+ setup: git_dir: $2
+ setup: worktree: $3
+ setup: cwd: $4
+ setup: prefix: $5
+ EOF
+}
+
+try_case () {
+ name=$1 worktreeenv=$2 gitdirenv=$3 &&
+ setup_env "$worktreeenv" "$gitdirenv" &&
+ expect "$name" "$4" "$5" "$6" "$7" &&
+ test_repo "$name"
+}
+
+run_wt_tests () {
+ N=$1 gitfile=$2
+
+ absgit="$here/$N/.git"
+ dotgit=.git
+ dotdotgit=../../.git
+
+ if test "$gitfile"
+ then
+ absgit="$here/$N.git"
+ dotgit=$absgit dotdotgit=$absgit
+ fi
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE and GIT_DIR at toplevel" '
+ try_case $N "$here/$N" .git \
+ "$dotgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N . .git \
+ "$dotgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N "$here/$N" "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" "(null)" &&
+ try_case $N . "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" "(null)"
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE and GIT_DIR in subdir" '
+ try_case $N/sub/sub "$here/$N" ../../.git \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub ../.. ../../.git \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub "$here/$N" "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/ &&
+ try_case $N/sub/sub ../.. "$here/$N/.git" \
+ "$absgit" "$here/$N" "$here/$N" sub/sub/
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE from parent of worktree" '
+ try_case $N "$here/$N/wt" .git \
+ "$dotgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N wt .git \
+ "$dotgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N wt "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N" "(null)" &&
+ try_case $N "$here/$N/wt" "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N" "(null)"
+ '
+
+ test_expect_success "#$N: explicit GIT_WORK_TREE from nephew of worktree" '
+ try_case $N/sub/sub "$here/$N/wt" ../../.git \
+ "$dotdotgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub ../../wt ../../.git \
+ "$dotdotgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub ../../wt "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)" &&
+ try_case $N/sub/sub "$here/$N/wt" "$here/$N/.git" \
+ "$absgit" "$here/$N/wt" "$here/$N/sub/sub" "(null)"
+ '
+
+ test_expect_success "#$N: chdir_to_toplevel uses worktree, not git dir" '
+ try_case $N "$here" .git \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N .. .git \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N .. "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/ &&
+ try_case $N "$here" "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/
+ '
+
+ test_expect_success "#$N: chdir_to_toplevel uses worktree (from subdir)" '
+ try_case $N/sub/sub "$here" ../../.git \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub ../../.. ../../.git \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub ../../../ "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/sub/sub/ &&
+ try_case $N/sub/sub "$here" "$here/$N/.git" \
+ "$absgit" "$here" "$here" $N/sub/sub/
+ '
+}
+
+# try_repo #c GIT_WORK_TREE GIT_DIR core.worktree .gitfile? core.bare \
+# (git dir) (work tree) (cwd) (prefix) \ <-- at toplevel
+# (git dir) (work tree) (cwd) (prefix) <-- from subdir
+try_repo () {
+ name=$1 worktreeenv=$2 gitdirenv=$3 &&
+ setup_repo "$name" "$4" "$5" "$6" &&
+ shift 6 &&
+ try_case "$name" "$worktreeenv" "$gitdirenv" \
+ "$1" "$2" "$3" "$4" &&
+ shift 4 &&
+ case "$gitdirenv" in
+ /* | ?:/* | unset) ;;
+ *)
+ gitdirenv=../$gitdirenv ;;
+ esac &&
+ try_case "$name/sub" "$worktreeenv" "$gitdirenv" \
+ "$1" "$2" "$3" "$4"
+}
+
+# Bit 0 = GIT_WORK_TREE
+# Bit 1 = GIT_DIR
+# Bit 2 = core.worktree
+# Bit 3 = .git is a file
+# Bit 4 = bare repo
+# Case# = encoding of the above 5 bits
+
+test_expect_success '#0: nonbare repo, no explicit configuration' '
+ try_repo 0 unset unset unset "" unset \
+ .git "$here/0" "$here/0" "(null)" \
+ .git "$here/0" "$here/0" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' '
+ mkdir -p wt &&
+ try_repo 1 "$here" unset unset "" unset \
+ "$here/1/.git" "$here" "$here" 1/ \
+ "$here/1/.git" "$here" "$here" 1/sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#2: worktree defaults to cwd with explicit GIT_DIR' '
+ try_repo 2 unset "$here/2/.git" unset "" unset \
+ "$here/2/.git" "$here/2" "$here/2" "(null)" \
+ "$here/2/.git" "$here/2/sub" "$here/2/sub" "(null)"
+'
+
+test_expect_success '#2b: relative GIT_DIR' '
+ try_repo 2b unset ".git" unset "" unset \
+ ".git" "$here/2b" "$here/2b" "(null)" \
+ "../.git" "$here/2b/sub" "$here/2b/sub" "(null)"
+'
+
+test_expect_success '#3: setup' '
+ setup_repo 3 unset "" unset &&
+ mkdir -p 3/sub/sub 3/wt/sub
+'
+run_wt_tests 3
+
+test_expect_success '#4: core.worktree without GIT_DIR set is accepted' '
+ setup_repo 4 ../sub "" unset &&
+ mkdir -p 4/sub sub &&
+ try_case 4 unset unset \
+ .git "$here/4/sub" "$here/4" "(null)" \
+ "$here/4/.git" "$here/4/sub" "$here/4/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' '
+ # or: you cannot intimidate away the lack of GIT_DIR setting
+ try_repo 5 "$here" unset "$here/5" "" unset \
+ "$here/5/.git" "$here" "$here" 5/ \
+ "$here/5/.git" "$here" "$here" 5/sub/ 2>message &&
+ try_repo 5a .. unset "$here/5a" "" unset \
+ "$here/5a/.git" "$here" "$here" 5a/ \
+ "$here/5a/.git" "$here/5a" "$here/5a" sub/ &&
+ ! test -s message
+'
+
+test_expect_success '#6: setting GIT_DIR brings core.worktree to life' '
+ setup_repo 6 "$here/6" "" unset &&
+ try_case 6 unset .git \
+ .git "$here/6" "$here/6" "(null)" &&
+ try_case 6 unset "$here/6/.git" \
+ "$here/6/.git" "$here/6" "$here/6" "(null)" &&
+ try_case 6/sub/sub unset ../../.git \
+ "$here/6/.git" "$here/6" "$here/6" sub/sub/ &&
+ try_case 6/sub/sub unset "$here/6/.git" \
+ "$here/6/.git" "$here/6" "$here/6" sub/sub/
+'
+
+test_expect_success '#6b: GIT_DIR set, core.worktree relative' '
+ setup_repo 6b .. "" unset &&
+ try_case 6b unset .git \
+ .git "$here/6b" "$here/6b" "(null)" &&
+ try_case 6b unset "$here/6b/.git" \
+ "$here/6b/.git" "$here/6b" "$here/6b" "(null)" &&
+ try_case 6b/sub/sub unset ../../.git \
+ "$here/6b/.git" "$here/6b" "$here/6b" sub/sub/ &&
+ try_case 6b/sub/sub unset "$here/6b/.git" \
+ "$here/6b/.git" "$here/6b" "$here/6b" sub/sub/
+'
+
+test_expect_success '#6c: GIT_DIR set, core.worktree=../wt (absolute)' '
+ setup_repo 6c "$here/6c/wt" "" unset &&
+ mkdir -p 6c/wt/sub &&
+
+ try_case 6c unset .git \
+ .git "$here/6c/wt" "$here/6c" "(null)" &&
+ try_case 6c unset "$here/6c/.git" \
+ "$here/6c/.git" "$here/6c/wt" "$here/6c" "(null)" &&
+ try_case 6c/sub/sub unset ../../.git \
+ ../../.git "$here/6c/wt" "$here/6c/sub/sub" "(null)" &&
+ try_case 6c/sub/sub unset "$here/6c/.git" \
+ "$here/6c/.git" "$here/6c/wt" "$here/6c/sub/sub" "(null)"
+'
+
+test_expect_success '#6d: GIT_DIR set, core.worktree=../wt (relative)' '
+ setup_repo 6d "$here/6d/wt" "" unset &&
+ mkdir -p 6d/wt/sub &&
+
+ try_case 6d unset .git \
+ .git "$here/6d/wt" "$here/6d" "(null)" &&
+ try_case 6d unset "$here/6d/.git" \
+ "$here/6d/.git" "$here/6d/wt" "$here/6d" "(null)" &&
+ try_case 6d/sub/sub unset ../../.git \
+ ../../.git "$here/6d/wt" "$here/6d/sub/sub" "(null)" &&
+ try_case 6d/sub/sub unset "$here/6d/.git" \
+ "$here/6d/.git" "$here/6d/wt" "$here/6d/sub/sub" "(null)"
+'
+
+test_expect_success '#6e: GIT_DIR set, core.worktree=../.. (absolute)' '
+ setup_repo 6e "$here" "" unset &&
+ try_case 6e unset .git \
+ "$here/6e/.git" "$here" "$here" 6e/ &&
+ try_case 6e unset "$here/6e/.git" \
+ "$here/6e/.git" "$here" "$here" 6e/ &&
+ try_case 6e/sub/sub unset ../../.git \
+ "$here/6e/.git" "$here" "$here" 6e/sub/sub/ &&
+ try_case 6e/sub/sub unset "$here/6e/.git" \
+ "$here/6e/.git" "$here" "$here" 6e/sub/sub/
+'
+
+test_expect_success '#6f: GIT_DIR set, core.worktree=../.. (relative)' '
+ setup_repo 6f ../../ "" unset &&
+ try_case 6f unset .git \
+ "$here/6f/.git" "$here" "$here" 6f/ &&
+ try_case 6f unset "$here/6f/.git" \
+ "$here/6f/.git" "$here" "$here" 6f/ &&
+ try_case 6f/sub/sub unset ../../.git \
+ "$here/6f/.git" "$here" "$here" 6f/sub/sub/ &&
+ try_case 6f/sub/sub unset "$here/6f/.git" \
+ "$here/6f/.git" "$here" "$here" 6f/sub/sub/
+'
+
+# case #7: GIT_WORK_TREE overrides core.worktree.
+test_expect_success '#7: setup' '
+ setup_repo 7 non-existent "" unset &&
+ mkdir -p 7/sub/sub 7/wt/sub
+'
+run_wt_tests 7
+
+test_expect_success '#8: gitfile, easy case' '
+ try_repo 8 unset unset unset gitfile unset \
+ "$here/8.git" "$here/8" "$here/8" "(null)" \
+ "$here/8.git" "$here/8" "$here/8" sub/
+'
+
+test_expect_success '#9: GIT_WORK_TREE accepted with gitfile' '
+ mkdir -p 9/wt &&
+ try_repo 9 wt unset unset gitfile unset \
+ "$here/9.git" "$here/9/wt" "$here/9" "(null)" \
+ "$here/9.git" "$here/9/sub/wt" "$here/9/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#10: GIT_DIR can point to gitfile' '
+ try_repo 10 unset "$here/10/.git" unset gitfile unset \
+ "$here/10.git" "$here/10" "$here/10" "(null)" \
+ "$here/10.git" "$here/10/sub" "$here/10/sub" "(null)"
+'
+
+test_expect_success '#10b: relative GIT_DIR can point to gitfile' '
+ try_repo 10b unset .git unset gitfile unset \
+ "$here/10b.git" "$here/10b" "$here/10b" "(null)" \
+ "$here/10b.git" "$here/10b/sub" "$here/10b/sub" "(null)"
+'
+
+# case #11: GIT_WORK_TREE works, gitfile case.
+test_expect_success '#11: setup' '
+ setup_repo 11 unset gitfile unset &&
+ mkdir -p 11/sub/sub 11/wt/sub
+'
+run_wt_tests 11 gitfile
+
+test_expect_success '#12: core.worktree with gitfile is accepted' '
+ try_repo 12 unset unset "$here/12" gitfile unset \
+ "$here/12.git" "$here/12" "$here/12" "(null)" \
+ "$here/12.git" "$here/12" "$here/12" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' '
+ # or: you cannot intimidate away the lack of GIT_DIR setting
+ try_repo 13 non-existent-too unset non-existent gitfile unset \
+ "$here/13.git" "$here/13/non-existent-too" "$here/13" "(null)" \
+ "$here/13.git" "$here/13/sub/non-existent-too" "$here/13/sub" "(null)" 2>message &&
+ ! test -s message
+'
+
+# case #14.
+# If this were more table-driven, it could share code with case #6.
+
+test_expect_success '#14: core.worktree with GIT_DIR pointing to gitfile' '
+ setup_repo 14 "$here/14" gitfile unset &&
+ try_case 14 unset .git \
+ "$here/14.git" "$here/14" "$here/14" "(null)" &&
+ try_case 14 unset "$here/14/.git" \
+ "$here/14.git" "$here/14" "$here/14" "(null)" &&
+ try_case 14/sub/sub unset ../../.git \
+ "$here/14.git" "$here/14" "$here/14" sub/sub/ &&
+ try_case 14/sub/sub unset "$here/14/.git" \
+ "$here/14.git" "$here/14" "$here/14" sub/sub/ &&
+
+ setup_repo 14c "$here/14c/wt" gitfile unset &&
+ mkdir -p 14c/wt/sub &&
+
+ try_case 14c unset .git \
+ "$here/14c.git" "$here/14c/wt" "$here/14c" "(null)" &&
+ try_case 14c unset "$here/14c/.git" \
+ "$here/14c.git" "$here/14c/wt" "$here/14c" "(null)" &&
+ try_case 14c/sub/sub unset ../../.git \
+ "$here/14c.git" "$here/14c/wt" "$here/14c/sub/sub" "(null)" &&
+ try_case 14c/sub/sub unset "$here/14c/.git" \
+ "$here/14c.git" "$here/14c/wt" "$here/14c/sub/sub" "(null)" &&
+
+ setup_repo 14d "$here/14d/wt" gitfile unset &&
+ mkdir -p 14d/wt/sub &&
+
+ try_case 14d unset .git \
+ "$here/14d.git" "$here/14d/wt" "$here/14d" "(null)" &&
+ try_case 14d unset "$here/14d/.git" \
+ "$here/14d.git" "$here/14d/wt" "$here/14d" "(null)" &&
+ try_case 14d/sub/sub unset ../../.git \
+ "$here/14d.git" "$here/14d/wt" "$here/14d/sub/sub" "(null)" &&
+ try_case 14d/sub/sub unset "$here/14d/.git" \
+ "$here/14d.git" "$here/14d/wt" "$here/14d/sub/sub" "(null)" &&
+
+ setup_repo 14e "$here" gitfile unset &&
+ try_case 14e unset .git \
+ "$here/14e.git" "$here" "$here" 14e/ &&
+ try_case 14e unset "$here/14e/.git" \
+ "$here/14e.git" "$here" "$here" 14e/ &&
+ try_case 14e/sub/sub unset ../../.git \
+ "$here/14e.git" "$here" "$here" 14e/sub/sub/ &&
+ try_case 14e/sub/sub unset "$here/14e/.git" \
+ "$here/14e.git" "$here" "$here" 14e/sub/sub/
+'
+
+test_expect_success '#14b: core.worktree is relative to actual git dir' '
+ setup_repo 14b ../14b gitfile unset &&
+ try_case 14b unset .git \
+ "$here/14b.git" "$here/14b" "$here/14b" "(null)" &&
+ try_case 14b unset "$here/14b/.git" \
+ "$here/14b.git" "$here/14b" "$here/14b" "(null)" &&
+ try_case 14b/sub/sub unset ../../.git \
+ "$here/14b.git" "$here/14b" "$here/14b" sub/sub/ &&
+ try_case 14b/sub/sub unset "$here/14b/.git" \
+ "$here/14b.git" "$here/14b" "$here/14b" sub/sub/ &&
+
+ setup_repo 14f ../ gitfile unset &&
+ try_case 14f unset .git \
+ "$here/14f.git" "$here" "$here" 14f/ &&
+ try_case 14f unset "$here/14f/.git" \
+ "$here/14f.git" "$here" "$here" 14f/ &&
+ try_case 14f/sub/sub unset ../../.git \
+ "$here/14f.git" "$here" "$here" 14f/sub/sub/ &&
+ try_case 14f/sub/sub unset "$here/14f/.git" \
+ "$here/14f.git" "$here" "$here" 14f/sub/sub/
+'
+
+# case #15: GIT_WORK_TREE overrides core.worktree (gitfile case).
+test_expect_success '#15: setup' '
+ setup_repo 15 non-existent gitfile unset &&
+ mkdir -p 15/sub/sub 15/wt/sub
+'
+run_wt_tests 15 gitfile
+
+test_expect_success '#16a: implicitly bare repo (cwd inside .git dir)' '
+ setup_repo 16a unset "" unset &&
+ mkdir -p 16a/.git/wt/sub &&
+
+ try_case 16a/.git unset unset \
+ . "(null)" "$here/16a/.git" "(null)" &&
+ try_case 16a/.git/wt unset unset \
+ "$here/16a/.git" "(null)" "$here/16a/.git/wt" "(null)" &&
+ try_case 16a/.git/wt/sub unset unset \
+ "$here/16a/.git" "(null)" "$here/16a/.git/wt/sub" "(null)"
+'
+
+test_expect_success '#16b: bare .git (cwd inside .git dir)' '
+ setup_repo 16b unset "" true &&
+ mkdir -p 16b/.git/wt/sub &&
+
+ try_case 16b/.git unset unset \
+ . "(null)" "$here/16b/.git" "(null)" &&
+ try_case 16b/.git/wt unset unset \
+ "$here/16b/.git" "(null)" "$here/16b/.git/wt" "(null)" &&
+ try_case 16b/.git/wt/sub unset unset \
+ "$here/16b/.git" "(null)" "$here/16b/.git/wt/sub" "(null)"
+'
+
+test_expect_success '#16c: bare .git has no worktree' '
+ try_repo 16c unset unset unset "" true \
+ .git "(null)" "$here/16c" "(null)" \
+ "$here/16c/.git" "(null)" "$here/16c/sub" "(null)"
+'
+
+test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (bare case)' '
+ # Just like #16.
+ setup_repo 17a unset "" true &&
+ setup_repo 17b unset "" true &&
+ mkdir -p 17a/.git/wt/sub &&
+ mkdir -p 17b/.git/wt/sub &&
+
+ try_case 17a/.git "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/ \
+ 2>message &&
+ try_case 17a/.git/wt "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/wt/ &&
+ try_case 17a/.git/wt/sub "$here/17a" unset \
+ "$here/17a/.git" "$here/17a" "$here/17a" .git/wt/sub/ &&
+
+ try_case 17b/.git "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/ &&
+ try_case 17b/.git/wt "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/wt/ &&
+ try_case 17b/.git/wt/sub "$here/17b" unset \
+ "$here/17b/.git" "$here/17b" "$here/17b" .git/wt/sub/ &&
+
+ try_repo 17c "$here/17c" unset unset "" true \
+ .git "$here/17c" "$here/17c" "(null)" \
+ "$here/17c/.git" "$here/17c" "$here/17c" sub/ 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#18: bare .git named by GIT_DIR has no worktree' '
+ try_repo 18 unset .git unset "" true \
+ .git "(null)" "$here/18" "(null)" \
+ ../.git "(null)" "$here/18/sub" "(null)" &&
+ try_repo 18b unset "$here/18b/.git" unset "" true \
+ "$here/18b/.git" "(null)" "$here/18b" "(null)" \
+ "$here/18b/.git" "(null)" "$here/18b/sub" "(null)"
+'
+
+# Case #19: GIT_DIR + GIT_WORK_TREE suppresses bareness.
+test_expect_success '#19: setup' '
+ setup_repo 19 unset "" true &&
+ mkdir -p 19/sub/sub 19/wt/sub
+'
+run_wt_tests 19
+
+test_expect_success '#20a: core.worktree without GIT_DIR accepted (inside .git)' '
+ # Unlike case #16a.
+ setup_repo 20a "$here/20a" "" unset &&
+ mkdir -p 20a/.git/wt/sub &&
+ try_case 20a/.git unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/ 2>message &&
+ try_case 20a/.git/wt unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/ &&
+ try_case 20a/.git/wt/sub unset unset \
+ "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/sub/ &&
+ ! test -s message
+'
+
+test_expect_success '#20b/c: core.worktree and core.bare conflict' '
+ setup_repo 20b non-existent "" true &&
+ mkdir -p 20b/.git/wt/sub &&
+ (
+ cd 20b/.git &&
+ test_must_fail git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ grep "core.bare and core.worktree" message
+'
+
+# Case #21: core.worktree/GIT_WORK_TREE overrides core.bare' '
+test_expect_success '#21: setup, core.worktree warns before overriding core.bare' '
+ setup_repo 21 non-existent "" unset &&
+ mkdir -p 21/.git/wt/sub &&
+ (
+ cd 21/.git &&
+ GIT_WORK_TREE="$here/21" &&
+ export GIT_WORK_TREE &&
+ git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ ! test -s message
+
+'
+run_wt_tests 21
+
+test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' '
+ # like case #6.
+
+ setup_repo 22a "$here/22a/.git" "" unset &&
+ setup_repo 22ab . "" unset
+ mkdir -p 22a/.git/sub 22a/sub &&
+ mkdir -p 22ab/.git/sub 22ab/sub &&
+ try_case 22a/.git unset . \
+ . "$here/22a/.git" "$here/22a/.git" "(null)" &&
+ try_case 22a/.git unset "$here/22a/.git" \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" "(null)" &&
+ try_case 22a/.git/sub unset .. \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" sub/ &&
+ try_case 22a/.git/sub unset "$here/22a/.git" \
+ "$here/22a/.git" "$here/22a/.git" "$here/22a/.git" sub/ &&
+
+ try_case 22ab/.git unset . \
+ . "$here/22ab/.git" "$here/22ab/.git" "(null)" &&
+ try_case 22ab/.git unset "$here/22ab/.git" \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" "(null)" &&
+ try_case 22ab/.git/sub unset .. \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" sub/ &&
+ try_case 22ab/.git unset "$here/22ab/.git" \
+ "$here/22ab/.git" "$here/22ab/.git" "$here/22ab/.git" "(null)"
+'
+
+test_expect_success '#22b: core.worktree child of .git, GIT_DIR=.git' '
+ setup_repo 22b "$here/22b/.git/wt" "" unset &&
+ setup_repo 22bb wt "" unset &&
+ mkdir -p 22b/.git/sub 22b/sub 22b/.git/wt/sub 22b/wt/sub &&
+ mkdir -p 22bb/.git/sub 22bb/sub 22bb/.git/wt 22bb/wt &&
+
+ try_case 22b/.git unset . \
+ . "$here/22b/.git/wt" "$here/22b/.git" "(null)" &&
+ try_case 22b/.git unset "$here/22b/.git" \
+ "$here/22b/.git" "$here/22b/.git/wt" "$here/22b/.git" "(null)" &&
+ try_case 22b/.git/sub unset .. \
+ .. "$here/22b/.git/wt" "$here/22b/.git/sub" "(null)" &&
+ try_case 22b/.git/sub unset "$here/22b/.git" \
+ "$here/22b/.git" "$here/22b/.git/wt" "$here/22b/.git/sub" "(null)" &&
+
+ try_case 22bb/.git unset . \
+ . "$here/22bb/.git/wt" "$here/22bb/.git" "(null)" &&
+ try_case 22bb/.git unset "$here/22bb/.git" \
+ "$here/22bb/.git" "$here/22bb/.git/wt" "$here/22bb/.git" "(null)" &&
+ try_case 22bb/.git/sub unset .. \
+ .. "$here/22bb/.git/wt" "$here/22bb/.git/sub" "(null)" &&
+ try_case 22bb/.git/sub unset "$here/22bb/.git" \
+ "$here/22bb/.git" "$here/22bb/.git/wt" "$here/22bb/.git/sub" "(null)"
+'
+
+test_expect_success '#22c: core.worktree = .git/.., GIT_DIR=.git' '
+ setup_repo 22c "$here/22c" "" unset &&
+ setup_repo 22cb .. "" unset &&
+ mkdir -p 22c/.git/sub 22c/sub &&
+ mkdir -p 22cb/.git/sub 22cb/sub &&
+
+ try_case 22c/.git unset . \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/ &&
+ try_case 22c/.git unset "$here/22c/.git" \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/ &&
+ try_case 22c/.git/sub unset .. \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/sub/ &&
+ try_case 22c/.git/sub unset "$here/22c/.git" \
+ "$here/22c/.git" "$here/22c" "$here/22c" .git/sub/ &&
+
+ try_case 22cb/.git unset . \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/ &&
+ try_case 22cb/.git unset "$here/22cb/.git" \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/ &&
+ try_case 22cb/.git/sub unset .. \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/sub/ &&
+ try_case 22cb/.git/sub unset "$here/22cb/.git" \
+ "$here/22cb/.git" "$here/22cb" "$here/22cb" .git/sub/
+'
+
+test_expect_success '#22.2: core.worktree and core.bare conflict' '
+ setup_repo 22 "$here/22" "" true &&
+ (
+ cd 22/.git &&
+ GIT_DIR=. &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ (
+ cd 22 &&
+ GIT_DIR=.git &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ grep "core.bare and core.worktree" 22/.git/result &&
+ grep "core.bare and core.worktree" 22/result
+'
+
+# Case #23: GIT_DIR + GIT_WORK_TREE(+core.worktree) suppresses bareness.
+test_expect_success '#23: setup' '
+ setup_repo 23 non-existent "" true &&
+ mkdir -p 23/sub/sub 23/wt/sub
+'
+run_wt_tests 23
+
+test_expect_success '#24: bare repo has no worktree (gitfile case)' '
+ try_repo 24 unset unset unset gitfile true \
+ "$here/24.git" "(null)" "$here/24" "(null)" \
+ "$here/24.git" "(null)" "$here/24/sub" "(null)"
+'
+
+test_expect_success '#25: GIT_WORK_TREE accepted if GIT_DIR unset (bare gitfile case)' '
+ try_repo 25 "$here/25" unset unset gitfile true \
+ "$here/25.git" "$here/25" "$here/25" "(null)" \
+ "$here/25.git" "$here/25" "$here/25" "sub/" 2>message &&
+ ! test -s message
+'
+
+test_expect_success '#26: bare repo has no worktree (GIT_DIR -> gitfile case)' '
+ try_repo 26 unset "$here/26/.git" unset gitfile true \
+ "$here/26.git" "(null)" "$here/26" "(null)" \
+ "$here/26.git" "(null)" "$here/26/sub" "(null)" &&
+ try_repo 26b unset .git unset gitfile true \
+ "$here/26b.git" "(null)" "$here/26b" "(null)" \
+ "$here/26b.git" "(null)" "$here/26b/sub" "(null)"
+'
+
+# Case #27: GIT_DIR + GIT_WORK_TREE suppresses bareness (with gitfile).
+test_expect_success '#27: setup' '
+ setup_repo 27 unset gitfile true &&
+ mkdir -p 27/sub/sub 27/wt/sub
+'
+run_wt_tests 27 gitfile
+
+test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' '
+ setup_repo 28 "$here/28" gitfile true &&
+ (
+ cd 28 &&
+ test_must_fail git symbolic-ref HEAD
+ ) 2>message &&
+ ! grep "^warning:" message &&
+ grep "core.bare and core.worktree" message
+'
+
+# Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case).
+test_expect_success '#29: setup' '
+ setup_repo 29 non-existent gitfile true &&
+ mkdir -p 29/sub/sub 29/wt/sub
+ (
+ cd 29 &&
+ GIT_WORK_TREE="$here/29" &&
+ export GIT_WORK_TREE &&
+ git symbolic-ref HEAD >/dev/null
+ ) 2>message &&
+ ! test -s message
+'
+run_wt_tests 29 gitfile
+
+test_expect_success '#30: core.worktree and core.bare conflict (gitfile version)' '
+ # Just like case #22.
+ setup_repo 30 "$here/30" gitfile true &&
+ (
+ cd 30 &&
+ GIT_DIR=.git &&
+ export GIT_DIR &&
+ test_must_fail git symbolic-ref HEAD 2>result
+ ) &&
+ grep "core.bare and core.worktree" 30/result
+'
+
+# Case #31: GIT_DIR + GIT_WORK_TREE(+core.worktree) suppresses
+# bareness (gitfile version).
+test_expect_success '#31: setup' '
+ setup_repo 31 non-existent gitfile true &&
+ mkdir -p 31/sub/sub 31/wt/sub
+'
+run_wt_tests 31 gitfile
+
+test_done
diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh
new file mode 100755
index 0000000..e043cb7
--- /dev/null
+++ b/t/t1511-rev-parse-caret.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='tests for ref^{stuff}'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo blob >a-blob &&
+ git tag -a -m blob blob-tag `git hash-object -w a-blob`
+ mkdir a-tree &&
+ echo moreblobs >a-tree/another-blob &&
+ git add . &&
+ TREE_SHA1=`git write-tree` &&
+ git tag -a -m tree tree-tag "$TREE_SHA1" &&
+ git commit -m Initial &&
+ git tag -a -m commit commit-tag &&
+ git branch ref &&
+ git checkout master &&
+ echo modified >>a-blob &&
+ git add -u &&
+ git commit -m Modified
+'
+
+test_expect_success 'ref^{non-existent}' '
+ test_must_fail git rev-parse ref^{non-existent}
+'
+
+test_expect_success 'ref^{}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{commit}' '
+ git rev-parse ref >expected &&
+ git rev-parse ref^{commit} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{commit} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse tree-tag^{commit} &&
+ test_must_fail git rev-parse blob-tag^{commit}
+'
+
+test_expect_success 'ref^{tree}' '
+ echo $TREE_SHA1 >expected &&
+ git rev-parse ref^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse commit-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ git rev-parse tree-tag^{tree} >actual &&
+ test_cmp expected actual &&
+ test_must_fail git rev-parse blob-tag^{tree}
+'
+
+test_expect_success 'ref^{/.}' '
+ git rev-parse master >expected &&
+ git rev-parse master^{/.} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'ref^{/non-existent}' '
+ test_must_fail git rev-parse master^{/non-existent}
+'
+
+test_expect_success 'ref^{/Initial}' '
+ git rev-parse ref >expected &&
+ git rev-parse master^{/Initial} >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh
new file mode 100755
index 0000000..b855983
--- /dev/null
+++ b/t/t2006-checkout-index-basic.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='basic checkout-index tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'checkout-index --gobbledegook' '
+ test_expect_code 129 git checkout-index --gobbledegook 2>err &&
+ grep "[Uu]sage" err
+'
+
+test_expect_success 'checkout-index -h in broken repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git checkout-index -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
+test_done
diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh
index 27e2127..e6f59f1 100755
--- a/t/t2007-checkout-symlink.sh
+++ b/t/t2007-checkout-symlink.sh
@@ -6,13 +6,7 @@ test_description='git checkout to switch between branches with symlink<->dir'
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say "symbolic links not supported - skipping tests"
- test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
mkdir frotz &&
echo hello >frotz/filfre &&
@@ -23,7 +17,7 @@ test_expect_success setup '
git branch side &&
echo goodbye >nitfol &&
- git add nitfol
+ git add nitfol &&
test_tick &&
git commit -m "master adds file nitfol" &&
@@ -38,18 +32,18 @@ test_expect_success setup '
'
-test_expect_success 'switch from symlink to dir' '
+test_expect_success SYMLINKS 'switch from symlink to dir' '
git checkout master
'
-test_expect_success 'Remove temporary directories & switch to master' '
+test_expect_success SYMLINKS 'Remove temporary directories & switch to master' '
rm -fr frotz xyzzy nitfol &&
git checkout -f master
'
-test_expect_success 'switch from dir to symlink' '
+test_expect_success SYMLINKS 'switch from dir to symlink' '
git checkout side
diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh
index 15ebdc2..300f8bf 100755
--- a/t/t2011-checkout-invalid-head.sh
+++ b/t/t2011-checkout-invalid-head.sh
@@ -15,7 +15,7 @@ test_expect_success 'checkout should not start branch from a tree' '
'
test_expect_success 'checkout master from invalid HEAD' '
- echo 0000000000000000000000000000000000000000 >.git/HEAD &&
+ echo $_z40 >.git/HEAD &&
git checkout master --
'
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
index fda3f0a..70edbb3 100755
--- a/t/t2013-checkout-submodule.sh
+++ b/t/t2013-checkout-submodule.sh
@@ -39,4 +39,27 @@ test_expect_success '"checkout <submodule>" updates the index only' '
git diff-files --quiet
'
+test_expect_success '"checkout <submodule>" honors diff.ignoreSubmodules' '
+ git config diff.ignoreSubmodules dirty &&
+ echo x> submodule/untracked &&
+ git checkout HEAD >actual 2>&1 &&
+ ! test -s actual
+'
+
+test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitmodules' '
+ git config diff.ignoreSubmodules none &&
+ git config -f .gitmodules submodule.submodule.path submodule &&
+ git config -f .gitmodules submodule.submodule.ignore untracked &&
+ git checkout HEAD >actual 2>&1 &&
+ ! test -s actual
+'
+
+test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/config' '
+ git config -f .gitmodules submodule.submodule.ignore none &&
+ git config submodule.submodule.path submodule &&
+ git config submodule.submodule.ignore all &&
+ git checkout HEAD >actual 2>&1 &&
+ ! test -s actual
+'
+
test_done
diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh
index 2144184..9cd0ac4 100755
--- a/t/t2016-checkout-patch.sh
+++ b/t/t2016-checkout-patch.sh
@@ -4,7 +4,7 @@ test_description='git checkout --patch'
. ./lib-patch-mode.sh
-test_expect_success 'setup' '
+test_expect_success PERL 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
@@ -18,55 +18,55 @@ test_expect_success 'setup' '
# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar'
-test_expect_success 'saying "n" does nothing' '
+test_expect_success PERL 'saying "n" does nothing' '
set_and_save_state dir/foo work head &&
(echo n; echo n) | git checkout -p &&
verify_saved_state bar &&
verify_saved_state dir/foo
'
-test_expect_success 'git checkout -p' '
+test_expect_success PERL 'git checkout -p' '
(echo n; echo y) | git checkout -p &&
verify_saved_state bar &&
verify_state dir/foo head head
'
-test_expect_success 'git checkout -p with staged changes' '
- set_state dir/foo work index
+test_expect_success PERL 'git checkout -p with staged changes' '
+ set_state dir/foo work index &&
(echo n; echo y) | git checkout -p &&
verify_saved_state bar &&
verify_state dir/foo index index
'
-test_expect_success 'git checkout -p HEAD with NO staged changes: abort' '
+test_expect_success PERL 'git checkout -p HEAD with NO staged changes: abort' '
set_and_save_state dir/foo work head &&
(echo n; echo y; echo n) | git checkout -p HEAD &&
verify_saved_state bar &&
verify_saved_state dir/foo
'
-test_expect_success 'git checkout -p HEAD with NO staged changes: apply' '
+test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' '
(echo n; echo y; echo y) | git checkout -p HEAD &&
verify_saved_state bar &&
verify_state dir/foo head head
'
-test_expect_success 'git checkout -p HEAD with change already staged' '
- set_state dir/foo index index
+test_expect_success PERL 'git checkout -p HEAD with change already staged' '
+ set_state dir/foo index index &&
# the third n is to get out in case it mistakenly does not apply
(echo n; echo y; echo n) | git checkout -p HEAD &&
verify_saved_state bar &&
verify_state dir/foo head head
'
-test_expect_success 'git checkout -p HEAD^' '
+test_expect_success PERL 'git checkout -p HEAD^' '
# the third n is to get out in case it mistakenly does not apply
(echo n; echo y; echo n) | git checkout -p HEAD^ &&
verify_saved_state bar &&
verify_state dir/foo parent parent
'
-test_expect_success 'git checkout -p handles deletion' '
+test_expect_success PERL 'git checkout -p handles deletion' '
set_state dir/foo work index &&
rm dir/foo &&
(echo n; echo y) | git checkout -p &&
@@ -79,28 +79,28 @@ test_expect_success 'git checkout -p handles deletion' '
# dir/foo. There's always an extra 'n' to reject edits to dir/foo in
# the failure case (and thus get out of the loop).
-test_expect_success 'path limiting works: dir' '
+test_expect_success PERL 'path limiting works: dir' '
set_state dir/foo work head &&
(echo y; echo n) | git checkout -p dir &&
verify_saved_state bar &&
verify_state dir/foo head head
'
-test_expect_success 'path limiting works: -- dir' '
+test_expect_success PERL 'path limiting works: -- dir' '
set_state dir/foo work head &&
(echo y; echo n) | git checkout -p -- dir &&
verify_saved_state bar &&
verify_state dir/foo head head
'
-test_expect_success 'path limiting works: HEAD^ -- dir' '
+test_expect_success PERL 'path limiting works: HEAD^ -- dir' '
# the third n is to get out in case it mistakenly does not apply
(echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&
verify_saved_state bar &&
verify_state dir/foo parent parent
'
-test_expect_success 'path limiting works: foo inside dir' '
+test_expect_success PERL 'path limiting works: foo inside dir' '
set_state dir/foo work head &&
# the third n is to get out in case it mistakenly does not apply
(echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&
@@ -108,7 +108,7 @@ test_expect_success 'path limiting works: foo inside dir' '
verify_state dir/foo head head
'
-test_expect_success 'none of this moved HEAD' '
+test_expect_success PERL 'none of this moved HEAD' '
verify_saved_head
'
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index be88d4b..0e3b858 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -14,7 +14,7 @@ TEST_FILE=foo
test_expect_success 'Setup' '
echo "Initial" >"$TEST_FILE" &&
git add "$TEST_FILE" &&
- git commit -m "First Commit"
+ git commit -m "First Commit" &&
test_tick &&
echo "State 1" >>"$TEST_FILE" &&
git add "$TEST_FILE" &&
@@ -68,41 +68,34 @@ test_expect_success '--orphan makes reflog by default' '
git checkout master &&
git config --unset core.logAllRefUpdates &&
git checkout --orphan delta &&
- ! test -f .git/logs/refs/heads/delta &&
- test_must_fail PAGER= git reflog show delta &&
+ test_must_fail git rev-parse --verify delta@{0} &&
git commit -m Delta &&
- test -f .git/logs/refs/heads/delta &&
- PAGER= git reflog show delta
+ git rev-parse --verify delta@{0}
'
test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' '
git checkout master &&
git config core.logAllRefUpdates false &&
git checkout --orphan epsilon &&
- ! test -f .git/logs/refs/heads/epsilon &&
- test_must_fail PAGER= git reflog show epsilon &&
+ test_must_fail git rev-parse --verify epsilon@{0} &&
git commit -m Epsilon &&
- ! test -f .git/logs/refs/heads/epsilon &&
- test_must_fail PAGER= git reflog show epsilon
+ test_must_fail git rev-parse --verify epsilon@{0}
'
test_expect_success '--orphan with -l makes reflog when core.logAllRefUpdates = false' '
git checkout master &&
git checkout -l --orphan zeta &&
- test -f .git/logs/refs/heads/zeta &&
- test_must_fail PAGER= git reflog show zeta &&
+ test_must_fail git rev-parse --verify zeta@{0} &&
git commit -m Zeta &&
- PAGER= git reflog show zeta
+ git rev-parse --verify zeta@{0}
'
test_expect_success 'giving up --orphan not committed when -l and core.logAllRefUpdates = false deletes reflog' '
git checkout master &&
git checkout -l --orphan eta &&
- test -f .git/logs/refs/heads/eta &&
- test_must_fail PAGER= git reflog show eta &&
+ test_must_fail git rev-parse --verify eta@{0} &&
git checkout master &&
- ! test -f .git/logs/refs/heads/eta &&
- test_must_fail PAGER= git reflog show eta
+ test_must_fail git rev-parse --verify eta@{0}
'
test_expect_success '--orphan is rejected with an existing name' '
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
new file mode 100755
index 0000000..a42e039
--- /dev/null
+++ b/t/t2018-checkout-branch.sh
@@ -0,0 +1,183 @@
+#!/bin/sh
+
+test_description='checkout '
+
+. ./test-lib.sh
+
+# Arguments: <branch> <sha> [<checkout options>]
+#
+# Runs "git checkout" to switch to <branch>, testing that
+#
+# 1) we are on the specified branch, <branch>;
+# 2) HEAD is <sha>; if <sha> is not specified, the old HEAD is used.
+#
+# If <checkout options> is not specified, "git checkout" is run with -b.
+do_checkout() {
+ exp_branch=$1 &&
+ exp_ref="refs/heads/$exp_branch" &&
+
+ # if <sha> is not specified, use HEAD.
+ exp_sha=${2:-$(git rev-parse --verify HEAD)} &&
+
+ # default options for git checkout: -b
+ if [ -z "$3" ]; then
+ opts="-b"
+ else
+ opts="$3"
+ fi
+
+ git checkout $opts $exp_branch $exp_sha &&
+
+ test $exp_ref = $(git rev-parse --symbolic-full-name HEAD) &&
+ test $exp_sha = $(git rev-parse --verify HEAD)
+}
+
+test_dirty_unmergeable() {
+ ! git diff --exit-code >/dev/null
+}
+
+setup_dirty_unmergeable() {
+ echo >>file1 change2
+}
+
+test_dirty_mergeable() {
+ ! git diff --cached --exit-code >/dev/null
+}
+
+setup_dirty_mergeable() {
+ echo >file2 file2 &&
+ git add file2
+}
+
+test_expect_success 'setup' '
+ test_commit initial file1 &&
+ HEAD1=$(git rev-parse --verify HEAD) &&
+
+ test_commit change1 file1 &&
+ HEAD2=$(git rev-parse --verify HEAD) &&
+
+ git branch -m branch1
+'
+
+test_expect_success 'checkout -b to a new branch, set to HEAD' '
+ do_checkout branch2
+'
+
+test_expect_success 'checkout -b to a new branch, set to an explicit ref' '
+ git checkout branch1 &&
+ git branch -D branch2 &&
+
+ do_checkout branch2 $HEAD1
+'
+
+test_expect_success 'checkout -b to a new branch with unmergeable changes fails' '
+ git checkout branch1 &&
+
+ # clean up from previous test
+ git branch -D branch2 &&
+
+ setup_dirty_unmergeable &&
+ test_must_fail do_checkout branch2 $HEAD1 &&
+ test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -f -b to a new branch with unmergeable changes discards changes' '
+ # still dirty and on branch1
+ do_checkout branch2 $HEAD1 "-f -b" &&
+ test_must_fail test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -b to a new branch preserves mergeable changes' '
+ git checkout branch1 &&
+
+ # clean up from previous test
+ git branch -D branch2 &&
+
+ setup_dirty_mergeable &&
+ do_checkout branch2 $HEAD1 &&
+ test_dirty_mergeable
+'
+
+test_expect_success 'checkout -f -b to a new branch with mergeable changes discards changes' '
+ # clean up from previous test
+ git reset --hard &&
+
+ git checkout branch1 &&
+
+ # clean up from previous test
+ git branch -D branch2 &&
+
+ setup_dirty_mergeable &&
+ do_checkout branch2 $HEAD1 "-f -b" &&
+ test_must_fail test_dirty_mergeable
+'
+
+test_expect_success 'checkout -b to an existing branch fails' '
+ git reset --hard HEAD &&
+
+ test_must_fail do_checkout branch2 $HEAD2
+'
+
+test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
+ git checkout branch1 &&
+
+ do_checkout branch2 "" -B
+'
+
+test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' '
+ git checkout $(git rev-parse --verify HEAD) &&
+
+ do_checkout branch2 "" -B
+'
+
+test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' '
+ git checkout branch1 &&
+
+ do_checkout branch2 $HEAD1 -B
+'
+
+test_expect_success 'checkout -B to an existing branch with unmergeable changes fails' '
+ git checkout branch1 &&
+
+ setup_dirty_unmergeable &&
+ test_must_fail do_checkout branch2 $HEAD1 -B &&
+ test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
+ # still dirty and on branch1
+ do_checkout branch2 $HEAD1 "-f -B" &&
+ test_must_fail test_dirty_unmergeable
+'
+
+test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
+ git checkout branch1 &&
+
+ setup_dirty_mergeable &&
+ do_checkout branch2 $HEAD1 -B &&
+ test_dirty_mergeable
+'
+
+test_expect_success 'checkout -f -B to an existing branch with mergeable changes discards changes' '
+ # clean up from previous test
+ git reset --hard &&
+
+ git checkout branch1 &&
+
+ setup_dirty_mergeable &&
+ do_checkout branch2 $HEAD1 "-f -B" &&
+ test_must_fail test_dirty_mergeable
+'
+
+test_expect_success 'checkout -b <describe>' '
+ git tag -f -m "First commit" initial initial &&
+ git checkout -f change1 &&
+ name=$(git describe) &&
+ git checkout -b $name &&
+ git diff --exit-code change1 &&
+ echo "refs/heads/$name" >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t2019-checkout-ambiguous-ref.sh b/t/t2019-checkout-ambiguous-ref.sh
new file mode 100755
index 0000000..b99d519
--- /dev/null
+++ b/t/t2019-checkout-ambiguous-ref.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+test_description='checkout handling of ambiguous (branch/tag) refs'
+. ./test-lib.sh
+
+test_expect_success 'setup ambiguous refs' '
+ test_commit branch file &&
+ git branch ambiguity &&
+ git branch vagueness &&
+ test_commit tag file &&
+ git tag ambiguity &&
+ git tag vagueness HEAD:file &&
+ test_commit other file
+'
+
+test_expect_success 'checkout ambiguous ref succeeds' '
+ git checkout ambiguity >stdout 2>stderr
+'
+
+test_expect_success 'checkout produces ambiguity warning' '
+ grep "warning.*ambiguous" stderr
+'
+
+test_expect_success 'checkout chooses branch over tag' '
+ echo refs/heads/ambiguity >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual &&
+ echo branch >expect &&
+ test_cmp expect file
+'
+
+test_expect_success 'checkout reports switch to branch' '
+ test_i18ngrep "Switched to branch" stderr &&
+ test_i18ngrep ! "^HEAD is now at" stderr
+'
+
+test_expect_success 'checkout vague ref succeeds' '
+ git checkout vagueness >stdout 2>stderr &&
+ test_set_prereq VAGUENESS_SUCCESS
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout produces ambiguity warning' '
+ grep "warning.*ambiguous" stderr
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout chooses branch over tag' '
+ echo refs/heads/vagueness >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual &&
+ echo branch >expect &&
+ test_cmp expect file
+'
+
+test_expect_success VAGUENESS_SUCCESS 'checkout reports switch to branch' '
+ test_i18ngrep "Switched to branch" stderr &&
+ test_i18ngrep ! "^HEAD is now at" stderr
+'
+
+test_done
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
new file mode 100755
index 0000000..2366f0f
--- /dev/null
+++ b/t/t2020-checkout-detach.sh
@@ -0,0 +1,151 @@
+#!/bin/sh
+
+test_description='checkout into detached HEAD state'
+. ./test-lib.sh
+
+check_detached () {
+ test_must_fail git symbolic-ref -q HEAD >/dev/null
+}
+
+check_not_detached () {
+ git symbolic-ref -q HEAD >/dev/null
+}
+
+ORPHAN_WARNING='you are leaving .* commit.*behind'
+check_orphan_warning() {
+ test_i18ngrep "$ORPHAN_WARNING" "$1"
+}
+check_no_orphan_warning() {
+ test_i18ngrep ! "$ORPHAN_WARNING" "$1"
+}
+
+reset () {
+ git checkout master &&
+ check_not_detached
+}
+
+test_expect_success 'setup' '
+ test_commit one &&
+ test_commit two &&
+ test_commit three && git tag -d three &&
+ test_commit four && git tag -d four &&
+ git branch branch &&
+ git tag tag
+'
+
+test_expect_success 'checkout branch does not detach' '
+ reset &&
+ git checkout branch &&
+ check_not_detached
+'
+
+test_expect_success 'checkout tag detaches' '
+ reset &&
+ git checkout tag &&
+ check_detached
+'
+
+test_expect_success 'checkout branch by full name detaches' '
+ reset &&
+ git checkout refs/heads/branch &&
+ check_detached
+'
+
+test_expect_success 'checkout non-ref detaches' '
+ reset &&
+ git checkout branch^ &&
+ check_detached
+'
+
+test_expect_success 'checkout ref^0 detaches' '
+ reset &&
+ git checkout branch^0 &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach detaches' '
+ reset &&
+ git checkout --detach branch &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach without branch name' '
+ reset &&
+ git checkout --detach &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach errors out for non-commit' '
+ reset &&
+ test_must_fail git checkout --detach one^{tree} &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach errors out for extra argument' '
+ reset &&
+ git checkout master &&
+ test_must_fail git checkout --detach tag one.t &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detached and -b are incompatible' '
+ reset &&
+ test_must_fail git checkout --detach -b newbranch tag &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach moves HEAD' '
+ reset &&
+ git checkout one &&
+ git checkout --detach two &&
+ git diff --exit-code HEAD &&
+ git diff --exit-code two
+'
+
+test_expect_success 'checkout warns on orphan commits' '
+ reset &&
+ git checkout --detach two &&
+ echo content >orphan &&
+ git add orphan &&
+ git commit -a -m orphan &&
+ git checkout master 2>stderr
+'
+
+test_expect_success 'checkout warns on orphan commits: output' '
+ check_orphan_warning stderr
+'
+
+test_expect_success 'checkout does not warn leaving ref tip' '
+ reset &&
+ git checkout --detach two &&
+ git checkout master 2>stderr
+'
+
+test_expect_success 'checkout does not warn leaving ref tip' '
+ check_no_orphan_warning stderr
+'
+
+test_expect_success 'checkout does not warn leaving reachable commit' '
+ reset &&
+ git checkout --detach HEAD^ &&
+ git checkout master 2>stderr
+'
+
+test_expect_success 'checkout does not warn leaving reachable commit' '
+ check_no_orphan_warning stderr
+'
+
+cat >expect <<'EOF'
+Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
+EOF
+test_expect_success 'tracking count is accurate after orphan check' '
+ reset &&
+ git branch child master^ &&
+ git config branch.child.remote . &&
+ git config branch.child.merge refs/heads/master &&
+ git checkout child^ &&
+ git checkout child >stdout &&
+ test_cmp expect stdout
+'
+
+test_done
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh
new file mode 100755
index 0000000..5da63e9
--- /dev/null
+++ b/t/t2021-checkout-overwrite.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='checkout must not overwrite an untracked objects'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ mkdir -p a/b/c &&
+ >a/b/c/d &&
+ git add -A &&
+ git commit -m base &&
+ git tag start
+'
+
+test_expect_success 'create a commit where dir a/b changed to file' '
+
+ git checkout -b file &&
+ rm -rf a/b &&
+ >a/b &&
+ git add -A &&
+ git commit -m "dir to file"
+'
+
+test_expect_success 'checkout commit with dir must not remove untracked a/b' '
+
+ git rm --cached a/b &&
+ git commit -m "un-track the file" &&
+ test_must_fail git checkout start &&
+ test -f a/b
+'
+
+test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
+
+ rm -rf a/b && # cleanup if previous test failed
+ git checkout -f -b symlink start &&
+ rm -rf a/b &&
+ ln -s foo a/b &&
+ git add -A &&
+ git commit -m "dir to symlink"
+'
+
+test_expect_success SYMLINKS 'checkout commit with dir must not remove untracked a/b' '
+
+ git rm --cached a/b &&
+ git commit -m "un-track the symlink" &&
+ test_must_fail git checkout start &&
+ test -h a/b
+'
+
+test_done
diff --git a/t/t2050-git-dir-relative.sh b/t/t2050-git-dir-relative.sh
index b7131d8..21f4659 100755
--- a/t/t2050-git-dir-relative.sh
+++ b/t/t2050-git-dir-relative.sh
@@ -26,7 +26,7 @@ chmod +x .git/hooks/post-commit'
test_expect_success 'post-commit hook used ordinarily' '
echo initial >top &&
-git add top
+git add top &&
git commit -m initial &&
test -r "${COMMIT_FILE}"
'
@@ -45,7 +45,7 @@ test -r "${COMMIT_FILE}"
rm -rf "${COMMIT_FILE}"
test_expect_success 'post-commit-hook from sub dir' '
-echo changed again >top
+echo changed again >top &&
cd subdir &&
git --git-dir .git --work-tree .. add ../top &&
git --git-dir .git --work-tree .. commit -m subcommit &&
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
index 648184f..c8bce8c 100755
--- a/t/t2101-update-index-reupdate.sh
+++ b/t/t2101-update-index-reupdate.sh
@@ -51,7 +51,7 @@ test_expect_success 'update-index again' \
echo hello world >dir1/file3 &&
echo goodbye people >file2 &&
git update-index --add file2 dir1/file3 &&
- echo hello everybody >file2
+ echo hello everybody >file2 &&
echo happy >dir1/file3 &&
git update-index --again &&
git ls-files -s >current &&
@@ -63,10 +63,10 @@ cat > expected <<\EOF
EOF
test_expect_success 'update-index --update from subdir' \
'echo not so happy >file2 &&
- cd dir1 &&
+ (cd dir1 &&
cat ../file2 >file3 &&
- git update-index --again &&
- cd .. &&
+ git update-index --again
+ ) &&
git ls-files -s >current &&
cmp current expected'
diff --git a/t/t2102-update-index-symlinks.sh b/t/t2102-update-index-symlinks.sh
index 1ed44ee..4d0d0a3 100755
--- a/t/t2102-update-index-symlinks.sh
+++ b/t/t2102-update-index-symlinks.sh
@@ -24,7 +24,7 @@ git update-index symlink'
test_expect_success \
'the index entry must still be a symbolic link' '
case "`git ls-files --stage --cached symlink`" in
-120000" "*symlink) echo ok;;
+120000" "*symlink) echo pass;;
*) echo fail; git ls-files --stage --cached symlink; (exit 1);;
esac'
diff --git a/t/t2105-update-index-gitfile.sh b/t/t2105-update-index-gitfile.sh
index 641607d..a7f3d47 100755
--- a/t/t2105-update-index-gitfile.sh
+++ b/t/t2105-update-index-gitfile.sh
@@ -13,7 +13,7 @@ test_expect_success 'submodule with absolute .git file' '
(cd sub1 &&
git init &&
REAL="$(pwd)/.real" &&
- mv .git "$REAL"
+ mv .git "$REAL" &&
echo "gitdir: $REAL" >.git &&
test_commit first)
'
diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh
new file mode 100755
index 0000000..809fafe
--- /dev/null
+++ b/t/t2107-update-index-basic.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='basic update-index tests
+
+Tests for command-line parsing and basic operation.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'update-index --nonsense fails' '
+ test_must_fail git update-index --nonsense 2>msg &&
+ cat msg &&
+ test -s msg
+'
+
+test_expect_success 'update-index --nonsense dumps usage' '
+ test_expect_code 129 git update-index --nonsense 2>err &&
+ grep "[Uu]sage: git update-index" err
+'
+
+test_expect_success 'update-index -h with corrupt index' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git update-index -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage: git update-index" broken/usage
+'
+
+test_done
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index 2ad2819..4cdebda 100755
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
@@ -25,7 +25,7 @@ test_expect_success setup '
echo initial >dir1/sub2 &&
echo initial >dir2/sub3 &&
git add check dir1 dir2 top foo &&
- test_tick
+ test_tick &&
git commit -m initial &&
echo changed >check &&
@@ -124,7 +124,7 @@ test_expect_success 'add -n -u should not add but just report' '
after=$(git ls-files -s check top) &&
test "$before" = "$after" &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
@@ -149,31 +149,21 @@ test_expect_success 'add -u resolves unmerged paths' '
echo 3 >path1 &&
echo 2 >path3 &&
echo 2 >path5 &&
- git add -u &&
- git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
- {
- echo "100644 $three 0 path1"
- echo "100644 $one 1 path3"
- echo "100644 $one 1 path4"
- echo "100644 $one 3 path5"
- echo "100644 $one 3 path6"
- } >expect &&
- test_cmp expect actual &&
- # Bonus tests. Explicit resolving
- git add path3 path5 &&
+ # Explicit resolving by adding removed paths should fail
test_must_fail git add path4 &&
test_must_fail git add path6 &&
- git rm path4 &&
- git rm path6 &&
- git ls-files -s "path?" >actual &&
+ # "add -u" should notice removals no matter what stages
+ # the index entries are in.
+ git add -u &&
+ git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
{
echo "100644 $three 0 path1"
echo "100644 $two 0 path3"
echo "100644 $two 0 path5"
- } >expect
-
+ } >expect &&
+ test_cmp expect actual
'
test_expect_success '"add -u non-existent" should fail' '
diff --git a/t/t2201-add-update-typechange.sh b/t/t2201-add-update-typechange.sh
index 2e8f702..954fc51 100755
--- a/t/t2201-add-update-typechange.sh
+++ b/t/t2201-add-update-typechange.sh
@@ -4,8 +4,6 @@ test_description='more git add -u'
. ./test-lib.sh
-_z40=0000000000000000000000000000000000000000
-
test_expect_success setup '
>xyzzy &&
_empty=$(git hash-object --stdin <xyzzy) &&
diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh
index 24afdab..8340ac2 100755
--- a/t/t2204-add-ignored.sh
+++ b/t/t2204-add-ignored.sh
@@ -31,18 +31,21 @@ do
rm -f .git/index &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out &&
- grep -e "Use -f if" err &&
- cat err
+ ! test -s out
+ '
+
+ test_expect_success "complaints for ignored $i output" '
+ test_i18ngrep -e "Use -f if" err
'
test_expect_success "complaints for ignored $i with unignored file" '
rm -f .git/index &&
test_must_fail git add "$i" file 2>err &&
git ls-files "$i" >out &&
- ! test -s out &&
- grep -e "Use -f if" err &&
- cat err
+ ! test -s out
+ '
+ test_expect_success "complaints for ignored $i with unignored file output" '
+ test_i18ngrep -e "Use -f if" err
'
done
@@ -54,9 +57,14 @@ do
cd dir &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out &&
- grep -e "Use -f if" err &&
- cat err
+ ! test -s out
+ )
+ '
+
+ test_expect_success "complaints for ignored $i in dir output" '
+ (
+ cd dir &&
+ test_i18ngrep -e "Use -f if" err
)
'
done
@@ -69,9 +77,14 @@ do
cd sub &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out &&
- grep -e "Use -f if" err &&
- cat err
+ ! test -s out
+ )
+ '
+
+ test_expect_success "complaints for ignored $i in sub output" '
+ (
+ cd sub &&
+ test_i18ngrep -e "Use -f if" err
)
'
done
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
index 86291e8..2eec011 100755
--- a/t/t3000-ls-files-others.sh
+++ b/t/t3000-ls-files-others.sh
@@ -17,57 +17,52 @@ filesystem.
'
. ./test-lib.sh
-date >path0
-if test_have_prereq SYMLINKS
-then
- ln -s xyzzy path1
-else
- date > path1
-fi
-mkdir path2 path3 path4
-date >path2/file2
-date >path2-junk
-date >path3/file3
-date >path3-junk
-git update-index --add path3-junk path3/file3
-
-cat >expected1 <<EOF
-expected1
-expected2
-expected3
-output
-path0
-path1
-path2-junk
-path2/file2
-EOF
-sed -e 's|path2/file2|path2/|' <expected1 >expected2
-cat <expected2 >expected3
-echo path4/ >>expected2
-
-test_expect_success \
- 'git ls-files --others to show output.' \
- 'git ls-files --others >output'
-
-test_expect_success \
- 'git ls-files --others should pick up symlinks.' \
- 'test_cmp expected1 output'
+test_expect_success 'setup ' '
+ date >path0 &&
+ if test_have_prereq SYMLINKS
+ then
+ ln -s xyzzy path1
+ else
+ date >path1
+ fi &&
+ mkdir path2 path3 path4 &&
+ date >path2/file2 &&
+ date >path2-junk &&
+ date >path3/file3 &&
+ date >path3-junk &&
+ git update-index --add path3-junk path3/file3
+'
-test_expect_success \
- 'git ls-files --others --directory to show output.' \
- 'git ls-files --others --directory >output'
+test_expect_success 'setup: expected output' '
+ cat >expected1 <<-\EOF &&
+ expected1
+ expected2
+ expected3
+ output
+ path0
+ path1
+ path2-junk
+ path2/file2
+ EOF
+ sed -e "s|path2/file2|path2/|" <expected1 >expected2 &&
+ cp expected2 expected3 &&
+ echo path4/ >>expected2
+'
-test_expect_success \
- 'git ls-files --others --directory should not get confused.' \
- 'test_cmp expected2 output'
+test_expect_success 'ls-files --others' '
+ git ls-files --others >output &&
+ test_cmp expected1 output
+'
-test_expect_success \
- 'git ls-files --others --directory --no-empty-directory to show output.' \
- 'git ls-files --others --directory --no-empty-directory >output'
+test_expect_success 'ls-files --others --directory' '
+ git ls-files --others --directory >output &&
+ test_cmp expected2 output
+'
-test_expect_success \
- '--no-empty-directory hides empty directory' \
- 'test_cmp expected3 output'
+test_expect_success '--no-empty-directory hides empty directory' '
+ git ls-files --others --directory --no-empty-directory >output &&
+ test_cmp expected3 output
+'
test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 6d2f2b6..c8fe978 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -156,7 +156,7 @@ test_expect_success 'trailing slash in exclude allows directory match (2)' '
test_expect_success 'trailing slash in exclude forces directory match (1)' '
- >two
+ >two &&
git ls-files --others --exclude=two/ >output &&
grep "^two" output
diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh
new file mode 100755
index 0000000..490e052
--- /dev/null
+++ b/t/t3004-ls-files-basic.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='basic ls-files tests
+
+This test runs git ls-files with various unusual or malformed
+command-line arguments.
+'
+
+. ./test-lib.sh
+
+>empty
+
+test_expect_success 'ls-files in empty repository' '
+ git ls-files >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonexistent path' '
+ git ls-files doesnotexist >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success 'ls-files with nonsense option' '
+ test_expect_code 129 git ls-files --nonsense 2>actual &&
+ grep "[Uu]sage: git ls-files" actual
+'
+
+test_expect_success 'ls-files -h in corrupt repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git ls-files -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage: git ls-files " broken/usage
+'
+
+test_done
diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh
index a7d8187..ca01053 100755
--- a/t/t3020-ls-files-error-unmatch.sh
+++ b/t/t3020-ls-files-error-unmatch.sh
@@ -26,4 +26,3 @@ test_expect_success \
'git ls-files --error-unmatch foo bar'
test_done
-1
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index d541544..0c02d56 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -23,6 +23,12 @@ test_expect_success 'setup 1' '
git branch df-3 &&
git branch remove &&
git branch submod &&
+ git branch copy &&
+ git branch rename &&
+ if test_have_prereq SYMLINKS
+ then
+ git branch rename-ln
+ fi &&
echo hello >>a &&
cp a d/e &&
@@ -248,6 +254,31 @@ test_expect_success 'setup 7' '
git commit -m "make d/ a submodule"
'
+test_expect_success 'setup 8' '
+ git checkout rename &&
+ git mv a e &&
+ git add e &&
+ test_tick &&
+ git commit -m "rename a->e" &&
+ if test_have_prereq SYMLINKS
+ then
+ git checkout rename-ln &&
+ git mv a e &&
+ ln -s e a &&
+ git add a e &&
+ test_tick &&
+ git commit -m "rename a->e, symlink a->e"
+ fi
+'
+
+test_expect_success 'setup 9' '
+ git checkout copy &&
+ cp a e &&
+ git add e &&
+ test_tick &&
+ git commit -m "copy a->e"
+'
+
test_expect_success 'merge-recursive simple' '
rm -fr [abcd] &&
@@ -288,13 +319,13 @@ test_expect_success 'fail if the index has unresolved entries' '
test_must_fail git merge "$c5" &&
test_must_fail git merge "$c5" 2> out &&
- grep "not possible because you have unmerged files" out &&
+ test_i18ngrep "not possible because you have unmerged files" out &&
git add -u &&
test_must_fail git merge "$c5" 2> out &&
- grep "You have not concluded your merge" out &&
+ test_i18ngrep "You have not concluded your merge" out &&
rm -f .git/MERGE_HEAD &&
test_must_fail git merge "$c5" 2> out &&
- grep "Your local changes to .* would be overwritten by merge." out
+ test_i18ngrep "Your local changes to the following files would be overwritten by merge:" out
'
test_expect_success 'merge-recursive remove conflict' '
@@ -526,7 +557,7 @@ test_expect_success 'reset and bind merge' '
echo "100644 $o0 0 c"
echo "100644 $o1 0 d/e"
) >expected &&
- test_cmp expected actual
+ test_cmp expected actual &&
git read-tree --prefix=z/ master &&
git ls-files -s >actual &&
@@ -580,4 +611,43 @@ test_expect_failure 'merge-recursive simple w/submodule result' '
test_cmp expected actual
'
+test_expect_success 'merge-recursive copy vs. rename' '
+ git checkout -f copy &&
+ git merge rename &&
+ ( git ls-tree -r HEAD && git ls-files -s ) >actual &&
+ (
+ echo "100644 blob $o0 b"
+ echo "100644 blob $o0 c"
+ echo "100644 blob $o0 d/e"
+ echo "100644 blob $o0 e"
+ echo "100644 $o0 0 b"
+ echo "100644 $o0 0 c"
+ echo "100644 $o0 0 d/e"
+ echo "100644 $o0 0 e"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+if test_have_prereq SYMLINKS
+then
+ test_expect_success 'merge-recursive rename vs. rename/symlink' '
+
+ git checkout -f rename &&
+ git merge rename-ln &&
+ ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+ (
+ echo "100644 blob $o0 b"
+ echo "100644 blob $o0 c"
+ echo "100644 blob $o0 d/e"
+ echo "100644 blob $o0 e"
+ echo "100644 $o0 0 b"
+ echo "100644 $o0 0 c"
+ echo "100644 $o0 0 d/e"
+ echo "100644 $o0 0 e"
+ ) >expected &&
+ test_cmp expected actual
+ '
+fi
+
+
test_done
diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-options.sh
new file mode 100755
index 0000000..2b17311
--- /dev/null
+++ b/t/t3032-merge-recursive-options.sh
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+test_description='merge-recursive options
+
+* [master] Clarify
+ ! [remote] Remove cruft
+--
+ + [remote] Remove cruft
+* [master] Clarify
+*+ [remote^] Initial revision
+* ok 1: setup
+'
+
+. ./test-lib.sh
+
+test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
+test_have_prereq MINGW && export GREP_OPTIONS=-U
+
+test_expect_success 'setup' '
+ conflict_hunks () {
+ sed $SED_OPTIONS -n -e "
+ /^<<<</ b conflict
+ b
+ : conflict
+ p
+ /^>>>>/ b
+ n
+ b conflict
+ " "$@"
+ } &&
+
+ cat <<-\EOF >text.txt &&
+ Hope, he says, cherishes the soul of him who lives in
+ justice and holiness and is the nurse of his age and the
+ companion of his journey;--hope which is mightiest to sway
+ the restless soul of man.
+
+ How admirable are his words! And the great blessing of riches, I do
+ not say to every man, but to a good man, is, that he has had no
+ occasion to deceive or to defraud others, either intentionally or
+ unintentionally; and when he departs to the world below he is not in
+ any apprehension about offerings due to the gods or debts which he owes
+ to men. Now to this peace of mind the possession of wealth greatly
+ contributes; and therefore I say, that, setting one thing against
+ another, of the many advantages which wealth has to give, to a man of
+ sense this is in my opinion the greatest.
+
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ it?--to speak the truth and to pay your debts--no more than this? And
+ even to this are there not exceptions? Suppose that a friend when in
+ his right mind has deposited arms with me and he asks for them when he
+ is not in his right mind, ought I to give them back to him? No one
+ would say that I ought or that I should be right in doing so, any more
+ than they would say that I ought always to speak the truth to one who
+ is in his condition.
+
+ You are quite right, he replied.
+
+ But then, I said, speaking the truth and paying your debts is not a
+ correct definition of justice.
+
+ CEPHALUS - SOCRATES - POLEMARCHUS
+
+ Quite correct, Socrates, if Simonides is to be believed, said
+ Polemarchus interposing.
+
+ I fear, said Cephalus, that I must go now, for I have to look after the
+ sacrifices, and I hand over the argument to Polemarchus and the company.
+ EOF
+ git add text.txt &&
+ test_tick &&
+ git commit -m "Initial revision" &&
+
+ git checkout -b remote &&
+ sed -e "
+ s/\. /\. /g
+ s/[?] /? /g
+ s/ / /g
+ s/--/---/g
+ s/but as concerning/but as con cerning/
+ /CEPHALUS - SOCRATES - POLEMARCHUS/ d
+ " text.txt >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Remove cruft" &&
+
+ git checkout master &&
+ sed -e "
+ s/\(not in his right mind\),\(.*\)/\1;\2Q/
+ s/Quite correct\(.*\)/It is too correct\1Q/
+ s/unintentionally/un intentionally/
+ /un intentionally/ s/$/Q/
+ s/Polemarchus interposing./Polemarchus, interposing.Q/
+ /justice and holiness/ s/$/Q/
+ /pay your debts/ s/$/Q/
+ " text.txt | q_to_cr >text.txt+ &&
+ mv text.txt+ text.txt &&
+ git commit -a -m "Clarify" &&
+ git show-branch --all
+'
+
+test_expect_success 'naive merge fails' '
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive HEAD^ -- HEAD remote &&
+ test_must_fail git update-index --refresh &&
+ grep "<<<<<<" text.txt
+'
+
+test_expect_success '--ignore-space-change makes merge succeed' '
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote
+'
+
+test_expect_success 'naive cherry-pick fails' '
+ git read-tree --reset -u HEAD &&
+ test_must_fail git cherry-pick --no-commit remote &&
+ git read-tree --reset -u HEAD &&
+ test_must_fail git cherry-pick remote &&
+ test_must_fail git update-index --refresh &&
+ grep "<<<<<<" text.txt
+'
+
+test_expect_success '-Xignore-space-change makes cherry-pick succeed' '
+ git read-tree --reset -u HEAD &&
+ git cherry-pick --no-commit -Xignore-space-change remote
+'
+
+test_expect_success '--ignore-space-change: our w/s-only change wins' '
+ q_to_cr <<-\EOF >expected &&
+ justice and holiness and is the nurse of his age and theQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "justice and holiness" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: their real change wins over w/s' '
+ cat <<-\EOF >expected &&
+ it?---to speak the truth and to pay your debts---no more than this? And
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "pay your debts" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-change: does not ignore new spaces' '
+ cat <<-\EOF >expected1 &&
+ Well said, Cephalus, I replied; but as con cerning justice, what is
+ EOF
+ q_to_cr <<-\EOF >expected2 &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-space-change HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual1 &&
+ grep "when he departs" text.txt >actual2 &&
+ test_cmp expected1 actual1 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success '--ignore-all-space drops their new spaces' '
+ cat <<-\EOF >expected &&
+ Well said, Cephalus, I replied; but as concerning justice, what is
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "Well said" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-all-space keeps our new spaces' '
+ q_to_cr <<-\EOF >expected &&
+ un intentionally; and when he departs to the world below he is not inQ
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ git merge-recursive --ignore-all-space HEAD^ -- HEAD remote &&
+ grep "when he departs" text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '--ignore-space-at-eol' '
+ q_to_cr <<-\EOF >expected &&
+ <<<<<<< HEAD
+ is not in his right mind; ought I to give them back to him? No oneQ
+ =======
+ is not in his right mind, ought I to give them back to him? No one
+ >>>>>>> remote
+ EOF
+
+ git read-tree --reset -u HEAD &&
+ test_must_fail git merge-recursive --ignore-space-at-eol \
+ HEAD^ -- HEAD remote &&
+ conflict_hunks text.txt >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3050-subprojects-fetch.sh b/t/t3050-subprojects-fetch.sh
index 4261e96..2f5f41a 100755
--- a/t/t3050-subprojects-fetch.sh
+++ b/t/t3050-subprojects-fetch.sh
@@ -10,10 +10,10 @@ test_expect_success setup '
cd sub &&
git init &&
>subfile &&
- git add subfile
+ git add subfile &&
git commit -m "subproject commit #1"
) &&
- >mainfile
+ >mainfile &&
git add sub mainfile &&
test_tick &&
git commit -m "superproject commit #1"
diff --git a/t/t3060-ls-files-with-tree.sh b/t/t3060-ls-files-with-tree.sh
index 3ce501b..61c1f53 100755
--- a/t/t3060-ls-files-with-tree.sh
+++ b/t/t3060-ls-files-with-tree.sh
@@ -53,17 +53,15 @@ test_expect_success setup '
git add .
'
-# We have to run from a sub-directory to trigger prune_path
-# Then we finally get to run our --with-tree test
-cd sub
-
test_expect_success 'git -ls-files --with-tree should succeed from subdir' '
-
- git ls-files --with-tree=HEAD~1 >../output
-
+ # We have to run from a sub-directory to trigger prune_path
+ # Then we finally get to run our --with-tree test
+ (
+ cd sub &&
+ git ls-files --with-tree=HEAD~1 >../output
+ )
'
-cd ..
test_expect_success \
'git -ls-files --with-tree should add entries from named tree.' \
'test_cmp expected output'
diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
index eee0d34..81d90b6 100755
--- a/t/t3100-ls-tree-restrict.sh
+++ b/t/t3100-ls-tree-restrict.sh
@@ -165,4 +165,13 @@ test_expect_success \
EOF
test_output'
+test_expect_success \
+ 'ls-tree with one path a prefix of the other' \
+ 'git ls-tree $tree path2/baz path2/bazbo >current &&
+ make_expected <<\EOF &&
+040000 tree X path2/baz
+120000 blob X path2/bazbo
+EOF
+ test_output'
+
test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 06654c6..026f9f8 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -21,33 +21,32 @@ entries. Also test odd filename and missing entries handling.
'
. ./test-lib.sh
-test_expect_success \
- 'setup' \
- 'echo 111 >1.txt &&
- echo 222 >2.txt &&
- mkdir path0 path0/a path0/a/b path0/a/b/c &&
- echo 111 >path0/a/b/c/1.txt &&
- mkdir path1 path1/b path1/b/c &&
- echo 111 >path1/b/c/1.txt &&
- mkdir path2 &&
- echo 111 >path2/1.txt &&
- mkdir path3 &&
- echo 111 >path3/1.txt &&
- echo 222 >path3/2.txt &&
- find *.txt path* \( -type f -o -type l \) -print |
- xargs git update-index --add &&
- tree=`git write-tree` &&
- echo $tree'
+test_expect_success 'setup' '
+ echo 111 >1.txt &&
+ echo 222 >2.txt &&
+ mkdir path0 path0/a path0/a/b path0/a/b/c &&
+ echo 111 >path0/a/b/c/1.txt &&
+ mkdir path1 path1/b path1/b/c &&
+ echo 111 >path1/b/c/1.txt &&
+ mkdir path2 &&
+ echo 111 >path2/1.txt &&
+ mkdir path3 &&
+ echo 111 >path3/1.txt &&
+ echo 222 >path3/2.txt &&
+ find *.txt path* \( -type f -o -type l \) -print |
+ xargs git update-index --add &&
+ tree=`git write-tree` &&
+ echo $tree
+'
test_output () {
- sed -e "s/ $_x40 / X /" <current >check
- test_cmp expected check
+ sed -e "s/ $_x40 / X /" <current >check &&
+ test_cmp expected check
}
-test_expect_success \
- 'ls-tree plain' \
- 'git ls-tree $tree >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree plain' '
+ git ls-tree $tree >current &&
+ cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X 2.txt
040000 tree X path0
@@ -55,13 +54,13 @@ test_expect_success \
040000 tree X path2
040000 tree X path3
EOF
- test_output'
+ test_output
+'
# Recursive does not show tree nodes anymore...
-test_expect_success \
- 'ls-tree recursive' \
- 'git ls-tree -r $tree >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree recursive' '
+ git ls-tree -r $tree >current &&
+ cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X 2.txt
100644 blob X path0/a/b/c/1.txt
@@ -70,68 +69,71 @@ test_expect_success \
100644 blob X path3/1.txt
100644 blob X path3/2.txt
EOF
- test_output'
+ test_output
+'
-test_expect_success \
- 'ls-tree filter 1.txt' \
- 'git ls-tree $tree 1.txt >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree filter 1.txt' '
+ git ls-tree $tree 1.txt >current &&
+ cat >expected <<\EOF &&
100644 blob X 1.txt
EOF
- test_output'
+ test_output
+'
-test_expect_success \
- 'ls-tree filter path1/b/c/1.txt' \
- 'git ls-tree $tree path1/b/c/1.txt >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree filter path1/b/c/1.txt' '
+ git ls-tree $tree path1/b/c/1.txt >current &&
+ cat >expected <<\EOF &&
100644 blob X path1/b/c/1.txt
EOF
- test_output'
+ test_output
+'
-test_expect_success \
- 'ls-tree filter all 1.txt files' \
- 'git ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree filter all 1.txt files' '
+ git ls-tree $tree 1.txt path0/a/b/c/1.txt \
+ path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+ cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X path0/a/b/c/1.txt
100644 blob X path1/b/c/1.txt
100644 blob X path2/1.txt
100644 blob X path3/1.txt
EOF
- test_output'
+ test_output
+'
# I am not so sure about this one after ls-tree doing pathspec match.
# Having both path0/a and path0/a/b/c makes path0/a redundant, and
# it behaves as if path0/a/b/c, path1/b/c, path2 and path3 are specified.
-test_expect_success \
- 'ls-tree filter directories' \
- 'git ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree filter directories' '
+ git ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+ cat >expected <<\EOF &&
040000 tree X path0/a/b/c
040000 tree X path1/b/c
040000 tree X path2
040000 tree X path3
EOF
- test_output'
+ test_output
+'
# Again, duplicates are filtered away so this is equivalent to
# having 1.txt and path3
-test_expect_success \
- 'ls-tree filter odd names' \
- 'git ls-tree $tree 1.txt ./1.txt .//1.txt path3/1.txt path3/./1.txt path3 path3// >current &&
- cat >expected <<\EOF &&
+test_expect_success 'ls-tree filter odd names' '
+ git ls-tree $tree 1.txt ./1.txt .//1.txt \
+ path3/1.txt path3/./1.txt path3 path3// >current &&
+ cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X path3/1.txt
100644 blob X path3/2.txt
EOF
- test_output'
+ test_output
+'
-test_expect_success \
- 'ls-tree filter missing files and extra slashes' \
- 'git ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
- cat >expected <<\EOF &&
-EOF
- test_output'
+test_expect_success 'ls-tree filter missing files and extra slashes' '
+ git ls-tree $tree 1.txt/ abc.txt \
+ path3//23.txt path3/2.txt/// >current &&
+ >expected &&
+ test_output
+'
test_expect_success 'ls-tree filter is leading path match' '
git ls-tree $tree pa path3/a >current &&
@@ -198,7 +200,7 @@ EOF
'
test_expect_success 'ls-tree --name-only' '
- git ls-tree --name-only $tree >current
+ git ls-tree --name-only $tree >current &&
cat >expected <<\EOF &&
1.txt
2.txt
@@ -211,7 +213,7 @@ EOF
'
test_expect_success 'ls-tree --name-only -r' '
- git ls-tree --name-only -r $tree >current
+ git ls-tree --name-only -r $tree >current &&
cat >expected <<\EOF &&
1.txt
2.txt
diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh
new file mode 100755
index 0000000..c286854
--- /dev/null
+++ b/t/t3102-ls-tree-wildcards.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='ls-tree with(out) globs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir a aa "a[a]" &&
+ touch a/one aa/two "a[a]/three" &&
+ git add a/one aa/two "a[a]/three" &&
+ git commit -m test
+'
+
+test_expect_success 'ls-tree a[a] matches literally' '
+ cat >expected <<EOF &&
+100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three
+EOF
+ git ls-tree -r HEAD "a[a]" >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 859b99a..9e69c8c 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -26,6 +26,17 @@ test_expect_success \
! test -f .git/refs/heads/--help
'
+test_expect_success 'branch -h in broken repository' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/refs/heads/master &&
+ test_expect_code 129 git branch -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
test_expect_success \
'git branch abc should create a branch' \
'git branch abc && test -f .git/refs/heads/abc'
@@ -35,7 +46,7 @@ test_expect_success \
'git branch a/b/c && test -f .git/refs/heads/a/b/c'
cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
+$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
@@ -195,7 +206,9 @@ test_expect_success 'test deleting branch deletes branch config' \
test_expect_success 'test deleting branch without config' \
'git branch my7 s &&
sha1=$(git rev-parse my7 | cut -c 1-7) &&
- test "$(git branch -d my7 2>&1)" = "Deleted branch my7 (was $sha1)."'
+ echo "Deleted branch my7 (was $sha1)." >expect &&
+ git branch -d my7 >actual 2>&1 &&
+ test_i18ncmp expect actual'
test_expect_success 'test --track without .fetch entries' \
'git branch --track my8 &&
@@ -212,9 +225,14 @@ test_expect_success \
'branch from non-branch HEAD w/--track causes failure' \
'test_must_fail git branch --track my10 HEAD^'
+test_expect_success \
+ 'branch from tag w/--track causes failure' \
+ 'git tag foobar &&
+ test_must_fail git branch --track my11 foobar'
+
# Keep this test last, as it changes the current branch
cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
+$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
test_expect_success \
'git checkout -b g/h/i -l should create a branch and a log' \
@@ -228,24 +246,21 @@ test_expect_success 'checkout -b makes reflog by default' '
git checkout master &&
git config --unset core.logAllRefUpdates &&
git checkout -b alpha &&
- test -f .git/logs/refs/heads/alpha &&
- PAGER= git reflog show alpha
+ git rev-parse --verify alpha@{0}
'
test_expect_success 'checkout -b does not make reflog when core.logAllRefUpdates = false' '
git checkout master &&
git config core.logAllRefUpdates false &&
git checkout -b beta &&
- ! test -f .git/logs/refs/heads/beta &&
- test_must_fail PAGER= git reflog show beta
+ test_must_fail git rev-parse --verify beta@{0}
'
test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates = false' '
git checkout master &&
git checkout -lb gamma &&
git config --unset core.logAllRefUpdates &&
- test -f .git/logs/refs/heads/gamma &&
- PAGER= git reflog show gamma
+ git rev-parse --verify gamma@{0}
'
test_expect_success 'avoid ambiguous track' '
@@ -480,6 +495,15 @@ test_expect_success 'autosetuprebase always on an untracked remote branch' '
test "z$(git config branch.myr20.rebase)" = z
'
+test_expect_success 'autosetuprebase always on detached HEAD' '
+ git config branch.autosetupmerge always &&
+ test_when_finished git checkout master &&
+ git checkout HEAD^0 &&
+ git branch my11 &&
+ test -z "$(git config branch.my11.remote)" &&
+ test -z "$(git config branch.my11.merge)"
+'
+
test_expect_success 'detect misconfigured autosetuprebase (bad value)' '
git config branch.autosetuprebase garbage &&
test_must_fail git branch
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index 809d1c4..6b7c118 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -12,13 +12,13 @@ test_expect_success 'make commits' '
'
test_expect_success 'make branches' '
- git branch branch-one
+ git branch branch-one &&
git branch branch-two HEAD^
'
test_expect_success 'make remote branches' '
- git update-ref refs/remotes/origin/branch-one branch-one
- git update-ref refs/remotes/origin/branch-two branch-two
+ git update-ref refs/remotes/origin/branch-one branch-one &&
+ git update-ref refs/remotes/origin/branch-two branch-two &&
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/branch-one
'
@@ -75,7 +75,7 @@ EOF
test_expect_success 'git branch shows detached HEAD properly' '
git checkout HEAD^0 &&
git branch >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index 5251740..cd04361 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -60,6 +60,12 @@ test_expect_success 'see if git pack-refs --prune remove ref files' '
! test -f .git/refs/heads/f
'
+test_expect_success 'see if git pack-refs --prune removes empty dirs' '
+ git branch r/s/t &&
+ git pack-refs --all --prune &&
+ ! test -e .git/refs/heads/r
+'
+
test_expect_success \
'git branch g should work when git branch g/h has been deleted' \
'git branch g/h &&
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index db46d53..5e29a05 100755
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
@@ -24,101 +24,131 @@ EOF
cat 2>/dev/null >"$p1" "$p0"
echo 'Foo Bar Baz' >"$p2"
-test -f "$p1" && cmp "$p0" "$p1" || {
+if test -f "$p1" && cmp "$p0" "$p1"
+then
+ test_set_prereq TABS_IN_FILENAMES
+else
# since FAT/NTFS does not allow tabs in filenames, skip this test
- say 'Your filesystem does not allow tabs in filenames, test skipped.'
- test_done
-}
+ say 'Your filesystem does not allow tabs in filenames'
+fi
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
echo 'just space
no-funny' >expected
-test_expect_success 'git ls-files no-funny' \
+"
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \
'git update-index --add "$p0" "$p2" &&
git ls-files >current &&
test_cmp expected current'
-t0=`git write-tree`
-echo "$t0" >t0
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
+t0=`git write-tree` &&
+echo "$t0" >t0 &&
cat > expected <<\EOF
just space
no-funny
"tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git ls-files with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files with-funny' \
'git update-index --add "$p1" &&
git ls-files >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
echo 'just space
no-funny
-tabs ," (dq) and spaces' >expected
-test_expect_success 'git ls-files -z with-funny' \
+tabs ,\" (dq) and spaces' >expected
+"
+
+test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \
'git ls-files -z | perl -pe y/\\000/\\012/ >current &&
test_cmp expected current'
-t1=`git write-tree`
-echo "$t1" >t1
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
+t1=`git write-tree` &&
+echo "$t1" >t1 &&
cat > expected <<\EOF
just space
no-funny
"tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git ls-tree with funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git ls-tree with funny' \
'git ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat > expected <<\EOF
A "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git diff-index with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-index with-funny' \
'git diff-index --name-status $t0 >current &&
test_cmp expected current'
-test_expect_success 'git diff-tree with-funny' \
+test_expect_success TABS_IN_FILENAMES 'git diff-tree with-funny' \
'git diff-tree --name-status $t0 $t1 >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' "
echo 'A
-tabs ," (dq) and spaces' >expected
-test_expect_success 'git diff-index -z with-funny' \
+tabs ,\" (dq) and spaces' >expected
+"
+
+test_expect_success TABS_IN_FILENAMES 'git diff-index -z with-funny' \
'git diff-index -z --name-status $t0 | perl -pe y/\\000/\\012/ >current &&
test_cmp expected current'
-test_expect_success 'git diff-tree -z with-funny' \
+test_expect_success TABS_IN_FILENAMES 'git diff-tree -z with-funny' \
'git diff-tree -z --name-status $t0 $t1 | perl -pe y/\\000/\\012/ >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat > expected <<\EOF
CNUM no-funny "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git diff-tree -C with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree -C with-funny' \
'git diff-tree -C --find-copies-harder --name-status \
$t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat > expected <<\EOF
RNUM no-funny "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git diff-tree delete with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
'git update-index --force-remove "$p0" &&
git diff-index -M --name-status \
$t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat > expected <<\EOF
diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
similarity index NUM%
rename from no-funny
rename to "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git diff-tree delete with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
'git diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
test_cmp expected current'
-chmod +x "$p1"
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
+chmod +x "$p1" &&
cat > expected <<\EOF
diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
old mode 100644
@@ -127,31 +157,39 @@ similarity index NUM%
rename from no-funny
rename to "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git diff-tree delete with-funny' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
'git diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat >expected <<\EOF
"tabs\t,\" (dq) and spaces"
1 files changed, 0 insertions(+), 0 deletions(-)
EOF
-test_expect_success 'git diff-tree rename with-funny applied' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree rename with-funny applied' \
'git diff-index -M -p $t0 |
git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
test_cmp expected current'
+test_expect_success TABS_IN_FILENAMES 'setup expect' '
cat > expected <<\EOF
no-funny
"tabs\t,\" (dq) and spaces"
2 files changed, 3 insertions(+), 3 deletions(-)
EOF
-test_expect_success 'git diff-tree delete with-funny applied' \
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny applied' \
'git diff-index -p $t0 |
git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
test_cmp expected current'
-test_expect_success 'git apply non-git diff' \
+test_expect_success TABS_IN_FILENAMES 'git apply non-git diff' \
'git diff-index -p $t0 |
sed -ne "/^[-+@]/p" |
git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 2d67a40..16de05a 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -52,7 +52,7 @@ test_expect_success 'refusing to edit notes in refs/remotes/' '
# 1 indicates caught gracefully by die, 128 means git-show barked
test_expect_success 'handle empty notes gracefully' '
- git notes show ; test 1 = $?
+ test_expect_code 1 git notes show
'
test_expect_success 'show non-existent notes entry with %N' '
@@ -101,8 +101,8 @@ test_expect_success 'edit existing notes' '
test_must_fail git notes show HEAD^
'
-test_expect_success 'cannot add note where one exists' '
- ! MSG=b2 git notes add &&
+test_expect_success 'cannot "git notes add -m" where notes already exists' '
+ test_must_fail git notes add -m "b2" &&
test ! -f .git/NOTES_EDITMSG &&
test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
test b3 = $(git notes show) &&
@@ -110,6 +110,24 @@ test_expect_success 'cannot add note where one exists' '
test_must_fail git notes show HEAD^
'
+test_expect_success 'can overwrite existing note with "git notes add -f -m"' '
+ git notes add -f -m "b1" &&
+ test ! -f .git/NOTES_EDITMSG &&
+ test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+ test b1 = $(git notes show) &&
+ git show HEAD^ &&
+ test_must_fail git notes show HEAD^
+'
+
+test_expect_success 'add w/no options on existing note morphs into edit' '
+ MSG=b2 git notes add &&
+ test ! -f .git/NOTES_EDITMSG &&
+ test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+ test b2 = $(git notes show) &&
+ git show HEAD^ &&
+ test_must_fail git notes show HEAD^
+'
+
test_expect_success 'can overwrite existing note with "git notes add -f"' '
MSG=b1 git notes add -f &&
test ! -f .git/NOTES_EDITMSG &&
@@ -194,6 +212,13 @@ test_expect_success 'show -F notes' '
test_cmp expect-F output
'
+test_expect_success 'Re-adding -F notes without -f fails' '
+ echo "zyxxy" > note5 &&
+ test_must_fail git notes add -F note5 &&
+ git log -3 > output &&
+ test_cmp expect-F output
+'
+
cat >expect << EOF
commit 15023535574ded8b1a89052b32673f84cf9582b8
tree e070e3af51011e47b183c33adf9736736a525709
@@ -247,6 +272,44 @@ do
'
done
+test_expect_success 'setup alternate notes ref' '
+ git notes --ref=alternate add -m alternate
+'
+
+test_expect_success 'git log --notes shows default notes' '
+ git log -1 --notes >output &&
+ grep xyzzy output &&
+ ! grep alternate output
+'
+
+test_expect_success 'git log --notes=X shows only X' '
+ git log -1 --notes=alternate >output &&
+ ! grep xyzzy output &&
+ grep alternate output
+'
+
+test_expect_success 'git log --notes --notes=X shows both' '
+ git log -1 --notes --notes=alternate >output &&
+ grep xyzzy output &&
+ grep alternate output
+'
+
+test_expect_success 'git log --no-notes resets default state' '
+ git log -1 --notes --notes=alternate \
+ --no-notes --notes=alternate \
+ >output &&
+ ! grep xyzzy output &&
+ grep alternate output
+'
+
+test_expect_success 'git log --no-notes resets ref list' '
+ git log -1 --notes --notes=alternate \
+ --no-notes --notes \
+ >output &&
+ grep xyzzy output &&
+ ! grep alternate output
+'
+
test_expect_success 'create -m notes (setup)' '
: > a5 &&
git add a5 &&
@@ -299,7 +362,7 @@ cat expect-F >> expect-rm-F
test_expect_success 'verify note removal with -F /dev/null' '
git log -4 > output &&
test_cmp expect-rm-F output &&
- ! git notes show
+ test_must_fail git notes show
'
test_expect_success 'do not create empty note with -m "" (setup)' '
@@ -309,7 +372,7 @@ test_expect_success 'do not create empty note with -m "" (setup)' '
test_expect_success 'verify non-creation of note with -m ""' '
git log -4 > output &&
test_cmp expect-rm-F output &&
- ! git notes show
+ test_must_fail git notes show
'
cat > expect-combine_m_and_F << EOF
@@ -357,7 +420,7 @@ cat expect-multiline >> expect-rm-remove
test_expect_success 'verify note removal with "git notes remove"' '
git log -4 > output &&
test_cmp expect-rm-remove output &&
- ! git notes show HEAD^
+ test_must_fail git notes show HEAD^
'
cat > expect << EOF
@@ -365,6 +428,88 @@ c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd7
c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
EOF
+test_expect_success 'removing non-existing note should not create new commit' '
+ git rev-parse --verify refs/notes/commits > before_commit &&
+ test_must_fail git notes remove HEAD^ &&
+ git rev-parse --verify refs/notes/commits > after_commit &&
+ test_cmp before_commit after_commit
+'
+
+test_expect_success 'removing more than one' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+
+ # We have only two -- add another and make sure it stays
+ git notes add -m "extra" &&
+ git notes list HEAD >after-removal-expect &&
+ git notes remove HEAD^^ HEAD^^^ &&
+ git notes list | sed -e "s/ .*//" >actual &&
+ test_cmp after-removal-expect actual
+'
+
+test_expect_success 'removing is atomic' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+ test_must_fail git notes remove HEAD^^ HEAD^^^ HEAD^ &&
+ after=$(git rev-parse --verify refs/notes/commits) &&
+ test "$before" = "$after"
+'
+
+test_expect_success 'removing with --ignore-missing' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+
+ # We have only two -- add another and make sure it stays
+ git notes add -m "extra" &&
+ git notes list HEAD >after-removal-expect &&
+ git notes remove --ignore-missing HEAD^^ HEAD^^^ HEAD^ &&
+ git notes list | sed -e "s/ .*//" >actual &&
+ test_cmp after-removal-expect actual
+'
+
+test_expect_success 'removing with --ignore-missing but bogus ref' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+ test_must_fail git notes remove --ignore-missing HEAD^^ HEAD^^^ NO-SUCH-COMMIT &&
+ after=$(git rev-parse --verify refs/notes/commits) &&
+ test "$before" = "$after"
+'
+
+test_expect_success 'remove reads from --stdin' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+
+ # We have only two -- add another and make sure it stays
+ git notes add -m "extra" &&
+ git notes list HEAD >after-removal-expect &&
+ git rev-parse HEAD^^ HEAD^^^ >input &&
+ git notes remove --stdin <input &&
+ git notes list | sed -e "s/ .*//" >actual &&
+ test_cmp after-removal-expect actual
+'
+
+test_expect_success 'remove --stdin is also atomic' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+ git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
+ test_must_fail git notes remove --stdin <input &&
+ after=$(git rev-parse --verify refs/notes/commits) &&
+ test "$before" = "$after"
+'
+
+test_expect_success 'removing with --stdin --ignore-missing' '
+ before=$(git rev-parse --verify refs/notes/commits) &&
+ test_when_finished "git update-ref refs/notes/commits $before" &&
+
+ # We have only two -- add another and make sure it stays
+ git notes add -m "extra" &&
+ git notes list HEAD >after-removal-expect &&
+ git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
+ git notes remove --ignore-missing --stdin <input &&
+ git notes list | sed -e "s/ .*//" >actual &&
+ test_cmp after-removal-expect actual
+'
+
test_expect_success 'list notes with "git notes list"' '
git notes list > output &&
test_cmp expect output
@@ -620,16 +765,16 @@ test_expect_success '--show-notes=ref accumulates' '
test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
git config core.notesRef refs/notes/other &&
- echo "Note on a tree" > expect
+ echo "Note on a tree" > expect &&
git notes add -m "Note on a tree" HEAD: &&
git notes show HEAD: > actual &&
test_cmp expect actual &&
- echo "Note on a blob" > expect
+ echo "Note on a blob" > expect &&
filename=$(git ls-tree --name-only HEAD | head -n1) &&
git notes add -m "Note on a blob" HEAD:$filename &&
git notes show HEAD:$filename > actual &&
test_cmp expect actual &&
- echo "Note on a tag" > expect
+ echo "Note on a tag" > expect &&
git tag -a -m "This is an annotated tag" foobar HEAD^ &&
git notes add -m "Note on a tag" foobar &&
git notes show foobar > actual &&
@@ -693,7 +838,11 @@ test_expect_success 'create note from non-existing note with "git notes add -c"
git add a10 &&
test_tick &&
git commit -m 10th &&
- test_must_fail MSG="yet another note" git notes add -c deadbeef &&
+ (
+ MSG="yet another note" &&
+ export MSG &&
+ test_must_fail git notes add -c deadbeef
+ ) &&
test_must_fail git notes list HEAD
'
@@ -951,6 +1100,7 @@ Date: Thu Apr 7 15:27:13 2005 -0700
Notes (other):
a fresh note
+$whitespace
another fresh note
EOF
@@ -972,8 +1122,11 @@ Date: Thu Apr 7 15:27:13 2005 -0700
Notes (other):
a fresh note
+$whitespace
another fresh note
+$whitespace
append 1
+$whitespace
append 2
EOF
@@ -1050,4 +1203,23 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' '
test_must_fail git notes copy one two three
'
+test_expect_success 'git notes get-ref (no overrides)' '
+ git config --unset core.notesRef &&
+ sane_unset GIT_NOTES_REF &&
+ test "$(git notes get-ref)" = "refs/notes/commits"
+'
+
+test_expect_success 'git notes get-ref (core.notesRef)' '
+ git config core.notesRef refs/notes/foo &&
+ test "$(git notes get-ref)" = "refs/notes/foo"
+'
+
+test_expect_success 'git notes get-ref (GIT_NOTES_REF)' '
+ test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar"
+'
+
+test_expect_success 'git notes get-ref (--ref)' '
+ test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz"
+'
+
test_done
diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh
index ee84fc4..e35d781 100755
--- a/t/t3302-notes-index-expensive.sh
+++ b/t/t3302-notes-index-expensive.sh
@@ -7,11 +7,9 @@ test_description='Test commit notes index (expensive!)'
. ./test-lib.sh
-test -z "$GIT_NOTES_TIMING_TESTS" && {
- say Skipping timing tests
- test_done
- exit
-}
+test_set_prereq NOT_EXPENSIVE
+test -n "$GIT_NOTES_TIMING_TESTS" && test_set_prereq EXPENSIVE
+test -x /usr/bin/time && test_set_prereq USR_BIN_TIME
create_repo () {
number_of_commits=$1
@@ -98,21 +96,31 @@ time_notes () {
for mode in no-notes notes
do
echo $mode
- /usr/bin/time sh ../time_notes $mode $1
+ /usr/bin/time "$SHELL_PATH" ../time_notes $mode $1
done
}
-for count in 10 100 1000 10000; do
+do_tests () {
+ pr=$1
+ count=$2
+
+ test_expect_success $pr 'setup / mkdir' '
+ mkdir $count &&
+ cd $count
+ '
- mkdir $count
- (cd $count;
+ test_expect_success $pr "setup $count" "create_repo $count"
- test_expect_success "setup $count" "create_repo $count"
+ test_expect_success $pr 'notes work' "test_notes $count"
- test_expect_success 'notes work' "test_notes $count"
+ test_expect_success USR_BIN_TIME,$pr 'notes timing with /usr/bin/time' "time_notes 100"
+
+ test_expect_success $pr 'teardown / cd ..' 'cd ..'
+}
- test_expect_success 'notes timing' "time_notes 100"
- )
+do_tests NOT_EXPENSIVE 10
+for count in 100 1000 10000; do
+ do_tests EXPENSIVE $count
done
test_done
diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
index 75ec187..704aee8 100755
--- a/t/t3303-notes-subtrees.sh
+++ b/t/t3303-notes-subtrees.sh
@@ -168,15 +168,16 @@ INPUT_END
}
verify_concatenated_notes () {
- git log | grep "^ " > output &&
- i=$number_of_commits &&
- while [ $i -gt 0 ]; do
- echo " commit #$i" &&
- echo " first note for commit #$i" &&
- echo " second note for commit #$i" &&
- i=$(($i-1));
- done > expect &&
- test_cmp expect output
+ git log | grep "^ " > output &&
+ i=$number_of_commits &&
+ while [ $i -gt 0 ]; do
+ echo " commit #$i" &&
+ echo " first note for commit #$i" &&
+ echo " " &&
+ echo " second note for commit #$i" &&
+ i=$(($i-1));
+ done > expect &&
+ test_cmp expect output
}
test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""'
diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh
index b455404..86bf909 100755
--- a/t/t3306-notes-prune.sh
+++ b/t/t3306-notes-prune.sh
@@ -20,6 +20,9 @@ test_expect_success 'setup: create a few commits with notes' '
git add file3 &&
test_tick &&
git commit -m 3rd &&
+ COMMIT_FILE=.git/objects/5e/e1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+ test -f $COMMIT_FILE &&
+ test-chmtime =+0 $COMMIT_FILE &&
git notes add -m "Note #3"
'
@@ -67,7 +70,7 @@ test_expect_success 'remove some commits' '
test_expect_success 'verify that commits are gone' '
- ! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+ test_must_fail git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&
git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f
'
@@ -106,7 +109,7 @@ test_expect_success 'prune notes' '
test_expect_success 'verify that notes are gone' '
- ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+ test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
'
@@ -130,8 +133,8 @@ test_expect_success 'prune -v notes' '
test_expect_success 'verify that notes are gone' '
- ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
- ! git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
+ test_must_fail git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&
+ test_must_fail git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&
git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f
'
diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh
index 3269f2e..2ea3be6 100755
--- a/t/t3307-notes-man.sh
+++ b/t/t3307-notes-man.sh
@@ -26,7 +26,7 @@ test_expect_success 'example 1: notes to add an Acked-by line' '
'
test_expect_success 'example 2: binary notes' '
- cp "$TEST_DIRECTORY"/test4012.png .
+ cp "$TEST_DIRECTORY"/test4012.png . &&
git checkout B &&
blob=$(git hash-object -w test4012.png) &&
git notes --ref=logo add -C "$blob" &&
diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
new file mode 100755
index 0000000..24d82b4
--- /dev/null
+++ b/t/t3308-notes-merge.sh
@@ -0,0 +1,368 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test merging of notes trees'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th &&
+ # Create notes on 4 first commits
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "Notes on 1st commit" 1st &&
+ git notes add -m "Notes on 2nd commit" 2nd &&
+ git notes add -m "Notes on 3rd commit" 3rd &&
+ git notes add -m "Notes on 4th commit" 4th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+5e93d24084d32e1cb61f7070505b9d2530cca987 $commit_sha4
+8366731eeee53787d2bdf8fc1eff7d94757e8da0 $commit_sha3
+eede89064cd42441590d6afec6c37b321ada3389 $commit_sha2
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+Notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+$commit_sha2 2nd
+Notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify initial notes (x)' '
+ verify_notes x
+'
+
+cp expect_notes_x expect_notes_y
+cp expect_log_x expect_log_y
+
+test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y)' '
+ test_must_fail git -c "core.notesRef=refs/notes/y" notes merge z
+'
+
+test_expect_success 'fail to merge into various non-notes refs' '
+ test_must_fail git -c "core.notesRef=refs/notes" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/" notes merge x &&
+ mkdir -p .git/refs/notes/dir &&
+ test_must_fail git -c "core.notesRef=refs/notes/dir" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/dir/" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/heads/master" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/y:" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/y:foo" notes merge x &&
+ test_must_fail git -c "core.notesRef=refs/notes/foo^{bar" notes merge x
+'
+
+test_expect_success 'fail to merge various non-note-trees' '
+ git config core.notesRef refs/notes/y &&
+ test_must_fail git notes merge refs/notes &&
+ test_must_fail git notes merge refs/notes/ &&
+ test_must_fail git notes merge refs/notes/dir &&
+ test_must_fail git notes merge refs/notes/dir/ &&
+ test_must_fail git notes merge refs/heads/master &&
+ test_must_fail git notes merge x: &&
+ test_must_fail git notes merge x:foo &&
+ test_must_fail git notes merge foo^{bar
+'
+
+test_expect_success 'merge notes into empty notes ref (x => y)' '
+ git config core.notesRef refs/notes/y &&
+ git notes merge x &&
+ verify_notes y &&
+ # x and y should point to the same notes commit
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+ git notes merge z &&
+ # y should not change (still == x)
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'change notes on other notes ref (y)' '
+ # Not touching notes to 1st commit
+ git notes remove 2nd &&
+ git notes append -m "More notes on 3rd commit" 3rd &&
+ git notes add -f -m "New notes on 4th commit" 4th &&
+ git notes add -m "Notes on 5th commit" 5th
+'
+
+test_expect_success 'merge previous notes commit (y^ => y) => No-op' '
+ pre_state="$(git rev-parse refs/notes/y)" &&
+ git notes merge y^ &&
+ # y should not move
+ test "$pre_state" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'verify changed notes on other notes ref (y)' '
+ verify_notes y
+'
+
+test_expect_success 'verify unchanged notes on original notes ref (x)' '
+ verify_notes x
+'
+
+test_expect_success 'merge original notes (x) into changed notes (y) => No-op' '
+ git notes merge -vvv x &&
+ verify_notes y &&
+ verify_notes x
+'
+
+cp expect_notes_y expect_notes_x
+cp expect_log_y expect_log_x
+
+test_expect_success 'merge changed (y) into original (x) => Fast-forward' '
+ git config core.notesRef refs/notes/x &&
+ git notes merge y &&
+ verify_notes x &&
+ verify_notes y &&
+ # x and y should point to same the notes commit
+ test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'merge empty notes ref (z => y)' '
+ # Prepare empty (but valid) notes ref (z)
+ git config core.notesRef refs/notes/z &&
+ git notes add -m "foo" &&
+ git notes remove &&
+ git notes >output_notes_z &&
+ test_cmp /dev/null output_notes_z &&
+ # Do the merge (z => y)
+ git config core.notesRef refs/notes/y &&
+ git notes merge z &&
+ verify_notes y &&
+ # y should no longer point to the same notes commit as x
+ test "$(git rev-parse refs/notes/x)" != "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_y
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+dec2502dac3ea161543f71930044deff93fa945c $commit_sha4
+4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+$commit_sha3 3rd
+Notes on 3rd commit
+
+More notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on other notes ref (y)' '
+ # Append to 1st commit notes
+ git notes append -m "More notes on 1st commit" 1st &&
+ # Add new notes to 2nd commit
+ git notes add -m "New notes on 2nd commit" 2nd &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+Notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes on notes ref (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes remove 3rd &&
+ git notes append -m "More notes on 4th commit" 4th &&
+ verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge y into x => Non-conflicting 3-way merge' '
+ git notes merge y &&
+ verify_notes x &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_w
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'create notes on new, separate notes ref (w)' '
+ git config core.notesRef refs/notes/w &&
+ # Add same note as refs/notes/y on 2nd commit
+ git notes add -m "New notes on 2nd commit" 2nd &&
+ # Add new note on 3rd commit (non-conflicting)
+ git notes add -m "New notes on 3rd commit" 3rd &&
+ # Verify state of notes on new, separate notes ref (w)
+ verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_x
+0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5
+1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4
+05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3
+d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2
+43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+Notes on 5th commit
+
+$commit_sha4 4th
+New notes on 4th commit
+
+More notes on 4th commit
+
+$commit_sha3 3rd
+New notes on 3rd commit
+
+$commit_sha2 2nd
+New notes on 2nd commit
+
+$commit_sha1 1st
+Notes on 1st commit
+
+More notes on 1st commit
+
+EOF
+
+test_expect_success 'merge w into x => Non-conflicting history-less merge' '
+ git config core.notesRef refs/notes/x &&
+ git notes merge w &&
+ # Verify new state of notes on other notes ref (x)
+ verify_notes x &&
+ # Also verify that nothing changed on other notes refs (y and w)
+ verify_notes y &&
+ verify_notes w
+'
+
+test_done
diff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh
new file mode 100755
index 0000000..461fd84
--- /dev/null
+++ b/t/t3309-notes-merge-auto-resolve.sh
@@ -0,0 +1,647 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with auto-resolving strategies'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with all kinds of potential conflicts
+test_expect_success 'setup commits' '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th &&
+ test_commit 6th &&
+ test_commit 7th &&
+ test_commit 8th &&
+ test_commit 9th &&
+ test_commit 10th &&
+ test_commit 11th &&
+ test_commit 12th &&
+ test_commit 13th &&
+ test_commit 14th &&
+ test_commit 15th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+commit_sha6=$(git rev-parse 6th^{commit})
+commit_sha7=$(git rev-parse 7th^{commit})
+commit_sha8=$(git rev-parse 8th^{commit})
+commit_sha9=$(git rev-parse 9th^{commit})
+commit_sha10=$(git rev-parse 10th^{commit})
+commit_sha11=$(git rev-parse 11th^{commit})
+commit_sha12=$(git rev-parse 12th^{commit})
+commit_sha13=$(git rev-parse 13th^{commit})
+commit_sha14=$(git rev-parse 14th^{commit})
+commit_sha15=$(git rev-parse 15th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ suffix="$2"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$suffix" &&
+ test_cmp "expect_notes_$suffix" "output_notes_$suffix" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$suffix" &&
+ test_cmp "expect_log_$suffix" "output_log_$suffix"
+}
+
+test_expect_success 'setup merge base (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "x notes on 6th commit" 6th &&
+ git notes add -m "x notes on 7th commit" 7th &&
+ git notes add -m "x notes on 8th commit" 8th &&
+ git notes add -m "x notes on 9th commit" 9th &&
+ git notes add -m "x notes on 10th commit" 10th &&
+ git notes add -m "x notes on 11th commit" 11th &&
+ git notes add -m "x notes on 12th commit" 12th &&
+ git notes add -m "x notes on 13th commit" 13th &&
+ git notes add -m "x notes on 14th commit" 14th &&
+ git notes add -m "x notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_x
+457a85d6c814ea208550f15fcc48f804ac8dc023 $commit_sha15
+b0c95b954301d69da2bc3723f4cb1680d355937c $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+dd161bc149470fd890dd4ab52a4cbd79bbd18c36 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6 $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+11d97fdebfa5ceee540a3da07bce6fa0222bc082 $commit_sha6
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha15 15th
+x notes on 15th commit
+
+$commit_sha14 14th
+x notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+x notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+x notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+x notes on 6th commit
+
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of merge base (x)' 'verify_notes x x'
+
+test_expect_success 'setup local branch (y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ git notes add -f -m "y notes on 3rd commit" 3rd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ git notes add -f -m "y notes on 5th commit" 5th &&
+ git notes remove 6th &&
+ git notes remove 7th &&
+ git notes remove 8th &&
+ git notes add -f -m "y notes on 12th commit" 12th &&
+ git notes add -f -m "y notes on 13th commit" 13th &&
+ git notes add -f -m "y notes on 14th commit" 14th &&
+ git notes add -f -m "y notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_y
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+x notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+x notes on 9th commit
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of local branch (y)' 'verify_notes y y'
+
+test_expect_success 'setup remote branch (z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "z notes on 2nd commit" 2nd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ git notes add -f -m "z notes on 5th commit" 5th &&
+ git notes remove 6th &&
+ git notes add -f -m "z notes on 8th commit" 8th &&
+ git notes remove 9th &&
+ git notes add -f -m "z notes on 11th commit" 11th &&
+ git notes remove 12th &&
+ git notes add -f -m "y notes on 14th commit" 14th &&
+ git notes add -f -m "z notes on 15th commit" 15th
+'
+
+cat <<EOF | sort >expect_notes_z
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+897003322b53bc6ca098e9324ee508362347e734 $commit_sha7
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+x notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+x notes on 7th commit
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'verify state of remote branch (z)' 'verify_notes z z'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x | local/y | remote/z | diff from x to y/z | result
+# -------|---------|---------|----------|----------------------------|-------
+# 1st | [none] | [none] | [none] | unchanged / unchanged | [none]
+# 2nd | [none] | [none] | 283b482 | unchanged / added | 283b482
+# 3rd | [none] | 5772f42 | [none] | added / unchanged | 5772f42
+# 4th | [none] | e2bfd06 | e2bfd06 | added / added (same) | e2bfd06
+# 5th | [none] | 154508c | 99fc34a | added / added (diff) | ???
+# 6th | 11d97fd | [none] | [none] | removed / removed | [none]
+# 7th | 8970033 | [none] | 8970033 | removed / unchanged | [none]
+# 8th | a3daf8a | [none] | 851e163 | removed / changed | ???
+# 9th | 20c613c | 20c613c | [none] | unchanged / removed | [none]
+# 10th | b8d03e1 | b8d03e1 | b8d03e1 | unchanged / unchanged | b8d03e1
+# 11th | 7abbc45 | 7abbc45 | 7e3c535 | unchanged / changed | 7e3c535
+# 12th | dd161bc | a66055f | [none] | changed / removed | ???
+# 13th | 5d30216 | 3a631fd | 5d30216 | changed / unchanged | 3a631fd
+# 14th | b0c95b9 | 5de7ea7 | 5de7ea7 | changed / changed (same) | 5de7ea7
+# 15th | 457a85d | 68b8630 | 9b4b2c6 | changed / changed (diff) | ???
+
+test_expect_success 'merge z into y with invalid strategy => Fail/No changes' '
+ git config core.notesRef refs/notes/y &&
+ test_must_fail git notes merge --strategy=foo z &&
+ # Verify no changes (y)
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_ours
+68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_ours <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "ours" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=ours z &&
+ verify_notes y ours
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_theirs
+9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_theirs <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "theirs" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=theirs z &&
+ verify_notes y theirs
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union
+7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+6c841cc36ea496027290967ca96bd2bef54dbb47 $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge z into y with "union" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=union z &&
+ verify_notes y union
+'
+
+test_expect_success 'reset to pre-merge state (y)' '
+ git update-ref refs/notes/y refs/notes/y^1 &&
+ # Verify pre-merge state
+ verify_notes y y
+'
+
+cat <<EOF | sort >expect_notes_union2
+d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_union2 <<EOF
+$commit_sha15 15th
+z notes on 15th commit
+
+y notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+z notes on 5th commit
+
+y notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "union" strategy => Non-conflicting 3-way merge' '
+ git config core.notesRef refs/notes/z &&
+ git notes merge --strategy=union y &&
+ verify_notes z union2
+'
+
+test_expect_success 'reset to pre-merge state (z)' '
+ git update-ref refs/notes/z refs/notes/z^1 &&
+ # Verify pre-merge state
+ verify_notes z z
+'
+
+cat <<EOF | sort >expect_notes_cat_sort_uniq
+6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15
+5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
+3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13
+a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12
+7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11
+b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10
+851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8
+660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+EOF
+
+cat >expect_log_cat_sort_uniq <<EOF
+$commit_sha15 15th
+y notes on 15th commit
+z notes on 15th commit
+
+$commit_sha14 14th
+y notes on 14th commit
+
+$commit_sha13 13th
+y notes on 13th commit
+
+$commit_sha12 12th
+y notes on 12th commit
+
+$commit_sha11 11th
+z notes on 11th commit
+
+$commit_sha10 10th
+x notes on 10th commit
+
+$commit_sha9 9th
+
+$commit_sha8 8th
+z notes on 8th commit
+
+$commit_sha7 7th
+
+$commit_sha6 6th
+
+$commit_sha5 5th
+y notes on 5th commit
+z notes on 5th commit
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflicting 3-way merge' '
+ git notes merge --strategy=cat_sort_uniq y &&
+ verify_notes z cat_sort_uniq
+'
+
+test_done
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
new file mode 100755
index 0000000..4ec4d11
--- /dev/null
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -0,0 +1,556 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging with manual conflict resolution'
+
+. ./test-lib.sh
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup commits' '
+ test_commit 1st &&
+ test_commit 2nd &&
+ test_commit 3rd &&
+ test_commit 4th &&
+ test_commit 5th
+'
+
+commit_sha1=$(git rev-parse 1st^{commit})
+commit_sha2=$(git rev-parse 2nd^{commit})
+commit_sha3=$(git rev-parse 3rd^{commit})
+commit_sha4=$(git rev-parse 4th^{commit})
+commit_sha5=$(git rev-parse 5th^{commit})
+
+verify_notes () {
+ notes_ref="$1"
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+cat <<EOF | sort >expect_notes_x
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+
+EOF
+
+test_expect_success 'setup merge base (x)' '
+ git config core.notesRef refs/notes/x &&
+ git notes add -m "x notes on 2nd commit" 2nd &&
+ git notes add -m "x notes on 3rd commit" 3rd &&
+ git notes add -m "x notes on 4th commit" 4th &&
+ verify_notes x
+'
+
+cat <<EOF | sort >expect_notes_y
+e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+b0a6021ec006d07e80e9b20ec9b444cbd9d560d3 $commit_sha1
+EOF
+
+cat >expect_log_y <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+
+$commit_sha1 1st
+y notes on 1st commit
+
+EOF
+
+test_expect_success 'setup local branch (y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ git notes add -f -m "y notes on 1st commit" 1st &&
+ git notes remove 2nd &&
+ git notes add -f -m "y notes on 3rd commit" 3rd &&
+ git notes add -f -m "y notes on 4th commit" 4th &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_z
+cff59c793c20bb49a4e01bc06fb06bad642e0d54 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'setup remote branch (z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "z notes on 1st commit" 1st &&
+ git notes add -f -m "z notes on 2nd commit" 2nd &&
+ git notes remove 3rd &&
+ git notes add -f -m "z notes on 4th commit" 4th &&
+ verify_notes z
+'
+
+# At this point, before merging z into y, we have the following status:
+#
+# commit | base/x | local/y | remote/z | diff from x to y/z
+# -------|---------|---------|----------|---------------------------
+# 1st | [none] | b0a6021 | 0a81da8 | added / added (diff)
+# 2nd | ceefa67 | [none] | 283b482 | removed / changed
+# 3rd | e5388c1 | 5772f42 | [none] | changed / removed
+# 4th | 6e8e3fe | e2bfd06 | cff59c7 | changed / changed (diff)
+# 5th | [none] | [none] | [none] | [none]
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha1
+$commit_sha2
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha1 <<EOF
+<<<<<<< refs/notes/m
+y notes on 1st commit
+=======
+z notes on 1st commit
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha2 <<EOF
+z notes on 2nd commit
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+y notes on 3rd commit
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+00494adecf2d9635a02fa431308d67993f853968 $commit_sha4
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+z notes on 4th commit
+
+More z notes on 4th commit
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+z notes on 1st commit
+
+EOF
+
+test_expect_success 'change notes in z' '
+ git notes --ref z append -m "More z notes on 4th commit" 4th &&
+ verify_notes z
+'
+
+test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' '
+ test -d .git/NOTES_MERGE_WORKTREE &&
+ test_must_fail git notes merge z >output 2>&1 &&
+ # Output should indicate what is wrong
+ grep -q "\\.git/NOTES_MERGE_\\* exists" output
+'
+
+# Setup non-conflicting merge between x and new notes ref w
+
+cat <<EOF | sort >expect_notes_w
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'setup unrelated notes ref (w)' '
+ git config core.notesRef refs/notes/w &&
+ git notes add -m "w notes on 1st commit" 1st &&
+ git notes add -m "x notes on 2nd commit" 2nd &&
+ verify_notes w
+'
+
+cat <<EOF | sort >expect_notes_w
+6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4
+e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3
+ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2
+f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1
+EOF
+
+cat >expect_log_w <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+x notes on 4th commit
+
+$commit_sha3 3rd
+x notes on 3rd commit
+
+$commit_sha2 2nd
+x notes on 2nd commit
+
+$commit_sha1 1st
+w notes on 1st commit
+
+EOF
+
+test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' '
+ test -d .git/NOTES_MERGE_WORKTREE &&
+ git notes merge x &&
+ verify_notes w &&
+ # Verify that other notes refs has not changed (x and y)
+ verify_notes x &&
+ verify_notes y
+'
+
+cat <<EOF | sort >expect_notes_m
+021faa20e931fb48986ffc6282b4bb05553ac946 $commit_sha4
+5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+
+$commit_sha4 4th
+y and z notes on 4th commit
+
+$commit_sha3 3rd
+y notes on 3rd commit
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'finalize conflicting merge (z => m)' '
+ # Resolve conflicts and finalize merge
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+ git notes merge --commit &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # Merge commit has pre-merge y and pre-merge z as parents
+ test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+ test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+ # Merge commit mentions the notes refs merged
+ git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+ grep -q refs/notes/m merge_commit_msg &&
+ grep -q refs/notes/z merge_commit_msg &&
+ # Merge commit mentions conflicting notes
+ grep -q "Conflicts" merge_commit_msg &&
+ ( for sha1 in $(cat expect_conflicts); do
+ grep -q "$sha1" merge_commit_msg ||
+ exit 1
+ done ) &&
+ # Verify contents of merge result
+ verify_notes m &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+cat >expect_conflict_$commit_sha4 <<EOF
+<<<<<<< refs/notes/m
+y notes on 4th commit
+=======
+z notes on 4th commit
+
+More z notes on 4th commit
+>>>>>>> refs/notes/z
+EOF
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+test_expect_success 'abort notes merge' '
+ git notes merge --abort &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # m has not moved (still == y)
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+git rev-parse refs/notes/y > pre_merge_y
+git rev-parse refs/notes/z > pre_merge_z
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cat <<EOF | sort >expect_notes_m
+304dfb4325cf243025b9957486eb605a9b51c199 $commit_sha5
+283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2
+0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1
+EOF
+
+cat >expect_log_m <<EOF
+$commit_sha5 5th
+new note on 5th commit
+
+$commit_sha4 4th
+
+$commit_sha3 3rd
+
+$commit_sha2 2nd
+z notes on 2nd commit
+
+$commit_sha1 1st
+y and z notes on 1st commit
+
+EOF
+
+test_expect_success 'add + remove notes in finalized merge (z => m)' '
+ # Resolve one conflict
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ # Remove another conflict
+ rm .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+ # Remove a D/F conflict
+ rm .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+ # Add a new note
+ echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 &&
+ # Finalize merge
+ git notes merge --commit &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # Merge commit has pre-merge y and pre-merge z as parents
+ test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
+ test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
+ # Merge commit mentions the notes refs merged
+ git log -1 --format=%B refs/notes/m > merge_commit_msg &&
+ grep -q refs/notes/m merge_commit_msg &&
+ grep -q refs/notes/z merge_commit_msg &&
+ # Merge commit mentions conflicting notes
+ grep -q "Conflicts" merge_commit_msg &&
+ ( for sha1 in $(cat expect_conflicts); do
+ grep -q "$sha1" merge_commit_msg ||
+ exit 1
+ done ) &&
+ # Verify contents of merge result
+ verify_notes m &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+cp expect_notes_y expect_notes_m
+cp expect_log_y expect_log_m
+
+test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
+ git update-ref refs/notes/m refs/notes/y &&
+ test_must_fail git notes merge z >output &&
+ # Output should point to where to resolve conflicts
+ grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+ # Inspect merge conflicts
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == y)
+ verify_notes y &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+'
+
+cp expect_notes_w expect_notes_m
+cp expect_log_w expect_log_m
+
+test_expect_success 'reset notes ref m to somewhere else (w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ verify_notes m &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
+ # Resolve conflicts
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&
+y and z notes on 1st commit
+EOF
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF &&
+y and z notes on 4th commit
+EOF
+ # Fail to finalize merge
+ test_must_fail git notes merge --commit >output 2>&1 &&
+ # .git/NOTES_MERGE_* must remain
+ test -f .git/NOTES_MERGE_PARTIAL &&
+ test -f .git/NOTES_MERGE_REF &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
+ test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
+ # Refs are unchanged
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+ test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+ test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+ # Mention refs/notes/m, and its current and expected value in output
+ grep -q "refs/notes/m" output &&
+ grep -q "$(git rev-parse refs/notes/m)" output &&
+ grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output &&
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+test_expect_success 'resolve situation by aborting the notes merge' '
+ git notes merge --abort &&
+ # No .git/NOTES_MERGE_* files left
+ test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
+ test_cmp /dev/null output &&
+ # m has not moved (still == w)
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+ # Verify that other notes refs has not changed (w, x, y and z)
+ verify_notes w &&
+ verify_notes x &&
+ verify_notes y &&
+ verify_notes z
+'
+
+test_done
diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
new file mode 100755
index 0000000..93516ef
--- /dev/null
+++ b/t/t3311-notes-merge-fanout.sh
@@ -0,0 +1,436 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Johan Herland
+#
+
+test_description='Test notes merging at various fanout levels'
+
+. ./test-lib.sh
+
+verify_notes () {
+ notes_ref="$1"
+ commit="$2"
+ if test -f "expect_notes_$notes_ref"
+ then
+ git -c core.notesRef="refs/notes/$notes_ref" notes |
+ sort >"output_notes_$notes_ref" &&
+ test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" ||
+ return 1
+ fi &&
+ git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \
+ "$commit" >"output_log_$notes_ref" &&
+ test_cmp "expect_log_$notes_ref" "output_log_$notes_ref"
+}
+
+verify_fanout () {
+ notes_ref="$1"
+ # Expect entire notes tree to have a fanout == 1
+ git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+ git ls-tree -r --name-only "refs/notes/$notes_ref" |
+ while read path
+ do
+ case "$path" in
+ ??/??????????????????????????????????????)
+ : true
+ ;;
+ *)
+ echo "Invalid path \"$path\"" &&
+ return 1
+ ;;
+ esac
+ done
+}
+
+verify_no_fanout () {
+ notes_ref="$1"
+ # Expect entire notes tree to have a fanout == 0
+ git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null &&
+ git ls-tree -r --name-only "refs/notes/$notes_ref" |
+ while read path
+ do
+ case "$path" in
+ ????????????????????????????????????????)
+ : true
+ ;;
+ *)
+ echo "Invalid path \"$path\"" &&
+ return 1
+ ;;
+ esac
+ done
+}
+
+# Set up a notes merge scenario with different kinds of conflicts
+test_expect_success 'setup a few initial commits with notes (notes ref: x)' '
+ git config core.notesRef refs/notes/x &&
+ for i in 1 2 3 4 5
+ do
+ test_commit "commit$i" >/dev/null &&
+ git notes add -m "notes for commit$i" || return 1
+ done
+'
+
+commit_sha1=$(git rev-parse commit1^{commit})
+commit_sha2=$(git rev-parse commit2^{commit})
+commit_sha3=$(git rev-parse commit3^{commit})
+commit_sha4=$(git rev-parse commit4^{commit})
+commit_sha5=$(git rev-parse commit5^{commit})
+
+cat <<EOF | sort >expect_notes_x
+aed91155c7a72c2188e781fdf40e0f3761b299db $commit_sha5
+99fab268f9d7ee7b011e091a436c78def8eeee69 $commit_sha4
+953c20ae26c7aa0b428c20693fe38bc687f9d1a9 $commit_sha3
+6358796131b8916eaa2dde6902642942a1cb37e1 $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_x <<EOF
+$commit_sha5 commit5
+notes for commit5
+
+$commit_sha4 commit4
+notes for commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'sanity check (x)' '
+ verify_notes x commit5 &&
+ verify_no_fanout x
+'
+
+num=300
+
+cp expect_log_x expect_log_y
+
+test_expect_success 'Add a few hundred commits w/notes to trigger fanout (x -> y)' '
+ git update-ref refs/notes/y refs/notes/x &&
+ git config core.notesRef refs/notes/y &&
+ i=5 &&
+ while test $i -lt $num
+ do
+ i=$(($i + 1)) &&
+ test_commit "commit$i" >/dev/null &&
+ git notes add -m "notes for commit$i" || return 1
+ done &&
+ test "$(git rev-parse refs/notes/y)" != "$(git rev-parse refs/notes/x)" &&
+ # Expected number of commits and notes
+ test $(git rev-list HEAD | wc -l) = $num &&
+ test $(git notes list | wc -l) = $num &&
+ # 5 first notes unchanged
+ verify_notes y commit5
+'
+
+test_expect_success 'notes tree has fanout (y)' 'verify_fanout y'
+
+test_expect_success 'No-op merge (already included) (x => y)' '
+ git update-ref refs/notes/m refs/notes/y &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge x &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+test_expect_success 'Fast-forward merge (y => x)' '
+ git update-ref refs/notes/m refs/notes/x &&
+ git notes merge y &&
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+'
+
+cat <<EOF | sort >expect_notes_z
+9f506ee70e20379d7f78204c77b334f43d77410d $commit_sha3
+23a47d6ea7d589895faf800752054818e1e7627b $commit_sha2
+b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1
+EOF
+
+cat >expect_log_z <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+notes for commit1
+
+EOF
+
+test_expect_success 'change some of the initial 5 notes (x -> z)' '
+ git update-ref refs/notes/z refs/notes/x &&
+ git config core.notesRef refs/notes/z &&
+ git notes add -f -m "new notes for commit2" commit2 &&
+ git notes append -m "appended notes for commit3" commit3 &&
+ git notes remove commit4 &&
+ git notes remove commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree has no fanout (z)' 'verify_no_fanout z'
+
+cp expect_log_z expect_log_m
+
+test_expect_success 'successful merge without conflicts (y => z)' '
+ git update-ref refs/notes/m refs/notes/z &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge y &&
+ verify_notes m commit5 &&
+ # x/y/z unchanged
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_w <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'introduce conflicting changes (y -> w)' '
+ git update-ref refs/notes/w refs/notes/y &&
+ git config core.notesRef refs/notes/w &&
+ git notes add -f -m "other notes for commit1" commit1 &&
+ git notes add -f -m "other notes for commit3" commit3 &&
+ git notes add -f -m "other notes for commit4" commit4 &&
+ git notes remove commit5 &&
+ verify_notes w commit5
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "ours" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git config core.notesRef refs/notes/m &&
+ git notes merge -s ours z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+
+$commit_sha3 commit3
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "theirs" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s theirs z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "union" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s union z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+appended notes for commit3
+notes for commit3
+other notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'successful merge using "cat_sort_uniq" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ git notes merge -s cat_sort_uniq z &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+# We're merging z into w. Here are the conflicts we expect:
+#
+# commit | x -> w | x -> z | conflict?
+# -------|-----------|-----------|----------
+# 1 | changed | unchanged | no, use w
+# 2 | unchanged | changed | no, use z
+# 3 | changed | changed | yes (w, then z in conflict markers)
+# 4 | changed | deleted | yes (w)
+# 5 | deleted | deleted | no, deleted
+
+test_expect_success 'fails to merge using "manual" strategy (z => w)' '
+ git update-ref refs/notes/m refs/notes/w &&
+ test_must_fail git notes merge z
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+cat <<EOF | sort >expect_conflicts
+$commit_sha3
+$commit_sha4
+EOF
+
+cat >expect_conflict_$commit_sha3 <<EOF
+<<<<<<< refs/notes/m
+other notes for commit3
+=======
+notes for commit3
+
+appended notes for commit3
+>>>>>>> refs/notes/z
+EOF
+
+cat >expect_conflict_$commit_sha4 <<EOF
+other notes for commit4
+EOF
+
+test_expect_success 'verify conflict entries (with no fanout)' '
+ ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
+ test_cmp expect_conflicts output_conflicts &&
+ ( for f in $(cat expect_conflicts); do
+ test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" ||
+ exit 1
+ done ) &&
+ # Verify that current notes tree (pre-merge) has not changed (m == w)
+ test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+'
+
+cat >expect_log_m <<EOF
+$commit_sha5 commit5
+
+$commit_sha4 commit4
+other notes for commit4
+
+$commit_sha3 commit3
+other notes for commit3
+
+appended notes for commit3
+
+$commit_sha2 commit2
+new notes for commit2
+
+$commit_sha1 commit1
+other notes for commit1
+
+EOF
+
+test_expect_success 'resolve and finalize merge (z => w)' '
+ cat >.git/NOTES_MERGE_WORKTREE/$commit_sha3 <<EOF &&
+other notes for commit3
+
+appended notes for commit3
+EOF
+ git notes merge --commit &&
+ verify_notes m commit5 &&
+ # w/x/y/z unchanged
+ verify_notes w commit5 &&
+ verify_notes x commit5 &&
+ verify_notes y commit5 &&
+ verify_notes z commit5
+'
+
+test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m'
+
+test_done
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index d98c7b5..6eaecec 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -14,140 +14,174 @@ GIT_AUTHOR_NAME=author@name
GIT_AUTHOR_EMAIL=bogus@email@address
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-test_expect_success \
- 'prepare repository with topic branches' \
- 'git config core.logAllRefUpdates true &&
- echo First > A &&
- git update-index --add A &&
- git commit -m "Add A." &&
- git checkout -b my-topic-branch &&
- echo Second > B &&
- git update-index --add B &&
- git commit -m "Add B." &&
- git checkout -f master &&
- echo Third >> A &&
- git update-index A &&
- git commit -m "Modify A." &&
- git checkout -b side my-topic-branch &&
- echo Side >> C &&
- git add C &&
- git commit -m "Add C" &&
- git checkout -b nonlinear my-topic-branch &&
- echo Edit >> B &&
- git add B &&
- git commit -m "Modify B" &&
- git merge side &&
- git checkout -b upstream-merged-nonlinear &&
- git merge master &&
- git checkout -f my-topic-branch &&
- git tag topic
+test_expect_success 'prepare repository with topic branches' '
+ git config core.logAllRefUpdates true &&
+ echo First >A &&
+ git update-index --add A &&
+ git commit -m "Add A." &&
+ git checkout -b force-3way &&
+ echo Dummy >Y &&
+ git update-index --add Y &&
+ git commit -m "Add Y." &&
+ git checkout -b filemove &&
+ git reset --soft master &&
+ mkdir D &&
+ git mv A D/A &&
+ git commit -m "Move A." &&
+ git checkout -b my-topic-branch master &&
+ echo Second >B &&
+ git update-index --add B &&
+ git commit -m "Add B." &&
+ git checkout -f master &&
+ echo Third >>A &&
+ git update-index A &&
+ git commit -m "Modify A." &&
+ git checkout -b side my-topic-branch &&
+ echo Side >>C &&
+ git add C &&
+ git commit -m "Add C" &&
+ git checkout -b nonlinear my-topic-branch &&
+ echo Edit >>B &&
+ git add B &&
+ git commit -m "Modify B" &&
+ git merge side &&
+ git checkout -b upstream-merged-nonlinear &&
+ git merge master &&
+ git checkout -f my-topic-branch &&
+ git tag topic
'
test_expect_success 'rebase on dirty worktree' '
- echo dirty >> A &&
- test_must_fail git rebase master'
+ echo dirty >>A &&
+ test_must_fail git rebase master
+'
test_expect_success 'rebase on dirty cache' '
- git add A &&
- test_must_fail git rebase master'
+ git add A &&
+ test_must_fail git rebase master
+'
test_expect_success 'rebase against master' '
- git reset --hard HEAD &&
- git rebase master'
+ git reset --hard HEAD &&
+ git rebase master
+'
test_expect_success 'rebase against master twice' '
- git rebase master >out &&
- grep "Current branch my-topic-branch is up to date" out
+ git rebase master >out &&
+ grep "Current branch my-topic-branch is up to date" out
'
test_expect_success 'rebase against master twice with --force' '
- git rebase --force-rebase master >out &&
- grep "Current branch my-topic-branch is up to date, rebase forced" out
+ git rebase --force-rebase master >out &&
+ grep "Current branch my-topic-branch is up to date, rebase forced" out
'
test_expect_success 'rebase against master twice from another branch' '
- git checkout my-topic-branch^ &&
- git rebase master my-topic-branch >out &&
- grep "Current branch my-topic-branch is up to date" out
+ git checkout my-topic-branch^ &&
+ git rebase master my-topic-branch >out &&
+ grep "Current branch my-topic-branch is up to date" out
'
test_expect_success 'rebase fast-forward to master' '
- git checkout my-topic-branch^ &&
- git rebase my-topic-branch >out &&
- grep "Fast-forwarded HEAD to my-topic-branch" out
+ git checkout my-topic-branch^ &&
+ git rebase my-topic-branch >out &&
+ grep "Fast-forwarded HEAD to my-topic-branch" out
'
-test_expect_success \
- 'the rebase operation should not have destroyed author information' \
- '! (git log | grep "Author:" | grep "<>")'
+test_expect_success 'the rebase operation should not have destroyed author information' '
+ ! (git log | grep "Author:" | grep "<>")
+'
-test_expect_success \
- 'the rebase operation should not have destroyed author information (2)' \
- "git log -1 | grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'"
+test_expect_success 'the rebase operation should not have destroyed author information (2)' "
+ git log -1 |
+ grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'
+"
test_expect_success 'HEAD was detached during rebase' '
- test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
+ test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
'
test_expect_success 'rebase after merge master' '
- git reset --hard topic &&
- git merge master &&
- git rebase master &&
- ! (git show | grep "^Merge:")
+ git reset --hard topic &&
+ git merge master &&
+ git rebase master &&
+ ! (git show | grep "^Merge:")
'
test_expect_success 'rebase of history with merges is linearized' '
- git checkout nonlinear &&
- test 4 = $(git rev-list master.. | wc -l) &&
- git rebase master &&
- test 3 = $(git rev-list master.. | wc -l)
+ git checkout nonlinear &&
+ test 4 = $(git rev-list master.. | wc -l) &&
+ git rebase master &&
+ test 3 = $(git rev-list master.. | wc -l)
'
-test_expect_success \
- 'rebase of history with merges after upstream merge is linearized' '
- git checkout upstream-merged-nonlinear &&
- test 5 = $(git rev-list master.. | wc -l) &&
- git rebase master &&
- test 3 = $(git rev-list master.. | wc -l)
+test_expect_success 'rebase of history with merges after upstream merge is linearized' '
+ git checkout upstream-merged-nonlinear &&
+ test 5 = $(git rev-list master.. | wc -l) &&
+ git rebase master &&
+ test 3 = $(git rev-list master.. | wc -l)
'
test_expect_success 'rebase a single mode change' '
- git checkout master &&
- echo 1 > X &&
- git add X &&
- test_tick &&
- git commit -m prepare &&
- git checkout -b modechange HEAD^ &&
- echo 1 > X &&
- git add X &&
- test_chmod +x A &&
- test_tick &&
- git commit -m modechange &&
- GIT_TRACE=1 git rebase master
+ git checkout master &&
+ echo 1 >X &&
+ git add X &&
+ test_tick &&
+ git commit -m prepare &&
+ git checkout -b modechange HEAD^ &&
+ echo 1 >X &&
+ git add X &&
+ test_chmod +x A &&
+ test_tick &&
+ git commit -m modechange &&
+ GIT_TRACE=1 git rebase master
+'
+
+test_expect_success 'rebase is not broken by diff.renames' '
+ git config diff.renames copies &&
+ test_when_finished "git config --unset diff.renames" &&
+ git checkout filemove &&
+ GIT_TRACE=1 git rebase force-3way
+'
+
+test_expect_success 'setup: recover' '
+ test_might_fail git rebase --abort &&
+ git reset --hard &&
+ git checkout modechange
'
test_expect_success 'Show verbose error when HEAD could not be detached' '
- : > B &&
- test_must_fail git rebase topic 2> output.err > output.out &&
- grep "Untracked working tree file .B. would be overwritten" output.err
+ >B &&
+ test_must_fail git rebase topic 2>output.err >output.out &&
+ grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
+ grep B output.err
'
rm -f B
-test_expect_success 'dump usage when upstream arg is missing' '
- git checkout -b usage topic &&
- test_must_fail git rebase 2>error1 &&
- grep "[Uu]sage" error1 &&
- test_must_fail git rebase --abort 2>error2 &&
- grep "No rebase in progress" error2 &&
- test_must_fail git rebase --onto master 2>error3 &&
- grep "[Uu]sage" error3 &&
- ! grep "can.t shift" error3
+test_expect_success 'fail when upstream arg is missing and not on branch' '
+ git checkout topic &&
+ test_must_fail git rebase >output.out &&
+ grep "You are not currently on a branch" output.out
+'
+
+test_expect_success 'fail when upstream arg is missing and not configured' '
+ git checkout -b no-config topic &&
+ test_must_fail git rebase >output.out &&
+ grep "branch.no-config.merge" output.out
+'
+
+test_expect_success 'default to @{upstream} when upstream arg is missing' '
+ git checkout -b default topic &&
+ git config branch.default.remote .
+ git config branch.default.merge refs/heads/master
+ git rebase &&
+ test "$(git rev-parse default~1)" = "$(git rev-parse master)"
'
test_expect_success 'rebase -q is quiet' '
- git checkout -b quiet topic &&
- git rebase -q master > output.out 2>&1 &&
- test ! -s output.out
+ git checkout -b quiet topic &&
+ git rebase -q master >output.out 2>&1 &&
+ test ! -s output.out
'
test_expect_success 'Rebase a commit that sprinkles CRs in' '
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index 7b7d072..be8c1d5 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -74,6 +74,15 @@ test_expect_success 'rebase the other way' '
git rebase --merge side
'
+test_expect_success 'rebase -Xtheirs' '
+ git checkout -b conflicting master~2 &&
+ echo "AB $T" >> original &&
+ git commit -mconflicting original &&
+ git rebase -Xtheirs master &&
+ grep AB original &&
+ ! grep 11 original
+'
+
test_expect_success 'merge and rebase should match' '
git diff-tree -r test-rebase test-merge >difference &&
if test -s difference
@@ -108,4 +117,25 @@ test_expect_success 'picking rebase' '
esac
'
+test_expect_success 'rebase -s funny -Xopt' '
+ test_when_finished "rm -fr test-bin funny.was.run" &&
+ mkdir test-bin &&
+ cat >test-bin/git-merge-funny <<-EOF &&
+ #!$SHELL_PATH
+ case "\$1" in --opt) ;; *) exit 2 ;; esac
+ shift &&
+ >funny.was.run &&
+ exec git merge-recursive "\$@"
+ EOF
+ chmod +x test-bin/git-merge-funny &&
+ git reset --hard &&
+ git checkout -b test-funny master^ &&
+ test_commit funny &&
+ (
+ PATH=./test-bin:$PATH
+ git rebase -s funny -Xopt master
+ ) &&
+ test -f funny.was.run
+'
+
test_done
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index 64446e3..826500b 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -35,6 +35,11 @@ test_expect_success 'rebase with git am -3 (default)' '
test_must_fail git rebase master
'
+test_expect_success 'rebase --skip can not be used with other options' '
+ test_must_fail git rebase -v --skip &&
+ test_must_fail git rebase --skip -v
+'
+
test_expect_success 'rebase --skip with am -3' '
git rebase --skip
'
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ee9a1b2..8538813 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -7,34 +7,39 @@ test_description='git rebase interactive
This test runs git rebase "interactively", by faking an edit, and verifies
that the result still makes sense.
+
+Initial setup:
+
+ one - two - three - four (conflict-branch)
+ /
+ A - B - C - D - E (master)
+ | \
+ | F - G - H (branch1)
+ | \
+ |\ I (branch2)
+ | \
+ | J - K - L - M (no-conflict-branch)
+ \
+ N - O - P (no-ff-branch)
+
+ where A, B, D and G all touch file1, and one, two, three, four all
+ touch file "conflict".
'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
+test_cmp_rev () {
+ git rev-parse --verify "$1" >expect.rev &&
+ git rev-parse --verify "$2" >actual.rev &&
+ test_cmp expect.rev actual.rev
+}
+
set_fake_editor
-# Set up the repository like this:
-#
-# one - two - three - four (conflict-branch)
-# /
-# A - B - C - D - E (master)
-# | \
-# | F - G - H (branch1)
-# | \
-# |\ I (branch2)
-# | \
-# | J - K - L - M (no-conflict-branch)
-# \
-# N - O - P (no-ff-branch)
-#
-# where A, B, D and G all touch file1, and one, two, three, four all
-# touch file "conflict".
-#
# WARNING: Modifications to the initial repository can change the SHA ID used
# in the expect2 file for the 'stop on conflicting pick' test.
-
test_expect_success 'setup' '
test_commit A file1 &&
test_commit B file1 &&
@@ -46,22 +51,71 @@ test_expect_success 'setup' '
test_commit G file1 &&
test_commit H file5 &&
git checkout -b branch2 F &&
- test_commit I file6
+ test_commit I file6 &&
git checkout -b conflict-branch A &&
- for n in one two three four
- do
- test_commit $n conflict
- done &&
+ test_commit one conflict &&
+ test_commit two conflict &&
+ test_commit three conflict &&
+ test_commit four conflict &&
git checkout -b no-conflict-branch A &&
- for n in J K L M
- do
- test_commit $n file$n
- done &&
+ test_commit J fileJ &&
+ test_commit K fileK &&
+ test_commit L fileL &&
+ test_commit M fileM &&
git checkout -b no-ff-branch A &&
- for n in N O P
- do
- test_commit $n file$n
- done
+ test_commit N fileN &&
+ test_commit O fileO &&
+ test_commit P fileP
+'
+
+# "exec" commands are ran with the user shell by default, but this may
+# be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work
+# to create a file. Unseting SHELL avoids such non-portable behavior
+# in tests. It must be exported for it to take effect where needed.
+SHELL=
+export SHELL
+
+test_expect_success 'rebase -i with the exec command' '
+ git checkout master &&
+ (
+ FAKE_LINES="1 exec_>touch-one
+ 2 exec_>touch-two exec_false exec_>touch-three
+ 3 4 exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon 5" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i A
+ ) &&
+ test_path_is_file touch-one &&
+ test_path_is_file touch-two &&
+ test_path_is_missing touch-three " (should have stopped before)" &&
+ test_cmp_rev C HEAD &&
+ git rebase --continue &&
+ test_path_is_file touch-three &&
+ test_path_is_file "touch-file name with spaces" &&
+ test_path_is_file touch-after-semicolon &&
+ test_cmp_rev master HEAD &&
+ rm -f touch-*
+'
+
+test_expect_success 'rebase -i with the exec command runs from tree root' '
+ git checkout master &&
+ mkdir subdir && (cd subdir &&
+ FAKE_LINES="1 exec_>touch-subdir" \
+ git rebase -i HEAD^
+ ) &&
+ test_path_is_file touch-subdir &&
+ rm -fr subdir
+'
+
+test_expect_success 'rebase -i with the exec command checks tree cleanness' '
+ git checkout master &&
+ (
+ FAKE_LINES="exec_echo_foo_>file1 1" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i HEAD^
+ ) &&
+ test_cmp_rev master^ HEAD &&
+ git reset --hard &&
+ git rebase --continue
'
test_expect_success 'no changes are a nop' '
@@ -143,16 +197,17 @@ test_expect_success 'abort' '
git rebase --abort &&
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
- ! test -d .git/rebase-merge
+ test_path_is_missing .git/rebase-merge
'
test_expect_success 'abort with error when new base cannot be checked out' '
git rm --cached file1 &&
git commit -m "remove file in base" &&
test_must_fail git rebase -i master > output 2>&1 &&
- grep "Untracked working tree file .file1. would be overwritten" \
+ grep "The following untracked working tree files would be overwritten by checkout:" \
output &&
- ! test -d .git/rebase-merge &&
+ grep "file1" output &&
+ test_path_is_missing .git/rebase-merge &&
git reset --hard HEAD^
'
@@ -240,7 +295,7 @@ test_expect_success 'preserve merges with -p' '
'
test_expect_success 'edit ancestor with -p' '
- FAKE_LINES="1 edit 2 3 4" git rebase -i -p HEAD~3 &&
+ FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 &&
echo 2 > unrelated-file &&
test_tick &&
git commit -m L2-modified --amend unrelated-file &&
@@ -262,7 +317,7 @@ test_expect_success '--continue tries to commit' '
'
test_expect_success 'verbose flag is heeded, even after --continue' '
- git reset --hard HEAD@{1} &&
+ git reset --hard master@{1} &&
test_tick &&
test_must_fail git rebase -v -i --onto new-branch1 HEAD^ &&
echo resolved > file1 &&
@@ -522,7 +577,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
git checkout -b branch4 HEAD &&
GIT_EDITOR=: git commit --amend \
- --author="Somebody else <somebody@else.com>"
+ --author="Somebody else <somebody@else.com>" &&
test $(git rev-parse branch3) != $(git rev-parse branch4) &&
git rebase -i branch3 &&
test $(git rev-parse branch3) = $(git rev-parse branch4)
@@ -537,7 +592,7 @@ test_expect_success 'submodule rebase setup' '
git add elif && git commit -m "submodule initial"
) &&
echo 1 >file1 &&
- git add file1 sub
+ git add file1 sub &&
test_tick &&
git commit -m "One" &&
echo 2 >file1 &&
@@ -593,6 +648,7 @@ test_expect_success 'rebase -i can copy notes' '
cat >expect <<EOF
an earlier note
+
a note
EOF
@@ -630,4 +686,28 @@ test_expect_success 'always cherry-pick with --no-ff' '
test_cmp empty out
'
+test_expect_success 'set up commits with funny messages' '
+ git checkout -b funny A &&
+ echo >>file1 &&
+ test_tick &&
+ git commit -a -m "end with slash\\" &&
+ echo >>file1 &&
+ test_tick &&
+ git commit -a -m "something (\000) that looks like octal" &&
+ echo >>file1 &&
+ test_tick &&
+ git commit -a -m "something (\n) that looks like a newline" &&
+ echo >>file1 &&
+ test_tick &&
+ git commit -a -m "another commit"
+'
+
+test_expect_success 'rebase-i history with funny messages' '
+ git rev-list A..funny >expect &&
+ test_tick &&
+ FAKE_LINES="1 2 3 4" git rebase -i A &&
+ git rev-list A.. >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 85fc7c4..fe5f936 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -43,20 +43,20 @@ test_expect_success 'rebase -m' '
'
test_expect_success 'rebase --stat' '
- git reset --hard start
+ git reset --hard start &&
git rebase --stat master >diffstat.txt &&
grep "^ fileX | *1 +$" diffstat.txt
'
test_expect_success 'rebase w/config rebase.stat' '
- git reset --hard start
+ git reset --hard start &&
git config rebase.stat true &&
git rebase master >diffstat.txt &&
grep "^ fileX | *1 +$" diffstat.txt
'
test_expect_success 'rebase -n overrides config rebase.stat config' '
- git reset --hard start
+ git reset --hard start &&
git config rebase.stat true &&
git rebase -n master >diffstat.txt &&
! grep "^ fileX | *1 +$" diffstat.txt
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 2999e78..a6a6c40 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -38,7 +38,7 @@ testrebase() {
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type master &&
- test -d "$dotest" &&
+ test_path_is_dir "$dotest" &&
git rebase --abort &&
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
test ! -d "$dotest"
@@ -49,7 +49,7 @@ testrebase() {
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type master &&
- test -d "$dotest" &&
+ test_path_is_dir "$dotest" &&
test_must_fail git rebase --skip &&
test $(git rev-parse HEAD) = $(git rev-parse master) &&
git rebase --abort &&
@@ -62,7 +62,7 @@ testrebase() {
# Clean up the state from the previous one
git reset --hard pre-rebase &&
test_must_fail git rebase$type master &&
- test -d "$dotest" &&
+ test_path_is_dir "$dotest" &&
echo c > a &&
echo d >> a &&
git add a &&
@@ -72,6 +72,28 @@ testrebase() {
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
test ! -d "$dotest"
'
+
+ test_expect_success "rebase$type --abort does not update reflog" '
+ cd "$work_dir" &&
+ # Clean up the state from the previous one
+ git reset --hard pre-rebase &&
+ git reflog show to-rebase > reflog_before &&
+ test_must_fail git rebase$type master &&
+ git rebase --abort &&
+ git reflog show to-rebase > reflog_after &&
+ test_cmp reflog_before reflog_after &&
+ rm reflog_before reflog_after
+ '
+
+ test_expect_success 'rebase --abort can not be used with other options' '
+ cd "$work_dir" &&
+ # Clean up the state from the previous one
+ git reset --hard pre-rebase &&
+ test_must_fail git rebase$type master &&
+ test_must_fail git rebase -v --abort &&
+ test_must_fail git rebase --abort -v &&
+ git rebase --abort
+ '
}
testrebase "" .git/rebase-apply
diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh
index 2062b85..6b84e60 100755
--- a/t/t3408-rebase-multi-line.sh
+++ b/t/t3408-rebase-multi-line.sh
@@ -16,7 +16,7 @@ test_expect_success setup '
git commit -a -m "A sample commit log message that has a long
summary that spills over multiple lines.
-But otherwise with a sane description."
+But otherwise with a sane description." &&
git branch side &&
diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh
index 8f785e7..6de4e22 100755
--- a/t/t3409-rebase-preserve-merges.sh
+++ b/t/t3409-rebase-preserve-merges.sh
@@ -27,7 +27,25 @@ export GIT_AUTHOR_EMAIL
# \
# B2 <-- origin/topic
#
-# In both cases, 'topic' is rebased onto 'origin/topic'.
+# Clone 3 (no-ff merge):
+#
+# A1--A2--B3 <-- origin/master
+# \
+# B1------M <-- topic
+# \ /
+# \--A3 <-- topic2
+# \
+# B2 <-- origin/topic
+#
+# Clone 4 (merge using second parent as base):
+#
+# A1--A2--B3 <-- origin/master
+# \
+# B1--A3--M <-- topic
+# \ /
+# \--A4 <-- topic2
+# \
+# B2 <-- origin/topic
test_expect_success 'setup for merge-preserving rebase' \
'echo First > A &&
@@ -42,23 +60,41 @@ test_expect_success 'setup for merge-preserving rebase' \
git commit -a -m "Modify A2" &&
git clone ./. clone1 &&
- cd clone1 &&
+ (cd clone1 &&
git checkout -b topic origin/topic &&
- git merge origin/master &&
- cd .. &&
+ git merge origin/master
+ ) &&
+
+ git clone ./. clone4 &&
+ (
+ cd clone4 &&
+ git checkout -b topic origin/topic &&
+ git merge origin/master
+ ) &&
echo Fifth > B &&
git add B &&
git commit -m "Add different B" &&
git clone ./. clone2 &&
- cd clone2 &&
- git checkout -b topic origin/topic &&
- test_must_fail git merge origin/master &&
- echo Resolved > B &&
- git add B &&
- git commit -m "Merge origin/master into topic" &&
- cd .. &&
+ (
+ cd clone2 &&
+ git checkout -b topic origin/topic &&
+ test_must_fail git merge origin/master &&
+ echo Resolved >B &&
+ git add B &&
+ git commit -m "Merge origin/master into topic"
+ ) &&
+
+ git clone ./. clone3 &&
+ (
+ cd clone3 &&
+ git checkout -b topic2 origin/topic &&
+ echo Sixth > A &&
+ git commit -a -m "Modify A3" &&
+ git checkout -b topic origin/topic &&
+ git merge --no-ff topic2
+ ) &&
git checkout topic &&
echo Fourth >> B &&
@@ -71,7 +107,7 @@ test_expect_success 'rebase -p fakes interactive rebase' '
git fetch &&
git rebase -p origin/topic &&
test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
- test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote branch " | wc -l)
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
)
'
@@ -92,4 +128,25 @@ test_expect_success '--continue works after a conflict' '
)
'
+test_expect_success 'rebase -p preserves no-ff merges' '
+ (
+ cd clone3 &&
+ git fetch &&
+ git rebase -p origin/topic &&
+ test 3 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Merge branch" | wc -l)
+ )
+'
+
+test_expect_success 'rebase -p works when base inside second parent' '
+ (
+ cd clone4 &&
+ git fetch &&
+ git rebase -p HEAD^2 &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) &&
+ test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l)
+ )
+'
+
test_done
diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh
index c49143a..6f73b95 100755
--- a/t/t3410-rebase-preserve-dropped-merges.sh
+++ b/t/t3410-rebase-preserve-dropped-merges.sh
@@ -43,11 +43,11 @@ test_expect_success 'setup' '
# G2 = same changes as G
test_expect_success 'skip same-resolution merges with -p' '
git checkout H &&
- ! git merge E &&
+ test_must_fail git merge E &&
test_commit L file1 23 &&
git checkout I &&
test_commit G2 file1 3 &&
- ! git merge E &&
+ test_must_fail git merge E &&
test_commit J file1 23 &&
test_commit K file7 file7 &&
git rebase -i -p L &&
@@ -65,11 +65,11 @@ test_expect_success 'skip same-resolution merges with -p' '
# G2 = different changes as G
test_expect_success 'keep different-resolution merges with -p' '
git checkout H &&
- ! git merge E &&
+ test_must_fail git merge E &&
test_commit L2 file1 23 &&
git checkout I &&
test_commit G3 file1 4 &&
- ! git merge E &&
+ test_must_fail git merge E &&
test_commit J2 file1 24 &&
test_commit K2 file7 file7 &&
test_must_fail git rebase -i -p L2 &&
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
index 14a23cd..ace8e54 100755
--- a/t/t3411-rebase-preserve-around-merges.sh
+++ b/t/t3411-rebase-preserve-around-merges.sh
@@ -37,7 +37,7 @@ test_expect_success 'setup' '
# -- C1 --
#
test_expect_success 'squash F1 into D1' '
- FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
+ FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
git tag E2
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 5869061..086c91c 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -173,14 +173,14 @@ EOF
test_expect_success 'pre-rebase hook stops rebase' '
git checkout -b stops1 other &&
test_must_fail git rebase --root --onto master &&
- test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1 &&
test 0 = $(git rev-list other...stops1 | wc -l)
'
test_expect_success 'pre-rebase hook stops rebase -i' '
git checkout -b stops2 other &&
test_must_fail git rebase --root --onto master &&
- test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2 &&
test 0 = $(git rev-list other...stops2 | wc -l)
'
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
index b63f4e2..b38be8e 100755
--- a/t/t3415-rebase-autosquash.sh
+++ b/t/t3415-rebase-autosquash.sh
@@ -14,6 +14,7 @@ test_expect_success setup '
git add . &&
test_tick &&
git commit -m "first commit" &&
+ git tag first-commit &&
echo 3 >file3 &&
git add . &&
test_tick &&
@@ -21,38 +22,62 @@ test_expect_success setup '
git tag base
'
-test_expect_success 'auto fixup' '
+test_auto_fixup () {
git reset --hard base &&
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "fixup! first"
+ git commit -m "fixup! first" &&
- git tag final-fixup &&
+ git tag $1 &&
test_tick &&
- git rebase --autosquash -i HEAD^^^ &&
+ git rebase $2 -i HEAD^^^ &&
git log --oneline >actual &&
test 3 = $(wc -l <actual) &&
- git diff --exit-code final-fixup &&
+ git diff --exit-code $1 &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'auto fixup (option)' '
+ test_auto_fixup final-fixup-option --autosquash
+'
+
+test_expect_success 'auto fixup (config)' '
+ git config rebase.autosquash true &&
+ test_auto_fixup final-fixup-config-true &&
+ test_must_fail test_auto_fixup fixup-config-true-no --no-autosquash &&
+ git config rebase.autosquash false &&
+ test_must_fail test_auto_fixup final-fixup-config-false
'
-test_expect_success 'auto squash' '
+test_auto_squash () {
git reset --hard base &&
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "squash! first"
+ git commit -m "squash! first" &&
- git tag final-squash &&
+ git tag $1 &&
test_tick &&
- git rebase --autosquash -i HEAD^^^ &&
+ git rebase $2 -i HEAD^^^ &&
git log --oneline >actual &&
test 3 = $(wc -l <actual) &&
- git diff --exit-code final-squash &&
+ git diff --exit-code $1 &&
test 1 = "$(git cat-file blob HEAD^:file1)" &&
test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'auto squash (option)' '
+ test_auto_squash final-squash --autosquash
+'
+
+test_expect_success 'auto squash (config)' '
+ git config rebase.autosquash true &&
+ test_auto_squash final-squash-config-true &&
+ test_must_fail test_auto_squash squash-config-true-no --no-autosquash &&
+ git config rebase.autosquash false &&
+ test_must_fail test_auto_squash final-squash-config-false
'
test_expect_success 'misspelled auto squash' '
@@ -60,7 +85,7 @@ test_expect_success 'misspelled auto squash' '
echo 1 >file1 &&
git add -u &&
test_tick &&
- git commit -m "squash! forst"
+ git commit -m "squash! forst" &&
git tag final-missquash &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@@ -70,4 +95,102 @@ test_expect_success 'misspelled auto squash' '
test 0 = $(git rev-list final-missquash...HEAD | wc -l)
'
+test_expect_success 'auto squash that matches 2 commits' '
+ git reset --hard base &&
+ echo 4 >file4 &&
+ git add file4 &&
+ test_tick &&
+ git commit -m "first new commit" &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! first" &&
+ git tag final-multisquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD~4 &&
+ git log --oneline >actual &&
+ test 4 = $(wc -l <actual) &&
+ git diff --exit-code final-multisquash &&
+ test 1 = "$(git cat-file blob HEAD^^:file1)" &&
+ test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
+ test 1 = $(git cat-file commit HEAD | grep first | wc -l)
+'
+
+test_expect_success 'auto squash that matches a commit after the squash' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! third" &&
+ echo 4 >file4 &&
+ git add file4 &&
+ test_tick &&
+ git commit -m "third commit" &&
+ git tag final-presquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD~4 &&
+ git log --oneline >actual &&
+ test 5 = $(wc -l <actual) &&
+ git diff --exit-code final-presquash &&
+ test 0 = "$(git cat-file blob HEAD^^:file1)" &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD | grep third | wc -l) &&
+ test 1 = $(git cat-file commit HEAD^ | grep third | wc -l)
+'
+test_expect_success 'auto squash that matches a sha1' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! $(git rev-parse --short HEAD^)" &&
+ git tag final-shasquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-shasquash &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+'
+
+test_expect_success 'auto squash that matches longer sha1' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! $(git rev-parse --short=11 HEAD^)" &&
+ git tag final-longshasquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-longshasquash &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
+'
+
+test_auto_commit_flags () {
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit --$1 first-commit &&
+ git tag final-commit-$1 &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-commit-$1 &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+}
+
+test_expect_success 'use commit --fixup' '
+ test_auto_commit_flags fixup 1
+'
+
+test_expect_success 'use commit --squash' '
+ test_auto_commit_flags squash 2
+'
+
test_done
diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh
index 220a740..1fb3e49 100755
--- a/t/t3417-rebase-whitespace-fix.sh
+++ b/t/t3417-rebase-whitespace-fix.sh
@@ -89,7 +89,7 @@ test_expect_success 'same, but do not remove trailing spaces' '
git config core.whitespace "-blank-at-eol" &&
git reset --hard HEAD^ &&
cp third file && git add file && git commit -m third &&
- git rebase --whitespace=fix HEAD^^
+ git rebase --whitespace=fix HEAD^^ &&
git diff --exit-code HEAD^:file expect-second &&
test_cmp file third
'
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
new file mode 100755
index 0000000..1e855cd
--- /dev/null
+++ b/t/t3418-rebase-continue.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+test_description='git rebase --continue tests'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'setup' '
+ test_commit "commit-new-file-F1" F1 1 &&
+ test_commit "commit-new-file-F2" F2 2 &&
+
+ git checkout -b topic HEAD^ &&
+ test_commit "commit-new-file-F2-on-topic-branch" F2 22 &&
+
+ git checkout master
+'
+
+test_expect_success 'interactive rebase --continue works with touched file' '
+ rm -fr .git/rebase-* &&
+ git reset --hard &&
+ git checkout master &&
+
+ FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+ test-chmtime =-60 F1 &&
+ git rebase --continue
+'
+
+test_expect_success 'non-interactive rebase --continue works with touched file' '
+ rm -fr .git/rebase-* &&
+ git reset --hard &&
+ git checkout master &&
+
+ test_must_fail git rebase --onto master master topic &&
+ echo "Resolved" >F2 &&
+ git add F2 &&
+ test-chmtime =-60 F1 &&
+ git rebase --continue
+'
+
+test_expect_success 'rebase --continue can not be used with other options' '
+ test_must_fail git rebase -v --continue &&
+ test_must_fail git rebase --continue -v
+'
+
+test_expect_success 'rebase --continue remembers merge strategy and options' '
+ rm -fr .git/rebase-* &&
+ git reset --hard commit-new-file-F2-on-topic-branch &&
+ test_commit "commit-new-file-F3-on-topic-branch" F3 32 &&
+ test_when_finished "rm -fr test-bin funny.was.run" &&
+ mkdir test-bin &&
+ cat >test-bin/git-merge-funny <<-EOF
+ #!$SHELL_PATH
+ case "\$1" in --opt) ;; *) exit 2 ;; esac
+ shift &&
+ >funny.was.run &&
+ exec git merge-recursive "\$@"
+ EOF
+ chmod +x test-bin/git-merge-funny &&
+ (
+ PATH=./test-bin:$PATH
+ test_must_fail git rebase -s funny -Xopt master topic
+ ) &&
+ test -f funny.was.run &&
+ rm funny.was.run &&
+ echo "Resolved" >F2 &&
+ git add F2 &&
+ (
+ PATH=./test-bin:$PATH
+ git rebase --continue
+ ) &&
+ test -f funny.was.run
+'
+
+test_expect_success 'rebase --continue remembers --rerere-autoupdate' '
+ rm -fr .git/rebase-* &&
+ git reset --hard commit-new-file-F3-on-topic-branch &&
+ git checkout master
+ test_commit "commit-new-file-F3" F3 3 &&
+ git config rerere.enabled true &&
+ test_must_fail git rebase -m master topic &&
+ echo "Resolved" >F2 &&
+ git add F2 &&
+ test_must_fail git rebase --continue &&
+ echo "Resolved" >F3 &&
+ git add F3 &&
+ git rebase --continue &&
+ git reset --hard topic@{1} &&
+ test_must_fail git rebase -m --rerere-autoupdate master &&
+ test "$(cat F2)" = "Resolved" &&
+ test_must_fail git rebase --continue &&
+ test "$(cat F3)" = "Resolved" &&
+ git rebase --continue
+'
+
+test_done
diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh
new file mode 100755
index 0000000..bd8efaf
--- /dev/null
+++ b/t/t3419-rebase-patch-id.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+test_description='git rebase - test patch id computation'
+
+. ./test-lib.sh
+
+test_set_prereq NOT_EXPENSIVE
+test -n "$GIT_PATCHID_TIMING_TESTS" && test_set_prereq EXPENSIVE
+test -x /usr/bin/time && test_set_prereq USR_BIN_TIME
+
+count()
+{
+ i=0
+ while test $i -lt $1
+ do
+ echo "$i"
+ i=$(($i+1))
+ done
+}
+
+scramble()
+{
+ i=0
+ while read x
+ do
+ if test $i -ne 0
+ then
+ echo "$x"
+ fi
+ i=$((($i+1) % 10))
+ done < "$1" > "$1.new"
+ mv -f "$1.new" "$1"
+}
+
+run()
+{
+ echo \$ "$@"
+ /usr/bin/time "$@" >/dev/null
+}
+
+test_expect_success 'setup' '
+ git commit --allow-empty -m initial
+ git tag root
+'
+
+do_tests()
+{
+ pr=$1
+ nlines=$2
+
+ test_expect_success $pr "setup: $nlines lines" "
+ rm -f .gitattributes &&
+ git checkout -q -f master &&
+ git reset --hard root &&
+ count $nlines >file &&
+ git add file &&
+ git commit -q -m initial &&
+ git branch -f other &&
+
+ scramble file &&
+ git add file &&
+ git commit -q -m 'change big file' &&
+
+ git checkout -q other &&
+ : >newfile &&
+ git add newfile &&
+ git commit -q -m 'add small file' &&
+
+ git cherry-pick master >/dev/null 2>&1
+ "
+
+ test_debug "
+ run git diff master^\!
+ "
+
+ test_expect_success $pr 'setup attributes' "
+ echo 'file binary' >.gitattributes
+ "
+
+ test_debug "
+ run git format-patch --stdout master &&
+ run git format-patch --stdout --ignore-if-in-upstream master
+ "
+
+ test_expect_success $pr 'detect upstream patch' "
+ git checkout -q master &&
+ scramble file &&
+ git add file &&
+ git commit -q -m 'change big file again' &&
+ git checkout -q other^{} &&
+ git rebase master &&
+ test_must_fail test -n \"\$(git rev-list master...HEAD~)\"
+ "
+
+ test_expect_success $pr 'do not drop patch' "
+ git branch -f squashed master &&
+ git checkout -q -f squashed &&
+ git reset -q --soft HEAD~2 &&
+ git commit -q -m squashed &&
+ git checkout -q other^{} &&
+ test_must_fail git rebase squashed &&
+ rm -rf .git/rebase-apply
+ "
+}
+
+do_tests NOT_EXPENSIVE 500
+do_tests EXPENSIVE 50000
+
+test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index bc7aedd..595d2ff 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -81,12 +81,22 @@ test_expect_success 'revert after renaming branch' '
'
+test_expect_success 'cherry-pick on stat-dirty working tree' '
+ git clone . copy &&
+ (
+ cd copy &&
+ git checkout initial &&
+ test-chmtime +40 oops &&
+ git cherry-pick added
+ )
+'
+
test_expect_success 'revert forbidden on dirty working tree' '
echo content >extra_file &&
git add extra_file &&
test_must_fail git revert HEAD 2>errors &&
- grep "Your local changes would be overwritten by " errors
+ test_i18ngrep "Your local changes would be overwritten by " errors
'
diff --git a/t/t3503-cherry-pick-root.sh b/t/t3503-cherry-pick-root.sh
index b0faa29..9aefe3a 100755
--- a/t/t3503-cherry-pick-root.sh
+++ b/t/t3503-cherry-pick-root.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test cherry-picking a root commit'
+test_description='test cherry-picking (and reverting) a root commit'
. ./test-lib.sh
@@ -23,7 +23,30 @@ test_expect_success setup '
test_expect_success 'cherry-pick a root commit' '
git cherry-pick master &&
- test first = $(cat file1)
+ echo first >expect &&
+ test_cmp expect file1
+
+'
+
+test_expect_success 'revert a root commit' '
+
+ git revert master &&
+ test_path_is_missing file1
+
+'
+
+test_expect_success 'cherry-pick a root commit with an external strategy' '
+
+ git cherry-pick --strategy=resolve master &&
+ echo first >expect &&
+ test_cmp expect file1
+
+'
+
+test_expect_success 'revert a root commit with an external strategy' '
+
+ git revert --strategy=resolve master &&
+ test_path_is_missing file1
'
diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh
index f7b3518..e6a6481 100755
--- a/t/t3504-cherry-pick-rerere.sh
+++ b/t/t3504-cherry-pick-rerere.sh
@@ -23,7 +23,7 @@ test_expect_success 'conflicting merge' '
test_expect_success 'fixup' '
echo foo-dev >foo &&
git add foo && test_tick && git commit -q -m 4 &&
- git reset --hard HEAD^
+ git reset --hard HEAD^ &&
echo foo-dev >expect
'
@@ -33,7 +33,7 @@ test_expect_success 'cherry-pick conflict' '
'
test_expect_success 'reconfigure' '
- git config rerere.enabled false
+ git config rerere.enabled false &&
git reset --hard
'
diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh
index e51e505..c10b28c 100755
--- a/t/t3505-cherry-pick-empty.sh
+++ b/t/t3505-cherry-pick-empty.sh
@@ -13,12 +13,30 @@ test_expect_success setup '
git checkout -b empty-branch &&
test_tick &&
- git commit --allow-empty -m "empty"
+ git commit --allow-empty -m "empty" &&
+
+ echo third >> file1 &&
+ git add file1 &&
+ test_tick &&
+ git commit --allow-empty-message -m ""
'
test_expect_success 'cherry-pick an empty commit' '
git checkout master && {
+ git cherry-pick empty-branch^
+ test "$?" = 1
+ }
+'
+
+test_expect_success 'index lockfile was removed' '
+
+ test ! -f .git/index.lock
+
+'
+
+test_expect_success 'cherry-pick a commit with an empty message' '
+ git checkout master && {
git cherry-pick empty-branch
test "$?" = 1
}
diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
index e17ae71..51ca391 100755
--- a/t/t3506-cherry-pick-ff.sh
+++ b/t/t3506-cherry-pick-ff.sh
@@ -95,4 +95,14 @@ test_expect_success 'cherry pick a merge relative to nonexistent parent with --f
test_must_fail git cherry-pick --ff -m 3 C
'
+test_expect_success 'cherry pick a root commit with --ff' '
+ git reset --hard first -- &&
+ git rm file1 &&
+ echo first >file2 &&
+ git add file2 &&
+ git commit --amend -m "file2" &&
+ git cherry-pick --ff first &&
+ test "$(git rev-parse --verify HEAD)" = "1df192cd8bc58a2b275d842cede4d221ad9000d1"
+'
+
test_done
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index e25cf80..212ec54 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh
@@ -11,6 +11,18 @@ test_description='test cherry-pick and revert with conflicts
. ./test-lib.sh
+test_cmp_rev () {
+ git rev-parse --verify "$1" >expect.rev &&
+ git rev-parse --verify "$2" >actual.rev &&
+ test_cmp expect.rev actual.rev
+}
+
+pristine_detach () {
+ git checkout -f "$1^0" &&
+ git read-tree -u --reset HEAD &&
+ git clean -d -f -f -q -x
+}
+
test_expect_success setup '
echo unrelated >unrelated &&
@@ -23,13 +35,7 @@ test_expect_success setup '
'
test_expect_success 'failed cherry-pick does not advance HEAD' '
-
- git checkout -f initial^0 &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
-
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
+ pristine_detach initial &&
head=$(git rev-parse HEAD) &&
test_must_fail git cherry-pick picked &&
@@ -38,14 +44,97 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
test "$head" = "$newhead"
'
-test_expect_success 'failed cherry-pick produces dirty index' '
+test_expect_success 'advice from failed cherry-pick' "
+ pristine_detach initial &&
- git checkout -f initial^0 &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
+ picked=\$(git rev-parse --short picked) &&
+ cat <<-EOF >expected &&
+ error: could not apply \$picked... picked
+ hint: after resolving the conflicts, mark the corrected paths
+ hint: with 'git add <paths>' or 'git rm <paths>'
+ hint: and commit the result with 'git commit'
+ EOF
+ test_must_fail git cherry-pick picked 2>actual &&
+
+ test_i18ncmp expected actual
+"
+
+test_expect_success 'failed cherry-pick sets CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ test_must_fail git cherry-pick picked &&
+ test_cmp_rev picked CHERRY_PICK_HEAD
+'
+
+test_expect_success 'successful cherry-pick does not set CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ git cherry-pick base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ git cherry-pick --no-commit base &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+ (
+ GIT_CHERRY_PICK_HELP="and then do something else" &&
+ export GIT_CHERRY_PICK_HELP &&
+ test_must_fail git cherry-pick picked
+ ) &&
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success 'git reset clears CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+
+ test_must_fail git cherry-pick picked &&
+ git reset &&
+
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success 'failed commit does not clear CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+
+ test_must_fail git cherry-pick picked &&
+ test_must_fail git commit &&
+
+ test_cmp_rev picked CHERRY_PICK_HEAD
+'
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
+test_expect_success 'cancelled commit does not clear CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+
+ test_must_fail git cherry-pick picked &&
+ echo resolved >foo &&
+ git add foo &&
+ git update-index --refresh -q &&
+ test_must_fail git diff-index --exit-code HEAD &&
+ (
+ GIT_EDITOR=false &&
+ export GIT_EDITOR &&
+ test_must_fail git commit
+ ) &&
+
+ test_cmp_rev picked CHERRY_PICK_HEAD
+'
+
+test_expect_success 'successful commit clears CHERRY_PICK_HEAD' '
+ pristine_detach initial &&
+
+ test_must_fail git cherry-pick picked &&
+ echo resolved >foo &&
+ git add foo &&
+ git commit &&
+
+ test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success 'failed cherry-pick produces dirty index' '
+ pristine_detach initial &&
test_must_fail git cherry-pick picked &&
@@ -54,9 +143,7 @@ test_expect_success 'failed cherry-pick produces dirty index' '
'
test_expect_success 'failed cherry-pick registers participants in index' '
-
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
+ pristine_detach initial &&
{
git checkout base -- foo &&
git ls-files --stage foo &&
@@ -70,10 +157,7 @@ test_expect_success 'failed cherry-pick registers participants in index' '
2 s/ 0 / 2 /
3 s/ 0 / 3 /
" < stages > expected &&
- git checkout -f initial^0 &&
-
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
+ git read-tree -u --reset HEAD &&
test_must_fail git cherry-pick picked &&
git ls-files --stage --unmerged > actual &&
@@ -82,10 +166,7 @@ test_expect_success 'failed cherry-pick registers participants in index' '
'
test_expect_success 'failed cherry-pick describes conflict in work tree' '
-
- git checkout -f initial^0 &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
+ pristine_detach initial &&
cat <<-EOF > expected &&
<<<<<<< HEAD
a
@@ -94,9 +175,6 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' '
>>>>>>> objid picked
EOF
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
-
test_must_fail git cherry-pick picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
@@ -104,11 +182,8 @@ test_expect_success 'failed cherry-pick describes conflict in work tree' '
'
test_expect_success 'diff3 -m style' '
-
+ pristine_detach initial &&
git config merge.conflictstyle diff3 &&
- git checkout -f initial^0 &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
cat <<-EOF > expected &&
<<<<<<< HEAD
a
@@ -119,9 +194,6 @@ test_expect_success 'diff3 -m style' '
>>>>>>> objid picked
EOF
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
-
test_must_fail git cherry-pick picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
@@ -129,10 +201,8 @@ test_expect_success 'diff3 -m style' '
'
test_expect_success 'revert also handles conflicts sanely' '
-
git config --unset merge.conflictstyle &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
+ pristine_detach initial &&
cat <<-EOF > expected &&
<<<<<<< HEAD
a
@@ -153,10 +223,7 @@ test_expect_success 'revert also handles conflicts sanely' '
2 s/ 0 / 2 /
3 s/ 0 / 3 /
" < stages > expected-stages &&
- git checkout -f initial^0 &&
-
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
+ git read-tree -u --reset HEAD &&
head=$(git rev-parse HEAD) &&
test_must_fail git revert picked &&
@@ -172,10 +239,8 @@ test_expect_success 'revert also handles conflicts sanely' '
'
test_expect_success 'revert conflict, diff3 -m style' '
+ pristine_detach initial &&
git config merge.conflictstyle diff3 &&
- git checkout -f initial^0 &&
- git read-tree -u --reset HEAD &&
- git clean -d -f -f -q -x &&
cat <<-EOF > expected &&
<<<<<<< HEAD
a
@@ -186,9 +251,6 @@ test_expect_success 'revert conflict, diff3 -m style' '
>>>>>>> parent of objid picked
EOF
- git update-index --refresh &&
- git diff-index --exit-code HEAD &&
-
test_must_fail git revert picked &&
sed "s/[a-f0-9]*\.\.\./objid/" foo > actual &&
diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh
index f90ed3d..8e09fd0 100755
--- a/t/t3508-cherry-pick-many-commits.sh
+++ b/t/t3508-cherry-pick-many-commits.sh
@@ -4,6 +4,18 @@ test_description='test cherry-picking many commits'
. ./test-lib.sh
+check_head_differs_from() {
+ head=$(git rev-parse --verify HEAD) &&
+ arg=$(git rev-parse --verify "$1") &&
+ test "$head" != "$arg"
+}
+
+check_head_equals() {
+ head=$(git rev-parse --verify HEAD) &&
+ arg=$(git rev-parse --verify "$1") &&
+ test "$head" = "$arg"
+}
+
test_expect_success setup '
echo first > file1 &&
git add file1 &&
@@ -23,13 +35,55 @@ test_expect_success setup '
'
test_expect_success 'cherry-pick first..fourth works' '
+ cat <<-\EOF >expected &&
+ [master OBJID] second
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ [master OBJID] third
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ [master OBJID] fourth
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ EOF
+
+ git checkout -f master &&
+ git reset --hard first &&
+ test_tick &&
+ git cherry-pick first..fourth >actual &&
+ git diff --quiet other &&
+ git diff --quiet HEAD other &&
+
+ sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+ test_cmp expected actual.fuzzy &&
+ check_head_differs_from fourth
+'
+
+test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+ cat <<-\EOF >expected &&
+ Trying simple merge.
+ [master OBJID] second
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ Trying simple merge.
+ [master OBJID] third
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ Trying simple merge.
+ [master OBJID] fourth
+ Author: A U Thor <author@example.com>
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ EOF
+
git checkout -f master &&
git reset --hard first &&
test_tick &&
- git cherry-pick first..fourth &&
+ git cherry-pick --strategy resolve first..fourth >actual &&
git diff --quiet other &&
git diff --quiet HEAD other &&
- test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)"
+ sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+ test_cmp expected actual.fuzzy &&
+ check_head_differs_from fourth
'
test_expect_success 'cherry-pick --ff first..fourth works' '
@@ -39,7 +93,7 @@ test_expect_success 'cherry-pick --ff first..fourth works' '
git cherry-pick --ff first..fourth &&
git diff --quiet other &&
git diff --quiet HEAD other &&
- test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify fourth)"
+ check_head_equals fourth
'
test_expect_success 'cherry-pick -n first..fourth works' '
@@ -89,7 +143,7 @@ test_expect_success 'cherry-pick -3 fourth works' '
git cherry-pick -3 fourth &&
git diff --quiet other &&
git diff --quiet HEAD other &&
- test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)"
+ check_head_differs_from fourth
'
test_expect_success 'cherry-pick --stdin works' '
@@ -99,7 +153,7 @@ test_expect_success 'cherry-pick --stdin works' '
git rev-list --reverse first..fourth | git cherry-pick --stdin &&
git diff --quiet other &&
git diff --quiet HEAD other &&
- test "$(git rev-parse --verify HEAD)" != "$(git rev-parse --verify fourth)"
+ check_head_differs_from fourth
'
test_done
diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh
new file mode 100755
index 0000000..df921d1
--- /dev/null
+++ b/t/t3509-cherry-pick-merge-df.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='Test cherry-pick with directory/file conflicts'
+. ./test-lib.sh
+
+test_expect_success 'Initialize repository' '
+ mkdir a &&
+ >a/f &&
+ git add a &&
+ git commit -m a
+'
+
+test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' '
+ mkdir b &&
+ ln -s ../a b/a &&
+ git add b &&
+ git commit -m b &&
+
+ git checkout -b branch &&
+ rm b/a &&
+ mv a b/a &&
+ ln -s b/a a &&
+ git add . &&
+ git commit -m swap &&
+
+ >f1 &&
+ git add f1 &&
+ git commit -m f1
+'
+
+test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' '
+ git reset --hard &&
+ git checkout master^0 &&
+ git cherry-pick branch
+'
+
+test_expect_success 'Setup rename with file on one side matching directory name on other' '
+ git checkout --orphan nick-testcase &&
+ git rm -rf . &&
+
+ >empty &&
+ git add empty &&
+ git commit -m "Empty file" &&
+
+ git checkout -b simple &&
+ mv empty file &&
+ mkdir empty &&
+ mv file empty &&
+ git add empty/file &&
+ git commit -m "Empty file under empty dir" &&
+
+ echo content >newfile &&
+ git add newfile &&
+ git commit -m "New file"
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (resolve)' '
+ git reset --hard &&
+ git checkout -q nick-testcase^0 &&
+ git cherry-pick --strategy=resolve simple
+'
+
+test_expect_success 'Cherry-pick succeeds with was_a_dir/file -> was_a_dir (recursive)' '
+ git reset --hard &&
+ git checkout -q nick-testcase^0 &&
+ git cherry-pick --strategy=recursive simple
+'
+
+test_expect_success 'Setup rename with file on one side matching different dirname on other' '
+ git reset --hard &&
+ git checkout --orphan mergeme &&
+ git rm -rf . &&
+
+ mkdir sub &&
+ mkdir othersub &&
+ echo content > sub/file &&
+ echo foo > othersub/whatever &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf othersub &&
+ git mv sub/file othersub &&
+ git commit -m "Commit to merge" &&
+
+ git checkout -b newhead mergeme~1 &&
+ >independent-change &&
+ git add independent-change &&
+ git commit -m "Completely unrelated change"
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (resolve)' '
+ git reset --hard &&
+ git checkout -q newhead^0 &&
+ git cherry-pick --strategy=resolve mergeme
+'
+
+test_expect_success 'Cherry-pick with rename to different D/F conflict succeeds (recursive)' '
+ git reset --hard &&
+ git checkout -q newhead^0 &&
+ git cherry-pick --strategy=recursive mergeme
+'
+
+test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 0aaf0ad..9fd28bcf 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -28,22 +28,6 @@ embedded' &&
git commit -m 'add files with tabs and newlines'
"
-# Determine rm behavior
-# Later we will try removing an unremovable path to make sure
-# git rm barfs, but if the test is run as root that cannot be
-# arranged.
-: >test-file
-chmod a-w .
-rm -f test-file 2>/dev/null
-if test -f test-file
-then
- test_set_prereq RO_DIR
-else
- say 'skipping removal failure test (perhaps running as root?)'
-fi
-chmod 775 .
-rm -f test-file
-
test_expect_success \
'Pre-check that foo exists and is in index before git rm foo' \
'[ -f foo ] && git ls-files --error-unmatch foo'
@@ -112,7 +96,7 @@ test_expect_success FUNNYNAMES \
"git rm -f 'space embedded' 'tab embedded' 'newline
embedded'"
-test_expect_success RO_DIR 'Test that "git rm -f" fails if its rm fails' '
+test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' '
chmod a-w . &&
test_must_fail git rm -f baz &&
chmod 775 .
@@ -256,11 +240,10 @@ test_expect_success 'refresh index before checking if it is up-to-date' '
test_expect_success 'choking "git rm" should not let it die with cruft' '
git reset -q --hard &&
- H=0000000000000000000000000000000000000000 &&
i=0 &&
while test $i -lt 12000
do
- echo "100644 $H 0 some-file-$i"
+ echo "100644 $_z40 0 some-file-$i"
i=$(( $i + 1 ))
done | git update-index --index-info &&
git rm -n "some-file-*" | :;
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 525c9a8..575d950 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -26,7 +26,7 @@ test_expect_success \
chmod 755 xfoo1 &&
git add xfoo1 &&
case "`git ls-files --stage xfoo1`" in
- 100644" "*xfoo1) echo ok;;
+ 100644" "*xfoo1) echo pass;;
*) echo fail; git ls-files --stage xfoo1; (exit 1);;
esac'
@@ -35,7 +35,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym
ln -s foo xfoo1 &&
git add xfoo1 &&
case "`git ls-files --stage xfoo1`" in
- 120000" "*xfoo1) echo ok;;
+ 120000" "*xfoo1) echo pass;;
*) echo fail; git ls-files --stage xfoo1; (exit 1);;
esac
'
@@ -47,7 +47,7 @@ test_expect_success \
chmod 755 xfoo2 &&
git update-index --add xfoo2 &&
case "`git ls-files --stage xfoo2`" in
- 100644" "*xfoo2) echo ok;;
+ 100644" "*xfoo2) echo pass;;
*) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac'
@@ -56,7 +56,7 @@ test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by sym
ln -s foo xfoo2 &&
git update-index --add xfoo2 &&
case "`git ls-files --stage xfoo2`" in
- 120000" "*xfoo2) echo ok;;
+ 120000" "*xfoo2) echo pass;;
*) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac
'
@@ -67,7 +67,7 @@ test_expect_success SYMLINKS \
ln -s xfoo2 xfoo3 &&
git update-index --add xfoo3 &&
case "`git ls-files --stage xfoo3`" in
- 120000" "*xfoo3) echo ok;;
+ 120000" "*xfoo3) echo pass;;
*) echo fail; git ls-files --stage xfoo3; (exit 1);;
esac'
@@ -172,14 +172,14 @@ test_expect_success 'git add --refresh' '
test -z "`git diff-index HEAD -- foo`" &&
git read-tree HEAD &&
case "`git diff-index HEAD -- foo`" in
- :100644" "*"M foo") echo ok;;
+ :100644" "*"M foo") echo pass;;
*) echo fail; (exit 1);;
esac &&
git add --refresh -- foo &&
test -z "`git diff-index HEAD -- foo`"
'
-test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable file' '
+test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unreadable file' '
git reset --hard &&
date >foo1 &&
date >foo2 &&
@@ -190,7 +190,7 @@ test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable
rm -f foo2
-test_expect_success POSIXPERM 'git add --ignore-errors' '
+test_expect_success POSIXPERM,SANITY 'git add --ignore-errors' '
git reset --hard &&
date >foo1 &&
date >foo2 &&
@@ -201,7 +201,7 @@ test_expect_success POSIXPERM 'git add --ignore-errors' '
rm -f foo2
-test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
+test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors)' '
git config add.ignore-errors 1 &&
git reset --hard &&
date >foo1 &&
@@ -212,7 +212,7 @@ test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
'
rm -f foo2
-test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
+test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors = false)' '
git config add.ignore-errors 0 &&
git reset --hard &&
date >foo1 &&
@@ -223,7 +223,7 @@ test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
'
rm -f foo2
-test_expect_success POSIXPERM '--no-ignore-errors overrides config' '
+test_expect_success POSIXPERM,SANITY '--no-ignore-errors overrides config' '
git config add.ignore-errors 1 &&
git reset --hard &&
date >foo1 &&
@@ -260,4 +260,39 @@ test_expect_success '"add non-existent" should fail' '
! (git ls-files | grep "non-existent")
'
+test_expect_success 'git add --dry-run of existing changed file' "
+ echo new >>track-this &&
+ git add --dry-run track-this >actual 2>&1 &&
+ echo \"add 'track-this'\" | test_cmp - actual
+"
+
+test_expect_success 'git add --dry-run of non-existing file' "
+ echo ignored-file >>.gitignore &&
+ test_must_fail git add --dry-run track-this ignored-file >actual 2>&1
+"
+
+test_expect_success 'git add --dry-run of an existing file output' "
+ echo \"fatal: pathspec 'ignored-file' did not match any files\" >expect &&
+ test_i18ncmp expect actual
+"
+
+cat >expect.err <<\EOF
+The following paths are ignored by one of your .gitignore files:
+ignored-file
+Use -f if you really want to add them.
+fatal: no files added
+EOF
+cat >expect.out <<\EOF
+add 'track-this'
+EOF
+
+test_expect_success 'git add --dry-run --ignore-missing of non-existing file' '
+ test_must_fail git add --dry-run --ignore-missing track-this ignored-file >actual.out 2>actual.err
+'
+
+test_expect_success 'git add --dry-run --ignore-missing of non-existing file output' '
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index b6eba6a..9e236f9 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -2,22 +2,20 @@
test_description='add -i basic tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
-if ! test_have_prereq PERL; then
- say 'skipping git add -i tests, perl not available'
- test_done
-fi
-
-test_expect_success 'setup (initial)' '
+test_expect_success PERL 'setup (initial)' '
echo content >file &&
git add file &&
echo more >>file &&
echo lines >>file
'
-test_expect_success 'status works (initial)' '
+test_expect_success PERL 'status works (initial)' '
git add -i </dev/null >output &&
grep "+1/-0 *+2/-0 file" output
'
+
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
new file mode 100644
index 0000000..d95f3ad
@@ -26,19 +24,21 @@ index 0000000..d95f3ad
@@ -0,0 +1 @@
+content
EOF
-test_expect_success 'diff works (initial)' '
+'
+
+test_expect_success PERL 'diff works (initial)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/new file/,/content/p" <output >diff &&
test_cmp expected diff
'
-test_expect_success 'revert works (initial)' '
+test_expect_success PERL 'revert works (initial)' '
git add file &&
(echo r; echo 1) | git add -i &&
git ls-files >output &&
! grep . output
'
-test_expect_success 'setup (commit)' '
+test_expect_success PERL 'setup (commit)' '
echo baseline >file &&
git add file &&
git commit -m commit &&
@@ -47,10 +47,12 @@ test_expect_success 'setup (commit)' '
echo more >>file &&
echo lines >>file
'
-test_expect_success 'status works (commit)' '
+test_expect_success PERL 'status works (commit)' '
git add -i </dev/null >output &&
grep "+1/-0 *+2/-0 file" output
'
+
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
index 180b47c..b6f2c08 100644
--- a/file
@@ -59,60 +61,78 @@ index 180b47c..b6f2c08 100644
baseline
+content
EOF
-test_expect_success 'diff works (commit)' '
+'
+
+test_expect_success PERL 'diff works (commit)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/^index/,/content/p" <output >diff &&
test_cmp expected diff
'
-test_expect_success 'revert works (commit)' '
+test_expect_success PERL 'revert works (commit)' '
git add file &&
(echo r; echo 1) | git add -i &&
git add -i </dev/null >output &&
grep "unchanged *+3/-0 file" output
'
+
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
EOF
-cat >fake_editor.sh <<EOF
-EOF
-chmod a+x fake_editor.sh
-test_set_editor "$(pwd)/fake_editor.sh"
-test_expect_success 'dummy edit works' '
+'
+
+test_expect_success PERL 'setup fake editor' '
+ >fake_editor.sh &&
+ chmod a+x fake_editor.sh &&
+ test_set_editor "$(pwd)/fake_editor.sh"
+'
+
+test_expect_success PERL 'dummy edit works' '
(echo e; echo a) | git add -p &&
git diff > diff &&
test_cmp expected diff
'
+test_expect_success PERL 'setup patch' '
cat >patch <<EOF
@@ -1,1 +1,4 @@
this
+patch
--doesn't
+-does not
apply
EOF
-echo "#!$SHELL_PATH" >fake_editor.sh
-cat >>fake_editor.sh <<\EOF
+'
+
+test_expect_success PERL 'setup fake editor' '
+ echo "#!$SHELL_PATH" >fake_editor.sh &&
+ cat >>fake_editor.sh <<\EOF &&
mv -f "$1" oldpatch &&
mv -f patch "$1"
EOF
-chmod a+x fake_editor.sh
-test_set_editor "$(pwd)/fake_editor.sh"
-test_expect_success 'bad edit rejected' '
+ chmod a+x fake_editor.sh &&
+ test_set_editor "$(pwd)/fake_editor.sh"
+'
+
+test_expect_success PERL 'bad edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
+test_expect_success PERL 'setup patch' '
cat >patch <<EOF
this patch
is garbage
EOF
-test_expect_success 'garbage edit rejected' '
+'
+
+test_expect_success PERL 'garbage edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
+test_expect_success PERL 'setup patch' '
cat >patch <<EOF
@@ -1,0 +1,0 @@
baseline
@@ -120,6 +140,9 @@ cat >patch <<EOF
+newcontent
+lines
EOF
+'
+
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
diff --git a/file b/file
index b5dd6c9..f910ae9 100644
@@ -132,13 +155,15 @@ index b5dd6c9..f910ae9 100644
+more
lines
EOF
-test_expect_success 'real edit works' '
+'
+
+test_expect_success PERL 'real edit works' '
(echo e; echo n; echo d) | git add -p &&
git diff >output &&
test_cmp expected output
'
-test_expect_success 'skip files similarly as commit -a' '
+test_expect_success PERL 'skip files similarly as commit -a' '
git reset &&
echo file >.gitignore &&
echo changed >file &&
@@ -152,14 +177,7 @@ test_expect_success 'skip files similarly as commit -a' '
'
rm -f .gitignore
-if test "$(git config --bool core.filemode)" = false
-then
- say 'skipping filemode tests (filesystem does not properly support modes)'
-else
- test_set_prereq FILEMODE
-fi
-
-test_expect_success FILEMODE 'patch does not affect mode' '
+test_expect_success PERL,FILEMODE 'patch does not affect mode' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
@@ -168,7 +186,7 @@ test_expect_success FILEMODE 'patch does not affect mode' '
git diff file | grep "new mode"
'
-test_expect_success FILEMODE 'stage mode but not hunk' '
+test_expect_success PERL,FILEMODE 'stage mode but not hunk' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
@@ -178,7 +196,7 @@ test_expect_success FILEMODE 'stage mode but not hunk' '
'
-test_expect_success FILEMODE 'stage mode and hunk' '
+test_expect_success PERL,FILEMODE 'stage mode and hunk' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
@@ -190,13 +208,14 @@ test_expect_success FILEMODE 'stage mode and hunk' '
# end of tests disabled when filemode is not usable
-test_expect_success 'setup again' '
+test_expect_success PERL 'setup again' '
git reset --hard &&
test_chmod +x file &&
echo content >>file
'
# Write the patch file with a new line at the top and bottom
+test_expect_success PERL 'setup patch' '
cat >patch <<EOF
index 180b47c..b6f2c08 100644
--- a/file
@@ -207,7 +226,10 @@ index 180b47c..b6f2c08 100644
content
+lastline
EOF
+'
+
# Expected output, similar to the patch but w/ diff at the top
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
diff --git a/file b/file
index b6f2c08..61b9053 100755
@@ -219,8 +241,10 @@ index b6f2c08..61b9053 100755
content
+lastline
EOF
+'
+
# Test splitting the first patch, then adding both
-test_expect_success 'add first line works' '
+test_expect_success PERL 'add first line works' '
git commit -am "clear local changes" &&
git apply patch &&
(echo s; echo y; echo y) | git add -p file &&
@@ -228,6 +252,7 @@ test_expect_success 'add first line works' '
test_cmp expected diff
'
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
diff --git a/non-empty b/non-empty
deleted file mode 100644
@@ -237,7 +262,9 @@ index d95f3ad..0000000
@@ -1 +0,0 @@
-content
EOF
-test_expect_success 'deleting a non-empty file' '
+'
+
+test_expect_success PERL 'deleting a non-empty file' '
git reset --hard &&
echo content >non-empty &&
git add non-empty &&
@@ -248,13 +275,15 @@ test_expect_success 'deleting a non-empty file' '
test_cmp expected diff
'
+test_expect_success PERL 'setup expected' '
cat >expected <<EOF
diff --git a/empty b/empty
deleted file mode 100644
index e69de29..0000000
EOF
+'
-test_expect_success 'deleting an empty file' '
+test_expect_success PERL 'deleting an empty file' '
git reset --hard &&
> empty &&
git add empty &&
@@ -265,4 +294,40 @@ test_expect_success 'deleting an empty file' '
test_cmp expected diff
'
+test_expect_success PERL 'split hunk setup' '
+ git reset --hard &&
+ for i in 10 20 30 40 50 60
+ do
+ echo $i
+ done >test &&
+ git add test &&
+ test_tick &&
+ git commit -m test &&
+
+ for i in 10 15 20 21 22 23 24 30 40 50 60
+ do
+ echo $i
+ done >test
+'
+
+test_expect_success PERL 'split hunk "add -p (edit)"' '
+ # Split, say Edit and do nothing. Then:
+ #
+ # 1. Broken version results in a patch that does not apply and
+ # only takes [y/n] (edit again) so the first q is discarded
+ # and then n attempts to discard the edit. Repeat q enough
+ # times to get out.
+ #
+ # 2. Correct version applies the (not)edited version, and asks
+ # about the next hunk, against wich we say q and program
+ # exits.
+ for a in s e q n q q
+ do
+ echo $a
+ done |
+ EDITOR=: git add -p &&
+ git diff >actual &&
+ ! grep "^+15" actual
+'
+
test_done
diff --git a/t/t3703-add-magic-pathspec.sh b/t/t3703-add-magic-pathspec.sh
new file mode 100755
index 0000000..5115de7
--- /dev/null
+++ b/t/t3703-add-magic-pathspec.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='magic pathspec tests using git-add'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir sub anothersub &&
+ : >sub/foo &&
+ : >anothersub/foo
+'
+
+test_expect_success 'add :/' "
+ cat >expected <<-EOF &&
+ add 'anothersub/foo'
+ add 'expected'
+ add 'sub/actual'
+ add 'sub/foo'
+ EOF
+ (cd sub && git add -n :/ >actual) &&
+ test_cmp expected sub/actual
+"
+
+cat >expected <<EOF
+add 'anothersub/foo'
+EOF
+
+test_expect_success 'add :/anothersub' '
+ (cd sub && git add -n :/anothersub >actual) &&
+ test_cmp expected sub/actual
+'
+
+test_expect_success 'add :/non-existent' '
+ (cd sub && test_must_fail git add -n :/non-existent)
+'
+
+cat >expected <<EOF
+add 'sub/foo'
+EOF
+
+if mkdir ":" 2>/dev/null
+then
+ test_set_prereq COLON_DIR
+fi
+
+test_expect_success COLON_DIR 'a file with the same (long) magic name exists' '
+ : >":(icase)ha" &&
+ test_must_fail git add -n ":(icase)ha" &&
+ git add -n "./:(icase)ha"
+'
+
+test_expect_success COLON_DIR 'a file with the same (short) magic name exists' '
+ : >":/bar" &&
+ test_must_fail git add -n :/bar &&
+ git add -n "./:/bar"
+'
+
+test_done
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index 256c4c9..c06a5ee 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -133,4 +133,33 @@ do
'
done
+test_commit_autosquash_flags () {
+ H=$1
+ flag=$2
+ test_expect_success "commit --$flag with $H encoding" '
+ git config i18n.commitencoding $H &&
+ git checkout -b $H-$flag C0 &&
+ echo $H >>F &&
+ git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt &&
+ test_tick &&
+ echo intermediate stuff >>G &&
+ git add G &&
+ git commit -a -m "intermediate commit" &&
+ test_tick &&
+ echo $H $flag >>F &&
+ git commit -a --$flag HEAD~1 $3 &&
+ E=$(git cat-file commit '$H-$flag' |
+ sed -ne "s/^encoding //p") &&
+ test "z$E" = "z$H" &&
+ git config --unset-all i18n.commitencoding &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual)
+ '
+}
+
+test_commit_autosquash_flags eucJP fixup
+
+test_commit_autosquash_flags ISO-2022-JP squash '-m "squash message"'
+
test_done
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh
index 29103f6..da82b65 100755
--- a/t/t3902-quoted.sh
+++ b/t/t3902-quoted.sh
@@ -15,11 +15,13 @@ LF='
DQ='"'
echo foo 2>/dev/null > "Name and an${HT}HT"
-test -f "Name and an${HT}HT" || {
- # since FAT/NTFS does not allow tabs in filenames, skip this test
- say 'Your filesystem does not allow tabs in filenames, test skipped.'
- test_done
-}
+if ! test -f "Name and an${HT}HT"
+then
+ # FAT/NTFS does not allow tabs in filenames
+ say 'Your filesystem does not allow tabs in filenames'
+else
+ test_set_prereq TABS_IN_FILENAMES
+fi
for_each_name () {
for name in \
@@ -31,21 +33,22 @@ for_each_name () {
done
}
-test_expect_success setup '
+test_expect_success TABS_IN_FILENAMES 'setup' '
mkdir "$FN" &&
- for_each_name "echo initial >\"\$name\""
+ for_each_name "echo initial >\"\$name\"" &&
git add . &&
git commit -q -m Initial &&
for_each_name "echo second >\"\$name\"" &&
- git commit -a -m Second
+ git commit -a -m Second &&
for_each_name "echo modified >\"\$name\""
'
-cat >expect.quoted <<\EOF
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
+cat >expect.quoted <<\EOF &&
Name
"Name and a\nLF"
"Name and an\tHT"
@@ -72,75 +75,76 @@ With SP in it
濱野/file
濱野純
EOF
+'
-test_expect_success 'check fully quoted output from ls-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-files' '
git ls-files >current && test_cmp expect.quoted current
'
-test_expect_success 'check fully quoted output from diff-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-files' '
git diff --name-only >current &&
test_cmp expect.quoted current
'
-test_expect_success 'check fully quoted output from diff-index' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-index' '
git diff --name-only HEAD >current &&
test_cmp expect.quoted current
'
-test_expect_success 'check fully quoted output from diff-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-tree' '
git diff --name-only HEAD^ HEAD >current &&
test_cmp expect.quoted current
'
-test_expect_success 'check fully quoted output from ls-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-tree' '
git ls-tree --name-only -r HEAD >current &&
test_cmp expect.quoted current
'
-test_expect_success 'setting core.quotepath' '
+test_expect_success TABS_IN_FILENAMES 'setting core.quotepath' '
git config --bool core.quotepath false
'
-test_expect_success 'check fully quoted output from ls-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-files' '
git ls-files >current && test_cmp expect.raw current
'
-test_expect_success 'check fully quoted output from diff-files' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-files' '
git diff --name-only >current &&
test_cmp expect.raw current
'
-test_expect_success 'check fully quoted output from diff-index' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-index' '
git diff --name-only HEAD >current &&
test_cmp expect.raw current
'
-test_expect_success 'check fully quoted output from diff-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from diff-tree' '
git diff --name-only HEAD^ HEAD >current &&
test_cmp expect.raw current
'
-test_expect_success 'check fully quoted output from ls-tree' '
+test_expect_success TABS_IN_FILENAMES 'check fully quoted output from ls-tree' '
git ls-tree --name-only -r HEAD >current &&
test_cmp expect.raw current
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 62e208a..7197aae 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -37,14 +37,32 @@ test_expect_success 'parents of stash' '
test_cmp output expect
'
-test_expect_success 'apply needs clean working directory' '
- echo 4 > other-file &&
+test_expect_success 'applying bogus stash does nothing' '
+ test_must_fail git stash apply stash@{1} &&
+ echo 1 >expect &&
+ test_cmp expect file
+'
+
+test_expect_success 'apply does not need clean working directory' '
+ echo 4 >other-file &&
git add other-file &&
- echo 5 > other-file &&
- test_must_fail git stash apply
+ echo 5 >other-file &&
+ git stash apply &&
+ echo 3 >expect &&
+ test_cmp expect file
+'
+
+test_expect_success 'apply does not clobber working directory changes' '
+ git reset --hard &&
+ echo 4 >file &&
+ test_must_fail git stash apply &&
+ echo 4 >expect &&
+ test_cmp expect file
'
test_expect_success 'apply stashed changes' '
+ git reset --hard &&
+ echo 5 >other-file &&
git add other-file &&
test_tick &&
git commit -m other-file &&
@@ -69,9 +87,10 @@ test_expect_success 'apply stashed changes (including index)' '
test_expect_success 'unstashing in a subdirectory' '
git reset --hard HEAD &&
mkdir subdir &&
- cd subdir &&
- git stash apply &&
- cd ..
+ (
+ cd subdir &&
+ git stash apply
+ )
'
test_expect_success 'drop top stash' '
@@ -156,7 +175,7 @@ EOF
test_expect_success 'stash branch' '
echo foo > file &&
- git commit file -m first
+ git commit file -m first &&
echo bar > file &&
echo bar2 > file2 &&
git add file2 &&
@@ -217,6 +236,14 @@ test_expect_success 'stash -k' '
test bar,bar4 = $(cat file),$(cat file2)
'
+test_expect_success 'stash --no-keep-index' '
+ echo bar33 > file &&
+ echo bar44 > file2 &&
+ git add file2 &&
+ git stash --no-keep-index &&
+ test bar,bar2 = $(cat file),$(cat file2)
+'
+
test_expect_success 'stash --invalid-option' '
echo bar5 > file &&
echo bar6 > file2 &&
@@ -254,7 +281,7 @@ test_expect_success 'stash rm and ignore' '
echo file >.gitignore &&
git stash save "rm and ignore" &&
test bar = "$(cat file)" &&
- test file = "$(cat .gitignore)"
+ test file = "$(cat .gitignore)" &&
git stash apply &&
! test -r file &&
test file = "$(cat .gitignore)"
@@ -267,7 +294,7 @@ test_expect_success 'stash rm and ignore (stage .gitignore)' '
git add .gitignore &&
git stash save "rm and ignore (stage .gitignore)" &&
test bar = "$(cat file)" &&
- ! test -r .gitignore
+ ! test -r .gitignore &&
git stash apply &&
! test -r file &&
test file = "$(cat .gitignore)"
@@ -378,4 +405,200 @@ test_expect_failure 'stash file to directory' '
test foo = "$(cat file/file)"
'
+test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ git stash branch stash-branch ${STASH_ID} &&
+ test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+ test $(git ls-files --modified | wc -l) -eq 1
+'
+
+test_expect_success 'stash branch - stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ echo bar >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ git stash branch stash-branch ${STASH_ID} &&
+ test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
+ test $(git ls-files --modified | wc -l) -eq 1
+'
+
+test_expect_success 'stash show - stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ echo bar >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ cat >expected <<-EOF &&
+ file | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ EOF
+ git stash show ${STASH_ID} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'stash show -p - stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ git stash &&
+ test_when_finished "git stash drop" &&
+ echo bar >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ cat >expected <<-EOF &&
+ diff --git a/file b/file
+ index 7601807..935fbd3 100644
+ --- a/file
+ +++ b/file
+ @@ -1 +1,2 @@
+ baz
+ +bar
+ EOF
+ git stash show -p ${STASH_ID} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'stash show - no stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ cat >expected <<-EOF &&
+ file | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ EOF
+ git stash show ${STASH_ID} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'stash show -p - no stashes on stack, stash-like argument' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD" &&
+ git reset --hard &&
+ echo foo >> file &&
+ STASH_ID=$(git stash create) &&
+ git reset --hard &&
+ cat >expected <<-EOF &&
+ diff --git a/file b/file
+ index 7601807..71b52c4 100644
+ --- a/file
+ +++ b/file
+ @@ -1 +1,2 @@
+ baz
+ +foo
+ EOF
+ git stash show -p ${STASH_ID} >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD && git stash clear" &&
+ git reset --hard &&
+ echo foo > file &&
+ git stash &&
+ echo bar > file &&
+ git stash &&
+ test_must_fail git stash drop $(git rev-parse stash@{0}) &&
+ git stash pop &&
+ test bar = "$(cat file)" &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
+ git stash clear &&
+ test_when_finished "git reset --hard HEAD && git stash clear" &&
+ git reset --hard &&
+ echo foo > file &&
+ git stash &&
+ echo bar > file &&
+ git stash &&
+ test_must_fail git stash pop $(git rev-parse stash@{0}) &&
+ git stash pop &&
+ test bar = "$(cat file)" &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'ref with non-existent reflog' '
+ git stash clear &&
+ echo bar5 > file &&
+ echo bar6 > file2 &&
+ git add file2 &&
+ git stash &&
+ ! "git rev-parse --quiet --verify does-not-exist" &&
+ test_must_fail git stash drop does-not-exist &&
+ test_must_fail git stash drop does-not-exist@{0} &&
+ test_must_fail git stash pop does-not-exist &&
+ test_must_fail git stash pop does-not-exist@{0} &&
+ test_must_fail git stash apply does-not-exist &&
+ test_must_fail git stash apply does-not-exist@{0} &&
+ test_must_fail git stash show does-not-exist &&
+ test_must_fail git stash show does-not-exist@{0} &&
+ test_must_fail git stash branch tmp does-not-exist &&
+ test_must_fail git stash branch tmp does-not-exist@{0} &&
+ git stash drop
+'
+
+test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
+ git stash clear &&
+ test_must_fail git stash drop stash@{0} &&
+ echo bar5 > file &&
+ echo bar6 > file2 &&
+ git add file2 &&
+ git stash &&
+ test_must_fail git stash drop stash@{1} &&
+ test_must_fail git stash pop stash@{1} &&
+ test_must_fail git stash apply stash@{1} &&
+ test_must_fail git stash show stash@{1} &&
+ test_must_fail git stash branch tmp stash@{1} &&
+ git stash drop
+'
+
+test_expect_success 'stash branch should not drop the stash if the branch exists' '
+ git stash clear &&
+ echo foo >file &&
+ git add file &&
+ git commit -m initial &&
+ echo bar >file &&
+ git stash &&
+ test_must_fail git stash branch master stash@{0} &&
+ git rev-parse stash@{0} --
+'
+
+test_expect_success 'stash apply shows status same as git status (relative to current directory)' '
+ git stash clear &&
+ echo 1 >subdir/subfile1 &&
+ echo 2 >subdir/subfile2 &&
+ git add subdir/subfile1 &&
+ git commit -m subdir &&
+ (
+ cd subdir &&
+ echo x >subfile1 &&
+ echo x >../file &&
+ git status >../expect &&
+ git stash &&
+ sane_unset GIT_MERGE_VERBOSITY &&
+ git stash apply
+ ) |
+ sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..."
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index f37e3bc..781fd71 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -3,7 +3,7 @@
test_description='git checkout --patch'
. ./lib-patch-mode.sh
-test_expect_success 'setup' '
+test_expect_success PERL 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
@@ -19,14 +19,14 @@ test_expect_success 'setup' '
# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
-test_expect_success 'saying "n" does nothing' '
- set_state dir/foo work index
+test_expect_success PERL 'saying "n" does nothing' '
+ set_state dir/foo work index &&
(echo n; echo n) | test_must_fail git stash save -p &&
verify_state dir/foo work index &&
verify_saved_state bar
'
-test_expect_success 'git stash -p' '
+test_expect_success PERL 'git stash -p' '
(echo n; echo y) | git stash save -p &&
verify_state dir/foo head index &&
verify_saved_state bar &&
@@ -36,7 +36,7 @@ test_expect_success 'git stash -p' '
verify_state bar dummy dummy
'
-test_expect_success 'git stash -p --no-keep-index' '
+test_expect_success PERL 'git stash -p --no-keep-index' '
set_state dir/foo work index &&
set_state bar bar_work bar_index &&
(echo n; echo y) | git stash save -p --no-keep-index &&
@@ -48,7 +48,19 @@ test_expect_success 'git stash -p --no-keep-index' '
verify_state bar dummy bar_index
'
-test_expect_success 'none of this moved HEAD' '
+test_expect_success PERL 'git stash --no-keep-index -p' '
+ set_state dir/foo work index &&
+ set_state bar bar_work bar_index &&
+ (echo n; echo y) | git stash save --no-keep-index -p &&
+ verify_state dir/foo head head &&
+ verify_state bar bar_work dummy &&
+ git reset --hard &&
+ git stash apply --index &&
+ verify_state dir/foo work index &&
+ verify_state bar dummy bar_index
+'
+
+test_expect_success PERL 'none of this moved HEAD' '
verify_saved_head
'
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
index 71bac83..844277c 100755
--- a/t/t4001-diff-rename.sh
+++ b/t/t4001-diff-rename.sh
@@ -71,10 +71,35 @@ test_expect_success 'favour same basenames over different ones' '
git rm path1 &&
mkdir subdir &&
git mv another-path subdir/path1 &&
- git status | grep "renamed: .*path1 -> subdir/path1"'
+ git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
-test_expect_success 'favour same basenames even with minor differences' '
+test_expect_success 'favour same basenames even with minor differences' '
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
- git status | grep "renamed: .*path1 -> subdir/path1"'
+ git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
+
+test_expect_success 'setup for many rename source candidates' '
+ git reset --hard &&
+ for i in 0 1 2 3 4 5 6 7 8 9;
+ do
+ for j in 0 1 2 3 4 5 6 7 8 9;
+ do
+ echo "$i$j" >"path$i$j"
+ done
+ done &&
+ git add "path??" &&
+ test_tick &&
+ git commit -m "hundred" &&
+ (cat path1; echo new) >new-path &&
+ echo old >>path1 &&
+ git add new-path path1 &&
+ git diff -l 4 -C -C --cached --name-status >actual 2>actual.err &&
+ sed -e "s/^\([CM]\)[0-9]* /\1 /" actual >actual.munged &&
+ cat >expect <<-EOF &&
+ C path1 new-path
+ M path1
+ EOF
+ test_cmp expect actual.munged &&
+ grep warning actual.err
+'
test_done
diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh
index 73441a5..a5e8b83 100755
--- a/t/t4002-diff-basic.sh
+++ b/t/t4002-diff-basic.sh
@@ -126,15 +126,12 @@ cat >.test-recursive-AB <<\EOF
:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M Z/NM
EOF
-x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-x40="$x40$x40$x40$x40$x40$x40$x40$x40"
-z40='0000000000000000000000000000000000000000'
cmp_diff_files_output () {
# diff-files never reports additions. Also it does not fill in the
# object ID for the changed files because it wants you to look at the
# filesystem.
sed <"$2" >.test-tmp \
- -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\) /'$z40'\1 /' &&
+ -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\) /'$_z40'\1 /' &&
test_cmp "$1" .test-tmp
}
@@ -205,8 +202,8 @@ test_expect_success \
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_A &&
git checkout-index -f -a &&
- git read-tree --reset $tree_O || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_O &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OA'
@@ -215,8 +212,8 @@ test_expect_success \
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_B &&
git checkout-index -f -a &&
- git read-tree --reset $tree_O || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_O &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OB'
@@ -225,8 +222,8 @@ test_expect_success \
'rm -fr Z [A-Z][A-Z] &&
git read-tree $tree_B &&
git checkout-index -f -a &&
- git read-tree --reset $tree_A || return 1
- git update-index --refresh >/dev/null ;# this can exit non-zero
+ git read-tree --reset $tree_A &&
+ test_must_fail git update-index --refresh -q &&
git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-AB'
diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh
index c6130c4..bfa8835 100755
--- a/t/t4003-diff-rename-1.sh
+++ b/t/t4003-diff-rename-1.sh
@@ -29,7 +29,7 @@ test_expect_success \
# copy-and-edit one, and rename-and-edit the other. We do not say
# anything about rezrov.
-GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current
cat >expected <<\EOF
diff --git a/COPYING b/COPYING.1
copy from COPYING
diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh
index a4da119..6e562c8 100755
--- a/t/t4004-diff-rename-symlink.sh
+++ b/t/t4004-diff-rename-symlink.sh
@@ -12,13 +12,7 @@ by an edit for them.
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
-test_expect_success \
+test_expect_success SYMLINKS \
'prepare reference tree' \
'echo xyzzy | tr -d '\\\\'012 >yomin &&
ln -s xyzzy frotz &&
@@ -26,7 +20,7 @@ test_expect_success \
tree=$(git write-tree) &&
echo $tree'
-test_expect_success \
+test_expect_success SYMLINKS \
'prepare work tree' \
'mv frotz rezrov &&
rm -f yomin &&
@@ -40,8 +34,9 @@ test_expect_success \
# rezrov and nitfol are rename/copy of frotz and bozbar should be
# a new creation.
-GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
-cat >expected <<\EOF
+test_expect_success SYMLINKS 'setup diff output' "
+ GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current &&
+ cat >expected <<\EOF
diff --git a/bozbar b/bozbar
new file mode 120000
--- /dev/null
@@ -65,8 +60,9 @@ deleted file mode 100644
-xyzzy
\ No newline at end of file
EOF
+"
-test_expect_success \
+test_expect_success SYMLINKS \
'validate diff output' \
'compare_diff_patch current expected'
diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh
index 1ba359d..77d7f49 100755
--- a/t/t4005-diff-rename-2.sh
+++ b/t/t4005-diff-rename-2.sh
@@ -29,7 +29,7 @@ test_expect_success \
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git diff-index -M $tree >current
+git diff-index -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
index e19ca65..73b4a24 100755
--- a/t/t4008-diff-break-rewrite.sh
+++ b/t/t4008-diff-break-rewrite.sh
@@ -155,7 +155,7 @@ test_expect_success \
git checkout-index -f -u -a &&
sed -e "s/git/GIT/" file0 >file1 &&
sed -e "s/git/GET/" file0 >file2 &&
- rm -f file0
+ rm -f file0 &&
git update-index --add --remove file0 file1 file2'
test_expect_success \
@@ -173,8 +173,8 @@ test_expect_success \
'compare_diff_raw expected current'
test_expect_success \
- 'run diff with -B -M' \
- 'git diff-index -B -M "$tree" >current'
+ 'run diff with -B -C' \
+ 'git diff-index -B -C "$tree" >current'
cat >expected <<\EOF
:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1
diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh
index de3f174..f22c8e3 100755
--- a/t/t4009-diff-rename-4.sh
+++ b/t/t4009-diff-rename-4.sh
@@ -29,7 +29,7 @@ test_expect_success \
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git diff-index -z -M $tree >current
+git diff-index -z -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index 94df7ae..fbc8cd8 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -70,4 +70,36 @@ test_expect_success 'diff-tree pathspec' '
test_cmp expected current
'
+EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+ git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result &&
+ echo file0 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+ git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree with wildcard shows dir also matches' '
+ git diff-tree --name-only $tree $tree2 -- "path1/f*" >result &&
+ echo path1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard from beginning' '
+ git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-tree -r with wildcard' '
+ git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result &&
+ echo path1/file1 >expected &&
+ test_cmp expected result
+'
+
test_done
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
index e12fbea..408a19c 100755
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
@@ -9,12 +9,6 @@ test_description='Test diff of symlinks.
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
cat > expected << EOF
diff --git a/frotz b/frotz
new file mode 120000
@@ -26,7 +20,7 @@ index 0000000..7c465af
\ No newline at end of file
EOF
-test_expect_success \
+test_expect_success SYMLINKS \
'diff new symlink' \
'ln -s xyzzy frotz &&
git update-index &&
@@ -35,7 +29,7 @@ test_expect_success \
GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
-test_expect_success \
+test_expect_success SYMLINKS \
'diff unchanged symlink' \
'tree=$(git write-tree) &&
git update-index frotz &&
@@ -52,7 +46,7 @@ index 7c465af..0000000
\ No newline at end of file
EOF
-test_expect_success \
+test_expect_success SYMLINKS \
'diff removed symlink' \
'mv frotz frotz2 &&
git diff-index -M -p $tree > current &&
@@ -62,7 +56,7 @@ cat > expected << EOF
diff --git a/frotz b/frotz
EOF
-test_expect_success \
+test_expect_success SYMLINKS \
'diff identical, but newly created symlink' \
'ln -s xyzzy frotz &&
git diff-index -M -p $tree > current &&
@@ -80,18 +74,44 @@ index 7c465af..df1db54 120000
\ No newline at end of file
EOF
-test_expect_success \
+test_expect_success SYMLINKS \
'diff different symlink' \
'rm frotz &&
ln -s yxyyz frotz &&
git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
-test_expect_success \
+test_expect_success SYMLINKS \
'diff symlinks with non-existing targets' \
'ln -s narf pinky &&
ln -s take\ over brain &&
test_must_fail git diff --no-index pinky brain > output 2> output.err &&
grep narf output &&
! grep error output.err'
+
+test_expect_success SYMLINKS 'setup symlinks with attributes' '
+ echo "*.bin diff=bin" >>.gitattributes &&
+ echo content >file.bin &&
+ ln -s file.bin link.bin &&
+ git add -N file.bin link.bin
+'
+
+cat >expect <<'EOF'
+diff --git a/file.bin b/file.bin
+index e69de29..d95f3ad 100644
+Binary files a/file.bin and b/file.bin differ
+diff --git a/link.bin b/link.bin
+index e69de29..dce41ec 120000
+--- a/link.bin
++++ b/link.bin
+@@ -0,0 +1 @@
++file.bin
+\ No newline at end of file
+EOF
+test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' '
+ git config diff.bin.binary true &&
+ git diff file.bin link.bin >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index bc46563..05ec062 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -77,10 +77,6 @@ test_expect_success 'apply binary patch' \
tree1=`git write-tree` &&
test "$tree1" = "$tree0"'
-nul_to_q() {
- perl -pe 'y/\000/Q/'
-}
-
test_expect_success 'diff --no-index with binary creation' '
echo Q | q_to_nul >binary &&
(: hide error code from diff, which just indicates differences
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index dae6358..93a6f20 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -80,18 +80,31 @@ test_expect_success setup '
git config log.showroot false &&
git commit --amend &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+ git checkout -b rearrange initial &&
+ for i in B A; do echo $i; done >dir/sub &&
+ git add dir/sub &&
+ git commit -m "Rearranged lines in dir/sub" &&
+ git checkout master &&
+
git show-branch
'
: <<\EOF
! [initial] Initial
* [master] Merge branch 'side'
- ! [side] Side
----
- - [master] Merge branch 'side'
- *+ [side] Side
- * [master^] Second
-+*+ [initial] Initial
+ ! [rearrange] Rearranged lines in dir/sub
+ ! [side] Side
+----
+ + [rearrange] Rearranged lines in dir/sub
+ - [master] Merge branch 'side'
+ * + [side] Side
+ * [master^] Third
+ * [master~2] Second
++*++ [initial] Initial
EOF
V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'`
@@ -208,7 +221,14 @@ log -p --first-parent master
log -m -p --first-parent master
log -m -p master
log -SF master
+log -S F master
log -SF -p master
+log -SF master --max-count=0
+log -SF master --max-count=1
+log -SF master --max-count=2
+log -GF master
+log -GF -p master
+log -GF -p --pickaxe-all master
log --decorate --all
log --decorate=full --all
@@ -280,6 +300,23 @@ diff --no-index --name-status -- dir2 dir
diff --no-index dir dir3
diff master master^ side
diff --dirstat master~1 master~2
+diff --dirstat initial rearrange
+diff --dirstat-by-file initial rearrange
EOF
+test_expect_success 'log -S requires an argument' '
+ test_must_fail git log -S
+'
+
+test_expect_success 'diff --cached on unborn branch' '
+ echo ref: refs/heads/unborn >.git/HEAD &&
+ git diff --cached >result &&
+ test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached" result
+'
+
+test_expect_success 'diff --cached -- file on unborn branch' '
+ git diff --cached -- file0 >result &&
+ test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result
+'
+
test_done
diff --git a/t/t4013/diff.diff_--cached b/t/t4013/diff.diff_--cached
new file mode 100644
index 0000000..ff16e83
--- /dev/null
+++ b/t/t4013/diff.diff_--cached
@@ -0,0 +1,38 @@
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..992913c
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,8 @@
++A
++B
++C
++D
++E
++F
++1
++2
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..10a8a9f
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,9 @@
++1
++2
++3
++4
++5
++6
++A
++B
++C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
diff --git a/t/t4013/diff.diff_--cached_--_file0 b/t/t4013/diff.diff_--cached_--_file0
new file mode 100644
index 0000000..b9bb858
--- /dev/null
+++ b/t/t4013/diff.diff_--cached_--_file0
@@ -0,0 +1,15 @@
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..10a8a9f
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,9 @@
++1
++2
++3
++4
++5
++6
++A
++B
++C
diff --git a/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange b/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange
new file mode 100644
index 0000000..e48e33f
--- /dev/null
+++ b/t/t4013/diff.diff_--dirstat-by-file_initial_rearrange
@@ -0,0 +1,3 @@
+$ git diff --dirstat-by-file initial rearrange
+ 100.0% dir/
+$
diff --git a/t/t4013/diff.diff_--dirstat_initial_rearrange b/t/t4013/diff.diff_--dirstat_initial_rearrange
new file mode 100644
index 0000000..5fb02c1
--- /dev/null
+++ b/t/t4013/diff.diff_--dirstat_initial_rearrange
@@ -0,0 +1,3 @@
+$ git diff --dirstat initial rearrange
+ 100.0% dir/
+$
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
index 1f0f9ad..3b4e113 100644
--- a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
+++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
@@ -1,7 +1,7 @@
$ git format-patch --stdout --cover-letter -n initial..master^
From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
From: C O Mitter <committer@example.com>
-Date: Mon, 26 Jun 2006 00:05:00 +0000
+Date: Mon, 26 Jun 2006 00:06:00 +0000
Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE ***
*** BLURB HERE ***
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
index d155e0b..44d4525 100644
--- a/t/t4013/diff.log_--decorate=full_--all
+++ b/t/t4013/diff.log_--decorate=full_--all
@@ -1,4 +1,10 @@
$ git log --decorate=full --all
+commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ Rearranged lines in dir/sub
+
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master)
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
index fd7c3e6..27d3eab 100644
--- a/t/t4013/diff.log_--decorate_--all
+++ b/t/t4013/diff.log_--decorate_--all
@@ -1,4 +1,10 @@
$ git log --decorate --all
+commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ Rearranged lines in dir/sub
+
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master)
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_-GF_-p_--pickaxe-all_master b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master
new file mode 100644
index 0000000..d36f880
--- /dev/null
+++ b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master
@@ -0,0 +1,27 @@
+$ git log -GF -p --pickaxe-all master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+$
diff --git a/t/t4013/diff.log_-GF_-p_master b/t/t4013/diff.log_-GF_-p_master
new file mode 100644
index 0000000..9d93f2c
--- /dev/null
+++ b/t/t4013/diff.log_-GF_-p_master
@@ -0,0 +1,18 @@
+$ git log -GF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-GF_master
new file mode 100644
index 0000000..4c6708d
--- /dev/null
+++ b/t/t4013/diff.log_-GF_master
@@ -0,0 +1,7 @@
+$ git log -GF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=0 b/t/t4013/diff.log_-SF_master_--max-count=0
new file mode 100644
index 0000000..c1fc6c8
--- /dev/null
+++ b/t/t4013/diff.log_-SF_master_--max-count=0
@@ -0,0 +1,2 @@
+$ git log -SF master --max-count=0
+$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=1 b/t/t4013/diff.log_-SF_master_--max-count=1
new file mode 100644
index 0000000..c981a03
--- /dev/null
+++ b/t/t4013/diff.log_-SF_master_--max-count=1
@@ -0,0 +1,7 @@
+$ git log -SF master --max-count=1
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+$
diff --git a/t/t4013/diff.log_-SF_master_--max-count=2 b/t/t4013/diff.log_-SF_master_--max-count=2
new file mode 100644
index 0000000..a6c55fd
--- /dev/null
+++ b/t/t4013/diff.log_-SF_master_--max-count=2
@@ -0,0 +1,7 @@
+$ git log -SF master --max-count=2
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+$
diff --git a/t/t4013/diff.log_-S_F_master b/t/t4013/diff.log_-S_F_master
new file mode 100644
index 0000000..978d2b4
--- /dev/null
+++ b/t/t4013/diff.log_-S_F_master
@@ -0,0 +1,7 @@
+$ git log -S F master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+$
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index f87434b..92248d2 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -6,30 +6,36 @@
test_description='various format-patch tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success setup '
for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
cat file >elif &&
git add file elif &&
+ test_tick &&
git commit -m Initial &&
git checkout -b side &&
for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
test_chmod +x elif &&
+ test_tick &&
git commit -m "Side changes #1" &&
for i in D E F; do echo "$i"; done >>file &&
git update-index file &&
+ test_tick &&
git commit -m "Side changes #2" &&
git tag C2 &&
for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
git update-index file &&
+ test_tick &&
git commit -m "Side changes #3 with \\n backslash-n in it." &&
git checkout master &&
git diff-tree -p C2 | git apply --index &&
+ test_tick &&
git commit -m "Master accepts moral equivalent of #2"
'
@@ -51,6 +57,22 @@ test_expect_success "format-patch --ignore-if-in-upstream" '
'
+test_expect_success "format-patch doesn't consider merge commits" '
+
+ git checkout -b slave master &&
+ echo "Another line" >>file &&
+ test_tick &&
+ git commit -am "Slave change #1" &&
+ echo "Yet another line" >>file &&
+ test_tick &&
+ git commit -am "Slave change #2" &&
+ git checkout -b merger master &&
+ test_tick &&
+ git merge --no-ff slave &&
+ cnt=`git format-patch -3 --stdout | grep "^From " | wc -l` &&
+ test $cnt = 3
+'
+
test_expect_success "format-patch result applies" '
git checkout -b rebuild-0 master &&
@@ -594,11 +616,11 @@ echo "fatal: --check does not make sense" > expect.check
test_expect_success 'options no longer allowed for format-patch' '
test_must_fail git format-patch --name-only 2> output &&
- test_cmp expect.name-only output &&
+ test_i18ncmp expect.name-only output &&
test_must_fail git format-patch --name-status 2> output &&
- test_cmp expect.name-status output &&
+ test_i18ncmp expect.name-status output &&
test_must_fail git format-patch --check 2> output &&
- test_cmp expect.check output'
+ test_i18ncmp expect.check output'
test_expect_success 'format-patch --numstat should produce a patch' '
git format-patch --numstat --stdout master..side > output &&
@@ -665,4 +687,186 @@ test_expect_success 'format-patch --signature="" supresses signatures' '
! grep "^-- \$" output
'
+test_expect_success TTY 'format-patch --stdout paginates' '
+ rm -f pager_used &&
+ (
+ GIT_PAGER="wc >pager_used" &&
+ export GIT_PAGER &&
+ test_terminal git format-patch --stdout --all
+ ) &&
+ test_path_is_file pager_used
+'
+
+ test_expect_success TTY 'format-patch --stdout pagination can be disabled' '
+ rm -f pager_used &&
+ (
+ GIT_PAGER="wc >pager_used" &&
+ export GIT_PAGER &&
+ test_terminal git --no-pager format-patch --stdout --all &&
+ test_terminal git -c "pager.format-patch=false" format-patch --stdout --all
+ ) &&
+ test_path_is_missing pager_used &&
+ test_path_is_missing .git/pager_used
+'
+
+test_expect_success 'format-patch handles multi-line subjects' '
+ rm -rf patches/ &&
+ echo content >>file &&
+ for i in one two three; do echo $i; done >msg &&
+ git add file &&
+ git commit -F msg &&
+ git format-patch -o patches -1 &&
+ grep ^Subject: patches/0001-one.patch >actual &&
+ echo "Subject: [PATCH] one two three" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'format-patch handles multi-line encoded subjects' '
+ rm -rf patches/ &&
+ echo content >>file &&
+ for i in en två tre; do echo $i; done >msg &&
+ git add file &&
+ git commit -F msg &&
+ git format-patch -o patches -1 &&
+ grep ^Subject: patches/0001-en.patch >actual &&
+ echo "Subject: [PATCH] =?UTF-8?q?en=20tv=C3=A5=20tre?=" >expect &&
+ test_cmp expect actual
+'
+
+M8="foo bar "
+M64=$M8$M8$M8$M8$M8$M8$M8$M8
+M512=$M64$M64$M64$M64$M64$M64$M64$M64
+cat >expect <<'EOF'
+Subject: [PATCH] foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo
+ bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar
+ foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo
+ bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar
+ foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo
+ bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar
+ foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo
+ bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar
+ foo bar foo bar foo bar foo bar
+EOF
+test_expect_success 'format-patch wraps extremely long headers (ascii)' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "$M512" &&
+ git format-patch --stdout -1 >patch &&
+ sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+ test_cmp expect subject
+'
+
+M8="föö bar "
+M64=$M8$M8$M8$M8$M8$M8$M8$M8
+M512=$M64$M64$M64$M64$M64$M64$M64$M64
+cat >expect <<'EOF'
+Subject: [PATCH] =?UTF-8?q?f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+EOF
+test_expect_success 'format-patch wraps extremely long headers (rfc2047)' '
+ rm -rf patches/ &&
+ echo content >>file &&
+ git add file &&
+ git commit -m "$M512" &&
+ git format-patch --stdout -1 >patch &&
+ sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+ test_cmp expect subject
+'
+
+M8="foo_bar_"
+M64=$M8$M8$M8$M8$M8$M8$M8$M8
+cat >expect <<EOF
+From: $M64
+ <foobar@foo.bar>
+EOF
+test_expect_success 'format-patch wraps non-quotable headers' '
+ rm -rf patches/ &&
+ echo content >>file &&
+ git add file &&
+ git commit -mfoo --author "$M64 <foobar@foo.bar>" &&
+ git format-patch --stdout -1 >patch &&
+ sed -n "/^From: /p; /^ /p; /^$/q" <patch >from &&
+ test_cmp expect from
+'
+
+check_author() {
+ echo content >>file &&
+ git add file &&
+ GIT_AUTHOR_NAME=$1 git commit -m author-check &&
+ git format-patch --stdout -1 >patch &&
+ grep ^From: patch >actual &&
+ test_cmp expect actual
+}
+
+cat >expect <<'EOF'
+From: "Foo B. Bar" <author@example.com>
+EOF
+test_expect_success 'format-patch quotes dot in headers' '
+ check_author "Foo B. Bar"
+'
+
+cat >expect <<'EOF'
+From: "Foo \"The Baz\" Bar" <author@example.com>
+EOF
+test_expect_success 'format-patch quotes double-quote in headers' '
+ check_author "Foo \"The Baz\" Bar"
+'
+
+cat >expect <<'EOF'
+From: =?UTF-8?q?"F=C3=B6o=20B.=20Bar"?= <author@example.com>
+EOF
+test_expect_success 'rfc2047-encoded headers also double-quote 822 specials' '
+ check_author "Föo B. Bar"
+'
+
+cat >expect <<'EOF'
+Subject: header with . in it
+EOF
+test_expect_success 'subject lines do not have 822 atom-quoting' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "header with . in it" &&
+ git format-patch -k -1 --stdout >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PREFIX 1/1] header with . in it
+EOF
+test_expect_success 'subject prefixes have space prepended' '
+ git format-patch -n -1 --stdout --subject-prefix=PREFIX >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [1/1] header with . in it
+EOF
+test_expect_success 'empty subject prefix does not have extra space' '
+ git format-patch -n -1 --stdout --subject-prefix= >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 935d101..9059bcd 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -330,7 +330,7 @@ test_expect_success 'check space before tab in indent (space-before-tab: on)' '
test_expect_success 'check spaces as indentation (indent-with-non-tab: off)' '
- git config core.whitespace "-indent-with-non-tab"
+ git config core.whitespace "-indent-with-non-tab" &&
echo " foo ();" > x &&
git diff --check
@@ -344,6 +344,13 @@ test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' '
'
+test_expect_success 'ditto, but tabwidth=9' '
+
+ git config core.whitespace "indent-with-non-tab,tabwidth=9" &&
+ git diff --check
+
+'
+
test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: on)' '
git config core.whitespace "indent-with-non-tab" &&
@@ -352,6 +359,20 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab:
'
+test_expect_success 'ditto, but tabwidth=10' '
+
+ git config core.whitespace "indent-with-non-tab,tabwidth=10" &&
+ test_must_fail git diff --check
+
+'
+
+test_expect_success 'ditto, but tabwidth=20' '
+
+ git config core.whitespace "indent-with-non-tab,tabwidth=20" &&
+ git diff --check
+
+'
+
test_expect_success 'check tabs as indentation (tab-in-indent: off)' '
git config core.whitespace "-tab-in-indent" &&
@@ -376,6 +397,13 @@ test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
'
+test_expect_success 'ditto, but tabwidth=1 (must be irrelevant)' '
+
+ git config core.whitespace "tab-in-indent,tabwidth=1" &&
+ test_must_fail git diff --check
+
+'
+
test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' '
git config core.whitespace "tab-in-indent,indent-with-non-tab" &&
@@ -491,4 +519,41 @@ test_expect_success 'combined diff with autocrlf conversion' '
'
+# Start testing the colored format for whitespace checks
+
+test_expect_success 'setup diff colors' '
+ git config color.diff always &&
+ git config color.diff.plain normal &&
+ git config color.diff.meta bold &&
+ git config color.diff.frag cyan &&
+ git config color.diff.func normal &&
+ git config color.diff.old red &&
+ git config color.diff.new green &&
+ git config color.diff.commit yellow &&
+ git config color.diff.whitespace "normal red" &&
+
+ git config core.autocrlf false
+'
+cat >expected <<\EOF
+<BOLD>diff --git a/x b/x<RESET>
+<BOLD>index 9daeafb..2874b91 100644<RESET>
+<BOLD>--- a/x<RESET>
+<BOLD>+++ b/x<RESET>
+<CYAN>@@ -1 +1,4 @@<RESET>
+ test<RESET>
+<GREEN>+<RESET><GREEN>{<RESET>
+<GREEN>+<RESET><BRED> <RESET>
+<GREEN>+<RESET><GREEN>}<RESET>
+EOF
+
+test_expect_success 'diff that introduces a line with only tabs' '
+ git config core.whitespace blank-at-eol &&
+ git reset --hard &&
+ echo "test" > x &&
+ git commit -m "initial" x &&
+ echo "{NTN}" | tr "NT" "\n\t" >> x &&
+ git -c color.diff=always diff | test_decode_color >current &&
+ test_cmp expected current
+'
+
test_done
diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh
index 55eb5f8..ab0c2f0 100755
--- a/t/t4016-diff-quote.sh
+++ b/t/t4016-diff-quote.sh
@@ -13,12 +13,14 @@ P1='pathname with HT'
P2='pathname with SP'
P3='pathname
with LF'
-: 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || {
- say 'Your filesystem does not allow tabs in filenames, test skipped.'
- test_done
-}
+if : 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1"
+then
+ test_set_prereq TABS_IN_FILENAMES
+else
+ say 'Your filesystem does not allow tabs in filenames'
+fi
-test_expect_success setup '
+test_expect_success TABS_IN_FILENAMES setup '
echo P0.0 >"$P0.0" &&
echo P0.1 >"$P0.1" &&
echo P0.2 >"$P0.2" &&
@@ -38,6 +40,7 @@ test_expect_success setup '
:
'
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
cat >expect <<\EOF
rename pathname.1 => "Rpathname\twith HT.0" (100%)
rename pathname.3 => "Rpathname\nwith LF.0" (100%)
@@ -47,11 +50,14 @@ cat >expect <<\EOF
rename pathname.0 => Rpathname.0 (100%)
rename "pathname\twith HT.0" => Rpathname.1 (100%)
EOF
-test_expect_success 'git diff --summary -M HEAD' '
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff --summary -M HEAD' '
git diff --summary -M HEAD >actual &&
test_cmp expect actual
'
+test_expect_success TABS_IN_FILENAMES 'setup expected files' '
cat >expect <<\EOF
pathname.1 => "Rpathname\twith HT.0" | 0
pathname.3 => "Rpathname\nwith LF.0" | 0
@@ -62,7 +68,9 @@ cat >expect <<\EOF
"pathname\twith HT.0" => Rpathname.1 | 0
7 files changed, 0 insertions(+), 0 deletions(-)
EOF
-test_expect_success 'git diff --stat -M HEAD' '
+'
+
+test_expect_success TABS_IN_FILENAMES 'git diff --stat -M HEAD' '
git diff --stat -M HEAD >actual &&
test_cmp expect actual
'
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
index 6158985..95a7ca7 100755
--- a/t/t4017-diff-retval.sh
+++ b/t/t4017-diff-retval.sh
@@ -29,66 +29,49 @@ test_expect_success 'git diff --quiet -w HEAD^ HEAD' '
'
test_expect_success 'git diff-tree HEAD^ HEAD' '
- git diff-tree --exit-code HEAD^ HEAD
- test $? = 1
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD
'
test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
git diff-tree --exit-code HEAD^ HEAD -- a
- test $? = 0
'
test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
- git diff-tree --exit-code HEAD^ HEAD -- b
- test $? = 1
+ test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- b
'
test_expect_success 'echo HEAD | git diff-tree --stdin' '
- echo $(git rev-parse HEAD) | git diff-tree --exit-code --stdin
- test $? = 1
+ echo $(git rev-parse HEAD) | test_expect_code 1 git diff-tree --exit-code --stdin
'
test_expect_success 'git diff-tree HEAD HEAD' '
git diff-tree --exit-code HEAD HEAD
- test $? = 0
'
test_expect_success 'git diff-files' '
git diff-files --exit-code
- test $? = 0
'
test_expect_success 'git diff-index --cached HEAD' '
git diff-index --exit-code --cached HEAD
- test $? = 0
'
test_expect_success 'git diff-index --cached HEAD^' '
- git diff-index --exit-code --cached HEAD^
- test $? = 1
+ test_expect_code 1 git diff-index --exit-code --cached HEAD^
'
test_expect_success 'git diff-index --cached HEAD^' '
echo text >>b &&
echo 3 >c &&
- git add . && {
- git diff-index --exit-code --cached HEAD^
- test $? = 1
- }
+ git add . &&
+ test_expect_code 1 git diff-index --exit-code --cached HEAD^
'
test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
- git commit -m "text in b" && {
- git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
- test $? = 1
- }
+ git commit -m "text in b" &&
+ test_expect_code 1 git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
'
test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
git diff-tree -p --exit-code -Snot-found HEAD^ HEAD -- b
- test $? = 0
'
test_expect_success 'git diff-files' '
- echo 3 >>c && {
- git diff-files --exit-code
- test $? = 1
- }
+ echo 3 >>c &&
+ test_expect_code 1 git diff-files --exit-code
'
test_expect_success 'git diff-index --cached HEAD' '
- git update-index c && {
- git diff-index --exit-code --cached HEAD
- test $? = 1
- }
+ git update-index c &&
+ test_expect_code 1 git diff-index --exit-code --cached HEAD
'
test_expect_success '--check --exit-code returns 0 for no difference' '
@@ -100,30 +83,26 @@ test_expect_success '--check --exit-code returns 0 for no difference' '
test_expect_success '--check --exit-code returns 1 for a clean difference' '
echo "good" > a &&
- git diff --check --exit-code
- test $? = 1
+ test_expect_code 1 git diff --check --exit-code
'
test_expect_success '--check --exit-code returns 3 for a dirty difference' '
echo "bad " >> a &&
- git diff --check --exit-code
- test $? = 3
+ test_expect_code 3 git diff --check --exit-code
'
test_expect_success '--check with --no-pager returns 2 for dirty difference' '
- git --no-pager diff --check
- test $? = 2
+ test_expect_code 2 git --no-pager diff --check
'
test_expect_success 'check should test not just the last line' '
echo "" >>a &&
- git --no-pager diff --check
- test $? = 2
+ test_expect_code 2 git --no-pager diff --check
'
@@ -133,10 +112,8 @@ test_expect_success 'check detects leftover conflict markers' '
echo binary >>b &&
git commit -m "side" b &&
test_must_fail git merge master &&
- git add b && (
- git --no-pager diff --cached --check >test.out
- test $? = 2
- ) &&
+ git add b &&
+ test_expect_code 2 git --no-pager diff --cached --check >test.out &&
test 3 = $(grep "conflict marker" test.out | wc -l) &&
git reset --hard
'
@@ -146,19 +123,13 @@ test_expect_success 'check honors conflict marker length' '
echo ">>>>>>> boo" >>b &&
echo "======" >>a &&
git diff --check a &&
- (
- git diff --check b
- test $? = 2
- ) &&
+ test_expect_code 2 git diff --check b &&
git reset --hard &&
echo ">>>>>>>> boo" >>b &&
echo "========" >>a &&
git diff --check &&
echo "b conflict-marker-size=8" >.gitattributes &&
- (
- git diff --check b
- test $? = 2
- ) &&
+ test_expect_code 2 git diff --check b &&
git diff --check a &&
git reset --hard
'
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index 5b10e97..b68c56b 100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
@@ -9,8 +9,7 @@ test_description='Test custom diff function name patterns'
LF='
'
-
-cat > Beer.java << EOF
+cat >Beer.java <<\EOF
public class Beer
{
int special;
@@ -29,56 +28,163 @@ public class Beer
}
}
EOF
+sed 's/beer\\/beer,\\/' <Beer.java >Beer-correct.java
+cat >Beer.perl <<\EOT
+package Beer;
+
+use strict;
+use warnings;
+use parent qw(Exporter);
+our @EXPORT_OK = qw(round finalround);
+
+sub other; # forward declaration
+
+# hello
+
+sub round {
+ my ($n) = @_;
+ print "$n bottles of beer on the wall ";
+ print "$n bottles of beer\n";
+ print "Take one down, pass it around, ";
+ $n = $n - 1;
+ print "$n bottles of beer on the wall.\n";
+}
+
+sub finalround
+{
+ print "Go to the store, buy some more\n";
+ print "99 bottles of beer on the wall.\n");
+}
+
+sub withheredocument {
+ print <<"EOF"
+decoy here-doc
+EOF
+ # some lines of context
+ # to pad it out
+ print "hello\n";
+}
+
+__END__
+
+=head1 NAME
+
+Beer - subroutine to output fragment of a drinking song
+
+=head1 SYNOPSIS
+
+ use Beer qw(round finalround);
+
+ sub song {
+ for (my $i = 99; $i > 0; $i--) {
+ round $i;
+ }
+ finalround;
+ }
+
+ song;
-sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
+=cut
+EOT
+sed -e '
+ s/hello/goodbye/
+ s/beer\\/beer,\\/
+ s/more\\/more,\\/
+ s/song;/song();/
+' <Beer.perl >Beer-correct.perl
-builtin_patterns="bibtex cpp html java objc pascal php python ruby tex"
-for p in $builtin_patterns
+test_config () {
+ git config "$1" "$2" &&
+ test_when_finished "git config --unset $1"
+}
+
+test_expect_funcname () {
+ lang=${2-java}
+ test_expect_code 1 git diff --no-index -U1 \
+ "Beer.$lang" "Beer-correct.$lang" >diff &&
+ grep "^@@.*@@ $1" diff
+}
+
+for p in bibtex cpp csharp fortran html java objc pascal perl php python ruby tex
do
test_expect_success "builtin $p pattern compiles" '
- echo "*.java diff=$p" > .gitattributes &&
- ! ( git diff --no-index Beer.java Beer-correct.java 2>&1 |
- grep "fatal" > /dev/null )
+ echo "*.java diff=$p" >.gitattributes &&
+ test_expect_code 1 git diff --no-index \
+ Beer.java Beer-correct.java 2>msg &&
+ ! grep fatal msg &&
+ ! grep error msg
+ '
+ test_expect_success "builtin $p wordRegex pattern compiles" '
+ echo "*.java diff=$p" >.gitattributes &&
+ test_expect_code 1 git diff --no-index --word-diff \
+ Beer.java Beer-correct.java 2>msg &&
+ ! grep fatal msg &&
+ ! grep error msg
'
done
test_expect_success 'default behaviour' '
rm -f .gitattributes &&
- git diff --no-index Beer.java Beer-correct.java |
- grep "^@@.*@@ public class Beer"
+ test_expect_funcname "public class Beer\$"
+'
+
+test_expect_success 'set up .gitattributes declaring drivers to test' '
+ cat >.gitattributes <<-\EOF
+ *.java diff=java
+ *.perl diff=perl
+ EOF
'
test_expect_success 'preset java pattern' '
- echo "*.java diff=java" >.gitattributes &&
- git diff --no-index Beer.java Beer-correct.java |
- grep "^@@.*@@ public static void main("
+ test_expect_funcname "public static void main("
'
-git config diff.java.funcname '!static
-!String
-[^ ].*s.*'
+test_expect_success 'preset perl pattern' '
+ test_expect_funcname "sub round {\$" perl
+'
+
+test_expect_success 'perl pattern accepts K&R style brace placement, too' '
+ test_expect_funcname "sub finalround\$" perl
+'
+
+test_expect_success 'but is not distracted by end of <<here document' '
+ test_expect_funcname "sub withheredocument {\$" perl
+'
+
+test_expect_success 'perl pattern is not distracted by sub within POD' '
+ test_expect_funcname "=head" perl
+'
+
+test_expect_success 'perl pattern gets full line of POD header' '
+ test_expect_funcname "=head1 SYNOPSIS\$" perl
+'
+
+test_expect_success 'perl pattern is not distracted by forward declaration' '
+ test_expect_funcname "package Beer;\$" perl
+'
test_expect_success 'custom pattern' '
- git diff --no-index Beer.java Beer-correct.java |
- grep "^@@.*@@ int special;$"
+ test_config diff.java.funcname "!static
+!String
+[^ ].*s.*" &&
+ test_expect_funcname "int special;\$"
'
test_expect_success 'last regexp must not be negated' '
- git config diff.java.funcname "!static" &&
- git diff --no-index Beer.java Beer-correct.java 2>&1 |
- grep "fatal: Last expression must not be negated:"
+ test_config diff.java.funcname "!static" &&
+ test_expect_code 128 git diff --no-index Beer.java Beer-correct.java 2>msg &&
+ grep ": Last expression must not be negated:" msg
'
test_expect_success 'pattern which matches to end of line' '
- git config diff.java.funcname "Beer$" &&
- git diff --no-index Beer.java Beer-correct.java |
- grep "^@@.*@@ Beer"
+ test_config diff.java.funcname "Beer\$" &&
+ test_expect_funcname "Beer\$"
'
test_expect_success 'alternation in pattern' '
- git config diff.java.xfuncname "^[ ]*((public|static).*)$" &&
- git diff --no-index Beer.java Beer-correct.java |
- grep "^@@.*@@ public static void main("
+ test_config diff.java.funcname "Beer$" &&
+ test_config diff.java.xfuncname "^[ ]*((public|static).*)$" &&
+ test_expect_funcname "public static void main("
'
test_done
diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh
index f6d1f1e..a501975 100755
--- a/t/t4019-diff-wserror.sh
+++ b/t/t4019-diff-wserror.sh
@@ -36,11 +36,12 @@ prepare_output () {
git diff --color >output
$grep_a "$blue_grep" output >error
$grep_a -v "$blue_grep" output >normal
+ return 0
}
test_expect_success default '
- prepare_output
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -50,10 +51,67 @@ test_expect_success default '
'
+test_expect_success 'default (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight error >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'default, tabwidth=10 (attribute)' '
+
+ git config core.whitespace "tabwidth=10" &&
+ echo "F whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return error >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F -whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'no check, tabwidth=10 (attribute), must be irrelevant' '
+
+ git config core.whitespace "tabwidth=10" &&
+ echo "F -whitespace" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
test_expect_success 'without -trail' '
- git config core.whitespace -trail
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace -trail &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -65,9 +123,9 @@ test_expect_success 'without -trail' '
test_expect_success 'without -trail (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=-trail" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=-trail" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -79,9 +137,9 @@ test_expect_success 'without -trail (attribute)' '
test_expect_success 'without -space' '
- rm -f .gitattributes
- git config core.whitespace -space
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace -space &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
@@ -93,9 +151,9 @@ test_expect_success 'without -space' '
test_expect_success 'without -space (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=-space" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=-space" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
@@ -107,9 +165,9 @@ test_expect_success 'without -space (attribute)' '
test_expect_success 'with indent-non-tab only' '
- rm -f .gitattributes
- git config core.whitespace indent,-trailing,-space
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace indent,-trailing,-space &&
+ prepare_output &&
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
@@ -121,9 +179,9 @@ test_expect_success 'with indent-non-tab only' '
test_expect_success 'with indent-non-tab only (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=indent,-trailing,-space" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=indent,-trailing,-space" >.gitattributes &&
+ prepare_output &&
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
@@ -133,11 +191,39 @@ test_expect_success 'with indent-non-tab only (attribute)' '
'
+test_expect_success 'with indent-non-tab only, tabwidth=10' '
+
+ rm -f .gitattributes &&
+ git config core.whitespace indent,tabwidth=10,-trailing,-space &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with indent-non-tab only, tabwidth=10 (attribute)' '
+
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=indent,-trailing,-space,tabwidth=10" >.gitattributes &&
+ prepare_output &&
+
+ grep Eight normal >/dev/null &&
+ grep HT normal >/dev/null &&
+ grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
test_expect_success 'with cr-at-eol' '
- rm -f .gitattributes
- git config core.whitespace cr-at-eol
- prepare_output
+ rm -f .gitattributes &&
+ git config core.whitespace cr-at-eol &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -149,9 +235,9 @@ test_expect_success 'with cr-at-eol' '
test_expect_success 'with cr-at-eol (attribute)' '
- git config --unset core.whitespace
- echo "F whitespace=trailing,cr-at-eol" >.gitattributes
- prepare_output
+ test_might_fail git config --unset core.whitespace &&
+ echo "F whitespace=trailing,cr-at-eol" >.gitattributes &&
+ prepare_output &&
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -178,12 +264,21 @@ test_expect_success 'trailing empty lines (2)' '
'
+test_expect_success 'checkdiff shows correct line number for trailing blank lines' '
+
+ printf "a\nb\n" > G &&
+ git add G &&
+ printf "x\nx\nx\na\nb\nc\n\n" > G &&
+ [ "$(git diff --check -- G)" = "G:7: new blank line at EOF." ]
+
+'
+
test_expect_success 'do not color trailing cr in context' '
- git config --unset core.whitespace
+ test_might_fail git config --unset core.whitespace &&
rm -f .gitattributes &&
echo AAAQ | tr Q "\015" >G &&
git add G &&
- echo BBBQ | tr Q "\015" >>G
+ echo BBBQ | tr Q "\015" >>G &&
git diff --color G | tr "\015" Q >output &&
grep "BBB.*${blue_grep}Q" output &&
grep "AAA.*\[mQ" output
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index a7602cf..083f62d 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -4,8 +4,6 @@ test_description='external diff interface test'
. ./test-lib.sh
-_z40=0000000000000000000000000000000000000000
-
test_expect_success setup '
test_tick &&
diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh
index 709b323..886494b 100755
--- a/t/t4021-format-patch-numbered.sh
+++ b/t/t4021-format-patch-numbered.sh
@@ -95,7 +95,7 @@ test_expect_success 'format.numbered && --keep-subject' '
test_expect_success 'format.numbered = auto' '
- git config format.numbered auto
+ git config format.numbered auto &&
git format-patch --stdout HEAD~2 > patch5 &&
test_numbered patch5
diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh
index 2a537a2..c00a94b 100755
--- a/t/t4022-diff-rewrite.sh
+++ b/t/t4022-diff-rewrite.sh
@@ -11,7 +11,9 @@ test_expect_success setup '
tr \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \
- <"$TEST_DIRECTORY"/../COPYING >test
+ <"$TEST_DIRECTORY"/../COPYING >test &&
+ echo "to be deleted" >test2 &&
+ git add test2
'
@@ -25,5 +27,44 @@ test_expect_success 'detect rewrite' '
'
+cat >expect <<EOF
+diff --git a/test2 b/test2
+deleted file mode 100644
+index 4202011..0000000
+--- a/test2
++++ /dev/null
+@@ -1 +0,0 @@
+-to be deleted
+EOF
+test_expect_success 'show deletion diff without -D' '
+
+ rm test2 &&
+ git diff -- test2 >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF
+diff --git a/test2 b/test2
+deleted file mode 100644
+index 4202011..0000000
+EOF
+test_expect_success 'suppress deletion diff with -D' '
+
+ git diff -D -- test2 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'show deletion diff with -B' '
+
+ git diff -B -- test >actual &&
+ grep "Linus Torvalds" actual
+'
+
+test_expect_success 'suppress deletion diff with -B -D' '
+
+ git diff -B -D -- test >actual &&
+ grep -v "Linus Torvalds" actual
+'
+
test_done
diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh
index 9bdf659..5d20acf 100755
--- a/t/t4023-diff-rename-typechange.sh
+++ b/t/t4023-diff-rename-typechange.sh
@@ -4,13 +4,7 @@ test_description='typechange rename detection'
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
rm -f foo bar &&
cat "$TEST_DIRECTORY"/../COPYING >foo &&
@@ -56,7 +50,7 @@ test_expect_success setup '
'
-test_expect_success 'cross renames to be detected for regular files' '
+test_expect_success SYMLINKS 'cross renames to be detected for regular files' '
git diff-tree five six -r --name-status -B -M | sort >actual &&
{
@@ -67,7 +61,7 @@ test_expect_success 'cross renames to be detected for regular files' '
'
-test_expect_success 'cross renames to be detected for typechange' '
+test_expect_success SYMLINKS 'cross renames to be detected for typechange' '
git diff-tree one two -r --name-status -B -M | sort >actual &&
{
@@ -78,7 +72,7 @@ test_expect_success 'cross renames to be detected for typechange' '
'
-test_expect_success 'moves and renames' '
+test_expect_success SYMLINKS 'moves and renames' '
git diff-tree three four -r --name-status -B -M | sort >actual &&
{
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
index d5ccdd0..3726a0e 100755
--- a/t/t4026-color.sh
+++ b/t/t4026-color.sh
@@ -74,7 +74,6 @@ test_expect_success 'extra character after attribute' '
'
test_expect_success 'unknown color slots are ignored (diff)' '
- git config --unset diff.color.new
git config color.diff.nosuchslotwilleverbedefined white &&
git diff --color
'
diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh
index 1bd8e5e..518bf95 100755
--- a/t/t4027-diff-submodule.sh
+++ b/t/t4027-diff-submodule.sh
@@ -5,7 +5,6 @@ test_description='difference in submodules'
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
-_z40=0000000000000000000000000000000000000000
test_expect_success setup '
test_tick &&
test_create_repo sub &&
@@ -114,6 +113,69 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)'
! test -s actual4
'
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' '
+ git config diff.ignoreSubmodules all &&
+ git diff HEAD >actual &&
+ ! test -s actual &&
+ git config submodule.subname.ignore none &&
+ git config submodule.subname.path sub &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config submodule.subname.ignore all &&
+ git diff HEAD >actual2 &&
+ ! test -s actual2 &&
+ git config submodule.subname.ignore untracked &&
+ git diff HEAD >actual3 &&
+ sed -e "1,/^@@/d" actual3 >actual3.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual3.body &&
+ git config submodule.subname.ignore dirty &&
+ git diff HEAD >actual4 &&
+ ! test -s actual4 &&
+ git diff HEAD --ignore-submodules=none >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config --remove-section submodule.subname &&
+ git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' '
+ git config diff.ignoreSubmodules dirty &&
+ git diff HEAD >actual &&
+ ! test -s actual &&
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sub &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config -f .gitmodules submodule.subname.ignore all &&
+ git config -f .gitmodules submodule.subname.path sub &&
+ git diff HEAD >actual2 &&
+ ! test -s actual2 &&
+ git config -f .gitmodules submodule.subname.ignore untracked &&
+ git diff HEAD >actual3 &&
+ sed -e "1,/^@@/d" actual3 >actual3.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual3.body &&
+ git config -f .gitmodules submodule.subname.ignore dirty &&
+ git diff HEAD >actual4 &&
+ ! test -s actual4 &&
+ git config submodule.subname.ignore none &&
+ git config submodule.subname.path sub &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config --remove-section submodule.subname &&
+ git config --remove-section -f .gitmodules submodule.subname &&
+ git config --unset diff.ignoreSubmodules &&
+ rm .gitmodules
+'
+
test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '
(
cd sub &&
@@ -146,6 +208,103 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)'
! test -s actual4
'
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' '
+ git config submodule.subname.ignore all &&
+ git config submodule.subname.path sub &&
+ git diff HEAD >actual2 &&
+ ! test -s actual2 &&
+ git config submodule.subname.ignore untracked &&
+ git diff HEAD >actual3 &&
+ ! test -s actual3 &&
+ git config submodule.subname.ignore dirty &&
+ git diff HEAD >actual4 &&
+ ! test -s actual4 &&
+ git diff --ignore-submodules=none HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config --remove-section submodule.subname
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' '
+ git config --add -f .gitmodules submodule.subname.ignore all &&
+ git config --add -f .gitmodules submodule.subname.path sub &&
+ git diff HEAD >actual2 &&
+ ! test -s actual2 &&
+ git config -f .gitmodules submodule.subname.ignore untracked &&
+ git diff HEAD >actual3 &&
+ ! test -s actual3 &&
+ git config -f .gitmodules submodule.subname.ignore dirty &&
+ git diff HEAD >actual4 &&
+ ! test -s actual4 &&
+ git config submodule.subname.ignore none &&
+ git config submodule.subname.path sub &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body &&
+ git config --remove-section submodule.subname &&
+ git config --remove-section -f .gitmodules submodule.subname &&
+ rm .gitmodules
+'
+
+test_expect_success 'git diff between submodule commits' '
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git diff --ignore-submodules HEAD^..HEAD >actual &&
+ ! test -s actual
+'
+
+test_expect_success 'git diff between submodule commits [.git/config]' '
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git config submodule.subname.ignore dirty &&
+ git config submodule.subname.path sub &&
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git config submodule.subname.ignore all &&
+ git diff HEAD^..HEAD >actual &&
+ ! test -s actual &&
+ git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ git config --remove-section submodule.subname
+'
+
+test_expect_success 'git diff between submodule commits [.gitmodules]' '
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git config --add -f .gitmodules submodule.subname.ignore dirty &&
+ git config --add -f .gitmodules submodule.subname.path sub &&
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body &&
+ git config -f .gitmodules submodule.subname.ignore all &&
+ git diff HEAD^..HEAD >actual &&
+ ! test -s actual &&
+ git config submodule.subname.ignore dirty &&
+ git config submodule.subname.path sub &&
+ git diff HEAD^..HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ git config --remove-section submodule.subname &&
+ git config --remove-section -f .gitmodules submodule.subname &&
+ rm .gitmodules
+'
+
test_expect_success 'git diff (empty submodule dir)' '
: >empty &&
rm -rf sub/* sub/.git &&
@@ -156,11 +315,11 @@ test_expect_success 'git diff (empty submodule dir)' '
test_expect_success 'conflicted submodule setup' '
# 39 efs
- c=fffffffffffffffffffffffffffffffffffffff
+ c=fffffffffffffffffffffffffffffffffffffff &&
(
- echo "000000 $_z40 0 sub"
- echo "160000 1$c 1 sub"
- echo "160000 2$c 2 sub"
+ echo "000000 $_z40 0 sub" &&
+ echo "160000 1$c 1 sub" &&
+ echo "160000 2$c 2 sub" &&
echo "160000 3$c 3 sub"
) | git update-index --index-info &&
echo >expect.nosub '\''diff --cc sub
diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh
index 7e7b307..7d7470f 100755
--- a/t/t4031-diff-rewrite-binary.sh
+++ b/t/t4031-diff-rewrite-binary.sh
@@ -44,6 +44,13 @@ test_expect_success 'rewrite diff can show binary patch' '
grep "GIT binary patch" diff
'
+test_expect_success 'rewrite diff --stat shows binary changes' '
+ git diff -B --stat --summary >diff &&
+ grep "Bin" diff &&
+ grep "0 insertions.*0 deletions" diff &&
+ grep " rewrite file" diff
+'
+
{
echo "#!$SHELL_PATH"
cat <<'EOF'
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 6f7548c..c374aa4 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -4,331 +4,333 @@ test_description='word diff colors'
. ./test-lib.sh
-test_expect_success setup '
-
- git config diff.color.old red
- git config diff.color.new green
- git config diff.color.func magenta
+cat >pre.simple <<-\EOF
+ h(4)
-'
+ a = b + c
+EOF
+cat >post.simple <<-\EOF
+ h(4),hh[44]
-word_diff () {
- test_must_fail git diff --no-index "$@" pre post > output &&
- test_decode_color <output >output.decrypted &&
- test_cmp expect output.decrypted
-}
+ a = b + c
-cat > pre <<\EOF
-h(4)
+ aa = a
-a = b + c
+ aeff = aeff * ( aaa )
EOF
+cat >expect.letter-runs-are-words <<-\EOF
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>hh<RESET>[44]
-cat > post <<\EOF
-h(4),hh[44]
+ a = b + c<RESET>
-a = b + c
+ <GREEN>aa = a<RESET>
-aa = a
-
-aeff = aeff * ( aaa )
+ <GREEN>aeff = aeff * ( aaa<RESET> )
EOF
+cat >expect.non-whitespace-is-word <<-\EOF
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4)<GREEN>,hh[44]<RESET>
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-
-a = b + c<RESET>
+ a = b + c<RESET>
-<GREEN>aa = a<RESET>
+ <GREEN>aa = a<RESET>
-<GREEN>aeff = aeff * ( aaa )<RESET>
+ <GREEN>aeff = aeff * ( aaa )<RESET>
EOF
-test_expect_success 'word diff with runs of whitespace' '
+word_diff () {
+ test_must_fail git diff --no-index "$@" pre post >output &&
+ test_decode_color <output >output.decrypted &&
+ test_cmp expect output.decrypted
+}
- word_diff --color-words
+test_language_driver () {
+ lang=$1
+ test_expect_success "diff driver '$lang'" '
+ cp "$TEST_DIRECTORY/t4034/'"$lang"'/pre" \
+ "$TEST_DIRECTORY/t4034/'"$lang"'/post" \
+ "$TEST_DIRECTORY/t4034/'"$lang"'/expect" . &&
+ echo "* diff='"$lang"'" >.gitattributes &&
+ word_diff --color-words
+ '
+}
+test_expect_success setup '
+ git config diff.color.old red &&
+ git config diff.color.new green &&
+ git config diff.color.func magenta
'
-test_expect_success '--word-diff=color' '
-
- word_diff --word-diff=color
-
+test_expect_success 'set up pre and post with runs of whitespace' '
+ cp pre.simple pre &&
+ cp post.simple post
'
-test_expect_success '--color --word-diff=color' '
-
+test_expect_success 'word diff with runs of whitespace' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+
+ a = b + c<RESET>
+
+ <GREEN>aa = a<RESET>
+
+ <GREEN>aeff = aeff * ( aaa )<RESET>
+ EOF
+ word_diff --color-words &&
+ word_diff --word-diff=color &&
word_diff --color --word-diff=color
-
'
-sed 's/#.*$//' > expect <<EOF
-diff --git a/pre b/post
-index 330b04f..5ed8eff 100644
---- a/pre
-+++ b/post
-@@ -1,3 +1,7 @@
--h(4)
-+h(4),hh[44]
-~
- # significant space
-~
- a = b + c
-~
-~
-+aa = a
-~
-~
-+aeff = aeff * ( aaa )
-~
-EOF
-
test_expect_success '--word-diff=porcelain' '
-
+ sed 's/#.*$//' >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 330b04f..5ed8eff 100644
+ --- a/pre
+ +++ b/post
+ @@ -1,3 +1,7 @@
+ -h(4)
+ +h(4),hh[44]
+ ~
+ # significant space
+ ~
+ a = b + c
+ ~
+ ~
+ +aa = a
+ ~
+ ~
+ +aeff = aeff * ( aaa )
+ ~
+ EOF
word_diff --word-diff=porcelain
-
'
-cat > expect <<EOF
-diff --git a/pre b/post
-index 330b04f..5ed8eff 100644
---- a/pre
-+++ b/post
-@@ -1,3 +1,7 @@
-[-h(4)-]{+h(4),hh[44]+}
-
-a = b + c
-
-{+aa = a+}
-
-{+aeff = aeff * ( aaa )+}
-EOF
-
test_expect_success '--word-diff=plain' '
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 330b04f..5ed8eff 100644
+ --- a/pre
+ +++ b/post
+ @@ -1,3 +1,7 @@
+ [-h(4)-]{+h(4),hh[44]+}
- word_diff --word-diff=plain
+ a = b + c
-'
-
-test_expect_success '--word-diff=plain --no-color' '
+ {+aa = a+}
+ {+aeff = aeff * ( aaa )+}
+ EOF
+ word_diff --word-diff=plain &&
word_diff --word-diff=plain --no-color
-
'
-cat > expect <<EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
-
-a = b + c<RESET>
+test_expect_success '--word-diff=plain --color' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ <RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
-<GREEN>{+aa = a+}<RESET>
+ a = b + c<RESET>
-<GREEN>{+aeff = aeff * ( aaa )+}<RESET>
-EOF
-
-test_expect_success '--word-diff=plain --color' '
+ <GREEN>{+aa = a+}<RESET>
+ <GREEN>{+aeff = aeff * ( aaa )+}<RESET>
+ EOF
word_diff --word-diff=plain --color
-
'
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa )<RESET>
-EOF
-
test_expect_success 'word diff without context' '
-
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+ <CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
+
+ <GREEN>aa = a<RESET>
+
+ <GREEN>aeff = aeff * ( aaa )<RESET>
+ EOF
word_diff --color-words --unified=0
-
'
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>hh<RESET>[44]
-
-a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa<RESET> )
-EOF
-cp expect expect.letter-runs-are-words
-
test_expect_success 'word diff with a regular expression' '
-
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
-
'
-test_expect_success 'set a diff driver' '
+test_expect_success 'set up a diff driver' '
git config diff.testdriver.wordRegex "[^[:space:]]" &&
- cat <<EOF > .gitattributes
-pre diff=testdriver
-post diff=testdriver
-EOF
+ cat <<-\EOF >.gitattributes
+ pre diff=testdriver
+ post diff=testdriver
+ EOF
'
test_expect_success 'option overrides .gitattributes' '
-
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
-
'
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4)<GREEN>,hh[44]<RESET>
-
-a = b + c<RESET>
-
-<GREEN>aa = a<RESET>
-
-<GREEN>aeff = aeff * ( aaa )<RESET>
-EOF
-cp expect expect.non-whitespace-is-word
-
test_expect_success 'use regex supplied by driver' '
-
+ cp expect.non-whitespace-is-word expect &&
word_diff --color-words
-
'
-test_expect_success 'set diff.wordRegex option' '
+test_expect_success 'set up diff.wordRegex option' '
git config diff.wordRegex "[[:alnum:]]+"
'
-cp expect.letter-runs-are-words expect
-
test_expect_success 'command-line overrides config' '
+ cp expect.letter-runs-are-words expect &&
word_diff --color-words="[a-z]+"
'
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>{+hh+}<RESET>[44]
-
-a = b + c<RESET>
+test_expect_success 'command-line overrides config: --word-diff-regex' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>{+hh+}<RESET>[44]
-<GREEN>{+aa = a+}<RESET>
+ a = b + c<RESET>
-<GREEN>{+aeff = aeff * ( aaa+}<RESET> )
-EOF
+ <GREEN>{+aa = a+}<RESET>
-test_expect_success 'command-line overrides config: --word-diff-regex' '
+ <GREEN>{+aeff = aeff * ( aaa+}<RESET> )
+ EOF
word_diff --color --word-diff-regex="[a-z]+"
'
-cp expect.non-whitespace-is-word expect
-
test_expect_success '.gitattributes override config' '
+ cp expect.non-whitespace-is-word expect &&
word_diff --color-words
'
-test_expect_success 'remove diff driver regex' '
- git config --unset diff.testdriver.wordRegex
+test_expect_success 'setup: remove diff driver regex' '
+ test_might_fail git config --unset diff.testdriver.wordRegex
'
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 330b04f..5ed8eff 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1,3 +1,7 @@<RESET>
-h(4),<GREEN>hh[44<RESET>]
-
-a = b + c<RESET>
+test_expect_success 'use configured regex' '
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 330b04f..5ed8eff 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1,3 +1,7 @@<RESET>
+ h(4),<GREEN>hh[44<RESET>]
-<GREEN>aa = a<RESET>
+ a = b + c<RESET>
-<GREEN>aeff = aeff * ( aaa<RESET> )
-EOF
+ <GREEN>aa = a<RESET>
-test_expect_success 'use configured regex' '
+ <GREEN>aeff = aeff * ( aaa<RESET> )
+ EOF
word_diff --color-words
'
-echo 'aaa (aaa)' > pre
-echo 'aaa (aaa) aaa' > post
-
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index c29453b..be22f37 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-aaa (aaa) <GREEN>aaa<RESET>
-EOF
-
test_expect_success 'test parsing words for newline' '
-
+ echo "aaa (aaa)" >pre &&
+ echo "aaa (aaa) aaa" >post &&
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index c29453b..be22f37 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ aaa (aaa) <GREEN>aaa<RESET>
+ EOF
word_diff --color-words="a+"
-
-
'
-echo '(:' > pre
-echo '(' > post
-
-cat > expect <<\EOF
-<WHITE>diff --git a/pre b/post<RESET>
-<WHITE>index 289cb9d..2d06f37 100644<RESET>
-<WHITE>--- a/pre<RESET>
-<WHITE>+++ b/post<RESET>
-<CYAN>@@ -1 +1 @@<RESET>
-(<RED>:<RESET>
-EOF
-
test_expect_success 'test when words are only removed at the end' '
-
+ echo "(:" >pre &&
+ echo "(" >post &&
+ cat >expect <<-\EOF &&
+ <BOLD>diff --git a/pre b/post<RESET>
+ <BOLD>index 289cb9d..2d06f37 100644<RESET>
+ <BOLD>--- a/pre<RESET>
+ <BOLD>+++ b/post<RESET>
+ <CYAN>@@ -1 +1 @@<RESET>
+ (<RED>:<RESET>
+ EOF
word_diff --color-words=.
-
'
-cat > expect <<\EOF
-diff --git a/pre b/post
-index 289cb9d..2d06f37 100644
---- a/pre
-+++ b/post
-@@ -1 +1 @@
--(:
-+(
-EOF
-
test_expect_success '--word-diff=none' '
-
+ echo "(:" >pre &&
+ echo "(" >post &&
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index 289cb9d..2d06f37 100644
+ --- a/pre
+ +++ b/post
+ @@ -1 +1 @@
+ -(:
+ +(
+ EOF
word_diff --word-diff=plain --word-diff=none
+'
+test_language_driver bibtex
+test_language_driver cpp
+test_language_driver csharp
+test_language_driver fortran
+test_language_driver html
+test_language_driver java
+test_language_driver objc
+test_language_driver pascal
+test_language_driver perl
+test_language_driver php
+test_language_driver python
+test_language_driver ruby
+test_language_driver tex
+
+test_expect_success 'word-diff with diff.sbe' '
+ cat >expect <<-\EOF &&
+ diff --git a/pre b/post
+ index a1a53b5..bc8fe6d 100644
+ --- a/pre
+ +++ b/post
+ @@ -1,3 +1,3 @@
+ a
+
+ [-b-]{+c+}
+ EOF
+ cat >pre <<-\EOF &&
+ a
+
+ b
+ EOF
+ cat >post <<-\EOF &&
+ a
+
+ c
+ EOF
+ test_when_finished "git config --unset diff.suppress-blank-empty" &&
+ git config diff.suppress-blank-empty true &&
+ word_diff --word-diff=plain
'
test_done
diff --git a/t/t4034/bibtex/expect b/t/t4034/bibtex/expect
new file mode 100644
index 0000000..a157774
--- /dev/null
+++ b/t/t4034/bibtex/expect
@@ -0,0 +1,15 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 95cd55b..ddcba9b 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,9 +1,10 @@<RESET>
+@article{aldous1987uie,<RESET>
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},<RESET>
+ author={Aldous, <RED>D.<RESET><GREEN>David<RESET>},
+ journal={Information Theory, IEEE Transactions on},<RESET>
+ volume={<RED>33<RESET><GREEN>Bogus.<RESET>},
+ number={<RED>2<RESET><GREEN>4<RESET>},
+ pages={219--223},<RESET>
+ year=<GREEN>1987,<RESET>
+<GREEN> note={This is in fact a rather funny read since ethernet works well in practice. The<RESET> {<RED>1987<RESET><GREEN>\em pre} reference is the right one, however.<RESET>}<RED>,<RESET>
+}<RESET>
diff --git a/t/t4034/bibtex/post b/t/t4034/bibtex/post
new file mode 100644
index 0000000..ddcba9b
--- /dev/null
+++ b/t/t4034/bibtex/post
@@ -0,0 +1,10 @@
+@article{aldous1987uie,
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},
+ author={Aldous, David},
+ journal={Information Theory, IEEE Transactions on},
+ volume={Bogus.},
+ number={4},
+ pages={219--223},
+ year=1987,
+ note={This is in fact a rather funny read since ethernet works well in practice. The {\em pre} reference is the right one, however.}
+}
diff --git a/t/t4034/bibtex/pre b/t/t4034/bibtex/pre
new file mode 100644
index 0000000..95cd55b
--- /dev/null
+++ b/t/t4034/bibtex/pre
@@ -0,0 +1,9 @@
+@article{aldous1987uie,
+ title={{Ultimate instability of exponential back-off protocol for acknowledgment-based transmission control of random access communication channels}},
+ author={Aldous, D.},
+ journal={Information Theory, IEEE Transactions on},
+ volume={33},
+ number={2},
+ pages={219--223},
+ year={1987},
+}
diff --git a/t/t4034/cpp/expect b/t/t4034/cpp/expect
new file mode 100644
index 0000000..37d1ea2
--- /dev/null
+++ b/t/t4034/cpp/expect
@@ -0,0 +1,36 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 23d5c8a..7e8c026 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,19 +1,19 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/cpp/post b/t/t4034/cpp/post
new file mode 100644
index 0000000..7e8c026
--- /dev/null
+++ b/t/t4034/cpp/post
@@ -0,0 +1,19 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/cpp/pre b/t/t4034/cpp/pre
new file mode 100644
index 0000000..23d5c8a
--- /dev/null
+++ b/t/t4034/cpp/pre
@@ -0,0 +1,19 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/csharp/expect b/t/t4034/csharp/expect
new file mode 100644
index 0000000..e5d1dd2
--- /dev/null
+++ b/t/t4034/csharp/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 9106d63..dd5f421 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/csharp/post b/t/t4034/csharp/post
new file mode 100644
index 0000000..dd5f421
--- /dev/null
+++ b/t/t4034/csharp/post
@@ -0,0 +1,18 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/csharp/pre b/t/t4034/csharp/pre
new file mode 100644
index 0000000..9106d63
--- /dev/null
+++ b/t/t4034/csharp/pre
@@ -0,0 +1,18 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/fortran/expect b/t/t4034/fortran/expect
new file mode 100644
index 0000000..b233dbd
--- /dev/null
+++ b/t/t4034/fortran/expect
@@ -0,0 +1,10 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 87f0d0b..d308da2 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,5 +1,5 @@<RESET>
+print *, "Hello World<RED>!<RESET><GREEN>?<RESET>"
+
+DO10I = 1,10<RESET>
+<RED>DO10I<RESET><GREEN>DO 10 I<RESET> = 1,10
+<RED>DO10I<RESET><GREEN>DO 1 0 I<RESET> = 1,10
diff --git a/t/t4034/fortran/post b/t/t4034/fortran/post
new file mode 100644
index 0000000..d308da2
--- /dev/null
+++ b/t/t4034/fortran/post
@@ -0,0 +1,5 @@
+print *, "Hello World?"
+
+DO10I = 1,10
+DO 10 I = 1,10
+DO 1 0 I = 1,10
diff --git a/t/t4034/fortran/pre b/t/t4034/fortran/pre
new file mode 100644
index 0000000..87f0d0b
--- /dev/null
+++ b/t/t4034/fortran/pre
@@ -0,0 +1,5 @@
+print *, "Hello World!"
+
+DO10I = 1,10
+DO10I = 1,10
+DO10I = 1,10
diff --git a/t/t4034/html/expect b/t/t4034/html/expect
new file mode 100644
index 0000000..447b49a
--- /dev/null
+++ b/t/t4034/html/expect
@@ -0,0 +1,8 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 8ca4aea..46921e5 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,3 +1,3 @@<RESET>
+<tag <GREEN>newattr="newvalue"<RESET>><GREEN>added<RESET> content</tag>
+<tag attr=<RED>"value"<RESET><GREEN>"newvalue"<RESET>><RED>content<RESET><GREEN>changed<RESET></tag>
+<<RED>tag<RESET><GREEN>newtag<RESET>>content <RED>&entity;<RESET><GREEN>&newentity;<RESET><<RED>/tag<RESET><GREEN>/newtag<RESET>>
diff --git a/t/t4034/html/post b/t/t4034/html/post
new file mode 100644
index 0000000..46921e5
--- /dev/null
+++ b/t/t4034/html/post
@@ -0,0 +1,3 @@
+<tag newattr="newvalue">added content</tag>
+<tag attr="newvalue">changed</tag>
+<newtag>content &newentity;</newtag>
diff --git a/t/t4034/html/pre b/t/t4034/html/pre
new file mode 100644
index 0000000..8ca4aea
--- /dev/null
+++ b/t/t4034/html/pre
@@ -0,0 +1,3 @@
+<tag>content</tag>
+<tag attr="value">content</tag>
+<tag>content &entity;</tag>
diff --git a/t/t4034/java/expect b/t/t4034/java/expect
new file mode 100644
index 0000000..37d1ea2
--- /dev/null
+++ b/t/t4034/java/expect
@@ -0,0 +1,36 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 23d5c8a..7e8c026 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,19 +1,19 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/java/post b/t/t4034/java/post
new file mode 100644
index 0000000..7e8c026
--- /dev/null
+++ b/t/t4034/java/post
@@ -0,0 +1,19 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/java/pre b/t/t4034/java/pre
new file mode 100644
index 0000000..23d5c8a
--- /dev/null
+++ b/t/t4034/java/pre
@@ -0,0 +1,19 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/objc/expect b/t/t4034/objc/expect
new file mode 100644
index 0000000..e5d1dd2
--- /dev/null
+++ b/t/t4034/objc/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 9106d63..dd5f421 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+Foo() : x(0<RED>&&1<RESET><GREEN>&42<RESET>) { <GREEN>bar(x);<RESET> }
+cout<<"Hello World<RED>!<RESET><GREEN>?<RESET>\n"<<endl;
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/objc/post b/t/t4034/objc/post
new file mode 100644
index 0000000..dd5f421
--- /dev/null
+++ b/t/t4034/objc/post
@@ -0,0 +1,18 @@
+Foo() : x(0&42) { bar(x); }
+cout<<"Hello World?\n"<<endl;
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/objc/pre b/t/t4034/objc/pre
new file mode 100644
index 0000000..9106d63
--- /dev/null
+++ b/t/t4034/objc/pre
@@ -0,0 +1,18 @@
+Foo():x(0&&1){}
+cout<<"Hello World!\n"<<endl;
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/pascal/expect b/t/t4034/pascal/expect
new file mode 100644
index 0000000..2ce4230
--- /dev/null
+++ b/t/t4034/pascal/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 077046c..8865e6b 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+writeln("Hello World<RED>!<RESET><GREEN>?<RESET>");
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
+<RED>a<RESET><GREEN>x<RESET>::<RED>b<RESET><GREEN>y<RESET>
diff --git a/t/t4034/pascal/post b/t/t4034/pascal/post
new file mode 100644
index 0000000..8865e6b
--- /dev/null
+++ b/t/t4034/pascal/post
@@ -0,0 +1,18 @@
+writeln("Hello World?");
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
+x::y
diff --git a/t/t4034/pascal/pre b/t/t4034/pascal/pre
new file mode 100644
index 0000000..077046c
--- /dev/null
+++ b/t/t4034/pascal/pre
@@ -0,0 +1,18 @@
+writeln("Hello World!");
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
+a::b
diff --git a/t/t4034/perl/expect b/t/t4034/perl/expect
new file mode 100644
index 0000000..a1deb6b
--- /dev/null
+++ b/t/t4034/perl/expect
@@ -0,0 +1,13 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index f6610d3..e8b72ef 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -4,8 +4,8 @@<RESET>
+
+package Frotz;<RESET>
+sub new {<RESET>
+ my <GREEN>(<RESET>$class<GREEN>, %opts)<RESET> = <RED>shift<RESET><GREEN>@_<RESET>;
+ return bless { <GREEN>xyzzy => "nitfol", %opts<RESET> }, $class;
+}<RESET>
+
+__END__<RESET>
diff --git a/t/t4034/perl/post b/t/t4034/perl/post
new file mode 100644
index 0000000..e8b72ef
--- /dev/null
+++ b/t/t4034/perl/post
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+
+package Frotz;
+sub new {
+ my ($class, %opts) = @_;
+ return bless { xyzzy => "nitfol", %opts }, $class;
+}
+
+__END__
+=head1 NAME
+
+frotz - Frotz
+
+=head1 SYNOPSIS
+
+ use frotz;
+
+ $nitfol = new Frotz();
+
+=cut
diff --git a/t/t4034/perl/pre b/t/t4034/perl/pre
new file mode 100644
index 0000000..f6610d3
--- /dev/null
+++ b/t/t4034/perl/pre
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+use strict;
+
+package Frotz;
+sub new {
+ my $class = shift;
+ return bless {}, $class;
+}
+
+__END__
+=head1 NAME
+
+frotz - Frotz
+
+=head1 SYNOPSIS
+
+ use frotz;
+
+ $nitfol = new Frotz();
+
+=cut
diff --git a/t/t4034/php/expect b/t/t4034/php/expect
new file mode 100644
index 0000000..0404408
--- /dev/null
+++ b/t/t4034/php/expect
@@ -0,0 +1,35 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index cf6e06b..4420a49 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,18 +1,18 @@<RESET>
+<GREEN>(<RESET>$var<GREEN>)<RESET> $ var
+<?="Hello World<RED>!<RESET><GREEN>?<RESET>"?>
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/php/post b/t/t4034/php/post
new file mode 100644
index 0000000..4420a49
--- /dev/null
+++ b/t/t4034/php/post
@@ -0,0 +1,18 @@
+($var) $ var
+<?="Hello World?"?>
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/php/pre b/t/t4034/php/pre
new file mode 100644
index 0000000..cf6e06b
--- /dev/null
+++ b/t/t4034/php/pre
@@ -0,0 +1,18 @@
+$var $var
+<?= "Hello World!" ?>
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/python/expect b/t/t4034/python/expect
new file mode 100644
index 0000000..8abb8a4
--- /dev/null
+++ b/t/t4034/python/expect
@@ -0,0 +1,34 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 438f776..68baf34 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,17 +1,17 @@<RESET>
+print<RED>u<RESET> "Hello World<RED>!<RESET><GREEN>?<RESET>\n"<GREEN>; print<RESET>
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>) u<RESET>'<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>?<RED>b<RESET><GREEN>y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/python/post b/t/t4034/python/post
new file mode 100644
index 0000000..68baf34
--- /dev/null
+++ b/t/t4034/python/post
@@ -0,0 +1,17 @@
+print "Hello World?\n"; print
+(1) (-1e10) (0xabcdef) u'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/python/pre b/t/t4034/python/pre
new file mode 100644
index 0000000..438f776
--- /dev/null
+++ b/t/t4034/python/pre
@@ -0,0 +1,17 @@
+print u"Hello World!\n"
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/ruby/expect b/t/t4034/ruby/expect
new file mode 100644
index 0000000..16e1dd5
--- /dev/null
+++ b/t/t4034/ruby/expect
@@ -0,0 +1,34 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 30ed9a1..7678f14 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,17 +1,17 @@<RESET>
+10.downto(1) {|<RED>x<RESET><GREEN>y<RESET>| puts <RED>x<RESET><GREEN>y<RESET>}
+<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>'
+[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET>
+!<RED>a<RESET><GREEN>x<RESET> ~<RED>a a<RESET><GREEN>x x<RESET>++ <RED>a<RESET><GREEN>x<RESET>-- <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<<RED>b a<RESET><GREEN>y x<RESET>>><RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>^<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>|<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>&&<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>||<RED>b<RESET>
+<RED>a?b<RESET><GREEN>y<RESET>
+<GREEN>x?y<RESET>:z
+<RED>a<RESET><GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET>
+<RED>a<RESET><GREEN>y<RESET>
+<GREEN>x<RESET>,y
diff --git a/t/t4034/ruby/post b/t/t4034/ruby/post
new file mode 100644
index 0000000..7678f14
--- /dev/null
+++ b/t/t4034/ruby/post
@@ -0,0 +1,17 @@
+10.downto(1) {|y| puts y}
+(1) (-1e10) (0xabcdef) 'y'
+[x] x->y x.y
+!x ~x x++ x-- x*y x&y
+x*y x/y x%y
+x+y x-y
+x<<y x>>y
+x<y x<=y x>y x>=y
+x==y x!=y
+x&y
+x^y
+x|y
+x&&y
+x||y
+x?y:z
+x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y
+x,y
diff --git a/t/t4034/ruby/pre b/t/t4034/ruby/pre
new file mode 100644
index 0000000..30ed9a1
--- /dev/null
+++ b/t/t4034/ruby/pre
@@ -0,0 +1,17 @@
+10.downto(1) {|x| puts x}
+1 -1e10 0xabcdef 'x'
+[a] a->b a.b
+!a ~a a++ a-- a*b a&b
+a*b a/b a%b
+a+b a-b
+a<<b a>>b
+a<b a<=b a>b a>=b
+a==b a!=b
+a&b
+a^b
+a|b
+a&&b
+a||b
+a?b:z
+a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b
+a,y
diff --git a/t/t4034/tex/expect b/t/t4034/tex/expect
new file mode 100644
index 0000000..604969b
--- /dev/null
+++ b/t/t4034/tex/expect
@@ -0,0 +1,9 @@
+<BOLD>diff --git a/pre b/post<RESET>
+<BOLD>index 2b2dfcb..65cab61 100644<RESET>
+<BOLD>--- a/pre<RESET>
+<BOLD>+++ b/post<RESET>
+<CYAN>@@ -1,4 +1,4 @@<RESET>
+\section{Something <GREEN>new<RESET>}
+<RED>\emph<RESET><GREEN>\textbf<RESET>{Macro style}
+{<RED>\em<RESET><GREEN>\bfseries<RESET> State toggle style}
+\\[<RED>1em<RESET><GREEN>1cm<RESET>]
diff --git a/t/t4034/tex/post b/t/t4034/tex/post
new file mode 100644
index 0000000..65cab61
--- /dev/null
+++ b/t/t4034/tex/post
@@ -0,0 +1,4 @@
+\section{Something new}
+\textbf{Macro style}
+{\bfseries State toggle style}
+\\[1cm]
diff --git a/t/t4034/tex/pre b/t/t4034/tex/pre
new file mode 100644
index 0000000..2b2dfcb
--- /dev/null
+++ b/t/t4034/tex/pre
@@ -0,0 +1,4 @@
+\section{Something}
+\emph{Macro style}
+{\em State toggle style}
+\\[1em]
diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh
index a30b03b..3c728a3 100755
--- a/t/t4040-whitespace-status.sh
+++ b/t/t4040-whitespace-status.sh
@@ -60,4 +60,16 @@ test_expect_success 'diff-files -b -p --exit-code' '
git diff-files -b -p --exit-code
'
+test_expect_success 'diff-files --diff-filter --quiet' '
+ git reset --hard &&
+ rm a/d &&
+ echo x >>b/e &&
+ test_must_fail git diff-files --diff-filter=M --quiet
+'
+
+test_expect_success 'diff-tree --diff-filter --quiet' '
+ git commit -a -m "worktree state" &&
+ test_must_fail git diff-tree --diff-filter=M --quiet HEAD^ HEAD
+'
+
test_done
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
index 8e391cf..bf9a752 100755
--- a/t/t4041-diff-submodule-option.sh
+++ b/t/t4041-diff-submodule-option.sh
@@ -37,9 +37,10 @@ head1=$(add_file sm1 foo1 foo2)
test_expect_success 'added submodule' "
git add sm1 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 0000000...$head1 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
@@ -47,33 +48,36 @@ head2=$(add_file sm1 foo3)
test_expect_success 'modified submodule(forward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward)' "
git diff --submodule=log >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward) --submodule' "
git diff --submodule >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
> Add foo3
EOF
+ test_cmp expected actual
"
fullhead1=$(cd sm1; git rev-list --max-count=1 $head1)
fullhead2=$(cd sm1; git rev-list --max-count=1 $head2)
test_expect_success 'modified submodule(forward) --submodule=short' "
git diff --submodule=short >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
index $head1..$head2 160000
--- a/sm1
@@ -82,34 +86,38 @@ index $head1..$head2 160000
-Subproject commit $fullhead1
+Subproject commit $fullhead2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
-cd sm1 &&
-git reset --hard HEAD~2 >/dev/null &&
-head3=$(git rev-parse --verify HEAD | cut -c1-7) &&
-cd ..
+head3=$(
+ cd sm1 &&
+ git reset --hard HEAD~2 >/dev/null &&
+ git rev-parse --verify HEAD | cut -c1-7
+)
test_expect_success 'modified submodule(backward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head2..$head3 (rewind):
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
head4=$(add_file sm1 foo4 foo5) &&
head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
test_expect_success 'modified submodule(backward and forward)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head2...$head4:
> Add foo5
> Add foo4
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
@@ -122,7 +130,7 @@ mv sm1-bak sm1
test_expect_success 'typechanged submodule(submodule->blob), --cached' "
git diff --submodule=log --cached >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 41fbea9...0000000 (submodule deleted)
diff --git a/sm1 b/sm1
new file mode 100644
@@ -132,11 +140,12 @@ index 0000000..9da5fb8
@@ -0,0 +1 @@
+sm1
EOF
+ test_cmp expected actual
"
test_expect_success 'typechanged submodule(submodule->blob)' "
git diff --submodule=log >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 100644
index 9da5fb8..0000000
@@ -146,13 +155,14 @@ index 9da5fb8..0000000
-sm1
Submodule sm1 0000000...$head4 (new submodule)
EOF
+ test_cmp expected actual
"
rm -rf sm1 &&
git checkout-index sm1
test_expect_success 'typechanged submodule(submodule->blob)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head4...0000000 (submodule deleted)
diff --git a/sm1 b/sm1
new file mode 100644
@@ -162,6 +172,7 @@ index 0000000..$head5
@@ -0,0 +1 @@
+sm1
EOF
+ test_cmp expected actual
"
rm -f sm1 &&
@@ -170,15 +181,16 @@ head6=$(add_file sm1 foo6 foo7)
fullhead6=$(cd sm1; git rev-list --max-count=1 $head6)
test_expect_success 'nonexistent commit' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head4...$head6 (commits not present)
EOF
+ test_cmp expected actual
"
commit_file
test_expect_success 'typechanged submodule(blob->submodule)' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 100644
index $head5..0000000
@@ -188,21 +200,24 @@ index $head5..0000000
-sm1
Submodule sm1 0000000...$head6 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
test_expect_success 'submodule is up to date' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked content' "
echo new > sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked content (untracked ignored)' "
@@ -223,18 +238,20 @@ test_expect_success 'submodule contains untracked content (all ignored)' "
test_expect_success 'submodule contains untracked and modifed content' "
echo new > sm1/foo6 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' "
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' "
@@ -252,45 +269,50 @@ test_expect_success 'submodule contains untracked and modifed content (all ignor
test_expect_success 'submodule contains modifed content' "
rm -f sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
EOF
+ test_cmp expected actual
"
(cd sm1; git commit -mchange foo6 >/dev/null) &&
head8=$(cd sm1; git rev-parse --verify HEAD | cut -c1-7) &&
test_expect_success 'submodule is modified' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content' "
echo new > sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (untracked ignored)' "
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (dirty ignored)' "
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked content (all ignored)' "
@@ -301,31 +323,34 @@ test_expect_success 'modified submodule contains untracked content (all ignored)
test_expect_success 'modified submodule contains untracked and modifed content' "
echo modification >> sm1/foo6 &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains untracked content
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' "
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' "
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' "
@@ -337,19 +362,21 @@ test_expect_success 'modified submodule contains untracked and modifed content (
test_expect_success 'modified submodule contains modifed content' "
rm -f sm1/new-file &&
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 contains modified content
Submodule sm1 $head6..$head8:
> change
EOF
+ test_cmp expected actual
"
rm -rf sm1
test_expect_success 'deleted submodule' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
EOF
+ test_cmp expected actual
"
test_create_repo sm2 &&
@@ -358,41 +385,45 @@ git add sm2
test_expect_success 'multiple submodules' "
git diff-index -p --submodule=log HEAD >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
test_expect_success 'path filter' "
git diff-index -p --submodule=log HEAD sm2 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
commit_file sm2
test_expect_success 'given commit' "
git diff-index -p --submodule=log HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
test_expect_success 'given commit --submodule' "
git diff-index -p --submodule HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
"
fullhead7=$(cd sm2; git rev-list --max-count=1 $head7)
test_expect_success 'given commit --submodule=short' "
git diff-index -p --submodule=short HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
diff --git a/sm1 b/sm1
deleted file mode 160000
index $head6..0000000
@@ -408,6 +439,7 @@ index 0000000..$head7
@@ -0,0 +1 @@
+Subproject commit $fullhead7
EOF
+ test_cmp expected actual
"
test_expect_success 'setup .git file for sm2' '
@@ -419,10 +451,11 @@ test_expect_success 'setup .git file for sm2' '
test_expect_success 'diff --submodule with .git file' '
git diff --submodule HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
Submodule sm1 $head6...0000000 (submodule deleted)
Submodule sm2 0000000...$head7 (new submodule)
EOF
+ test_cmp expected actual
'
test_done
diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh
new file mode 100755
index 0000000..8a3c63b
--- /dev/null
+++ b/t/t4045-diff-relative.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='diff --relative tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ git commit --allow-empty -m empty &&
+ echo content >file1 &&
+ mkdir subdir &&
+ echo other content >subdir/file2 &&
+ git add . &&
+ git commit -m one
+'
+
+check_diff() {
+expect=$1; shift
+cat >expected <<EOF
+diff --git a/$expect b/$expect
+new file mode 100644
+index 0000000..25c05ef
+--- /dev/null
++++ b/$expect
+@@ -0,0 +1 @@
++other content
+EOF
+test_expect_success "-p $*" "
+ git diff -p $* HEAD^ >actual &&
+ test_cmp expected actual
+"
+}
+
+check_stat() {
+expect=$1; shift
+cat >expected <<EOF
+ $expect | 1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+EOF
+test_expect_success "--stat $*" "
+ git diff --stat $* HEAD^ >actual &&
+ test_cmp expected actual
+"
+}
+
+check_raw() {
+expect=$1; shift
+cat >expected <<EOF
+:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect
+EOF
+test_expect_success "--raw $*" "
+ git diff --no-abbrev --raw $* HEAD^ >actual &&
+ test_cmp expected actual
+"
+}
+
+for type in diff stat raw; do
+ check_$type file2 --relative=subdir/
+ check_$type file2 --relative=subdir
+ check_$type dir/file2 --relative=sub
+done
+
+test_done
diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh
new file mode 100755
index 0000000..25d50a6
--- /dev/null
+++ b/t/t4046-diff-unmerged.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+test_description='diff with unmerged index entries'
+. ./test-lib.sh
+
+test_expect_success setup '
+ for i in 0 1 2 3
+ do
+ blob=$(echo $i | git hash-object --stdin) &&
+ eval "blob$i=$blob" &&
+ eval "m$i=\"100644 \$blob$i $i\"" || break
+ done &&
+ paths= &&
+ for b in o x
+ do
+ for o in o x
+ do
+ for t in o x
+ do
+ path="$b$o$t" &&
+ case "$path" in ooo) continue ;; esac
+ paths="$paths$path " &&
+ p=" $path" &&
+ case "$b" in x) echo "$m1$p" ;; esac &&
+ case "$o" in x) echo "$m2$p" ;; esac &&
+ case "$t" in x) echo "$m3$p" ;; esac ||
+ break
+ done || break
+ done || break
+ done >ls-files-s.expect &&
+ git update-index --index-info <ls-files-s.expect &&
+ git ls-files -s >ls-files-s.actual &&
+ test_cmp ls-files-s.expect ls-files-s.actual
+'
+
+test_expect_success 'diff-files -0' '
+ for path in $paths
+ do
+ >"$path" &&
+ echo ":000000 100644 $_z40 $_z40 U $path"
+ done >diff-files-0.expect &&
+ git diff-files -0 >diff-files-0.actual &&
+ test_cmp diff-files-0.expect diff-files-0.actual
+'
+
+test_expect_success 'diff-files -1' '
+ for path in $paths
+ do
+ >"$path" &&
+ echo ":000000 100644 $_z40 $_z40 U $path" &&
+ case "$path" in
+ x??) echo ":100644 100644 $blob1 $_z40 M $path"
+ esac
+ done >diff-files-1.expect &&
+ git diff-files -1 >diff-files-1.actual &&
+ test_cmp diff-files-1.expect diff-files-1.actual
+'
+
+test_expect_success 'diff-files -2' '
+ for path in $paths
+ do
+ >"$path" &&
+ echo ":000000 100644 $_z40 $_z40 U $path" &&
+ case "$path" in
+ ?x?) echo ":100644 100644 $blob2 $_z40 M $path"
+ esac
+ done >diff-files-2.expect &&
+ git diff-files -2 >diff-files-2.actual &&
+ test_cmp diff-files-2.expect diff-files-2.actual &&
+ git diff-files >diff-files-default-2.actual &&
+ test_cmp diff-files-2.expect diff-files-default-2.actual
+'
+
+test_expect_success 'diff-files -3' '
+ for path in $paths
+ do
+ >"$path" &&
+ echo ":000000 100644 $_z40 $_z40 U $path" &&
+ case "$path" in
+ ??x) echo ":100644 100644 $blob3 $_z40 M $path"
+ esac
+ done >diff-files-3.expect &&
+ git diff-files -3 >diff-files-3.actual &&
+ test_cmp diff-files-3.expect diff-files-3.actual
+'
+
+test_done
diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh
new file mode 100755
index 0000000..29e80a5
--- /dev/null
+++ b/t/t4047-diff-dirstat.sh
@@ -0,0 +1,979 @@
+#!/bin/sh
+
+test_description='diff --dirstat tests'
+. ./test-lib.sh
+
+# set up two commits where the second commit has these files
+# (10 lines in each file):
+#
+# unchanged/text (unchanged from 1st commit)
+# changed/text (changed 1st line)
+# rearranged/text (swapped 1st and 2nd line)
+# dst/copy/unchanged/text (copied from src/copy/unchanged/text, unchanged)
+# dst/copy/changed/text (copied from src/copy/changed/text, changed)
+# dst/copy/rearranged/text (copied from src/copy/rearranged/text, rearranged)
+# dst/move/unchanged/text (moved from src/move/unchanged/text, unchanged)
+# dst/move/changed/text (moved from src/move/changed/text, changed)
+# dst/move/rearranged/text (moved from src/move/rearranged/text, rearranged)
+
+test_expect_success 'setup' '
+ mkdir unchanged &&
+ mkdir changed &&
+ mkdir rearranged &&
+ mkdir src &&
+ mkdir src/copy &&
+ mkdir src/copy/unchanged &&
+ mkdir src/copy/changed &&
+ mkdir src/copy/rearranged &&
+ mkdir src/move &&
+ mkdir src/move/unchanged &&
+ mkdir src/move/changed &&
+ mkdir src/move/rearranged &&
+ cat <<EOF >unchanged/text &&
+unchanged line #0
+unchanged line #1
+unchanged line #2
+unchanged line #3
+unchanged line #4
+unchanged line #5
+unchanged line #6
+unchanged line #7
+unchanged line #8
+unchanged line #9
+EOF
+ cat <<EOF >changed/text &&
+changed line #0
+changed line #1
+changed line #2
+changed line #3
+changed line #4
+changed line #5
+changed line #6
+changed line #7
+changed line #8
+changed line #9
+EOF
+ cat <<EOF >rearranged/text &&
+rearranged line #0
+rearranged line #1
+rearranged line #2
+rearranged line #3
+rearranged line #4
+rearranged line #5
+rearranged line #6
+rearranged line #7
+rearranged line #8
+rearranged line #9
+EOF
+ cat <<EOF >src/copy/unchanged/text &&
+copy unchanged line #0
+copy unchanged line #1
+copy unchanged line #2
+copy unchanged line #3
+copy unchanged line #4
+copy unchanged line #5
+copy unchanged line #6
+copy unchanged line #7
+copy unchanged line #8
+copy unchanged line #9
+EOF
+ cat <<EOF >src/copy/changed/text &&
+copy changed line #0
+copy changed line #1
+copy changed line #2
+copy changed line #3
+copy changed line #4
+copy changed line #5
+copy changed line #6
+copy changed line #7
+copy changed line #8
+copy changed line #9
+EOF
+ cat <<EOF >src/copy/rearranged/text &&
+copy rearranged line #0
+copy rearranged line #1
+copy rearranged line #2
+copy rearranged line #3
+copy rearranged line #4
+copy rearranged line #5
+copy rearranged line #6
+copy rearranged line #7
+copy rearranged line #8
+copy rearranged line #9
+EOF
+ cat <<EOF >src/move/unchanged/text &&
+move unchanged line #0
+move unchanged line #1
+move unchanged line #2
+move unchanged line #3
+move unchanged line #4
+move unchanged line #5
+move unchanged line #6
+move unchanged line #7
+move unchanged line #8
+move unchanged line #9
+EOF
+ cat <<EOF >src/move/changed/text &&
+move changed line #0
+move changed line #1
+move changed line #2
+move changed line #3
+move changed line #4
+move changed line #5
+move changed line #6
+move changed line #7
+move changed line #8
+move changed line #9
+EOF
+ cat <<EOF >src/move/rearranged/text &&
+move rearranged line #0
+move rearranged line #1
+move rearranged line #2
+move rearranged line #3
+move rearranged line #4
+move rearranged line #5
+move rearranged line #6
+move rearranged line #7
+move rearranged line #8
+move rearranged line #9
+EOF
+ git add . &&
+ git commit -m "initial" &&
+ mkdir dst &&
+ mkdir dst/copy &&
+ mkdir dst/copy/unchanged &&
+ mkdir dst/copy/changed &&
+ mkdir dst/copy/rearranged &&
+ mkdir dst/move &&
+ mkdir dst/move/unchanged &&
+ mkdir dst/move/changed &&
+ mkdir dst/move/rearranged &&
+ cat <<EOF >changed/text &&
+CHANGED XXXXXXX line #0
+changed line #1
+changed line #2
+changed line #3
+changed line #4
+changed line #5
+changed line #6
+changed line #7
+changed line #8
+changed line #9
+EOF
+ cat <<EOF >rearranged/text &&
+rearranged line #1
+rearranged line #0
+rearranged line #2
+rearranged line #3
+rearranged line #4
+rearranged line #5
+rearranged line #6
+rearranged line #7
+rearranged line #8
+rearranged line #9
+EOF
+ cat <<EOF >dst/copy/unchanged/text &&
+copy unchanged line #0
+copy unchanged line #1
+copy unchanged line #2
+copy unchanged line #3
+copy unchanged line #4
+copy unchanged line #5
+copy unchanged line #6
+copy unchanged line #7
+copy unchanged line #8
+copy unchanged line #9
+EOF
+ cat <<EOF >dst/copy/changed/text &&
+copy XXXCHANGED line #0
+copy changed line #1
+copy changed line #2
+copy changed line #3
+copy changed line #4
+copy changed line #5
+copy changed line #6
+copy changed line #7
+copy changed line #8
+copy changed line #9
+EOF
+ cat <<EOF >dst/copy/rearranged/text &&
+copy rearranged line #1
+copy rearranged line #0
+copy rearranged line #2
+copy rearranged line #3
+copy rearranged line #4
+copy rearranged line #5
+copy rearranged line #6
+copy rearranged line #7
+copy rearranged line #8
+copy rearranged line #9
+EOF
+ cat <<EOF >dst/move/unchanged/text &&
+move unchanged line #0
+move unchanged line #1
+move unchanged line #2
+move unchanged line #3
+move unchanged line #4
+move unchanged line #5
+move unchanged line #6
+move unchanged line #7
+move unchanged line #8
+move unchanged line #9
+EOF
+ cat <<EOF >dst/move/changed/text &&
+move XXXCHANGED line #0
+move changed line #1
+move changed line #2
+move changed line #3
+move changed line #4
+move changed line #5
+move changed line #6
+move changed line #7
+move changed line #8
+move changed line #9
+EOF
+ cat <<EOF >dst/move/rearranged/text &&
+move rearranged line #1
+move rearranged line #0
+move rearranged line #2
+move rearranged line #3
+move rearranged line #4
+move rearranged line #5
+move rearranged line #6
+move rearranged line #7
+move rearranged line #8
+move rearranged line #9
+EOF
+ git add . &&
+ git rm -r src/move/unchanged &&
+ git rm -r src/move/changed &&
+ git rm -r src/move/rearranged &&
+ git commit -m "changes"
+'
+
+cat <<EOF >expect_diff_stat
+ changed/text | 2 +-
+ dst/copy/changed/text | 10 ++++++++++
+ dst/copy/rearranged/text | 10 ++++++++++
+ dst/copy/unchanged/text | 10 ++++++++++
+ dst/move/changed/text | 10 ++++++++++
+ dst/move/rearranged/text | 10 ++++++++++
+ dst/move/unchanged/text | 10 ++++++++++
+ rearranged/text | 2 +-
+ src/move/changed/text | 10 ----------
+ src/move/rearranged/text | 10 ----------
+ src/move/unchanged/text | 10 ----------
+ 11 files changed, 62 insertions(+), 32 deletions(-)
+EOF
+
+cat <<EOF >expect_diff_stat_M
+ changed/text | 2 +-
+ dst/copy/changed/text | 10 ++++++++++
+ dst/copy/rearranged/text | 10 ++++++++++
+ dst/copy/unchanged/text | 10 ++++++++++
+ {src => dst}/move/changed/text | 2 +-
+ {src => dst}/move/rearranged/text | 2 +-
+ {src => dst}/move/unchanged/text | 0
+ rearranged/text | 2 +-
+ 8 files changed, 34 insertions(+), 4 deletions(-)
+EOF
+
+cat <<EOF >expect_diff_stat_CC
+ changed/text | 2 +-
+ {src => dst}/copy/changed/text | 2 +-
+ {src => dst}/copy/rearranged/text | 2 +-
+ {src => dst}/copy/unchanged/text | 0
+ {src => dst}/move/changed/text | 2 +-
+ {src => dst}/move/rearranged/text | 2 +-
+ {src => dst}/move/unchanged/text | 0
+ rearranged/text | 2 +-
+ 8 files changed, 6 insertions(+), 6 deletions(-)
+EOF
+
+test_expect_success 'sanity check setup (--stat)' '
+ git diff --stat HEAD^..HEAD >actual_diff_stat &&
+ test_cmp expect_diff_stat actual_diff_stat &&
+ git diff --stat -M HEAD^..HEAD >actual_diff_stat_M &&
+ test_cmp expect_diff_stat_M actual_diff_stat_M &&
+ git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
+ test_cmp expect_diff_stat_CC actual_diff_stat_CC
+'
+
+# changed/text and rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+EOF
+
+# rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 5.8% dst/move/changed/
+EOF
+
+# rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 32.6% dst/move/changed/
+EOF
+
+test_expect_success 'various ways to misspell --dirstat' '
+ test_must_fail git show --dirstat10 &&
+ test_must_fail git show --dirstat10,files &&
+ test_must_fail git show -X=20 &&
+ test_must_fail git show -X=20,cumulative
+'
+
+test_expect_success 'vanilla --dirstat' '
+ git diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'vanilla -X' '
+ git diff -X HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'explicit defaults: --dirstat=changes,noncumulative,3' '
+ git diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'explicit defaults: -Xchanges,noncumulative,3' '
+ git diff -Xchanges,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -Xchanges,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -Xchanges,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'later options override earlier options:' '
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'non-defaults in config overridden by explicit defaults on command line' '
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 0.0% rearranged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 5.8% dst/move/changed/
+ 0.1% dst/move/rearranged/
+ 0.1% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 0.6% dst/copy/rearranged/
+ 32.6% dst/move/changed/
+ 0.6% dst/move/rearranged/
+ 0.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=0' '
+ git diff --dirstat=0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '-X0' '
+ git diff -X0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0' '
+ git -c diff.dirstat=0 diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0 diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0 diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 32.5% dst/copy/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 32.5% dst/move/
+ 65.1% dst/
+ 0.0% rearranged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+ 32.5% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 88.0% dst/copy/
+ 5.8% dst/move/changed/
+ 0.1% dst/move/rearranged/
+ 5.9% dst/move/
+ 94.0% dst/
+ 0.1% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 0.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 32.6% dst/move/changed/
+ 0.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 0.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=0 --cumulative' '
+ git diff --dirstat=0 --cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0 --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0 --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=0,cumulative' '
+ git diff --dirstat=0,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '-X0,cumulative' '
+ git diff -X0,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0,cumulative' '
+ git -c diff.dirstat=0,cumulative diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0,cumulative diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0,cumulative diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0 & --dirstat=cumulative' '
+ git -c diff.dirstat=0 diff --dirstat=cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0 diff --dirstat=cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0 diff --dirstat=cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 9.0% changed/
+ 9.0% dst/copy/changed/
+ 9.0% dst/copy/rearranged/
+ 9.0% dst/copy/unchanged/
+ 9.0% dst/move/changed/
+ 9.0% dst/move/rearranged/
+ 9.0% dst/move/unchanged/
+ 9.0% rearranged/
+ 9.0% src/move/changed/
+ 9.0% src/move/rearranged/
+ 9.0% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file' '
+ git diff --dirstat-by-file HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files' '
+ git diff --dirstat=files HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=files' '
+ git -c diff.dirstat=files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file=10' '
+ git diff --dirstat-by-file=10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file=10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file=10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,10' '
+ git diff --dirstat=files,10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=10,files' '
+ git -c diff.dirstat=10,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=10,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=10,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 9.0% changed/
+ 9.0% dst/copy/changed/
+ 9.0% dst/copy/rearranged/
+ 9.0% dst/copy/unchanged/
+ 27.2% dst/copy/
+ 9.0% dst/move/changed/
+ 9.0% dst/move/rearranged/
+ 9.0% dst/move/unchanged/
+ 27.2% dst/move/
+ 54.5% dst/
+ 9.0% rearranged/
+ 9.0% src/move/changed/
+ 9.0% src/move/rearranged/
+ 9.0% src/move/unchanged/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 42.8% dst/copy/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 28.5% dst/move/
+ 71.4% dst/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file --cumulative' '
+ git diff --dirstat-by-file --cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative' '
+ git diff --dirstat=files,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=cumulative,files' '
+ git -c diff.dirstat=cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 54.5% dst/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 42.8% dst/copy/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 28.5% dst/move/
+ 71.4% dst/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=files,cumulative,10' '
+ git diff --dirstat=files,cumulative,10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=10,cumulative,files' '
+ git -c diff.dirstat=10,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=10,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=10,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 54.5% dst/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 42.8% dst/copy/
+ 28.5% dst/move/
+ 71.4% dst/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 33.3% dst/copy/
+ 33.3% dst/move/
+ 66.6% dst/
+EOF
+
+test_expect_success '--dirstat=files,cumulative,16.7' '
+ git diff --dirstat=files,cumulative,16.7 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,16.7 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,16.7 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=16.7,cumulative,files' '
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=16.70,cumulative,files' '
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative,27.2' '
+ git diff --dirstat=files,cumulative,27.2 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,27.2 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,27.2 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative,27.09' '
+ git diff --dirstat=files,cumulative,27.09 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,27.09 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,27.09 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 10.6% dst/copy/changed/
+ 10.6% dst/copy/rearranged/
+ 10.6% dst/copy/unchanged/
+ 10.6% dst/move/changed/
+ 10.6% dst/move/rearranged/
+ 10.6% dst/move/unchanged/
+ 10.6% src/move/changed/
+ 10.6% src/move/rearranged/
+ 10.6% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.2% changed/
+ 26.3% dst/copy/changed/
+ 26.3% dst/copy/rearranged/
+ 26.3% dst/copy/unchanged/
+ 5.2% dst/move/changed/
+ 5.2% dst/move/rearranged/
+ 5.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=lines' '
+ git diff --dirstat=lines HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=lines -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=lines -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=lines' '
+ git -c diff.dirstat=lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.6% dst/copy/changed/
+ 10.6% dst/copy/rearranged/
+ 10.6% dst/copy/unchanged/
+ 10.6% dst/move/changed/
+ 10.6% dst/move/rearranged/
+ 10.6% dst/move/unchanged/
+ 2.1% rearranged/
+ 10.6% src/move/changed/
+ 10.6% src/move/rearranged/
+ 10.6% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.2% changed/
+ 26.3% dst/copy/changed/
+ 26.3% dst/copy/rearranged/
+ 26.3% dst/copy/unchanged/
+ 5.2% dst/move/changed/
+ 5.2% dst/move/rearranged/
+ 5.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=lines,0' '
+ git diff --dirstat=lines,0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=lines,0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=lines,0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0,lines' '
+ git -c diff.dirstat=0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=future_param,lines,0 should fail loudly' '
+ test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp /dev/null actual_diff_dirstat &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "\--dirstat" actual_error
+'
+
+test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' '
+ test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp /dev/null actual_diff_dirstat &&
+ test_i18ngrep -q "dummy1" actual_error &&
+ test_i18ngrep -q "2dummy" actual_error &&
+ test_i18ngrep -q "\--dirstat" actual_error
+'
+
+test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still work' '
+ git -c diff.dirstat=future_param,0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error &&
+
+ git -c diff.dirstat=future_param,0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error &&
+
+ git -c diff.dirstat=future_param,0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error
+'
+
+test_done
diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh
new file mode 100755
index 0000000..87a8949
--- /dev/null
+++ b/t/t4048-diff-combined-binary.sh
@@ -0,0 +1,212 @@
+#!/bin/sh
+
+test_description='combined and merge diff handle binary files and textconv'
+. ./test-lib.sh
+
+test_expect_success 'setup binary merge conflict' '
+ echo oneQ1 | q_to_nul >binary &&
+ git add binary &&
+ git commit -m one &&
+ echo twoQ2 | q_to_nul >binary &&
+ git commit -a -m two &&
+ git checkout -b branch-binary HEAD^ &&
+ echo threeQ3 | q_to_nul >binary &&
+ git commit -a -m three &&
+ test_must_fail git merge master &&
+ echo resolvedQhooray | q_to_nul >binary &&
+ git commit -a -m resolved
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/binary b/binary
+index 7ea6ded..9563691 100644
+Binary files a/binary and b/binary differ
+resolved
+
+diff --git a/binary b/binary
+index 6197570..9563691 100644
+Binary files a/binary and b/binary differ
+EOF
+test_expect_success 'diff -m indicates binary-ness' '
+ git show --format=%s -m >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff -c indicates binary-ness' '
+ git show --format=%s -c >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc binary
+index 7ea6ded,6197570..9563691
+Binary files differ
+EOF
+test_expect_success 'diff --cc indicates binary-ness' '
+ git show --format=%s --cc >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup non-binary with binary attribute' '
+ git checkout master &&
+ test_commit one text &&
+ test_commit two text &&
+ git checkout -b branch-text HEAD^ &&
+ test_commit three text &&
+ test_must_fail git merge master &&
+ test_commit resolved text &&
+ echo text -diff >.gitattributes
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+Binary files a/text and b/text differ
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+Binary files a/text and b/text differ
+EOF
+test_expect_success 'diff -m respects binary attribute' '
+ git show --format=%s -m >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff -c respects binary attribute' '
+ git show --format=%s -c >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+Binary files differ
+EOF
+test_expect_success 'diff --cc respects binary attribute' '
+ git show --format=%s --cc >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup textconv attribute' '
+ echo "text diff=upcase" >.gitattributes &&
+ git config diff.upcase.textconv "tr a-z A-Z <"
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --git a/text b/text
+index 2bdf67a..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-THREE
++RESOLVED
+resolved
+
+diff --git a/text b/text
+index f719efd..2ab19ae 100644
+--- a/text
++++ b/text
+@@ -1 +1 @@
+-TWO
++RESOLVED
+EOF
+test_expect_success 'diff -m respects textconv attribute' '
+ git show --format=%s -m >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff -c respects textconv attribute' '
+ git show --format=%s -c >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+resolved
+
+diff --cc text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- THREE
+ -TWO
+++RESOLVED
+EOF
+test_expect_success 'diff --cc respects textconv attribute' '
+ git show --format=%s --cc >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --combined text
+index 2bdf67a,f719efd..2ab19ae
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,1 @@@
+- three
+ -two
+++resolved
+EOF
+test_expect_success 'diff-tree plumbing does not respect textconv' '
+ git diff-tree HEAD -c -p >full &&
+ tail -n +2 full >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+diff --cc text
+index 2bdf67a,f719efd..0000000
+--- a/text
++++ b/text
+@@@ -1,1 -1,1 +1,5 @@@
+++<<<<<<< HEAD
+ +THREE
+++=======
++ TWO
+++>>>>>>> MASTER
+EOF
+test_expect_success 'diff --cc respects textconv on worktree file' '
+ git reset --hard HEAD^ &&
+ test_must_fail git merge master &&
+ git diff >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh
new file mode 100755
index 0000000..641e70d
--- /dev/null
+++ b/t/t4049-diff-stat-count.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (c) 2011, Google Inc.
+
+test_description='diff --stat-count'
+. ./test-lib.sh
+
+test_expect_success setup '
+ >a &&
+ >b &&
+ >c &&
+ >d &&
+ git add a b c d &&
+ chmod +x c d &&
+ echo a >a &&
+ echo b >b &&
+ cat >expect <<-\EOF
+ a | 1 +
+ b | 1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+ EOF
+ git diff --stat --stat-count=2 >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh
index 1597965..e3ea3d5 100755
--- a/t/t4102-apply-rename.sh
+++ b/t/t4102-apply-rename.sh
@@ -7,6 +7,7 @@ test_description='git apply handling copy/rename patch.
'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
# setup
@@ -31,13 +32,6 @@ test_expect_success setup \
test_expect_success apply \
'git apply --index --stat --summary --apply test-patch'
-if test "$(git config --bool core.filemode)" = false
-then
- say 'filemode disabled on the filesystem'
-else
- test_set_prereq FILEMODE
-fi
-
test_expect_success FILEMODE validate \
'test -f bar && ls -l bar | grep "^-..x......"'
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index 9692f16..dbbf56c 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -37,15 +37,24 @@ test_expect_success 'setup' "
git diff-tree -p -C master binary >C.diff &&
git diff-tree -p --binary master binary >BF.diff &&
- git diff-tree -p --binary -C master binary >CF.diff
+ git diff-tree -p --binary -C master binary >CF.diff &&
+
+ git diff-tree -p --full-index master binary >B-index.diff &&
+ git diff-tree -p -C --full-index master binary >C-index.diff &&
+
+ git init other-repo &&
+ (cd other-repo &&
+ git fetch .. master &&
+ git reset --hard FETCH_HEAD
+ )
"
test_expect_success 'stat binary diff -- should not fail.' \
- 'git checkout master
+ 'git checkout master &&
git apply --stat --summary B.diff'
test_expect_success 'stat binary diff (copy) -- should not fail.' \
- 'git checkout master
+ 'git checkout master &&
git apply --stat --summary C.diff'
test_expect_success 'check binary diff -- should fail.' \
@@ -69,11 +78,11 @@ test_expect_success \
'
test_expect_success 'check binary diff with replacement.' \
- 'git checkout master
+ 'git checkout master &&
git apply --check --allow-binary-replacement BF.diff'
test_expect_success 'check binary diff with replacement (copy).' \
- 'git checkout master
+ 'git checkout master &&
git apply --check --allow-binary-replacement CF.diff'
# Now we start applying them.
@@ -100,6 +109,22 @@ test_expect_success 'apply binary diff (copy) -- should fail.' \
'do_reset &&
test_must_fail git apply --index C.diff'
+test_expect_success 'apply binary diff with full-index' '
+ do_reset &&
+ git apply B-index.diff
+'
+
+test_expect_success 'apply binary diff with full-index (copy)' '
+ do_reset &&
+ git apply C-index.diff
+'
+
+test_expect_success 'apply full-index binary diff in new repo' '
+ (cd other-repo &&
+ do_reset &&
+ test_must_fail git apply ../B-index.diff)
+'
+
test_expect_success 'apply binary diff without replacement.' \
'do_reset &&
git apply BF.diff'
diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh
new file mode 100755
index 0000000..7c39843
--- /dev/null
+++ b/t/t4111-apply-subdir.sh
@@ -0,0 +1,142 @@
+#!/bin/sh
+
+test_description='patching from inconvenient places'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cat >patch <<-\EOF &&
+ diff file.orig file
+ --- a/file.orig
+ +++ b/file
+ @@ -1 +1,2 @@
+ 1
+ +2
+ EOF
+ patch="$(pwd)/patch" &&
+
+ echo 1 >preimage &&
+ printf "%s\n" 1 2 >postimage &&
+ echo 3 >other &&
+
+ test_tick &&
+ git commit --allow-empty -m basis
+'
+
+test_expect_success 'setup: subdir' '
+ reset_subdir() {
+ git reset &&
+ mkdir -p sub/dir/b &&
+ mkdir -p objects &&
+ cp "$1" file &&
+ cp "$1" objects/file &&
+ cp "$1" sub/dir/file &&
+ cp "$1" sub/dir/b/file &&
+ git add file sub/dir/file sub/dir/b/file objects/file &&
+ cp "$2" file &&
+ cp "$2" sub/dir/file &&
+ cp "$2" sub/dir/b/file &&
+ cp "$2" objects/file &&
+ test_might_fail git update-index --refresh -q
+ }
+'
+
+test_expect_success 'apply from subdir of toplevel' '
+ cp postimage expected &&
+ reset_subdir other preimage &&
+ (
+ cd sub/dir &&
+ git apply "$patch"
+ ) &&
+ test_cmp expected sub/dir/file
+'
+
+test_expect_success 'apply --cached from subdir of toplevel' '
+ cp postimage expected &&
+ cp other expected.working &&
+ reset_subdir preimage other &&
+ (
+ cd sub/dir &&
+ git apply --cached "$patch"
+ ) &&
+ git show :sub/dir/file >actual &&
+ test_cmp expected actual &&
+ test_cmp expected.working sub/dir/file
+'
+
+test_expect_success 'apply --index from subdir of toplevel' '
+ cp postimage expected &&
+ reset_subdir preimage other &&
+ (
+ cd sub/dir &&
+ test_must_fail git apply --index "$patch"
+ ) &&
+ reset_subdir other preimage &&
+ (
+ cd sub/dir &&
+ test_must_fail git apply --index "$patch"
+ ) &&
+ reset_subdir preimage preimage &&
+ (
+ cd sub/dir &&
+ git apply --index "$patch"
+ ) &&
+ git show :sub/dir/file >actual &&
+ test_cmp expected actual &&
+ test_cmp expected sub/dir/file
+'
+
+test_expect_success 'apply from .git dir' '
+ cp postimage expected &&
+ cp preimage .git/file &&
+ cp preimage .git/objects/file &&
+ (
+ cd .git &&
+ git apply "$patch"
+ ) &&
+ test_cmp expected .git/file
+'
+
+test_expect_success 'apply from subdir of .git dir' '
+ cp postimage expected &&
+ cp preimage .git/file &&
+ cp preimage .git/objects/file &&
+ (
+ cd .git/objects &&
+ git apply "$patch"
+ ) &&
+ test_cmp expected .git/objects/file
+'
+
+test_expect_success 'apply --cached from .git dir' '
+ cp postimage expected &&
+ cp other expected.working &&
+ cp other .git/file &&
+ reset_subdir preimage other &&
+ (
+ cd .git &&
+ git apply --cached "$patch"
+ ) &&
+ git show :file >actual &&
+ test_cmp expected actual &&
+ test_cmp expected.working file &&
+ test_cmp expected.working .git/file
+'
+
+test_expect_success 'apply --cached from subdir of .git dir' '
+ cp postimage expected &&
+ cp preimage expected.subdir &&
+ cp other .git/file &&
+ cp other .git/objects/file &&
+ reset_subdir preimage other &&
+ (
+ cd .git/objects &&
+ git apply --cached "$patch"
+ ) &&
+ git show :file >actual &&
+ git show :objects/file >actual.subdir &&
+ test_cmp expected actual &&
+ test_cmp expected.subdir actual.subdir
+'
+
+test_done
diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh
index 99ec13d..f12826f 100755
--- a/t/t4114-apply-typechange.sh
+++ b/t/t4114-apply-typechange.sh
@@ -9,13 +9,7 @@ test_description='git apply should not get confused with type changes.
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
-test_expect_success 'setup repository and commits' '
+test_expect_success SYMLINKS 'setup repository and commits' '
echo "hello world" > foo &&
echo "hi planet" > bar &&
git update-index --add foo bar &&
@@ -48,7 +42,7 @@ test_expect_success 'setup repository and commits' '
git branch foo-baz-renamed-from-foo
'
-test_expect_success 'file renamed from foo to foo/baz' '
+test_expect_success SYMLINKS 'file renamed from foo to foo/baz' '
git checkout -f initial &&
git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
git apply --index < patch
@@ -56,7 +50,7 @@ test_expect_success 'file renamed from foo to foo/baz' '
test_debug 'cat patch'
-test_expect_success 'file renamed from foo/baz to foo' '
+test_expect_success SYMLINKS 'file renamed from foo/baz to foo' '
git checkout -f foo-baz-renamed-from-foo &&
git diff-tree -M -p HEAD initial > patch &&
git apply --index < patch
@@ -64,7 +58,7 @@ test_expect_success 'file renamed from foo/baz to foo' '
test_debug 'cat patch'
-test_expect_success 'directory becomes file' '
+test_expect_success SYMLINKS 'directory becomes file' '
git checkout -f foo-becomes-a-directory &&
git diff-tree -p HEAD initial > patch &&
git apply --index < patch
@@ -72,7 +66,7 @@ test_expect_success 'directory becomes file' '
test_debug 'cat patch'
-test_expect_success 'file becomes directory' '
+test_expect_success SYMLINKS 'file becomes directory' '
git checkout -f initial &&
git diff-tree -p HEAD foo-becomes-a-directory > patch &&
git apply --index < patch
@@ -80,7 +74,7 @@ test_expect_success 'file becomes directory' '
test_debug 'cat patch'
-test_expect_success 'file becomes symlink' '
+test_expect_success SYMLINKS 'file becomes symlink' '
git checkout -f initial &&
git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
@@ -88,21 +82,21 @@ test_expect_success 'file becomes symlink' '
test_debug 'cat patch'
-test_expect_success 'symlink becomes file' '
+test_expect_success SYMLINKS 'symlink becomes file' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p HEAD foo-back-to-file > patch &&
git apply --index < patch
'
test_debug 'cat patch'
-test_expect_success 'binary file becomes symlink' '
+test_expect_success SYMLINKS 'binary file becomes symlink' '
git checkout -f foo-becomes-binary &&
git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
'
test_debug 'cat patch'
-test_expect_success 'symlink becomes binary file' '
+test_expect_success SYMLINKS 'symlink becomes binary file' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p --binary HEAD foo-becomes-binary > patch &&
git apply --index < patch
@@ -110,7 +104,7 @@ test_expect_success 'symlink becomes binary file' '
test_debug 'cat patch'
-test_expect_success 'symlink becomes directory' '
+test_expect_success SYMLINKS 'symlink becomes directory' '
git checkout -f foo-symlinked-to-bar &&
git diff-tree -p HEAD foo-becomes-a-directory > patch &&
git apply --index < patch
@@ -118,7 +112,7 @@ test_expect_success 'symlink becomes directory' '
test_debug 'cat patch'
-test_expect_success 'directory becomes symlink' '
+test_expect_success SYMLINKS 'directory becomes symlink' '
git checkout -f foo-becomes-a-directory &&
git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
git apply --index < patch
diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
index b852e58..7674dd2 100755
--- a/t/t4115-apply-symlink.sh
+++ b/t/t4115-apply-symlink.sh
@@ -9,13 +9,7 @@ test_description='git apply symlinks and partial files
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
ln -s path1/path2/path3/path4/path5 link1 &&
git add link? &&
@@ -34,7 +28,7 @@ test_expect_success setup '
'
-test_expect_success 'apply symlink patch' '
+test_expect_success SYMLINKS 'apply symlink patch' '
git checkout side &&
git apply patch &&
@@ -43,7 +37,7 @@ test_expect_success 'apply symlink patch' '
'
-test_expect_success 'apply --index symlink patch' '
+test_expect_success SYMLINKS 'apply --index symlink patch' '
git checkout -f side &&
git apply --index patch &&
diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh
index 3c73a78..3d0384d 100755
--- a/t/t4119-apply-config.sh
+++ b/t/t4119-apply-config.sh
@@ -73,7 +73,7 @@ D=`pwd`
test_expect_success 'apply --whitespace=strip in subdir' '
cd "$D" &&
- git config --unset-all apply.whitespace
+ git config --unset-all apply.whitespace &&
rm -f sub/file1 &&
cp saved sub/file1 &&
git update-index --refresh &&
diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh
index b463b4f..a33d510 100755
--- a/t/t4120-apply-popt.sh
+++ b/t/t4120-apply-popt.sh
@@ -6,25 +6,85 @@
test_description='git apply -p handling.'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
test_expect_success setup '
mkdir sub &&
echo A >sub/file1 &&
- cp sub/file1 file1 &&
+ cp sub/file1 file1.saved &&
git add sub/file1 &&
echo B >sub/file1 &&
git diff >patch.file &&
- rm sub/file1 &&
- rmdir sub
+ git checkout -- sub/file1 &&
+ git mv sub süb &&
+ echo B >süb/file1 &&
+ git diff >patch.escaped &&
+ grep "[\]" patch.escaped &&
+ rm süb/file1 &&
+ rmdir süb
'
test_expect_success 'apply git diff with -p2' '
+ cp file1.saved file1 &&
git apply -p2 patch.file
'
test_expect_success 'apply with too large -p' '
+ cp file1.saved file1 &&
test_must_fail git apply --stat -p3 patch.file 2>err &&
grep "removing 3 leading" err
'
+test_expect_success 'apply (-p2) traditional diff with funny filenames' '
+ cat >patch.quotes <<-\EOF &&
+ diff -u "a/"sub/file1 "b/"sub/file1
+ --- "a/"sub/file1
+ +++ "b/"sub/file1
+ @@ -1 +1 @@
+ -A
+ +B
+ EOF
+ echo B >expected &&
+
+ cp file1.saved file1 &&
+ git apply -p2 patch.quotes &&
+ test_cmp expected file1
+'
+
+test_expect_success 'apply with too large -p and fancy filename' '
+ cp file1.saved file1 &&
+ test_must_fail git apply --stat -p3 patch.escaped 2>err &&
+ grep "removing 3 leading" err
+'
+
+test_expect_success 'apply (-p2) diff, mode change only' '
+ cat >patch.chmod <<-\EOF &&
+ diff --git a/sub/file1 b/sub/file1
+ old mode 100644
+ new mode 100755
+ EOF
+ test_chmod -x file1 &&
+ git apply --index -p2 patch.chmod &&
+ case $(git ls-files -s file1) in 100755*) : good;; *) false;; esac
+'
+
+test_expect_success FILEMODE 'file mode was changed' '
+ test -x file1
+'
+
+test_expect_success 'apply (-p2) diff, rename' '
+ cat >patch.rename <<-\EOF &&
+ diff --git a/sub/file1 b/sub/file2
+ similarity index 100%
+ rename from sub/file1
+ rename to sub/file2
+ EOF
+ echo A >expected &&
+
+ cp file1.saved file1 &&
+ rm -f file2 &&
+ git apply -p2 patch.rename &&
+ test_cmp expected file2
+'
+
test_done
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 0d3c1d5..3940737 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -3,12 +3,6 @@
test_description='apply to deeper directory without getting fooled with symlink'
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
lecho () {
for l_
do
@@ -16,7 +10,7 @@ lecho () {
done
}
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
mkdir -p arch/i386/boot arch/x86_64 &&
lecho 1 2 3 4 5 >arch/i386/boot/Makefile &&
@@ -37,7 +31,7 @@ test_expect_success setup '
'
-test_expect_success apply '
+test_expect_success SYMLINKS apply '
git checkout test &&
git diff --exit-code test &&
@@ -46,7 +40,7 @@ test_expect_success apply '
'
-test_expect_success 'check result' '
+test_expect_success SYMLINKS 'check result' '
git diff --exit-code master &&
git diff --exit-code --cached master &&
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index 8a676a5..6f6ee88 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -10,7 +10,8 @@ prepare_test_file () {
# X RULE
# ! trailing-space
# @ space-before-tab
- # # indent-with-non-tab
+ # # indent-with-non-tab (default tab width 8)
+ # = indent-with-non-tab,tabwidth=16
# % tab-in-indent
sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF
An_SP in an ordinary line>and a HT.
@@ -25,8 +26,8 @@ prepare_test_file () {
________>_Eight SP, a HT and a SP (@#%).
_______________Fifteen SP (#).
_______________>Fifteen SP and a HT (@#%).
- ________________Sixteen SP (#).
- ________________>Sixteen SP and a HT (@#%).
+ ________________Sixteen SP (#=).
+ ________________>Sixteen SP and a HT (@#%=).
_____a__Five SP, a non WS, two SP.
A line with a (!) trailing SP_
A line with a (!) trailing HT>
@@ -121,6 +122,34 @@ test_expect_success 'whitespace=error-all, no rule (attribute)' '
'
+test_expect_success 'spaces inserted by tab-in-indent' '
+
+ git config core.whitespace -trailing,-space,-indent,tab &&
+ rm -f .gitattributes &&
+ test_fix % &&
+ sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF >expect &&
+ An_SP in an ordinary line>and a HT.
+ ________A HT (%).
+ ________A SP and a HT (@%).
+ _________A SP, a HT and a SP (@%).
+ _______Seven SP.
+ ________Eight SP (#).
+ ________Seven SP and a HT (@%).
+ ________________Eight SP and a HT (@#%).
+ _________Seven SP, a HT and a SP (@%).
+ _________________Eight SP, a HT and a SP (@#%).
+ _______________Fifteen SP (#).
+ ________________Fifteen SP and a HT (@#%).
+ ________________Sixteen SP (#=).
+ ________________________Sixteen SP and a HT (@#%=).
+ _____a__Five SP, a non WS, two SP.
+ A line with a (!) trailing SP_
+ A line with a (!) trailing HT>
+ EOF
+ test_cmp expect target
+
+'
+
for t in - ''
do
case "$t" in '') tt='!' ;; *) tt= ;; esac
@@ -129,7 +158,7 @@ do
case "$s" in '') ts='@' ;; *) ts= ;; esac
for i in - ''
do
- case "$i" in '') ti='#' ;; *) ti= ;; esac
+ case "$i" in '') ti='#' ti16='=';; *) ti= ti16= ;; esac
for h in - ''
do
[ -z "$h$i" ] && continue
@@ -142,12 +171,22 @@ do
test_fix "$tt$ts$ti$th"
'
+ test_expect_success "rule=$rule,tabwidth=16" '
+ git config core.whitespace "$rule,tabwidth=16" &&
+ test_fix "$tt$ts$ti16$th"
+ '
+
test_expect_success "rule=$rule (attributes)" '
git config --unset core.whitespace &&
echo "target whitespace=$rule" >.gitattributes &&
test_fix "$tt$ts$ti$th"
'
+ test_expect_success "rule=$rule,tabwidth=16 (attributes)" '
+ echo "target whitespace=$rule,tabwidth=16" >.gitattributes &&
+ test_fix "$tt$ts$ti16$th"
+ '
+
done
done
done
@@ -176,9 +215,8 @@ test_expect_success 'trailing whitespace & no newline at the end of file' '
'
test_expect_success 'blank at EOF with --whitespace=fix (1)' '
- : these can fail depending on what we did before
- git config --unset core.whitespace
- rm -f .gitattributes
+ test_might_fail git config --unset core.whitespace &&
+ rm -f .gitattributes &&
{ echo a; echo b; echo c; } >one &&
git add one &&
@@ -368,7 +406,7 @@ test_expect_success 'missing blanks at EOF must only match blank lines' '
git diff -- one >patch &&
echo a >one &&
- test_must_fail git apply patch
+ test_must_fail git apply patch &&
test_must_fail git apply --whitespace=fix patch &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch
'
@@ -419,7 +457,7 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one &&
- printf " \r\n" >>one
+ printf " \r\n" >>one &&
git add one &&
printf "d\r\n" >>one &&
cp one expect &&
@@ -436,7 +474,7 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' '
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one &&
- printf " \r\n" >>one
+ printf " \r\n" >>one &&
git add one &&
cp one expect &&
printf "d\r\n" >>one &&
diff --git a/t/t4127-apply-same-fn.sh b/t/t4127-apply-same-fn.sh
index 77200c0..972946c 100755
--- a/t/t4127-apply-same-fn.sh
+++ b/t/t4127-apply-same-fn.sh
@@ -31,7 +31,7 @@ test_expect_success 'apply same filename with independent changes' '
'
test_expect_success 'apply same filename with overlapping changes' '
- git reset --hard
+ git reset --hard &&
modify "s/^d/z/" same_fn &&
git diff > patch0 &&
git add same_fn &&
@@ -44,8 +44,8 @@ test_expect_success 'apply same filename with overlapping changes' '
'
test_expect_success 'apply same new filename after rename' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
@@ -58,12 +58,12 @@ test_expect_success 'apply same new filename after rename' '
'
test_expect_success 'apply same old filename after rename -- should fail.' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
- git mv new_fn same_fn
+ git mv new_fn same_fn &&
modify "s/^e/y/" same_fn &&
git diff >> patch1 &&
git reset --hard &&
@@ -71,13 +71,13 @@ test_expect_success 'apply same old filename after rename -- should fail.' '
'
test_expect_success 'apply A->B (rename), C->A (rename), A->A -- should pass.' '
- git reset --hard
- git mv same_fn new_fn
+ git reset --hard &&
+ git mv same_fn new_fn &&
modify "s/^d/z/" new_fn &&
git add new_fn &&
git diff -M --cached > patch1 &&
git commit -m "a rename" &&
- git mv other_fn same_fn
+ git mv other_fn same_fn &&
modify "s/^e/y/" same_fn &&
git add same_fn &&
git diff -M --cached >> patch1 &&
diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh
index fc7af04..0d36ebd 100755
--- a/t/t4129-apply-samemode.sh
+++ b/t/t4129-apply-samemode.sh
@@ -3,13 +3,7 @@
test_description='applying patch with mode bits'
. ./test-lib.sh
-
-if test "$(git config --bool core.filemode)" = false
-then
- say 'filemode disabled on the filesystem'
-else
- test_set_prereq FILEMODE
-fi
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
test_expect_success setup '
echo original >file &&
diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh
index 7cfa2d6..d173acd 100755
--- a/t/t4130-apply-criss-cross-rename.sh
+++ b/t/t4130-apply-criss-cross-rename.sh
@@ -44,7 +44,7 @@ test_expect_success 'criss-cross rename' '
git reset --hard &&
mv file1 tmp &&
mv file2 file1 &&
- mv file3 file2
+ mv file3 file2 &&
mv tmp file3 &&
cp file1 file1-swapped &&
cp file2 file2-swapped &&
diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh
index bb1ffe3..a2bc1cd 100755
--- a/t/t4132-apply-removal.sh
+++ b/t/t4132-apply-removal.sh
@@ -30,6 +30,7 @@ test_expect_success setup '
epocWest="1969-12-31 16:00:00.000000000 -0800" &&
epocGMT="1970-01-01 00:00:00.000000000 +0000" &&
epocEast="1970-01-01 09:00:00.000000000 +0900" &&
+ epocWest2="1969-12-31 16:00:00 -08:00" &&
sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch &&
sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch &&
@@ -46,6 +47,7 @@ test_expect_success setup '
sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch &&
sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch &&
sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch &&
+ sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest2/" <d >removeWest2.patch &&
echo something >something &&
>empty
diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh
index 3421807..94da990 100755
--- a/t/t4133-apply-filenames.sh
+++ b/t/t4133-apply-filenames.sh
@@ -8,7 +8,7 @@ test_description='git apply filename consistency check'
. ./test-lib.sh
test_expect_success setup '
- cat > bad1.patch <<EOF
+ cat > bad1.patch <<EOF &&
diff --git a/f b/f
new file mode 100644
index 0000000..d00491f
@@ -29,9 +29,9 @@ EOF
'
test_expect_success 'apply diff with inconsistent filenames in headers' '
- test_must_fail git apply bad1.patch 2>err
- grep "inconsistent new filename" err
- test_must_fail git apply bad2.patch 2>err
+ test_must_fail git apply bad1.patch 2>err &&
+ grep "inconsistent new filename" err &&
+ test_must_fail git apply bad2.patch 2>err &&
grep "inconsistent old filename" err
'
diff --git a/t/t4134-apply-submodule.sh b/t/t4134-apply-submodule.sh
index 1b82f93..0043930 100755
--- a/t/t4134-apply-submodule.sh
+++ b/t/t4134-apply-submodule.sh
@@ -8,7 +8,7 @@ test_description='git apply submodule tests'
. ./test-lib.sh
test_expect_success setup '
- cat > create-sm.patch <<EOF
+ cat > create-sm.patch <<EOF &&
diff --git a/dir/sm b/dir/sm
new file mode 160000
index 0000000..0123456
diff --git a/t/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh
new file mode 100755
index 0000000..bf5dc57
--- /dev/null
+++ b/t/t4135-apply-weird-filenames.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='git apply with weird postimage filenames'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ vector=$TEST_DIRECTORY/t4135 &&
+
+ test_tick &&
+ git commit --allow-empty -m preimage &&
+ git tag preimage &&
+
+ reset_preimage() {
+ git checkout -f preimage^0 &&
+ git read-tree -u --reset HEAD &&
+ git update-index --refresh
+ } &&
+
+ test_when_finished "rm -f \"tab embedded.txt\"" &&
+ test_when_finished "rm -f '\''\"quoteembedded\".txt'\''" &&
+ if touch -- "tab embedded.txt" '\''"quoteembedded".txt'\''
+ then
+ test_set_prereq FUNNYNAMES
+ fi
+'
+
+try_filename() {
+ desc=$1
+ postimage=$2
+ prereq=${3:-}
+ exp1=${4:-success}
+ exp2=${5:-success}
+ exp3=${6:-success}
+
+ test_expect_$exp1 $prereq "$desc, git-style file creation patch" "
+ echo postimage >expected &&
+ reset_preimage &&
+ rm -f '$postimage' &&
+ git apply -v \"\$vector\"/'git-$desc.diff' &&
+ test_cmp expected '$postimage'
+ "
+
+ test_expect_$exp2 $prereq "$desc, traditional patch" "
+ echo postimage >expected &&
+ reset_preimage &&
+ echo preimage >'$postimage' &&
+ git apply -v \"\$vector\"/'diff-$desc.diff' &&
+ test_cmp expected '$postimage'
+ "
+
+ test_expect_$exp3 $prereq "$desc, traditional file creation patch" "
+ echo postimage >expected &&
+ reset_preimage &&
+ rm -f '$postimage' &&
+ git apply -v \"\$vector\"/'add-$desc.diff' &&
+ test_cmp expected '$postimage'
+ "
+}
+
+try_filename 'plain' 'postimage.txt'
+try_filename 'with spaces' 'post image.txt'
+try_filename 'with tab' 'post image.txt' FUNNYNAMES
+try_filename 'with backslash' 'post\image.txt' BSLASHPSPEC
+try_filename 'with quote' '"postimage".txt' FUNNYNAMES success failure success
+
+test_expect_success 'whitespace-damaged traditional patch' '
+ echo postimage >expected &&
+ reset_preimage &&
+ rm -f postimage.txt &&
+ git apply -v "$vector/damaged.diff" &&
+ test_cmp expected postimage.txt
+'
+
+test_expect_success 'traditional patch with colon in timezone' '
+ echo postimage >expected &&
+ reset_preimage &&
+ rm -f "post image.txt" &&
+ git apply "$vector/funny-tz.diff" &&
+ test_cmp expected "post image.txt"
+'
+
+test_expect_success 'traditional, whitespace-damaged, colon in timezone' '
+ echo postimage >expected &&
+ reset_preimage &&
+ rm -f "post image.txt" &&
+ git apply "$vector/damaged-tz.diff" &&
+ test_cmp expected "post image.txt"
+'
+
+test_done
diff --git a/t/t4135/.gitignore b/t/t4135/.gitignore
new file mode 100644
index 0000000..3e58e65
--- /dev/null
+++ b/t/t4135/.gitignore
@@ -0,0 +1,3 @@
+/file-creation/
+/trad-creation/
+/trad-modification/
diff --git a/t/t4135/add-plain.diff b/t/t4135/add-plain.diff
new file mode 100644
index 0000000..cf5970a
--- /dev/null
+++ b/t/t4135/add-plain.diff
@@ -0,0 +1,5 @@
+diff -pruN a/postimage.txt b/postimage.txt
+--- a/postimage.txt 1969-12-31 18:00:00.000000000 -0600
++++ b/postimage.txt 2010-08-18 20:13:31.484002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with backslash.diff b/t/t4135/add-with backslash.diff
new file mode 100644
index 0000000..c6861e1
--- /dev/null
+++ b/t/t4135/add-with backslash.diff
@@ -0,0 +1,5 @@
+diff -pruN a/post\image.txt b/post\image.txt
+--- a/post\image.txt 1969-12-31 18:00:00.000000000 -0600
++++ b/post\image.txt 2010-08-18 20:13:31.692002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with quote.diff b/t/t4135/add-with quote.diff
new file mode 100644
index 0000000..866de78
--- /dev/null
+++ b/t/t4135/add-with quote.diff
@@ -0,0 +1,5 @@
+diff -pruN a/"postimage".txt b/"postimage".txt
+--- a/"postimage".txt 1969-12-31 18:00:00.000000000 -0600
++++ b/"postimage".txt 2010-08-18 20:13:31.756002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with spaces.diff b/t/t4135/add-with spaces.diff
new file mode 100644
index 0000000..a9a1212
--- /dev/null
+++ b/t/t4135/add-with spaces.diff
@@ -0,0 +1,5 @@
+diff -pruN a/post image.txt b/post image.txt
+--- a/post image.txt 1969-12-31 18:00:00.000000000 -0600
++++ b/post image.txt 2010-08-18 20:13:31.556002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/add-with tab.diff b/t/t4135/add-with tab.diff
new file mode 100644
index 0000000..bb67cb7
--- /dev/null
+++ b/t/t4135/add-with tab.diff
@@ -0,0 +1,5 @@
+diff -pruN a/post image.txt b/post image.txt
+--- a/post image.txt 1969-12-31 18:00:00.000000000 -0600
++++ b/post image.txt 2010-08-18 20:13:31.628002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/damaged-tz.diff b/t/t4135/damaged-tz.diff
new file mode 100644
index 0000000..07aaf08
--- /dev/null
+++ b/t/t4135/damaged-tz.diff
@@ -0,0 +1,5 @@
+diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt
+--- linux-2.6.12-rc2-mm3-vanilla/post image.txt 1969-12-31 16:00:00 -08:00
++++ linux-2.6.12-rc2-mm3/post image.txt 2005-04-12 02:14:06 -07:00
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/damaged.diff b/t/t4135/damaged.diff
new file mode 100644
index 0000000..68f7ede
--- /dev/null
+++ b/t/t4135/damaged.diff
@@ -0,0 +1,5 @@
+diff -pruN a/postimage.txt b/postimage.txt
+--- a/postimage.txt 1969-12-31 18:00:00.000000000 -0600
++++ b/postimage.txt 2010-08-18 20:13:31.484002255 -0500
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/diff-plain.diff b/t/t4135/diff-plain.diff
new file mode 100644
index 0000000..acedcfa
--- /dev/null
+++ b/t/t4135/diff-plain.diff
@@ -0,0 +1,5 @@
+--- postimage.txt.orig 2010-08-18 20:13:31.432002255 -0500
++++ postimage.txt 2010-08-18 20:13:31.432002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with backslash.diff b/t/t4135/diff-with backslash.diff
new file mode 100644
index 0000000..9068a61
--- /dev/null
+++ b/t/t4135/diff-with backslash.diff
@@ -0,0 +1,5 @@
+--- post\image.txt.orig 2010-08-18 20:13:31.680002255 -0500
++++ post\image.txt 2010-08-18 20:13:31.680002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with quote.diff b/t/t4135/diff-with quote.diff
new file mode 100644
index 0000000..c8e8cc1
--- /dev/null
+++ b/t/t4135/diff-with quote.diff
@@ -0,0 +1,5 @@
+--- "postimage".txt.orig 2010-08-18 20:13:31.744002255 -0500
++++ "postimage".txt 2010-08-18 20:13:31.744002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with spaces.diff b/t/t4135/diff-with spaces.diff
new file mode 100644
index 0000000..3512056
--- /dev/null
+++ b/t/t4135/diff-with spaces.diff
@@ -0,0 +1,5 @@
+--- post image.txt.orig 2010-08-18 20:13:31.544002255 -0500
++++ post image.txt 2010-08-18 20:13:31.544002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/diff-with tab.diff b/t/t4135/diff-with tab.diff
new file mode 100644
index 0000000..4e6d9b2
--- /dev/null
+++ b/t/t4135/diff-with tab.diff
@@ -0,0 +1,5 @@
+--- post image.txt.orig 2010-08-18 20:13:31.616002255 -0500
++++ post image.txt 2010-08-18 20:13:31.616002255 -0500
+@@ -1 +1 @@
+-preimage
++postimage
diff --git a/t/t4135/funny-tz.diff b/t/t4135/funny-tz.diff
new file mode 100644
index 0000000..998e3a8
--- /dev/null
+++ b/t/t4135/funny-tz.diff
@@ -0,0 +1,5 @@
+diff -urN -X /usr/people/jes/exclude-linux linux-2.6.12-rc2-mm3-vanilla/post image.txt linux-2.6.12-rc2-mm3/post image.txt
+--- linux-2.6.12-rc2-mm3-vanilla/post image.txt 1969-12-31 16:00:00 -08:00
++++ linux-2.6.12-rc2-mm3/post image.txt 2005-04-12 02:14:06 -07:00
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-plain.diff b/t/t4135/git-plain.diff
new file mode 100644
index 0000000..db47d1a
--- /dev/null
+++ b/t/t4135/git-plain.diff
@@ -0,0 +1,7 @@
+diff --git a/postimage.txt b/postimage.txt
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ b/postimage.txt
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with backslash.diff b/t/t4135/git-with backslash.diff
new file mode 100644
index 0000000..0e84a10
--- /dev/null
+++ b/t/t4135/git-with backslash.diff
@@ -0,0 +1,7 @@
+diff --git "a/post\\image.txt" "b/post\\image.txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/post\\image.txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with quote.diff b/t/t4135/git-with quote.diff
new file mode 100644
index 0000000..bdbea8a
--- /dev/null
+++ b/t/t4135/git-with quote.diff
@@ -0,0 +1,7 @@
+diff --git "a/\"postimage\".txt" "b/\"postimage\".txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/\"postimage\".txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with spaces.diff b/t/t4135/git-with spaces.diff
new file mode 100644
index 0000000..baaa810
--- /dev/null
+++ b/t/t4135/git-with spaces.diff
@@ -0,0 +1,7 @@
+diff --git a/post image.txt b/post image.txt
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ b/post image.txt
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/git-with tab.diff b/t/t4135/git-with tab.diff
new file mode 100644
index 0000000..cca3c92
--- /dev/null
+++ b/t/t4135/git-with tab.diff
@@ -0,0 +1,7 @@
+diff --git "a/post\timage.txt" "b/post\timage.txt"
+new file mode 100644
+index 0000000..eff0c54
+--- /dev/null
++++ "b/post\timage.txt"
+@@ -0,0 +1 @@
++postimage
diff --git a/t/t4135/make-patches b/t/t4135/make-patches
new file mode 100755
index 0000000..f5f45dd
--- /dev/null
+++ b/t/t4135/make-patches
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+do_filename() {
+ desc=$1
+ postimage=$2
+
+ rm -fr file-creation &&
+ git init file-creation &&
+ (
+ cd file-creation &&
+ git commit --allow-empty -m init &&
+ echo postimage >"$postimage" &&
+ git add -N "$postimage" &&
+ git diff HEAD >"../git-$desc.diff"
+ ) &&
+
+ rm -fr trad-modification &&
+ mkdir trad-modification &&
+ (
+ cd trad-modification &&
+ echo preimage >"$postimage.orig" &&
+ echo postimage >"$postimage" &&
+ ! diff -u "$postimage.orig" "$postimage" >"../diff-$desc.diff"
+ ) &&
+
+ rm -fr trad-creation &&
+ mkdir trad-creation &&
+ (
+ cd trad-creation &&
+ mkdir a b &&
+ echo postimage >"b/$postimage" &&
+ ! diff -pruN a b >"../add-$desc.diff"
+ )
+}
+
+do_filename plain postimage.txt &&
+do_filename 'with spaces' 'post image.txt' &&
+do_filename 'with tab' 'post image.txt' &&
+do_filename 'with backslash' 'post\image.txt' &&
+do_filename 'with quote' '"postimage".txt' &&
+expand add-plain.diff >damaged.diff ||
+{
+ echo >&2 Failed. &&
+ exit 1
+}
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 810b04b..151404e 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -4,66 +4,71 @@ test_description='git am running'
. ./test-lib.sh
-cat >msg <<EOF
-second
-
-Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy
-eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
-voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
-kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
-ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
-tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
-vero eos et accusam et justo duo dolores et ea rebum.
-
- Duis autem vel eum iriure dolor in hendrerit in vulputate velit
- esse molestie consequat, vel illum dolore eu feugiat nulla facilisis
- at vero eros et accumsan et iusto odio dignissim qui blandit
- praesent luptatum zzril delenit augue duis dolore te feugait nulla
- facilisi.
-
-
-Lorem ipsum dolor sit amet,
-consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
-laoreet dolore magna aliquam erat volutpat.
-
- git
- ---
- +++
-
-Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
-lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
-dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
-dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
-dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
-feugait nulla facilisi.
-EOF
-
-cat >failmail <<EOF
-From foo@example.com Fri May 23 10:43:49 2008
-From: foo@example.com
-To: bar@example.com
-Subject: Re: [RFC/PATCH] git-foo.sh
-Date: Fri, 23 May 2008 05:23:42 +0200
-
-Sometimes we have to find out that there's nothing left.
-
-EOF
-
-cat >pine <<EOF
-From MAILER-DAEMON Fri May 23 10:43:49 2008
-Date: 23 May 2008 05:23:42 +0200
-From: Mail System Internal Data <MAILER-DAEMON@example.com>
-Subject: DON'T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
-Message-ID: <foo-0001@example.com>
-
-This text is part of the internal format of your mail folder, and is not
-a real message. It is created automatically by the mail system software.
-If deleted, important folder data will be lost, and it will be re-created
-with the data reset to initial values.
-
-EOF
-
-echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected
+test_expect_success 'setup: messages' '
+ cat >msg <<-\EOF &&
+ second
+
+ Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy
+ eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
+ voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
+ kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
+ ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
+ tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
+ vero eos et accusam et justo duo dolores et ea rebum.
+
+ EOF
+ q_to_tab <<-\EOF >>msg &&
+ QDuis autem vel eum iriure dolor in hendrerit in vulputate velit
+ Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis
+ Qat vero eros et accumsan et iusto odio dignissim qui blandit
+ Qpraesent luptatum zzril delenit augue duis dolore te feugait nulla
+ Qfacilisi.
+ EOF
+ cat >>msg <<-\EOF &&
+
+ Lorem ipsum dolor sit amet,
+ consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
+ laoreet dolore magna aliquam erat volutpat.
+
+ git
+ ---
+ +++
+
+ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
+ lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
+ dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
+ dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
+ dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
+ feugait nulla facilisi.
+ EOF
+
+ cat >failmail <<-\EOF &&
+ From foo@example.com Fri May 23 10:43:49 2008
+ From: foo@example.com
+ To: bar@example.com
+ Subject: Re: [RFC/PATCH] git-foo.sh
+ Date: Fri, 23 May 2008 05:23:42 +0200
+
+ Sometimes we have to find out that there'\''s nothing left.
+
+ EOF
+
+ cat >pine <<-\EOF &&
+ From MAILER-DAEMON Fri May 23 10:43:49 2008
+ Date: 23 May 2008 05:23:42 +0200
+ From: Mail System Internal Data <MAILER-DAEMON@example.com>
+ Subject: DON'\''T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
+ Message-ID: <foo-0001@example.com>
+
+ This text is part of the internal format of your mail folder, and is not
+ a real message. It is created automatically by the mail system software.
+ If deleted, important folder data will be lost, and it will be re-created
+ with the data reset to initial values.
+
+ EOF
+
+ signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+'
test_expect_success setup '
echo hello >file &&
@@ -71,11 +76,13 @@ test_expect_success setup '
test_tick &&
git commit -m first &&
git tag first &&
+
echo world >>file &&
git add file &&
test_tick &&
git commit -s -F msg &&
git tag second &&
+
git format-patch --stdout first >patch1 &&
{
echo "X-Fake-Field: Line One" &&
@@ -89,74 +96,101 @@ test_expect_success setup '
echo "X-Fake-Field: Line Three" &&
git format-patch --stdout first | sed -e "1d"
} | append_cr >patch1-crlf.eml &&
+
sed -n -e "3,\$p" msg >file &&
git add file &&
test_tick &&
git commit -m third &&
+
git format-patch --stdout first >patch2 &&
+
git checkout -b lorem &&
sed -n -e "11,\$p" msg >file &&
head -n 9 msg >>file &&
test_tick &&
git commit -a -m "moved stuff" &&
+
echo goodbye >another &&
git add another &&
test_tick &&
git commit -m "added another file" &&
- git format-patch --stdout master >lorem-move.patch
-'
-# reset time
-unset test_tick
-test_tick
+ git format-patch --stdout master >lorem-move.patch &&
+
+ git checkout -b rename &&
+ git mv file renamed &&
+ git commit -m "renamed a file" &&
+
+ git format-patch -M --stdout lorem >rename.patch &&
+
+ git reset --soft lorem^ &&
+ git commit -m "renamed a file and added another" &&
+
+ git format-patch -M --stdout lorem^ >rename-add.patch &&
+
+ # reset time
+ unset test_tick &&
+ test_tick
+'
test_expect_success 'am applies patch correctly' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
git am <patch1 &&
! test -d .git/rebase-apply &&
- test -z "$(git diff second)" &&
+ git diff --exit-code second &&
test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
'
test_expect_success 'am applies patch e-mail not in a mbox' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
git am patch1.eml &&
! test -d .git/rebase-apply &&
- test -z "$(git diff second)" &&
+ git diff --exit-code second &&
test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
'
test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
git am patch1-crlf.eml &&
! test -d .git/rebase-apply &&
- test -z "$(git diff second)" &&
+ git diff --exit-code second &&
test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
'
-GIT_AUTHOR_NAME="Another Thor"
-GIT_AUTHOR_EMAIL="a.thor@example.com"
-GIT_COMMITTER_NAME="Co M Miter"
-GIT_COMMITTER_EMAIL="c.miter@example.com"
-export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+test_expect_success 'setup: new author and committer' '
+ GIT_AUTHOR_NAME="Another Thor" &&
+ GIT_AUTHOR_EMAIL="a.thor@example.com" &&
+ GIT_COMMITTER_NAME="Co M Miter" &&
+ GIT_COMMITTER_EMAIL="c.miter@example.com" &&
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
+'
compare () {
- test "$(git cat-file commit "$2" | grep "^$1 ")" = \
- "$(git cat-file commit "$3" | grep "^$1 ")"
+ a=$(git cat-file commit "$2" | grep "^$1 ") &&
+ b=$(git cat-file commit "$3" | grep "^$1 ") &&
+ test "$a" = "$b"
}
test_expect_success 'am changes committer and keeps author' '
test_tick &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
git am patch2 &&
! test -d .git/rebase-apply &&
test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
- test -z "$(git diff master..HEAD)" &&
- test -z "$(git diff master^..HEAD^)" &&
+ git diff --exit-code master..HEAD &&
+ git diff --exit-code master^..HEAD^ &&
compare author master HEAD &&
compare author master^ HEAD^ &&
test "$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
@@ -164,41 +198,55 @@ test_expect_success 'am changes committer and keeps author' '
'
test_expect_success 'am --signoff adds Signed-off-by: line' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout -b master2 first &&
git am --signoff <patch2 &&
+ printf "%s\n" "$signoff" >expected &&
echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
- test_cmp actual expected &&
+ test_cmp expected actual &&
echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
- test_cmp actual expected
+ test_cmp expected actual
'
test_expect_success 'am stays in branch' '
- test "refs/heads/master2" = "$(git symbolic-ref HEAD)"
+ echo refs/heads/master2 >expected &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expected actual
'
test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
git format-patch --stdout HEAD^ >patch3 &&
- sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4
+ sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2," patch3 >patch4 &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout HEAD^ &&
git am --signoff patch4 &&
- test "$(git cat-file commit HEAD | grep -c "^Signed-off-by:")" -eq 1
+ git cat-file commit HEAD >actual &&
+ test $(grep -c "^Signed-off-by:" actual) -eq 1
'
test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
- test "$(git rev-parse HEAD)" = "$(git rev-parse master2)"
+ git rev-parse HEAD >expected &&
+ git rev-parse master2 >actual &&
+ test_cmp expected actual
'
test_expect_success 'am --keep really keeps the subject' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout HEAD^ &&
git am --keep patch4 &&
! test -d .git/rebase-apply &&
- git cat-file commit HEAD |
- fgrep "Re: Re: Re: [PATCH 1/5 v2] third"
+ git cat-file commit HEAD >actual &&
+ grep "Re: Re: Re: \[PATCH 1/5 v2\] third" actual
'
test_expect_success 'am -3 falls back to 3-way merge' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout -b lorem2 master2 &&
sed -n -e "3,\$p" msg >file &&
head -n 9 msg >>file &&
@@ -207,34 +255,75 @@ test_expect_success 'am -3 falls back to 3-way merge' '
git commit -m "copied stuff" &&
git am -3 lorem-move.patch &&
! test -d .git/rebase-apply &&
- test -z "$(git diff lorem)"
+ git diff --exit-code lorem
+'
+
+test_expect_success 'am can rename a file' '
+ grep "^rename from" rename.patch &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout lorem^0 &&
+ git am rename.patch &&
+ ! test -d .git/rebase-apply &&
+ git update-index --refresh &&
+ git diff --exit-code rename
+'
+
+test_expect_success 'am -3 can rename a file' '
+ grep "^rename from" rename.patch &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout lorem^0 &&
+ git am -3 rename.patch &&
+ ! test -d .git/rebase-apply &&
+ git update-index --refresh &&
+ git diff --exit-code rename
+'
+
+test_expect_success 'am -3 can rename a file after falling back to 3-way merge' '
+ grep "^rename from" rename-add.patch &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
+ git checkout lorem^0 &&
+ git am -3 rename-add.patch &&
+ ! test -d .git/rebase-apply &&
+ git update-index --refresh &&
+ git diff --exit-code rename
'
test_expect_success 'am -3 -q is quiet' '
+ rm -fr .git/rebase-apply &&
+ git checkout -f lorem2 &&
git reset master2 --hard &&
sed -n -e "3,\$p" msg >file &&
head -n 9 msg >>file &&
git add file &&
test_tick &&
git commit -m "copied stuff" &&
- git am -3 -q lorem-move.patch > output.out 2>&1 &&
+ git am -3 -q lorem-move.patch >output.out 2>&1 &&
! test -s output.out
'
test_expect_success 'am pauses on conflict' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout lorem2^^ &&
test_must_fail git am lorem-move.patch &&
test -d .git/rebase-apply
'
test_expect_success 'am --skip works' '
+ echo goodbye >expected &&
git am --skip &&
! test -d .git/rebase-apply &&
- test -z "$(git diff lorem2^^ -- file)" &&
- test goodbye = "$(cat another)"
+ git diff --exit-code lorem2^^ -- file &&
+ test_cmp expected another
'
test_expect_success 'am --resolved works' '
+ echo goodbye >expected &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout lorem2^^ &&
test_must_fail git am lorem-move.patch &&
test -d .git/rebase-apply &&
@@ -242,22 +331,29 @@ test_expect_success 'am --resolved works' '
git add file &&
git am --resolved &&
! test -d .git/rebase-apply &&
- test goodbye = "$(cat another)"
+ test_cmp expected another
'
test_expect_success 'am takes patches from a Pine mailbox' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
cat pine patch1 | git am &&
! test -d .git/rebase-apply &&
- test -z "$(git diff master^..HEAD)"
+ git diff --exit-code master^..HEAD
'
test_expect_success 'am fails on mail without patch' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
test_must_fail git am <failmail &&
- rm -r .git/rebase-apply/
+ git am --abort &&
+ ! test -d .git/rebase-apply
'
test_expect_success 'am fails on empty patch' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
echo "---" >>failmail &&
test_must_fail git am <failmail &&
git am --skip &&
@@ -266,28 +362,34 @@ test_expect_success 'am fails on empty patch' '
test_expect_success 'am works from stdin in subdirectory' '
rm -fr subdir &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
(
mkdir -p subdir &&
cd subdir &&
git am <../patch1
) &&
- test -z "$(git diff second)"
+ git diff --exit-code second
'
test_expect_success 'am works from file (relative path given) in subdirectory' '
rm -fr subdir &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
(
mkdir -p subdir &&
cd subdir &&
git am ../patch1
) &&
- test -z "$(git diff second)"
+ git diff --exit-code second
'
test_expect_success 'am works from file (absolute path given) in subdirectory' '
rm -fr subdir &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
P=$(pwd) &&
(
@@ -295,27 +397,31 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' '
cd subdir &&
git am "$P/patch1"
) &&
- test -z "$(git diff second)"
+ git diff --exit-code second
'
test_expect_success 'am --committer-date-is-author-date' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
git am --committer-date-is-author-date patch1 &&
git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
- at=$(sed -ne "/^author /s/.*> //p" head1) &&
- ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
- test "$at" = "$ct"
+ sed -ne "/^author /s/.*> //p" head1 >at &&
+ sed -ne "/^committer /s/.*> //p" head1 >ct &&
+ test_cmp at ct
'
test_expect_success 'am without --committer-date-is-author-date' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
git am patch1 &&
git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
- at=$(sed -ne "/^author /s/.*> //p" head1) &&
- ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
- test "$at" != "$ct"
+ sed -ne "/^author /s/.*> //p" head1 >at &&
+ sed -ne "/^committer /s/.*> //p" head1 >ct &&
+ ! test_cmp at ct
'
# This checks for +0000 because TZ is set to UTC and that should
@@ -323,41 +429,51 @@ test_expect_success 'am without --committer-date-is-author-date' '
# by test_tick that uses -0700 timezone; if this feature does not
# work, we will see that instead of +0000.
test_expect_success 'am --ignore-date' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
git am --ignore-date patch1 &&
git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
- at=$(sed -ne "/^author /s/.*> //p" head1) &&
- echo "$at" | grep "+0000"
+ sed -ne "/^author /s/.*> //p" head1 >at &&
+ grep "+0000" at
'
test_expect_success 'am into an unborn branch' '
+ git rev-parse first^{tree} >expected &&
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
rm -fr subdir &&
- mkdir -p subdir &&
+ mkdir subdir &&
git format-patch --numbered-files -o subdir -1 first &&
(
cd subdir &&
git init &&
git am 1
) &&
- result=$(
- cd subdir && git rev-parse HEAD^{tree}
+ (
+ cd subdir &&
+ git rev-parse HEAD^{tree} >../actual
) &&
- test "z$result" = "z$(git rev-parse first^{tree})"
+ test_cmp expected actual
'
test_expect_success 'am newline in subject' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
- sed -e "s/second/second \\\n foo/" patch1 > patchnl &&
- git am < patchnl > output.out 2>&1 &&
- grep "^Applying: second \\\n foo$" output.out
+ sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
+ git am <patchnl >output.out 2>&1 &&
+ test_i18ngrep "^Applying: second \\\n foo$" output.out
'
test_expect_success 'am -q is quiet' '
+ rm -fr .git/rebase-apply &&
+ git reset --hard &&
git checkout first &&
test_tick &&
- git am -q < patch1 > output.out 2>&1 &&
+ git am -q <patch1 >output.out 2>&1 &&
! test -s output.out
'
diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh
index 2b912d7..1176bcc 100755
--- a/t/t4151-am-abort.sh
+++ b/t/t4151-am-abort.sh
@@ -45,9 +45,10 @@ do
test_expect_success "am$with3 --skip continue after failed am$with3" '
test_must_fail git am$with3 --skip >output &&
- test "$(grep "^Applying" output)" = "Applying: 6" &&
- test_cmp file-2-expect file-2 &&
- test ! -f .git/rr-cache/MERGE_RR
+ test_i18ngrep "^Applying" output >output.applying &&
+ test_i18ngrep "^Applying: 6$" output.applying &&
+ test_i18ncmp file-2-expect file-2 &&
+ test ! -f .git/MERGE_RR
'
test_expect_success "am --abort goes back after failed am$with3" '
@@ -57,9 +58,18 @@ do
test_cmp expect actual &&
test_cmp file-2-expect file-2 &&
git diff-index --exit-code --cached HEAD &&
- test ! -f .git/rr-cache/MERGE_RR
+ test ! -f .git/MERGE_RR
'
done
+test_expect_success 'am --abort will keep the local commits intact' '
+ test_must_fail git am 0004-*.patch &&
+ test_commit unrelated &&
+ git rev-parse HEAD >expect &&
+ git am --abort &&
+ git rev-parse HEAD >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4152-am-subjects.sh b/t/t4152-am-subjects.sh
new file mode 100755
index 0000000..4c68245
--- /dev/null
+++ b/t/t4152-am-subjects.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test subject preservation with format-patch | am'
+. ./test-lib.sh
+
+make_patches() {
+ type=$1
+ subject=$2
+ test_expect_success "create patches with $type subject" '
+ git reset --hard baseline &&
+ echo $type >file &&
+ git commit -a -m "$subject" &&
+ git format-patch -1 --stdout >$type.patch &&
+ git format-patch -1 --stdout -k >$type-k.patch
+ '
+}
+
+check_subject() {
+ git reset --hard baseline &&
+ git am $2 $1.patch &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup baseline commit' '
+ test_commit baseline file
+'
+
+SHORT_SUBJECT='short subject'
+make_patches short "$SHORT_SUBJECT"
+
+LONG_SUBJECT1='this is a long subject that is virtually guaranteed'
+LONG_SUBJECT2='to require wrapping via format-patch if it is all'
+LONG_SUBJECT3='going to appear on a single line'
+LONG_SUBJECT="$LONG_SUBJECT1 $LONG_SUBJECT2 $LONG_SUBJECT3"
+make_patches long "$LONG_SUBJECT"
+
+MULTILINE_SUBJECT="$LONG_SUBJECT1
+$LONG_SUBJECT2
+$LONG_SUBJECT3"
+make_patches multiline "$MULTILINE_SUBJECT"
+
+echo "$SHORT_SUBJECT" >expect
+test_expect_success 'short subject preserved (format-patch | am)' '
+ check_subject short
+'
+test_expect_success 'short subject preserved (format-patch -k | am)' '
+ check_subject short-k
+'
+test_expect_success 'short subject preserved (format-patch -k | am -k)' '
+ check_subject short-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'long subject preserved (format-patch | am)' '
+ check_subject long
+'
+test_expect_success 'long subject preserved (format-patch -k | am)' '
+ check_subject long-k
+'
+test_expect_success 'long subject preserved (format-patch -k | am -k)' '
+ check_subject long-k -k
+'
+
+echo "$LONG_SUBJECT" >expect
+test_expect_success 'multiline subject unwrapped (format-patch | am)' '
+ check_subject multiline
+'
+test_expect_success 'multiline subject unwrapped (format-patch -k | am)' '
+ check_subject multiline-k
+'
+echo "$MULTILINE_SUBJECT" >expect
+test_expect_success 'multiline subject preserved (format-patch -k | am -k)' '
+ check_subject multiline-k -k
+'
+
+test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 70856d0..36255d6 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -4,237 +4,391 @@
#
test_description='git rerere
+
+! [fifth] version1
+ ! [first] first
+ ! [fourth] version1
+ ! [master] initial
+ ! [second] prefer first over second
+ ! [third] version2
+------
+ + [third] version2
++ [fifth] version1
+ + [fourth] version1
++ + + [third^] third
+ - [second] prefer first over second
+ + + [first] first
+ + [second^] second
+++++++ [master] initial
'
. ./test-lib.sh
-test_expect_success 'setup' "
- cat > a1 <<- EOF &&
+test_expect_success 'setup' '
+ cat >a1 <<-\EOF &&
Some title
==========
- Whether 'tis nobler in the mind to suffer
+ Whether '\''tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles,
And by opposing end them? To die: to sleep;
No more; and by a sleep to say we end
The heart-ache and the thousand natural shocks
- That flesh is heir to, 'tis a consummation
- Devoutly to be wish'd.
+ That flesh is heir to, '\''tis a consummation
+ Devoutly to be wish'\''d.
EOF
git add a1 &&
+ test_tick &&
git commit -q -a -m initial &&
- git checkout -b first &&
- cat >> a1 <<- EOF &&
+ cat >>a1 <<-\EOF &&
Some title
==========
To die, to sleep;
- To sleep: perchance to dream: ay, there's the rub;
+ To sleep: perchance to dream: ay, there'\''s the rub;
For in that sleep of death what dreams may come
When we have shuffled off this mortal coil,
- Must give us pause: there's the respect
+ Must give us pause: there'\''s the respect
That makes calamity of so long life;
EOF
+
+ git checkout -b first &&
+ test_tick &&
git commit -q -a -m first &&
git checkout -b second master &&
git show first:a1 |
- sed -e 's/To die, t/To die! T/' -e 's/Some title/Some Title/' > a1 &&
- echo '* END *' >>a1 &&
+ sed -e "s/To die, t/To die! T/" -e "s/Some title/Some Title/" >a1 &&
+ echo "* END *" >>a1 &&
+ test_tick &&
git commit -q -a -m second
-"
+'
test_expect_success 'nothing recorded without rerere' '
- (rm -rf .git/rr-cache; git config rerere.enabled false) &&
+ rm -rf .git/rr-cache &&
+ git config rerere.enabled false &&
test_must_fail git merge first &&
! test -d .git/rr-cache
'
-# activate rerere, old style
-test_expect_success 'conflicting merge' '
+test_expect_success 'activate rerere, old style (conflicting merge)' '
git reset --hard &&
mkdir .git/rr-cache &&
- git config --unset rerere.enabled &&
- test_must_fail git merge first
-'
+ test_might_fail git config --unset rerere.enabled &&
+ test_must_fail git merge first &&
-sha1=$(perl -pe 's/ .*//' .git/MERGE_RR)
-rr=.git/rr-cache/$sha1
-test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
+ sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ rr=.git/rr-cache/$sha1 &&
+ grep "^=======\$" $rr/preimage &&
+ ! test -f $rr/postimage &&
+ ! test -f $rr/thisimage
+'
test_expect_success 'rerere.enabled works, too' '
rm -rf .git/rr-cache &&
git config rerere.enabled true &&
git reset --hard &&
test_must_fail git merge first &&
+
+ sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ rr=.git/rr-cache/$sha1 &&
grep ^=======$ $rr/preimage
'
-test_expect_success 'no postimage or thisimage yet' \
- "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+test_expect_success 'set up rr-cache' '
+ rm -rf .git/rr-cache &&
+ git config rerere.enabled true &&
+ git reset --hard &&
+ test_must_fail git merge first &&
+ sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) &&
+ rr=.git/rr-cache/$sha1
+'
-test_expect_success 'preimage has right number of lines' '
+test_expect_success 'rr-cache looks sane' '
+ # no postimage or thisimage yet
+ ! test -f $rr/postimage &&
+ ! test -f $rr/thisimage &&
+ # preimage has right number of lines
cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) &&
+ echo $cnt &&
test $cnt = 13
-
'
-git show first:a1 > a1
-
-cat > expect << EOF
---- a/a1
-+++ b/a1
-@@ -1,4 +1,4 @@
--Some Title
-+Some title
- ==========
- Whether 'tis nobler in the mind to suffer
- The slings and arrows of outrageous fortune,
-@@ -8,21 +8,11 @@
- The heart-ache and the thousand natural shocks
- That flesh is heir to, 'tis a consummation
- Devoutly to be wish'd.
--<<<<<<<
--Some Title
--==========
--To die! To sleep;
--=======
- Some title
- ==========
- To die, to sleep;
-->>>>>>>
- To sleep: perchance to dream: ay, there's the rub;
- For in that sleep of death what dreams may come
- When we have shuffled off this mortal coil,
- Must give us pause: there's the respect
- That makes calamity of so long life;
--<<<<<<<
--=======
--* END *
-->>>>>>>
-EOF
-git rerere diff > out
-
-test_expect_success 'rerere diff' 'test_cmp expect out'
-
-cat > expect << EOF
-a1
-EOF
-
-git rerere status > out
-
-test_expect_success 'rerere status' 'test_cmp expect out'
-
-test_expect_success 'commit succeeds' \
- "git commit -q -a -m 'prefer first over second'"
-
-test_expect_success 'recorded postimage' "test -f $rr/postimage"
-
-test_expect_success 'another conflicting merge' '
- git checkout -b third master &&
- git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
- git commit -q -a -m third &&
- test_must_fail git pull . first
+test_expect_success 'rerere diff' '
+ git show first:a1 >a1 &&
+ cat >expect <<-\EOF &&
+ --- a/a1
+ +++ b/a1
+ @@ -1,4 +1,4 @@
+ -Some Title
+ +Some title
+ ==========
+ Whether '\''tis nobler in the mind to suffer
+ The slings and arrows of outrageous fortune,
+ @@ -8,21 +8,11 @@
+ The heart-ache and the thousand natural shocks
+ That flesh is heir to, '\''tis a consummation
+ Devoutly to be wish'\''d.
+ -<<<<<<<
+ -Some Title
+ -==========
+ -To die! To sleep;
+ -=======
+ Some title
+ ==========
+ To die, to sleep;
+ ->>>>>>>
+ To sleep: perchance to dream: ay, there'\''s the rub;
+ For in that sleep of death what dreams may come
+ When we have shuffled off this mortal coil,
+ Must give us pause: there'\''s the respect
+ That makes calamity of so long life;
+ -<<<<<<<
+ -=======
+ -* END *
+ ->>>>>>>
+ EOF
+ git rerere diff >out &&
+ test_cmp expect out
'
-git show first:a1 | sed 's/To die: t/To die! T/' > expect
-test_expect_success 'rerere kicked in' "! grep ^=======$ a1"
+test_expect_success 'rerere status' '
+ echo a1 >expect &&
+ git rerere status >out &&
+ test_cmp expect out
+'
-test_expect_success 'rerere prefers first change' 'test_cmp a1 expect'
+test_expect_success 'first postimage wins' '
+ git show first:a1 | sed "s/To die: t/To die! T/" >expect &&
-rm $rr/postimage
-echo "$sha1 a1" | perl -pe 'y/\012/\000/' > .git/MERGE_RR
+ git commit -q -a -m "prefer first over second" &&
+ test -f $rr/postimage &&
-test_expect_success 'rerere clear' 'git rerere clear'
+ oldmtimepost=$(test-chmtime -v -60 $rr/postimage | cut -f 1) &&
-test_expect_success 'clear removed the directory' "test ! -d $rr"
+ git checkout -b third master &&
+ git show second^:a1 | sed "s/To die: t/To die! T/" >a1 &&
+ git commit -q -a -m third &&
-mkdir $rr
-echo Hello > $rr/preimage
-echo World > $rr/postimage
+ test_must_fail git pull . first &&
+ # rerere kicked in
+ ! grep "^=======\$" a1 &&
+ test_cmp expect a1
+'
-sha2=4000000000000000000000000000000000000000
-rr2=.git/rr-cache/$sha2
-mkdir $rr2
-echo Hello > $rr2/preimage
+test_expect_success 'rerere updates postimage timestamp' '
+ newmtimepost=$(test-chmtime -v +0 $rr/postimage | cut -f 1) &&
+ test $oldmtimepost -lt $newmtimepost
+'
-almost_15_days_ago=$((60-15*86400))
-just_over_15_days_ago=$((-1-15*86400))
-almost_60_days_ago=$((60-60*86400))
-just_over_60_days_ago=$((-1-60*86400))
+test_expect_success 'rerere clear' '
+ rm $rr/postimage &&
+ echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR &&
+ git rerere clear &&
+ ! test -d $rr
+'
-test-chmtime =$almost_60_days_ago $rr/preimage
-test-chmtime =$almost_15_days_ago $rr2/preimage
+test_expect_success 'set up for garbage collection tests' '
+ mkdir -p $rr &&
+ echo Hello >$rr/preimage &&
+ echo World >$rr/postimage &&
-test_expect_success 'garbage collection (part1)' 'git rerere gc'
+ sha2=4000000000000000000000000000000000000000 &&
+ rr2=.git/rr-cache/$sha2 &&
+ mkdir $rr2 &&
+ echo Hello >$rr2/preimage &&
-test_expect_success 'young records still live' \
- "test -f $rr/preimage && test -f $rr2/preimage"
+ almost_15_days_ago=$((60-15*86400)) &&
+ just_over_15_days_ago=$((-1-15*86400)) &&
+ almost_60_days_ago=$((60-60*86400)) &&
+ just_over_60_days_ago=$((-1-60*86400)) &&
-test-chmtime =$just_over_60_days_ago $rr/preimage
-test-chmtime =$just_over_15_days_ago $rr2/preimage
+ test-chmtime =$just_over_60_days_ago $rr/preimage &&
+ test-chmtime =$almost_60_days_ago $rr/postimage &&
+ test-chmtime =$almost_15_days_ago $rr2/preimage
+'
-test_expect_success 'garbage collection (part2)' 'git rerere gc'
+test_expect_success 'gc preserves young or recently used records' '
+ git rerere gc &&
+ test -f $rr/preimage &&
+ test -f $rr2/preimage
+'
-test_expect_success 'old records rest in peace' \
- "test ! -f $rr/preimage && test ! -f $rr2/preimage"
+test_expect_success 'old records rest in peace' '
+ test-chmtime =$just_over_60_days_ago $rr/postimage &&
+ test-chmtime =$just_over_15_days_ago $rr2/preimage &&
+ git rerere gc &&
+ ! test -f $rr/preimage &&
+ ! test -f $rr2/preimage
+'
-test_expect_success 'file2 added differently in two branches' '
+test_expect_success 'setup: file2 added differently in two branches' '
git reset --hard &&
+
git checkout -b fourth &&
- echo Hallo > file2 &&
+ echo Hallo >file2 &&
git add file2 &&
+ test_tick &&
git commit -m version1 &&
+
git checkout third &&
- echo Bello > file2 &&
+ echo Bello >file2 &&
git add file2 &&
+ test_tick &&
git commit -m version2 &&
+
test_must_fail git merge fourth &&
- echo Cello > file2 &&
+ echo Cello >file2 &&
git add file2 &&
git commit -m resolution
'
test_expect_success 'resolution was recorded properly' '
+ echo Cello >expected &&
+
git reset --hard HEAD~2 &&
git checkout -b fifth &&
- echo Hallo > file3 &&
+
+ echo Hallo >file3 &&
git add file3 &&
+ test_tick &&
git commit -m version1 &&
+
git checkout third &&
- echo Bello > file3 &&
+ echo Bello >file3 &&
git add file3 &&
+ test_tick &&
git commit -m version2 &&
git tag version2 &&
+
test_must_fail git merge fifth &&
- test Cello = "$(cat file3)" &&
- test 0 != $(git ls-files -u | wc -l)
+ test_cmp expected file3 &&
+ test_must_fail git update-index --refresh
'
test_expect_success 'rerere.autoupdate' '
- git config rerere.autoupdate true
+ git config rerere.autoupdate true &&
git reset --hard &&
git checkout version2 &&
test_must_fail git merge fifth &&
- test 0 = $(git ls-files -u | wc -l)
+ git update-index --refresh
'
test_expect_success 'merge --rerere-autoupdate' '
- git config --unset rerere.autoupdate
+ test_might_fail git config --unset rerere.autoupdate &&
git reset --hard &&
git checkout version2 &&
test_must_fail git merge --rerere-autoupdate fifth &&
- test 0 = $(git ls-files -u | wc -l)
+ git update-index --refresh
'
test_expect_success 'merge --no-rerere-autoupdate' '
- git config rerere.autoupdate true
+ headblob=$(git rev-parse version2:file3) &&
+ mergeblob=$(git rev-parse fifth:file3) &&
+ cat >expected <<-EOF &&
+ 100644 $headblob 2 file3
+ 100644 $mergeblob 3 file3
+ EOF
+
+ git config rerere.autoupdate true &&
git reset --hard &&
git checkout version2 &&
test_must_fail git merge --no-rerere-autoupdate fifth &&
- test 2 = $(git ls-files -u | wc -l)
+ git ls-files -u >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'set up an unresolved merge' '
+ headblob=$(git rev-parse version2:file3) &&
+ mergeblob=$(git rev-parse fifth:file3) &&
+ cat >expected.unresolved <<-EOF &&
+ 100644 $headblob 2 file3
+ 100644 $mergeblob 3 file3
+ EOF
+
+ test_might_fail git config --unset rerere.autoupdate &&
+ git reset --hard &&
+ git checkout version2 &&
+ fifth=$(git rev-parse fifth) &&
+ echo "$fifth branch 'fifth' of ." |
+ git fmt-merge-msg >msg &&
+ ancestor=$(git merge-base version2 fifth) &&
+ test_must_fail git merge-recursive "$ancestor" -- HEAD fifth &&
+
+ git ls-files --stage >failedmerge &&
+ cp file3 file3.conflict &&
+
+ git ls-files -u >actual &&
+ test_cmp expected.unresolved actual
+'
+
+test_expect_success 'explicit rerere' '
+ test_might_fail git config --unset rerere.autoupdate &&
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ test_must_fail git update-index --refresh -q &&
+
+ git rerere &&
+ git ls-files -u >actual &&
+ test_cmp expected.unresolved actual
+'
+
+test_expect_success 'explicit rerere with autoupdate' '
+ git config rerere.autoupdate true &&
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ test_must_fail git update-index --refresh -q &&
+
+ git rerere &&
+ git update-index --refresh
+'
+
+test_expect_success 'explicit rerere --rerere-autoupdate overrides' '
+ git config rerere.autoupdate false &&
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ git rerere &&
+ git ls-files -u >actual1 &&
+
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ git rerere --rerere-autoupdate &&
+ git update-index --refresh &&
+
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ git rerere --rerere-autoupdate --no-rerere-autoupdate &&
+ git ls-files -u >actual2 &&
+
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ git rerere --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate &&
+ git update-index --refresh &&
+
+ test_cmp expected.unresolved actual1 &&
+ test_cmp expected.unresolved actual2
+'
+
+test_expect_success 'rerere --no-no-rerere-autoupdate' '
+ git rm -fr --cached . &&
+ git update-index --index-info <failedmerge &&
+ cp file3.conflict file3 &&
+ test_must_fail git rerere --no-no-rerere-autoupdate 2>err &&
+ grep [Uu]sage err &&
+ test_must_fail git update-index --refresh
+'
+
+test_expect_success 'rerere -h' '
+ test_must_fail git rerere -h >help &&
+ grep [Uu]sage help
'
test_done
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index cdb70b4..6872ba1 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -35,7 +35,7 @@ test_expect_success 'setup' '
tr 1234 "\370\235\204\236")" a1 &&
echo 5 >a1 &&
- git commit --quiet -m "a 12 34 56 78" a1
+ git commit --quiet -m "a 12 34 56 78" a1 &&
echo 6 >a1 &&
git commit --quiet -m "Commit by someone else" \
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 2230e60..983e34b 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -100,13 +100,11 @@ test_expect_success 'oneline' '
test_expect_success 'diff-filter=A' '
- actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
- expect=$(echo fifth ; echo fourth ; echo third ; echo initial) &&
- test "$actual" = "$expect" || {
- echo Oops
- echo "Actual: $actual"
- false
- }
+ git log --pretty="format:%s" --diff-filter=A HEAD > actual &&
+ git log --pretty="format:%s" --diff-filter A HEAD > actual-separate &&
+ printf "fifth\nfourth\nthird\ninitial" > expect &&
+ test_cmp expect actual &&
+ test_cmp expect actual-separate
'
@@ -193,7 +191,7 @@ test_expect_success 'git show <commits> leaves list of commits as given' '
test_expect_success 'setup case sensitivity tests' '
echo case >one &&
test_tick &&
- git add one
+ git add one &&
git commit -a -m Second
'
@@ -203,6 +201,13 @@ test_expect_success 'log --grep' '
test_cmp expect actual
'
+test_expect_success 'log --grep option parsing' '
+ echo second >expect &&
+ git log -1 --pretty="tformat:%s" --grep sec >actual &&
+ test_cmp expect actual &&
+ test_must_fail git log -1 --pretty="tformat:%s" --grep
+'
+
test_expect_success 'log -i --grep' '
echo Second >expect &&
git log -1 --pretty="tformat:%s" -i --grep=sec >actual &&
@@ -336,7 +341,7 @@ test_expect_success 'set up more tangled history' '
test_commit octopus-b &&
git checkout master &&
test_commit seventh &&
- git merge octopus-a octopus-b
+ git merge octopus-a octopus-b &&
git merge reach
'
@@ -388,7 +393,7 @@ test_expect_success 'log --graph with merge' '
'
test_expect_success 'log.decorate configuration' '
- git config --unset-all log.decorate || :
+ test_might_fail git config --unset-all log.decorate &&
git log --oneline >expect.none &&
git log --oneline --decorate >expect.short &&
@@ -417,6 +422,15 @@ test_expect_success 'log.decorate configuration' '
test_cmp expect.full actual &&
git config --unset-all log.decorate &&
+ git config log.decorate 1 &&
+ git log --oneline >actual &&
+ test_cmp expect.short actual &&
+ git log --oneline --decorate=full >actual &&
+ test_cmp expect.full actual &&
+ git log --oneline --decorate=no >actual &&
+ test_cmp expect.none actual &&
+
+ git config --unset-all log.decorate &&
git config log.decorate short &&
git log --oneline >actual &&
test_cmp expect.short actual &&
@@ -434,7 +448,72 @@ test_expect_success 'log.decorate configuration' '
git log --oneline --decorate >actual &&
test_cmp expect.short actual
+ git config --unset-all log.decorate &&
+ git log --pretty=raw >expect.raw &&
+ git config log.decorate full &&
+ git log --pretty=raw >actual &&
+ test_cmp expect.raw actual
+
'
-test_done
+test_expect_success 'reflog is expected format' '
+ test_might_fail git config --remove-section log &&
+ git log -g --abbrev-commit --pretty=oneline >expect &&
+ git reflog >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'whatchanged is expected format' '
+ git log --no-merges --raw >expect &&
+ git whatchanged >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log.abbrevCommit configuration' '
+ test_when_finished "git config --unset log.abbrevCommit" &&
+
+ test_might_fail git config --unset log.abbrevCommit &&
+
+ git log --abbrev-commit >expect.log.abbrev &&
+ git log --no-abbrev-commit >expect.log.full &&
+ git log --pretty=raw >expect.log.raw &&
+ git reflog --abbrev-commit >expect.reflog.abbrev &&
+ git reflog --no-abbrev-commit >expect.reflog.full &&
+ git whatchanged --abbrev-commit >expect.whatchanged.abbrev &&
+ git whatchanged --no-abbrev-commit >expect.whatchanged.full &&
+ git config log.abbrevCommit true &&
+
+ git log >actual &&
+ test_cmp expect.log.abbrev actual &&
+ git log --no-abbrev-commit >actual &&
+ test_cmp expect.log.full actual &&
+
+ git log --pretty=raw >actual &&
+ test_cmp expect.log.raw actual &&
+
+ git reflog >actual &&
+ test_cmp expect.reflog.abbrev actual &&
+ git reflog --no-abbrev-commit >actual &&
+ test_cmp expect.reflog.full actual &&
+
+ git whatchanged >actual &&
+ test_cmp expect.whatchanged.abbrev actual &&
+ git whatchanged --no-abbrev-commit >actual &&
+ test_cmp expect.whatchanged.full actual
+'
+
+test_expect_success 'show added path under "--follow -M"' '
+ # This tests for a regression introduced in v1.7.2-rc0~103^2~2
+ test_create_repo regression &&
+ (
+ cd regression &&
+ test_commit needs-another-commit &&
+ test_commit foo.bar &&
+ git log -M --follow -p foo.bar.t &&
+ git log -M --follow --stat foo.bar.t &&
+ git log -M --follow --name-only foo.bar.t
+ )
+'
+
+test_done
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 9a7d1b4..1f182f6 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -4,6 +4,14 @@ test_description='.mailmap configurations'
. ./test-lib.sh
+fuzz_blame () {
+ sed "
+ s/$_x05[0-9a-f][0-9a-f][0-9a-f]/OBJID/g
+ s/$_x05[0-9a-f][0-9a-f]/OBJI/g
+ s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g
+ " "$@"
+}
+
test_expect_success setup '
echo one >one &&
git add one &&
@@ -11,6 +19,7 @@ test_expect_success setup '
git commit -m initial &&
echo two >>one &&
git add one &&
+ test_tick &&
git commit --author "nick1 <bugs@company.xx>" -m second
'
@@ -54,7 +63,7 @@ Repo Guy (1):
EOF
test_expect_success 'mailmap.file set' '
- mkdir internal_mailmap &&
+ mkdir -p internal_mailmap &&
echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
git config mailmap.file internal_mailmap/.mailmap &&
git shortlog HEAD >actual &&
@@ -85,7 +94,7 @@ nick1 (1):
EOF
-test_expect_success 'mailmap.file non-existant' '
+test_expect_success 'mailmap.file non-existent' '
rm internal_mailmap/.mailmap &&
rmdir internal_mailmap &&
git shortlog HEAD >actual &&
@@ -93,6 +102,40 @@ test_expect_success 'mailmap.file non-existant' '
'
cat >expect <<\EOF
+Internal Guy (1):
+ second
+
+Repo Guy (1):
+ initial
+
+EOF
+
+test_expect_success 'name entry after email entry' '
+ mkdir -p internal_mailmap &&
+ echo "<bugs@company.xy> <bugs@company.xx>" >internal_mailmap/.mailmap &&
+ echo "Internal Guy <bugs@company.xx>" >>internal_mailmap/.mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Internal Guy (1):
+ second
+
+Repo Guy (1):
+ initial
+
+EOF
+
+test_expect_success 'name entry after email entry, case-insensitive' '
+ mkdir -p internal_mailmap &&
+ echo "<bugs@company.xy> <bugs@company.xx>" >internal_mailmap/.mailmap &&
+ echo "Internal Guy <BUGS@Company.xx>" >>internal_mailmap/.mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
A U Thor (1):
initial
@@ -101,7 +144,7 @@ nick1 (1):
EOF
test_expect_success 'No mailmap files, but configured' '
- rm .mailmap &&
+ rm -f .mailmap internal_mailmap/.mailmap &&
git shortlog HEAD >actual &&
test_cmp expect actual
'
@@ -153,7 +196,7 @@ test_expect_success 'Shortlog output (complex mapping)' '
test_tick &&
git commit --author "CTO <cto@coompany.xx>" -m seventh &&
- mkdir internal_mailmap &&
+ mkdir -p internal_mailmap &&
echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
echo "<cto@company.xx> <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
echo "Some Dude <some@dude.xx> nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
@@ -198,18 +241,18 @@ test_expect_success 'Log output (complex mapping)' '
# git blame
cat >expect <<\EOF
-^3a2fdcb (A U Thor 2005-04-07 15:13:13 -0700 1) one
-7de6f99b (Some Dude 2005-04-07 15:13:13 -0700 2) two
-5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three
-ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four
-5ab6d4fa (Santa Claus 2005-04-07 15:16:13 -0700 5) five
-38a42d8b (Santa Claus 2005-04-07 15:17:13 -0700 6) six
-8ddc0386 (CTO 2005-04-07 15:18:13 -0700 7) seven
+^OBJI (A U Thor DATE 1) one
+OBJID (Some Dude DATE 2) two
+OBJID (Other Author DATE 3) three
+OBJID (Other Author DATE 4) four
+OBJID (Santa Claus DATE 5) five
+OBJID (Santa Claus DATE 6) six
+OBJID (CTO DATE 7) seven
EOF
-
test_expect_success 'Blame output (complex mapping)' '
git blame one >actual &&
- test_cmp expect actual
+ fuzz_blame actual >actual.fuzz &&
+ test_cmp expect actual.fuzz
'
test_done
diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh
index 68e2652..d2c930d 100755
--- a/t/t4204-patch-id.sh
+++ b/t/t4204-patch-id.sh
@@ -63,4 +63,40 @@ test_expect_success 'patch-id supports git-format-patch MIME output' '
test_cmp patch-id_master patch-id_same
'
+cat >nonl <<\EOF
+diff --git i/a w/a
+index e69de29..2e65efe 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+\ No newline at end of file
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+cat >withnl <<\EOF
+diff --git i/a w/a
+index e69de29..7898192 100644
+--- i/a
++++ w/a
+@@ -0,0 +1 @@
++a
+diff --git i/b w/b
+index e69de29..6178079 100644
+--- i/b
++++ w/b
+@@ -0,0 +1 @@
++b
+EOF
+
+test_expect_success 'patch-id handles no-nl-at-eof markers' '
+ cat nonl | calc_patch_id nonl &&
+ cat withnl | calc_patch_id withnl &&
+ test_cmp patch-id_nonl patch-id_withnl
+'
test_done
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index cb9f2bd..2ae9faa 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -45,7 +45,7 @@ test_expect_success 'alias user-defined tformat' '
test_cmp expected actual
'
-test_expect_success 'alias non-existant format' '
+test_expect_success 'alias non-existent format' '
git config pretty.test-alias format-that-will-never-exist &&
test_must_fail git log --pretty=test-alias
'
diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh
new file mode 100755
index 0000000..2c482b6
--- /dev/null
+++ b/t/t4208-log-magic-pathspec.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='magic pathspec tests using git-log'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit initial &&
+ test_tick &&
+ git commit --allow-empty -m empty &&
+ mkdir sub
+'
+
+test_expect_success '"git log :/" should be ambiguous' '
+ test_must_fail git log :/ 2>error &&
+ grep ambiguous error
+'
+
+test_expect_success '"git log :" should be ambiguous' '
+ test_must_fail git log : 2>error &&
+ grep ambiguous error
+'
+
+test_expect_success 'git log -- :' '
+ git log -- :
+'
+
+test_expect_success 'git log HEAD -- :/' '
+ cat >expected <<-EOF &&
+ 24b24cf initial
+ EOF
+ (cd sub && git log --oneline HEAD -- :/ >../actual) &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t4252-am-options.sh b/t/t4252-am-options.sh
index f603c1b..e758e63 100755
--- a/t/t4252-am-options.sh
+++ b/t/t4252-am-options.sh
@@ -59,7 +59,7 @@ test_expect_success 'interrupted am --directory="frotz nitfol"' '
'
test_expect_success 'apply to a funny path' '
- with_sq="with'\''sq"
+ with_sq="with'\''sq" &&
rm -fr .git/rebase-apply &&
git reset --hard initial &&
git am --directory="$with_sq" "$tm"/am-test-5-2 &&
diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh
new file mode 100755
index 0000000..46c3fe7
--- /dev/null
+++ b/t/t4300-merge-tree.sh
@@ -0,0 +1,257 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Will Palmer
+#
+
+test_description='git merge-tree'
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit "initial" "initial-file" "initial"
+'
+
+test_expect_success 'file add A, !B' '
+ cat >expected <<\EXPECTED &&
+added in remote
+ their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+@@ -0,0 +1 @@
++AAA
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "add-a-not-b" "ONE" "AAA" &&
+ git merge-tree initial initial add-a-not-b >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file add !A, B' '
+ cat >expected <<\EXPECTED &&
+added in local
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "add-not-a-b" "ONE" "AAA" &&
+ git merge-tree initial add-not-a-b initial >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file add A, B (same)' '
+ cat >expected <<\EXPECTED &&
+added in both
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "add-a-b-same-A" "ONE" "AAA" &&
+ git reset --hard initial &&
+ test_commit "add-a-b-same-B" "ONE" "AAA" &&
+ git merge-tree initial add-a-b-same-A add-a-b-same-B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file add A, B (different)' '
+ cat >expected <<\EXPECTED &&
+added in both
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+@@ -1 +1,5 @@
++<<<<<<< .our
+ AAA
++=======
++BBB
++>>>>>>> .their
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "add-a-b-diff-A" "ONE" "AAA" &&
+ git reset --hard initial &&
+ test_commit "add-a-b-diff-B" "ONE" "BBB" &&
+ git merge-tree initial add-a-b-diff-A add-a-b-diff-B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change A, !B' '
+ cat >expected <<\EXPECTED &&
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-a-not-b" "initial-file" "BBB" &&
+ git merge-tree initial change-a-not-b initial >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change !A, B' '
+ cat >expected <<\EXPECTED &&
+merged
+ result 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+ our 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+@@ -1 +1 @@
+-initial
++BBB
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-not-a-b" "initial-file" "BBB" &&
+ git merge-tree initial initial change-not-a-b >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (same)' '
+ cat >expected <<\EXPECTED &&
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-a-b-same-A" "initial-file" "AAA" &&
+ git reset --hard initial &&
+ test_commit "change-a-b-same-B" "initial-file" "AAA" &&
+ git merge-tree initial change-a-b-same-A change-a-b-same-B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (different)' '
+ cat >expected <<\EXPECTED &&
+changed in both
+ base 100644 e79c5e8f964493290a409888d5413a737e8e5dd5 initial-file
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d initial-file
+ their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 initial-file
+@@ -1 +1,5 @@
++<<<<<<< .our
+ AAA
++=======
++BBB
++>>>>>>> .their
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-a-b-diff-A" "initial-file" "AAA" &&
+ git reset --hard initial &&
+ test_commit "change-a-b-diff-B" "initial-file" "BBB" &&
+ git merge-tree initial change-a-b-diff-A change-a-b-diff-B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change A, B (mixed)' '
+ cat >expected <<\EXPECTED &&
+changed in both
+ base 100644 f4f1f998c7776568c4ff38f516d77fef9399b5a7 ONE
+ our 100644 af14c2c3475337c73759d561ef70b59e5c731176 ONE
+ their 100644 372d761493f524d44d59bd24700c3bdf914c973c ONE
+@@ -7,7 +7,11 @@
+ AAA
+ AAA
+ AAA
++<<<<<<< .our
+ BBB
++=======
++CCC
++>>>>>>> .their
+ AAA
+ AAA
+ AAA
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-a-b-mix-base" "ONE" "
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA
+AAA" &&
+ test_commit "change-a-b-mix-A" "ONE" \
+ "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/BBB/;}" <ONE)" &&
+ git reset --hard change-a-b-mix-base &&
+ test_commit "change-a-b-mix-B" "ONE" \
+ "$(sed -e "1{s/AAA/BBB/;}" -e "10{s/AAA/CCC/;}" <ONE)" &&
+ git merge-tree change-a-b-mix-base change-a-b-mix-A change-a-b-mix-B \
+ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file remove A, !B' '
+ cat >expected <<\EXPECTED &&
+removed in local
+ base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "rm-a-not-b-base" "ONE" "AAA" &&
+ git rm ONE &&
+ git commit -m "rm-a-not-b" &&
+ git tag "rm-a-not-b" &&
+ git merge-tree rm-a-not-b-base rm-a-not-b rm-a-not-b-base >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file remove !A, B' '
+ cat >expected <<\EXPECTED &&
+removed in remote
+ base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+@@ -1 +0,0 @@
+-AAA
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "rm-not-a-b-base" "ONE" "AAA" &&
+ git rm ONE &&
+ git commit -m "rm-not-a-b" &&
+ git tag "rm-not-a-b" &&
+ git merge-tree rm-a-not-b-base rm-a-not-b-base rm-a-not-b >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file change A, remove B' '
+ cat >expected <<\EXPECTED &&
+removed in remote
+ base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ our 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+@@ -1 +0,0 @@
+-BBB
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "change-a-rm-b-base" "ONE" "AAA" &&
+ test_commit "change-a-rm-b-A" "ONE" "BBB" &&
+ git reset --hard change-a-rm-b-base &&
+ git rm ONE &&
+ git commit -m "change-a-rm-b-B" &&
+ git tag "change-a-rm-b-B" &&
+ git merge-tree change-a-rm-b-base change-a-rm-b-A change-a-rm-b-B \
+ >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'file remove A, change B' '
+ cat >expected <<\EXPECTED &&
+removed in local
+ base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE
+ their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 ONE
+EXPECTED
+
+ git reset --hard initial &&
+ test_commit "rm-a-change-b-base" "ONE" "AAA" &&
+
+ git rm ONE &&
+ git commit -m "rm-a-change-b-A" &&
+ git tag "rm-a-change-b-A" &&
+ git reset --hard rm-a-change-b-base &&
+ test_commit "rm-a-change-b-B" "ONE" "BBB" &&
+ git merge-tree rm-a-change-b-base rm-a-change-b-A rm-a-change-b-B \
+ >actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 27bfba5..9e3ba98 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -26,6 +26,8 @@ commit id embedding:
. ./test-lib.sh
UNZIP=${UNZIP:-unzip}
+GZIP=${GZIP:-gzip}
+GUNZIP=${GUNZIP:-gzip -d}
SUBSTFORMAT=%H%n
@@ -94,7 +96,7 @@ test_expect_success 'git archive with --output' \
'git archive --output=b4.tar HEAD &&
test_cmp b.tar b4.tar'
-test_expect_success 'git archive --remote' \
+test_expect_success NOT_MINGW 'git archive --remote' \
'git archive --remote=. HEAD >b5.tar &&
test_cmp b.tar b5.tar'
@@ -252,4 +254,102 @@ test_expect_success 'git-archive --prefix=olde-' '
test -f h/olde-a/bin/sh
'
+test_expect_success 'setup tar filters' '
+ git config tar.tar.foo.command "tr ab ba" &&
+ git config tar.bar.command "tr ab ba" &&
+ git config tar.bar.remote true
+'
+
+test_expect_success 'archive --list mentions user filter' '
+ git archive --list >output &&
+ grep "^tar\.foo\$" output &&
+ grep "^bar\$" output
+'
+
+test_expect_success 'archive --list shows only enabled remote filters' '
+ git archive --list --remote=. >output &&
+ ! grep "^tar\.foo\$" output &&
+ grep "^bar\$" output
+'
+
+test_expect_success 'invoke tar filter by format' '
+ git archive --format=tar.foo HEAD >config.tar.foo &&
+ tr ab ba <config.tar.foo >config.tar &&
+ test_cmp b.tar config.tar &&
+ git archive --format=bar HEAD >config.bar &&
+ tr ab ba <config.bar >config.tar &&
+ test_cmp b.tar config.tar
+'
+
+test_expect_success 'invoke tar filter by extension' '
+ git archive -o config-implicit.tar.foo HEAD &&
+ test_cmp config.tar.foo config-implicit.tar.foo &&
+ git archive -o config-implicit.bar HEAD &&
+ test_cmp config.tar.foo config-implicit.bar
+'
+
+test_expect_success 'default output format remains tar' '
+ git archive -o config-implicit.baz HEAD &&
+ test_cmp b.tar config-implicit.baz
+'
+
+test_expect_success 'extension matching requires dot' '
+ git archive -o config-implicittar.foo HEAD &&
+ test_cmp b.tar config-implicittar.foo
+'
+
+test_expect_success 'only enabled filters are available remotely' '
+ test_must_fail git archive --remote=. --format=tar.foo HEAD \
+ >remote.tar.foo &&
+ git archive --remote=. --format=bar >remote.bar HEAD &&
+ test_cmp remote.bar config.bar
+'
+
+if $GZIP --version >/dev/null 2>&1; then
+ test_set_prereq GZIP
+else
+ say "Skipping some tar.gz tests because gzip not found"
+fi
+
+test_expect_success GZIP 'git archive --format=tgz' '
+ git archive --format=tgz HEAD >j.tgz
+'
+
+test_expect_success GZIP 'git archive --format=tar.gz' '
+ git archive --format=tar.gz HEAD >j1.tar.gz &&
+ test_cmp j.tgz j1.tar.gz
+'
+
+test_expect_success GZIP 'infer tgz from .tgz filename' '
+ git archive --output=j2.tgz HEAD &&
+ test_cmp j.tgz j2.tgz
+'
+
+test_expect_success GZIP 'infer tgz from .tar.gz filename' '
+ git archive --output=j3.tar.gz HEAD &&
+ test_cmp j.tgz j3.tar.gz
+'
+
+if $GUNZIP --version >/dev/null 2>&1; then
+ test_set_prereq GUNZIP
+else
+ say "Skipping some tar.gz tests because gunzip was not found"
+fi
+
+test_expect_success GZIP,GUNZIP 'extract tgz file' '
+ $GUNZIP -c <j.tgz >j.tar &&
+ test_cmp b.tar j.tar
+'
+
+test_expect_success GZIP 'remote tar.gz is allowed by default' '
+ git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
+ test_cmp j.tgz remote.tar.gz
+'
+
+test_expect_success GZIP 'remote tar.gz can be disabled' '
+ git config tar.tar.gz.remote false &&
+ test_must_fail git archive --remote=. --format=tar.gz HEAD \
+ >remote.tar.gz
+'
+
test_done
diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh
index 426b319..02d4d22 100755
--- a/t/t5001-archive-attr.sh
+++ b/t/t5001-archive-attr.sh
@@ -4,7 +4,7 @@ test_description='git archive attribute tests'
. ./test-lib.sh
-SUBSTFORMAT=%H%n
+SUBSTFORMAT='%H (%h)%n'
test_expect_exists() {
test_expect_success " $1 exists" "test -e $1"
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index bbb9c12..602806d 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -12,7 +12,7 @@ TRASH=`pwd`
test_expect_success \
'setup' \
- 'rm -f .git/index*
+ 'rm -f .git/index* &&
perl -e "print \"a\" x 4096;" > a &&
perl -e "print \"b\" x 4096;" > b &&
perl -e "print \"c\" x 4096;" > c &&
diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh
index 0a24e61..2fc5af6 100755
--- a/t/t5301-sliding-window.sh
+++ b/t/t5301-sliding-window.sh
@@ -8,7 +8,7 @@ test_description='mmap sliding window tests'
test_expect_success \
'setup' \
- 'rm -f .git/index*
+ 'rm -f .git/index* &&
for i in a b c
do
echo $i >$i &&
@@ -48,7 +48,7 @@ test_expect_success \
git repack -a -d &&
test "`git count-objects`" = "0 objects, 0 kilobytes" &&
pack2=`ls .git/objects/pack/*.pack` &&
- test -f "$pack2"
+ test -f "$pack2" &&
test "$pack1" \!= "$pack2"'
test_expect_success \
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index 4360e77..f8fa924 100755
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
@@ -8,7 +8,7 @@ test_description='pack index with 64-bit offsets and object CRC'
test_expect_success \
'setup' \
- 'rm -rf .git
+ 'rm -rf .git &&
git init &&
git config pack.threads 1 &&
i=1 &&
@@ -65,6 +65,14 @@ test_expect_success \
'cmp "test-1-${pack1}.idx" "1.idx" &&
cmp "test-2-${pack2}.idx" "2.idx"'
+test_expect_success 'index-pack --verify on index version 1' '
+ git index-pack --verify "test-1-${pack1}.pack"
+'
+
+test_expect_success 'index-pack --verify on index version 2' '
+ git index-pack --verify "test-2-${pack2}.pack"
+'
+
test_expect_success \
'index v2: force some 64-bit offsets with pack-objects' \
'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
@@ -74,7 +82,7 @@ if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
then
test_set_prereq OFF64_T
else
- say "skipping tests concerning 64-bit offsets"
+ say "# skipping tests concerning 64-bit offsets"
fi
test_expect_success OFF64_T \
@@ -93,6 +101,16 @@ test_expect_success OFF64_T \
'64-bit offsets: index-pack result should match pack-objects one' \
'cmp "test-3-${pack3}.idx" "3.idx"'
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' '
+ # This cheats by knowing which lower offset should still be encoded
+ # in 64-bit representation.
+ git index-pack --verify --index-version=2,0x40000 "test-3-${pack3}.pack"
+'
+
+test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' '
+ git index-pack --verify "test-3-${pack3}.pack"
+'
+
# returns the object number for given object in given pack index
index_obj_nr()
{
@@ -208,9 +226,8 @@ test_expect_success \
( while read obj
do git cat-file -p $obj >/dev/null || exit 1
done <obj-list ) &&
- err=$(test_must_fail git verify-pack \
- ".git/objects/pack/pack-${pack1}.pack" 2>&1) &&
- echo "$err" | grep "CRC mismatch"'
+ test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack"
+'
test_expect_success 'running index-pack in the object store' '
rm -f .git/objects/pack/* &&
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index e2ed13d..d645328 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -14,7 +14,8 @@ add_blob() {
BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
- test -f $BLOB_FILE
+ test -f $BLOB_FILE &&
+ test-chmtime =+0 $BLOB_FILE
}
test_expect_success setup '
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index c718253..0eace37 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -94,6 +94,29 @@ test_expect_success 'refuse deleting push with denyDeletes' '
test_must_fail git send-pack ./victim :extra master
'
+test_expect_success 'cannot override denyDeletes with git -c send-pack' '
+ (
+ cd victim &&
+ test_might_fail git branch -D extra &&
+ git config receive.denyDeletes true &&
+ git branch extra master
+ ) &&
+ test_must_fail git -c receive.denyDeletes=false \
+ send-pack ./victim :extra master
+'
+
+test_expect_success 'override denyDeletes with git -c receive-pack' '
+ (
+ cd victim &&
+ test_might_fail git branch -D extra &&
+ git config receive.denyDeletes true &&
+ git branch extra master
+ ) &&
+ git send-pack \
+ --receive-pack="git -c receive.denyDeletes=false receive-pack" \
+ ./victim :extra master
+'
+
test_expect_success 'denyNonFastforwards trumps --force' '
(
cd victim &&
@@ -106,7 +129,7 @@ test_expect_success 'denyNonFastforwards trumps --force' '
test "$victim_orig" = "$victim_head"
'
-test_expect_success 'push --all excludes remote tracking hierarchy' '
+test_expect_success 'push --all excludes remote-tracking hierarchy' '
mkdir parent &&
(
cd parent &&
@@ -167,7 +190,7 @@ test_expect_success 'pushing explicit refspecs respects forcing' '
+refs/heads/master:refs/heads/master
) &&
parent_head=$(cd parent && git rev-parse --verify master) &&
- child_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd child && git rev-parse --verify master) &&
test "$parent_head" = "$child_head"
'
@@ -187,7 +210,7 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
"+refs/heads/*:refs/heads/*"
) &&
parent_head=$(cd parent && git rev-parse --verify master) &&
- child_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd child && git rev-parse --verify master) &&
test "$parent_head" = "$child_head"
'
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 552da65..baa670c 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -10,7 +10,11 @@ test_expect_success 'setup' '
test_commit A foo A &&
test_commit B foo B &&
test_commit C foo C &&
- test_commit D foo D
+ test_commit D foo D &&
+ git checkout A^0 &&
+ test_commit E bar E &&
+ test_commit F foo F &&
+ git checkout master
'
mkdir .git/hooks
@@ -79,6 +83,18 @@ EOF
verify_hook_input
'
+test_expect_success 'git rebase --skip the last one' '
+ git reset --hard F &&
+ clear_hook_input &&
+ test_must_fail git rebase --onto D A &&
+ git rebase --skip &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse E) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
test_expect_success 'git rebase -m' '
git reset --hard D &&
clear_hook_input &&
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 18376d6..bafcca7 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -91,7 +91,7 @@ test_expect_success 'setup' '
prev=$cur &&
cur=$(($cur+1))
done &&
- add B1 $A1
+ add B1 $A1 &&
echo $ATIP > .git/refs/heads/A &&
echo $BTIP > .git/refs/heads/B &&
git symbolic-ref HEAD refs/heads/B
diff --git a/t/t5501-fetch-push-alternates.sh b/t/t5501-fetch-push-alternates.sh
new file mode 100755
index 0000000..b5ced84
--- /dev/null
+++ b/t/t5501-fetch-push-alternates.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test_description='fetch/push involving alternates'
+. ./test-lib.sh
+
+count_objects () {
+ loose=0 inpack=0
+ eval "$(
+ git count-objects -v |
+ sed -n -e 's/^count: \(.*\)/loose=\1/p' \
+ -e 's/^in-pack: \(.*\)/inpack=\1/p'
+ )" &&
+ echo $(( $loose + $inpack ))
+}
+
+
+test_expect_success setup '
+ (
+ git init original &&
+ cd original &&
+ i=0 &&
+ while test $i -le 100
+ do
+ echo "$i" >count &&
+ git add count &&
+ git commit -m "$i" || exit
+ i=$(($i + 1))
+ done
+ ) &&
+ (
+ git clone --reference=original "file:///$(pwd)/original" one &&
+ cd one &&
+ echo Z >count &&
+ git add count &&
+ git commit -m Z &&
+ count_objects >../one.count
+ ) &&
+ A=$(pwd)/original/.git/objects &&
+ git init receiver &&
+ echo "$A" >receiver/.git/objects/info/alternates &&
+ git init fetcher &&
+ echo "$A" >fetcher/.git/objects/info/alternates
+'
+
+test_expect_success 'pushing into a repository with the same alternate' '
+ (
+ cd one &&
+ git push ../receiver master:refs/heads/it
+ ) &&
+ (
+ cd receiver &&
+ count_objects >../receiver.count
+ ) &&
+ test_cmp one.count receiver.count
+'
+
+test_expect_success 'fetching from a repository with the same alternate' '
+ (
+ cd fetcher &&
+ git fetch ../one master:refs/heads/it &&
+ count_objects >../fetcher.count
+ ) &&
+ test_cmp one.count fetcher.count
+'
+
+test_done
diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh
index 1037a72..7a46cbd 100755
--- a/t/t5502-quickfetch.sh
+++ b/t/t5502-quickfetch.sh
@@ -57,7 +57,7 @@ test_expect_success 'copy commit and tree but not blob by hand' '
cd cloned &&
git count-objects | sed -e "s/ *objects,.*//"
) ) &&
- test $cnt -eq 6
+ test $cnt -eq 6 &&
blob=$(git rev-parse HEAD:file | sed -e "s|..|&/|") &&
test -f "cloned/.git/objects/$blob" &&
diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh
index d5db75d..60de2d6 100755
--- a/t/t5503-tagfollow.sh
+++ b/t/t5503-tagfollow.sh
@@ -4,11 +4,9 @@ test_description='test automatic tag following'
. ./test-lib.sh
-case $(uname -s) in
-*MINGW*)
+if ! test_have_prereq NOT_MINGW; then
say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
- test_done
-esac
+fi
# End state of the repository:
#
@@ -19,7 +17,7 @@ esac
# \ C - origin/cat \
# origin/master master
-test_expect_success setup '
+test_expect_success NOT_MINGW setup '
test_tick &&
echo ichi >file &&
git add file &&
@@ -42,13 +40,16 @@ test_expect_success setup '
U=UPLOAD_LOG
+test_expect_success NOT_MINGW 'setup expect' '
cat - <<EOF >expect
#S
want $A
#E
EOF
-test_expect_success 'fetch A (new commit : 1 connection)' '
- rm -f $U
+'
+
+test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' '
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
@@ -59,7 +60,7 @@ test_expect_success 'fetch A (new commit : 1 connection)' '
test_cmp expect actual
'
-test_expect_success "create tag T on A, create C on branch cat" '
+test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" '
git tag -a -m tag1 tag1 $A &&
T=$(git rev-parse --verify tag1) &&
@@ -71,14 +72,17 @@ test_expect_success "create tag T on A, create C on branch cat" '
git checkout master
'
+test_expect_success NOT_MINGW 'setup expect' '
cat - <<EOF >expect
#S
want $C
want $T
#E
EOF
-test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
- rm -f $U
+'
+
+test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' '
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
@@ -91,7 +95,7 @@ test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
test_cmp expect actual
'
-test_expect_success "create commits O, B, tag S on B" '
+test_expect_success NOT_MINGW "create commits O, B, tag S on B" '
test_tick &&
echo O >file &&
git add file &&
@@ -107,14 +111,17 @@ test_expect_success "create commits O, B, tag S on B" '
S=$(git rev-parse --verify tag2)
'
+test_expect_success NOT_MINGW 'setup expect' '
cat - <<EOF >expect
#S
want $B
want $S
#E
EOF
-test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
- rm -f $U
+'
+
+test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' '
+ rm -f $U &&
(
cd cloned &&
GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
@@ -127,13 +134,16 @@ test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
test_cmp expect actual
'
+test_expect_success NOT_MINGW 'setup expect' '
cat - <<EOF >expect
#S
want $B
want $S
#E
EOF
-test_expect_success 'new clone fetch master and tags' '
+'
+
+test_expect_success NOT_MINGW 'new clone fetch master and tags' '
git branch -D cat
rm -f $U
(
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 4c498b1..0d0222e 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -107,16 +107,18 @@ test_expect_success 'remove remote' '
)
'
-test_expect_success 'remove remote protects non-remote branches' '
+test_expect_success 'remove remote protects local branches' '
(
cd test &&
{ cat >expect1 <<EOF
-Note: A non-remote branch was not removed; to delete it, use:
+Note: A branch outside the refs/remotes/ hierarchy was not removed;
+to delete it, use:
git branch -d master
EOF
} &&
{ cat >expect2 <<EOF
-Note: Non-remote branches were not removed; to delete them, use:
+Note: Some branches outside the refs/remotes/ hierarchy were not removed;
+to delete them, use:
git branch -d foobranch
git branch -d master
EOF
@@ -302,6 +304,106 @@ test_expect_success 'add --mirror && prune' '
git rev-parse --verify refs/heads/side)
'
+test_expect_success 'add --mirror=fetch' '
+ mkdir mirror-fetch &&
+ git init mirror-fetch/parent &&
+ (cd mirror-fetch/parent &&
+ test_commit one) &&
+ git init --bare mirror-fetch/child &&
+ (cd mirror-fetch/child &&
+ git remote add --mirror=fetch -f parent ../parent)
+'
+
+test_expect_success 'fetch mirrors act as mirrors during fetch' '
+ (cd mirror-fetch/parent &&
+ git branch new &&
+ git branch -m master renamed
+ ) &&
+ (cd mirror-fetch/child &&
+ git fetch parent &&
+ git rev-parse --verify refs/heads/new &&
+ git rev-parse --verify refs/heads/renamed
+ )
+'
+
+test_expect_success 'fetch mirrors can prune' '
+ (cd mirror-fetch/child &&
+ git remote prune parent &&
+ test_must_fail git rev-parse --verify refs/heads/master
+ )
+'
+
+test_expect_success 'fetch mirrors do not act as mirrors during push' '
+ (cd mirror-fetch/parent &&
+ git checkout HEAD^0
+ ) &&
+ (cd mirror-fetch/child &&
+ git branch -m renamed renamed2 &&
+ git push parent
+ ) &&
+ (cd mirror-fetch/parent &&
+ git rev-parse --verify renamed &&
+ test_must_fail git rev-parse --verify refs/heads/renamed2
+ )
+'
+
+test_expect_success 'add fetch mirror with specific branches' '
+ git init --bare mirror-fetch/track &&
+ (cd mirror-fetch/track &&
+ git remote add --mirror=fetch -t heads/new parent ../parent
+ )
+'
+
+test_expect_success 'fetch mirror respects specific branches' '
+ (cd mirror-fetch/track &&
+ git fetch parent &&
+ git rev-parse --verify refs/heads/new &&
+ test_must_fail git rev-parse --verify refs/heads/renamed
+ )
+'
+
+test_expect_success 'add --mirror=push' '
+ mkdir mirror-push &&
+ git init --bare mirror-push/public &&
+ git init mirror-push/private &&
+ (cd mirror-push/private &&
+ test_commit one &&
+ git remote add --mirror=push public ../public
+ )
+'
+
+test_expect_success 'push mirrors act as mirrors during push' '
+ (cd mirror-push/private &&
+ git branch new &&
+ git branch -m master renamed &&
+ git push public
+ ) &&
+ (cd mirror-push/private &&
+ git rev-parse --verify refs/heads/new &&
+ git rev-parse --verify refs/heads/renamed &&
+ test_must_fail git rev-parse --verify refs/heads/master
+ )
+'
+
+test_expect_success 'push mirrors do not act as mirrors during fetch' '
+ (cd mirror-push/public &&
+ git branch -m renamed renamed2 &&
+ git symbolic-ref HEAD refs/heads/renamed2
+ ) &&
+ (cd mirror-push/private &&
+ git fetch public &&
+ git rev-parse --verify refs/heads/renamed &&
+ test_must_fail git rev-parse --verify refs/heads/renamed2
+ )
+'
+
+test_expect_success 'push mirrors do not allow you to specify refs' '
+ git init mirror-push/track &&
+ (cd mirror-push/track &&
+ test_must_fail git remote add --mirror=push -t new public ../public
+ )
+'
+
test_expect_success 'add alt && prune' '
(mkdir alttst &&
cd alttst &&
@@ -435,7 +537,7 @@ test_expect_success 'update --prune' '
git branch -m side2 side3) &&
(cd test &&
git remote update --prune &&
- (cd ../one && git branch -m side3 side2)
+ (cd ../one && git branch -m side3 side2) &&
git rev-parse refs/remotes/origin/side3 &&
test_must_fail git rev-parse refs/remotes/origin/side2)
'
diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh
index b7b7dda..530b016 100755
--- a/t/t5506-remote-groups.sh
+++ b/t/t5506-remote-groups.sh
@@ -43,10 +43,10 @@ test_expect_success 'no group updates all' '
repo_fetched two
'
-test_expect_success 'nonexistant group produces error' '
- mark nonexistant &&
+test_expect_success 'nonexistent group produces error' '
+ mark nonexistent &&
update_repos &&
- test_must_fail git remote update nonexistant &&
+ test_must_fail git remote update nonexistent &&
! repo_fetched one &&
! repo_fetched two
'
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 4eb10f6..7e433b1 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -21,27 +21,30 @@ test_expect_success setup '
test_expect_success "clone and setup child repos" '
git clone . one &&
- cd one &&
- echo >file updated by one &&
- git commit -a -m "updated by one" &&
- cd .. &&
+ (
+ cd one &&
+ echo >file updated by one &&
+ git commit -a -m "updated by one"
+ ) &&
git clone . two &&
- cd two &&
- git config branch.master.remote one &&
- git config remote.one.url ../one/.git/ &&
- git config remote.one.fetch refs/heads/master:refs/heads/one &&
- cd .. &&
+ (
+ cd two &&
+ git config branch.master.remote one &&
+ git config remote.one.url ../one/.git/ &&
+ git config remote.one.fetch refs/heads/master:refs/heads/one
+ ) &&
git clone . three &&
- cd three &&
- git config branch.master.remote two &&
- git config branch.master.merge refs/heads/one &&
- mkdir -p .git/remotes &&
- {
- echo "URL: ../two/.git/"
- echo "Pull: refs/heads/master:refs/heads/two"
- echo "Pull: refs/heads/one:refs/heads/one"
- } >.git/remotes/two &&
- cd .. &&
+ (
+ cd three &&
+ git config branch.master.remote two &&
+ git config branch.master.merge refs/heads/one &&
+ mkdir -p .git/remotes &&
+ {
+ echo "URL: ../two/.git/"
+ echo "Pull: refs/heads/master:refs/heads/two"
+ echo "Pull: refs/heads/one:refs/heads/one"
+ } >.git/remotes/two
+ ) &&
git clone . bundle &&
git clone . seven
'
@@ -116,7 +119,7 @@ test_expect_success 'fetch must not resolve short tag name' '
test_expect_success 'fetch must not resolve short remote name' '
cd "$D" &&
- git update-ref refs/remotes/six/HEAD HEAD
+ git update-ref refs/remotes/six/HEAD HEAD &&
mkdir six &&
cd six &&
@@ -240,6 +243,38 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '
git fetch blub
'
+# URL supplied to fetch does not match the url of the configured branch's remote
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '
+ one_head=$(cd one && git rev-parse HEAD) &&
+ this_head=$(git rev-parse HEAD) &&
+ git update-ref -d FETCH_HEAD &&
+ git fetch one &&
+ test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+ test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
+# URL supplied to fetch matches the url of the configured branch's remote and
+# the merge spec matches the branch the remote HEAD points to
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [2]' '
+ one_ref=$(cd one && git symbolic-ref HEAD) &&
+ git config branch.master.remote blub &&
+ git config branch.master.merge "$one_ref" &&
+ git update-ref -d FETCH_HEAD &&
+ git fetch one &&
+ test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+ test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
+# URL supplied to fetch matches the url of the configured branch's remote, but
+# the merge spec does not match the branch the remote HEAD points to
+test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '
+ git config branch.master.merge "${one_ref}_not" &&
+ git update-ref -d FETCH_HEAD &&
+ git fetch one &&
+ test $one_head = "$(git rev-parse --verify FETCH_HEAD)" &&
+ test $this_head = "$(git rev-parse --verify HEAD)"
+'
+
# the strange name is: a\!'b
test_expect_success 'quoting of a strangely named repo' '
test_must_fail git fetch "a\\!'\''b" > result 2>&1 &&
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index d191235..5c546c9 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -123,4 +123,28 @@ test_expect_success 'confuses pattern as remote when no remote specified' '
'
+test_expect_success 'die with non-2 for wrong repository even with --exit-code' '
+ git ls-remote --exit-code ./no-such-repository ;# not &&
+ status=$? &&
+ test $status != 2 && test $status != 0
+'
+
+test_expect_success 'Report success even when nothing matches' '
+ git ls-remote other.git "refs/nsn/*" >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Report no-match with --exit-code' '
+ test_expect_code 2 git ls-remote --exit-code other.git "refs/nsn/*" >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Report match with --exit-code' '
+ git ls-remote --exit-code other.git "refs/tags/*" >actual &&
+ git ls-remote . tags/mark >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5513-fetch-track.sh b/t/t5513-fetch-track.sh
index 9e74862..65d1e05 100755
--- a/t/t5513-fetch-track.sh
+++ b/t/t5513-fetch-track.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='fetch follows remote tracking branches correctly'
+test_description='fetch follows remote-tracking branches correctly'
. ./test-lib.sh
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index b737332..227dd56 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -27,7 +27,7 @@ test_expect_success setup '
(
cd two && git branch another
) &&
- git clone --mirror two three
+ git clone --mirror two three &&
git clone one test
'
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index b11da79..3abb290 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -367,7 +367,7 @@ test_expect_success 'push with colon-less refspec (4)' '
'
-test_expect_success 'push head with non-existant, incomplete dest' '
+test_expect_success 'push head with non-existent, incomplete dest' '
mk_test &&
git push testrepo master:branch &&
@@ -375,7 +375,7 @@ test_expect_success 'push head with non-existant, incomplete dest' '
'
-test_expect_success 'push tag with non-existant, incomplete dest' '
+test_expect_success 'push tag with non-existent, incomplete dest' '
mk_test &&
git tag -f v1.0 &&
@@ -384,14 +384,14 @@ test_expect_success 'push tag with non-existant, incomplete dest' '
'
-test_expect_success 'push sha1 with non-existant, incomplete dest' '
+test_expect_success 'push sha1 with non-existent, incomplete dest' '
mk_test &&
test_must_fail git push testrepo `git rev-parse master`:foo
'
-test_expect_success 'push ref expression with non-existant, incomplete dest' '
+test_expect_success 'push ref expression with non-existent, incomplete dest' '
mk_test &&
test_must_fail git push testrepo master^:branch
@@ -436,7 +436,7 @@ test_expect_success 'push with +HEAD' '
'
-test_expect_success 'push HEAD with non-existant, incomplete dest' '
+test_expect_success 'push HEAD with non-existent, incomplete dest' '
mk_test &&
git checkout master &&
@@ -586,7 +586,7 @@ test_expect_success 'push --delete refuses src:dest refspecs' '
'
test_expect_success 'warn on push to HEAD of non-bare repository' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
@@ -597,7 +597,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' '
'
test_expect_success 'deny push to HEAD of non-bare repository' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
@@ -607,7 +607,7 @@ test_expect_success 'deny push to HEAD of non-bare repository' '
'
test_expect_success 'allow push to HEAD of bare repository (bare)' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
@@ -619,7 +619,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' '
'
test_expect_success 'allow push to HEAD of non-bare repository (config)' '
- mk_test heads/master
+ mk_test heads/master &&
(
cd testrepo &&
git checkout master &&
diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh
index 96be523..c00c9b0 100755
--- a/t/t5519-push-alternates.sh
+++ b/t/t5519-push-alternates.sh
@@ -123,7 +123,7 @@ test_expect_success 'bob works and pushes again' '
(
cd alice-pub &&
git cat-file commit master >../bob-work/commit
- )
+ ) &&
(
# This time Bob does not pull from Alice, and
# the master branch at her public repository points
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 319e389..0e5eb67 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -4,6 +4,11 @@ test_description='pulling into void'
. ./test-lib.sh
+modify () {
+ sed -e "$1" <"$2" >"$2.x" &&
+ mv "$2.x" "$2"
+}
+
D=`pwd`
test_expect_success setup '
@@ -41,6 +46,17 @@ test_expect_success 'pulling into void using master:master' '
test_cmp file cloned-uho/file
'
+test_expect_success 'pulling into void does not overwrite untracked files' '
+ git init cloned-untracked &&
+ (
+ cd cloned-untracked &&
+ echo untracked >file &&
+ test_must_fail git pull .. master &&
+ echo untracked >expect &&
+ test_cmp expect file
+ )
+'
+
test_expect_success 'test . as a remote' '
git branch copy master &&
@@ -160,4 +176,68 @@ test_expect_success 'pull --rebase works on branch yet to be born' '
test_cmp expect actual
'
+test_expect_success 'setup for detecting upstreamed changes' '
+ mkdir src &&
+ (cd src &&
+ git init &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff &&
+ git add stuff &&
+ git commit -m "Initial revision"
+ ) &&
+ git clone src dst &&
+ (cd src &&
+ modify s/5/43/ stuff &&
+ git commit -a -m "5->43" &&
+ modify s/6/42/ stuff &&
+ git commit -a -m "Make it bigger"
+ ) &&
+ (cd dst &&
+ modify s/5/43/ stuff &&
+ git commit -a -m "Independent discovery of 5->43"
+ )
+'
+
+test_expect_success 'git pull --rebase detects upstreamed changes' '
+ (cd dst &&
+ git pull --rebase &&
+ test -z "$(git ls-files -u)"
+ )
+'
+
+test_expect_success 'setup for avoiding reapplying old patches' '
+ (cd dst &&
+ test_might_fail git rebase --abort &&
+ git reset --hard origin/master
+ ) &&
+ git clone --bare src src-replace.git &&
+ rm -rf src &&
+ mv src-replace.git src &&
+ (cd dst &&
+ modify s/2/22/ stuff &&
+ git commit -a -m "Change 2" &&
+ modify s/3/33/ stuff &&
+ git commit -a -m "Change 3" &&
+ modify s/4/44/ stuff &&
+ git commit -a -m "Change 4" &&
+ git push &&
+
+ modify s/44/55/ stuff &&
+ git commit --amend -a -m "Modified Change 4"
+ )
+'
+
+test_expect_success 'git pull --rebase does not reapply old patches' '
+ (cd dst &&
+ test_must_fail git pull --rebase &&
+ test 1 = $(find .git/rebase-apply -name "000*" | wc -l)
+ )
+'
+
+test_expect_success 'git pull --rebase against local branch' '
+ git checkout -b copy2 to-rebase-orig &&
+ git pull --rebase . to-rebase &&
+ test "conflicting modification" = "$(cat file)" &&
+ test file = "$(cat file2)"
+'
+
test_done
diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh
index 7206817..8e9b204 100755
--- a/t/t5522-pull-symlink.sh
+++ b/t/t5522-pull-symlink.sh
@@ -4,12 +4,6 @@ test_description='pulling from symlinked subdir'
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
# The scenario we are building:
#
# trash\ directory/
@@ -20,7 +14,7 @@ fi
#
# The working directory is subdir-link.
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
mkdir subdir &&
echo file >subdir/file &&
git add subdir/file &&
@@ -36,7 +30,7 @@ test_expect_success setup '
# Demonstrate that things work if we just avoid the symlink
#
-test_expect_success 'pulling from real subdir' '
+test_expect_success SYMLINKS 'pulling from real subdir' '
(
echo real >subdir/file &&
git commit -m real subdir/file &&
@@ -64,7 +58,7 @@ test_expect_success 'pulling from real subdir' '
# directory. A POSIX shell's "cd" works a little differently
# than chdir() in C; "cd -P" is much closer to chdir().
#
-test_expect_success 'pulling from symlinked subdir' '
+test_expect_success SYMLINKS 'pulling from symlinked subdir' '
(
echo link >subdir/file &&
git commit -m link subdir/file &&
@@ -77,7 +71,7 @@ test_expect_success 'pulling from symlinked subdir' '
# Prove that the remote end really is a repo, and other commands
# work fine in this context. It's just that "git pull" breaks.
#
-test_expect_success 'pushing from symlinked subdir' '
+test_expect_success SYMLINKS 'pushing from symlinked subdir' '
(
cd subdir-link/ &&
echo push >file &&
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index 00da707..c229fe6 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -2,9 +2,14 @@
test_description='push with --set-upstream'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+ensure_fresh_upstream() {
+ rm -rf parent && git init --bare parent
+}
test_expect_success 'setup bare parent' '
- git init --bare parent &&
+ ensure_fresh_upstream &&
git remote add upstream parent
'
@@ -66,4 +71,41 @@ test_expect_success 'push -u HEAD' '
check_config headbranch upstream refs/heads/headbranch
'
+test_expect_success TTY 'progress messages go to tty' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u upstream master >out 2>err &&
+ grep "Writing objects" err
+'
+
+test_expect_success 'progress messages do not go to non-tty' '
+ ensure_fresh_upstream &&
+
+ # skip progress messages, since stderr is non-tty
+ git push -u upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
+test_expect_success 'progress messages go to non-tty (forced)' '
+ ensure_fresh_upstream &&
+
+ # force progress messages to stderr, even though it is non-tty
+ git push -u --progress upstream master >out 2>err &&
+ grep "Writing objects" err
+'
+
+test_expect_success TTY 'push -q suppresses progress' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u -q upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
+test_expect_failure TTY 'push --no-progress suppresses progress' '
+ ensure_fresh_upstream &&
+
+ test_terminal git push -u --no-progress upstream master >out 2>err &&
+ ! grep "Writing objects" err
+'
+
test_done
diff --git a/t/t5525-fetch-tagopt.sh b/t/t5525-fetch-tagopt.sh
new file mode 100755
index 0000000..4fbf7a1
--- /dev/null
+++ b/t/t5525-fetch-tagopt.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='tagopt variable affects "git fetch" and is overridden by commandline.'
+
+. ./test-lib.sh
+
+setup_clone () {
+ git clone --mirror . $1 &&
+ git remote add remote_$1 $1 &&
+ (cd $1 &&
+ git tag tag_$1)
+}
+
+test_expect_success setup '
+ test_commit test &&
+ setup_clone one &&
+ git config remote.remote_one.tagopt --no-tags &&
+ setup_clone two &&
+ git config remote.remote_two.tagopt --tags
+ '
+
+test_expect_success "fetch with tagopt=--no-tags does not get tag" '
+ git fetch remote_one &&
+ test_must_fail git show-ref tag_one
+ '
+
+test_expect_success "fetch --tags with tagopt=--no-tags gets tag" '
+ git fetch --tags remote_one &&
+ git show-ref tag_one
+ '
+
+test_expect_success "fetch --no-tags with tagopt=--tags does not get tag" '
+ git fetch --no-tags remote_two &&
+ test_must_fail git show-ref tag_two
+ '
+
+test_expect_success "fetch with tagopt=--tags gets tag" '
+ git fetch remote_two &&
+ git show-ref tag_two
+ '
+test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
new file mode 100755
index 0000000..ca5b027
--- /dev/null
+++ b/t/t5526-fetch-submodules.sh
@@ -0,0 +1,453 @@
+#!/bin/sh
+# Copyright (c) 2010, Jens Lehmann
+
+test_description='Recursive "git fetch" for submodules'
+
+. ./test-lib.sh
+
+pwd=$(pwd)
+
+add_upstream_commit() {
+ (
+ cd submodule &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo new >> subfile &&
+ test_tick &&
+ git add subfile &&
+ git commit -m new subfile &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/submodule" > ../expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err
+ ) &&
+ (
+ cd deepsubmodule &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo new >> deepsubfile &&
+ test_tick &&
+ git add deepsubfile &&
+ git commit -m new deepsubfile &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/deepsubmodule" >> ../expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err
+ )
+}
+
+test_expect_success setup '
+ mkdir deepsubmodule &&
+ (
+ cd deepsubmodule &&
+ git init &&
+ echo deepsubcontent > deepsubfile &&
+ git add deepsubfile &&
+ git commit -m new deepsubfile
+ ) &&
+ mkdir submodule &&
+ (
+ cd submodule &&
+ git init &&
+ echo subcontent > subfile &&
+ git add subfile &&
+ git submodule add "$pwd/deepsubmodule" subdir/deepsubmodule &&
+ git commit -a -m new
+ ) &&
+ git submodule add "$pwd/submodule" submodule &&
+ git commit -am initial &&
+ git clone . downstream &&
+ (
+ cd downstream &&
+ git submodule update --init --recursive
+ ) &&
+ echo "Fetching submodule submodule" > expect.out &&
+ echo "Fetching submodule submodule/subdir/deepsubmodule" >> expect.out
+'
+
+test_expect_success "fetch --recurse-submodules recurses into submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "fetch alone only fetches superproject" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
+ (
+ cd downstream &&
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
+ (
+ cd downstream &&
+ git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides .gitmodules config" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" '
+ (
+ cd downstream &&
+ git config submodule.submodule.fetchRecurseSubmodules false &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err &&
+ git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules &&
+ git config --unset submodule.submodule.fetchRecurseSubmodules
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "--quiet propagates to submodules" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "--dry-run propagates to submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "Without --dry-run propagates to submodules" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "recurseSubmodules=true propagates into submodules" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules true
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "--recurse-submodules overrides config in submodule" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ (
+ cd submodule &&
+ git config fetch.recurseSubmodules false
+ ) &&
+ git fetch --recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "--no-recurse-submodules overrides config setting" '
+ add_upstream_commit &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules true
+ git fetch --no-recurse-submodules >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "Recursion doesn't happen when no new commits are fetched in the superproject" '
+ (
+ cd downstream &&
+ (
+ cd submodule &&
+ git config --unset fetch.recurseSubmodules
+ ) &&
+ git config --unset fetch.recurseSubmodules
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "Recursion stops when no new submodule commits are fetched" '
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "Fetching submodule submodule" > expect.out.sub &&
+ echo "From $pwd/." > expect.err.sub &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.sub
+ head -2 expect.err >> expect.err.sub &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.err.sub actual.err &&
+ test_i18ncmp expect.out.sub actual.out
+'
+
+test_expect_success "Recursion doesn't happen when new superproject commits don't change any submodules" '
+ add_upstream_commit &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo a > file &&
+ git add file &&
+ git commit -m "new file" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.file &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.file &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ test_i18ncmp expect.err.file actual.err
+'
+
+test_expect_success "Recursion picks up config in submodule" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules &&
+ (
+ cd submodule &&
+ git config fetch.recurseSubmodules true
+ )
+ ) &&
+ add_upstream_commit &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.sub &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.sub &&
+ cat expect.err >> expect.err.sub &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err &&
+ (
+ cd submodule &&
+ git config --unset fetch.recurseSubmodules
+ )
+ ) &&
+ test_i18ncmp expect.err.sub actual.err &&
+ test_i18ncmp expect.out actual.out
+'
+
+test_expect_success "Recursion picks up all submodules when necessary" '
+ add_upstream_commit &&
+ (
+ cd submodule &&
+ (
+ cd subdir/deepsubmodule &&
+ git fetch &&
+ git checkout -q FETCH_HEAD
+ ) &&
+ head1=$(git rev-parse --short HEAD^) &&
+ git add subdir/deepsubmodule &&
+ git commit -m "new deepsubmodule"
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/submodule" > ../expect.err.sub &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub
+ ) &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.2 &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.2 &&
+ cat expect.err.sub >> expect.err.2 &&
+ tail -2 expect.err >> expect.err.2 &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ test_i18ncmp expect.err.2 actual.err &&
+ test_i18ncmp expect.out actual.out
+'
+
+test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no new commits are fetched in the superproject (and ignores config)" '
+ add_upstream_commit &&
+ (
+ cd submodule &&
+ (
+ cd subdir/deepsubmodule &&
+ git fetch &&
+ git checkout -q FETCH_HEAD
+ ) &&
+ head1=$(git rev-parse --short HEAD^) &&
+ git add subdir/deepsubmodule &&
+ git commit -m "new deepsubmodule"
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/submodule" > ../expect.err.sub &&
+ echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub
+ ) &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules true &&
+ git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err &&
+ git config --unset fetch.recurseSubmodules
+ ) &&
+ ! test -s actual.out &&
+ ! test -s actual.err
+'
+
+test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necessary (and ignores config)" '
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ tail -2 expect.err > expect.err.deepsub &&
+ echo "From $pwd/." > expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err
+ cat expect.err.sub >> expect.err &&
+ cat expect.err.deepsub >> expect.err &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules false &&
+ (
+ cd submodule &&
+ git config -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive false
+ ) &&
+ git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err &&
+ git config --unset fetch.recurseSubmodules
+ (
+ cd submodule &&
+ git config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive
+ )
+ ) &&
+ test_i18ncmp expect.out actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_expect_success "'--recurse-submodules=on-demand' stops when no new submodule commits are found in the superproject (and ignores config)" '
+ add_upstream_commit &&
+ head1=$(git rev-parse --short HEAD) &&
+ echo a >> file &&
+ git add file &&
+ git commit -m "new file" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.file &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.file &&
+ (
+ cd downstream &&
+ git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ test_i18ncmp expect.err.file actual.err
+'
+
+test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules
+ ) &&
+ add_upstream_commit &&
+ git config --global fetch.recurseSubmodules false &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.2 &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.2
+ head -2 expect.err >> expect.err.2 &&
+ (
+ cd downstream &&
+ git config fetch.recurseSubmodules on-demand &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ git config --global --unset fetch.recurseSubmodules &&
+ (
+ cd downstream &&
+ git config --unset fetch.recurseSubmodules
+ ) &&
+ test_i18ncmp expect.out.sub actual.out &&
+ test_i18ncmp expect.err.2 actual.err
+'
+
+test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' overrides fetch.recurseSubmodules" '
+ (
+ cd downstream &&
+ git fetch --recurse-submodules
+ ) &&
+ add_upstream_commit &&
+ git config fetch.recurseSubmodules false &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "new submodule" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err.2 &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err.2
+ head -2 expect.err >> expect.err.2 &&
+ (
+ cd downstream &&
+ git config submodule.submodule.fetchRecurseSubmodules on-demand &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ git config --unset fetch.recurseSubmodules &&
+ (
+ cd downstream &&
+ git config --unset submodule.submodule.fetchRecurseSubmodules
+ ) &&
+ test_i18ncmp expect.out.sub actual.out &&
+ test_i18ncmp expect.err.2 actual.err
+'
+
+test_expect_success "don't fetch submodule when newly recorded commits are already present" '
+ (
+ cd submodule &&
+ git checkout -q HEAD^^
+ ) &&
+ head1=$(git rev-parse --short HEAD) &&
+ git add submodule &&
+ git commit -m "submodule rewound" &&
+ head2=$(git rev-parse --short HEAD) &&
+ echo "From $pwd/." > expect.err &&
+ echo " $head1..$head2 master -> origin/master" >> expect.err &&
+ (
+ cd downstream &&
+ git fetch >../actual.out 2>../actual.err
+ ) &&
+ ! test -s actual.out &&
+ test_i18ncmp expect.err actual.err
+'
+
+test_done
diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh
index 044603c..6b2a5f4 100755
--- a/t/t5530-upload-pack-error.sh
+++ b/t/t5530-upload-pack-error.sh
@@ -60,6 +60,15 @@ test_expect_success 'upload-pack fails due to error in rev-list' '
grep "bad tree object" output.err
'
+test_expect_success 'upload-pack error message when bad ref requested' '
+
+ printf "0045want %s multi_ack_detailed\n00000009done\n0000" \
+ "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" >input &&
+ test_must_fail git upload-pack . <input >output 2>output.err &&
+ grep -q "not our ref" output.err &&
+ ! grep -q multi_ack_detailed output.err
+'
+
test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
printf "0032want %s\n00000009done\n0000" \
diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh
index 65d8d47..faa2e96 100755
--- a/t/t5531-deep-submodule-push.sh
+++ b/t/t5531-deep-submodule-push.sh
@@ -6,7 +6,7 @@ test_description='unpack-objects'
test_expect_success setup '
mkdir pub.git &&
- GIT_DIR=pub.git git init --bare
+ GIT_DIR=pub.git git init --bare &&
GIT_DIR=pub.git git config receive.fsckobjects true &&
mkdir work &&
(
diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh
new file mode 100755
index 0000000..62f2460
--- /dev/null
+++ b/t/t5532-fetch-proxy.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='fetching via git:// using core.gitproxy'
+. ./test-lib.sh
+
+test_expect_success 'setup remote repo' '
+ git init remote &&
+ (cd remote &&
+ echo content >file &&
+ git add file &&
+ git commit -m one
+ )
+'
+
+cat >proxy <<'EOF'
+#!/bin/sh
+echo >&2 "proxying for $*"
+cmd=`perl -e '
+ read(STDIN, $buf, 4);
+ my $n = hex($buf) - 4;
+ read(STDIN, $buf, $n);
+ my ($cmd, $other) = split /\0/, $buf;
+ # drop absolute-path on repo name
+ $cmd =~ s{ /}{ };
+ print $cmd;
+'`
+echo >&2 "Running '$cmd'"
+exec $cmd
+EOF
+chmod +x proxy
+test_expect_success 'setup local repo' '
+ git remote add fake git://example.com/remote &&
+ git config core.gitproxy ./proxy
+'
+
+test_expect_success 'fetch through proxy works' '
+ git fetch fake &&
+ echo one >expect &&
+ git log -1 --format=%s FETCH_HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index 37fe875..a266ca5 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -11,7 +11,7 @@ This test runs various sanity checks on http-push.'
if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
then
- say "skipping test, USE_CURL_MULTI is not defined"
+ skip_all="skipping test, USE_CURL_MULTI is not defined"
test_done
fi
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
index 17e1bdc..a73c826 100755
--- a/t/t5541-http-push.sh
+++ b/t/t5541-http-push.sh
@@ -7,7 +7,7 @@ test_description='test smart pushing over http via http-backend'
. ./test-lib.sh
if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
+ skip_all='skipping test, git built without http support'
test_done
fi
@@ -65,14 +65,16 @@ test_expect_success 'clone remote repository' '
git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
'
-test_expect_success 'push to remote repository' '
+test_expect_success 'push to remote repository (standard)' '
cd "$ROOT_PATH"/test_repo_clone &&
: >path2 &&
git add path2 &&
test_tick &&
git commit -m path2 &&
HEAD=$(git rev-parse --verify HEAD) &&
- git push &&
+ GIT_CURL_VERBOSE=1 git push -v -v 2>err &&
+ ! grep "Expect: 100-continue" err &&
+ grep "POST git-receive-pack ([0-9]* bytes)" err &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
test $HEAD = $(git rev-parse --verify HEAD))
'
@@ -128,14 +130,29 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he
# push master too; this ensures there is at least one '"'push'"' command to
# the remote helper and triggers interaction with the helper.
- !(git push -v origin +master master:retsam >output 2>&1) &&
+ test_must_fail git push -v origin +master master:retsam >output 2>&1'
+test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: remote output' '
grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output &&
- grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output &&
+ grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output
+'
- grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' '
+ test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \
output
'
+test_expect_success 'push (chunked)' '
+ git checkout master &&
+ test_commit commit path3 &&
+ HEAD=$(git rev-parse --verify HEAD) &&
+ git config http.postbuffer 4 &&
+ test_when_finished "git config --unset http.postbuffer" &&
+ git push -v -v origin $BRANCH 2>err &&
+ grep "POST git-receive-pack (chunked)" err &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ test $HEAD = $(git rev-parse --verify HEAD))
+'
+
stop_httpd
test_done
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index fc675b5..a1883ca 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -4,7 +4,7 @@ test_description='test dumb fetching over http via static file'
. ./test-lib.sh
if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
+ skip_all='skipping test, git built without http support'
test_done
fi
@@ -30,18 +30,37 @@ test_expect_success 'create http-accessible bare repository' '
'
test_expect_success 'clone http repository' '
- git clone $HTTPD_URL/dumb/repo.git clone &&
+ git clone $HTTPD_URL/dumb/repo.git clone-tmpl &&
+ cp -R clone-tmpl clone &&
test_cmp file clone/file
'
+test_expect_success 'clone http repository with authentication' '
+ mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" &&
+ cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" &&
+ git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&
+ test_cmp file clone-auth/file
+'
+
test_expect_success 'fetch changes via http' '
echo content >>file &&
git commit -a -m two &&
- git push public
+ git push public &&
(cd clone && git pull) &&
test_cmp file clone/file
'
+test_expect_success 'fetch changes via manual http-fetch' '
+ cp -R clone-tmpl clone2 &&
+
+ HEAD=$(git rev-parse --verify HEAD) &&
+ (cd clone2 &&
+ git http-fetch -a -w heads/master-new $HEAD $(git config remote.origin.url) &&
+ git checkout master-new &&
+ test $HEAD = $(git rev-parse --verify HEAD)) &&
+ test_cmp file clone2/file
+'
+
test_expect_success 'http remote detects correct HEAD' '
git push public master:other &&
(cd clone &&
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
index 7faa31a..26d3557 100755
--- a/t/t5551-http-fetch.sh
+++ b/t/t5551-http-fetch.sh
@@ -4,7 +4,7 @@ test_description='test smart fetching over http via http-backend'
. ./test-lib.sh
if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
+ skip_all='skipping test, git built without http support'
test_done
fi
@@ -101,5 +101,13 @@ test_expect_success 'used upload-pack service' '
test_cmp exp act
'
+test_expect_success 'follow redirects (301)' '
+ git clone $HTTPD_URL/smart-redir-perm/repo.git --quiet repo-p
+'
+
+test_expect_success 'follow redirects (302)' '
+ git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t
+'
+
stop_httpd
test_done
diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh
index 44885b8..0ad7ce0 100755
--- a/t/t5560-http-backend-noserver.sh
+++ b/t/t5560-http-backend-noserver.sh
@@ -5,16 +5,17 @@ test_description='test git-http-backend-noserver'
HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
+test_have_prereq MINGW && export GREP_OPTIONS=-U
+
run_backend() {
echo "$2" |
QUERY_STRING="${1#*\?}" \
- GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
- PATH_INFO="${1%%\?*}" \
+ PATH_TRANSLATED="$HTTPD_DOCUMENT_ROOT_PATH/${1%%\?*}" \
git http-backend >act.out 2>act.err
}
GET() {
- export REQUEST_METHOD="GET" &&
+ REQUEST_METHOD="GET" && export REQUEST_METHOD &&
run_backend "/repo.git/$1" &&
unset REQUEST_METHOD &&
if ! grep "Status" act.out >act
@@ -26,8 +27,8 @@ GET() {
}
POST() {
- export REQUEST_METHOD="POST" &&
- export CONTENT_TYPE="application/x-$1-request" &&
+ REQUEST_METHOD="POST" && export REQUEST_METHOD &&
+ CONTENT_TYPE="application/x-$1-request" && export CONTENT_TYPE &&
run_backend "/repo.git/$1" "$2" &&
unset REQUEST_METHOD &&
unset CONTENT_TYPE &&
@@ -46,7 +47,7 @@ log_div() {
. "$TEST_DIRECTORY"/t556x_common
expect_aliased() {
- export REQUEST_METHOD="GET" &&
+ REQUEST_METHOD="GET" && export REQUEST_METHOD &&
if test $1 = 0; then
run_backend "$2"
else
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
index 8c6d0b2..b5d7fbc 100755
--- a/t/t5561-http-backend.sh
+++ b/t/t5561-http-backend.sh
@@ -4,7 +4,7 @@ test_description='test git-http-backend'
. ./test-lib.sh
if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
+ skip_all='skipping test, git built without http support'
test_done
fi
diff --git a/t/t556x_common b/t/t556x_common
index be024e5..82926cf 100755
--- a/t/t556x_common
+++ b/t/t556x_common
@@ -50,72 +50,72 @@ get_static_files() {
}
SMART=smart
-export GIT_HTTP_EXPORT_ALL=1
+GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
test_expect_success 'direct refs/heads/master not found' '
- log_div "refs/heads/master"
+ log_div "refs/heads/master" &&
GET refs/heads/master "404 Not Found"
'
test_expect_success 'static file is ok' '
- log_div "getanyfile default"
+ log_div "getanyfile default" &&
get_static_files "200 OK"
'
SMART=smart_noexport
unset GIT_HTTP_EXPORT_ALL
test_expect_success 'no export by default' '
- log_div "no git-daemon-export-ok"
+ log_div "no git-daemon-export-ok" &&
get_static_files "404 Not Found"
'
test_expect_success 'export if git-daemon-export-ok' '
- log_div "git-daemon-export-ok"
+ log_div "git-daemon-export-ok" &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
touch git-daemon-export-ok
) &&
get_static_files "200 OK"
'
SMART=smart
-export GIT_HTTP_EXPORT_ALL=1
+GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
test_expect_success 'static file if http.getanyfile true is ok' '
- log_div "getanyfile true"
+ log_div "getanyfile true" &&
config http.getanyfile true &&
get_static_files "200 OK"
'
test_expect_success 'static file if http.getanyfile false fails' '
- log_div "getanyfile false"
+ log_div "getanyfile false" &&
config http.getanyfile false &&
get_static_files "403 Forbidden"
'
test_expect_success 'http.uploadpack default enabled' '
- log_div "uploadpack default"
+ log_div "uploadpack default" &&
GET info/refs?service=git-upload-pack "200 OK" &&
POST git-upload-pack 0000 "200 OK"
'
test_expect_success 'http.uploadpack true' '
- log_div "uploadpack true"
+ log_div "uploadpack true" &&
config http.uploadpack true &&
GET info/refs?service=git-upload-pack "200 OK" &&
POST git-upload-pack 0000 "200 OK"
'
test_expect_success 'http.uploadpack false' '
- log_div "uploadpack false"
+ log_div "uploadpack false" &&
config http.uploadpack false &&
GET info/refs?service=git-upload-pack "403 Forbidden" &&
POST git-upload-pack 0000 "403 Forbidden"
'
test_expect_success 'http.receivepack default disabled' '
- log_div "receivepack default"
+ log_div "receivepack default" &&
GET info/refs?service=git-receive-pack "403 Forbidden" &&
POST git-receive-pack 0000 "403 Forbidden"
'
test_expect_success 'http.receivepack true' '
- log_div "receivepack true"
+ log_div "receivepack true" &&
config http.receivepack true &&
GET info/refs?service=git-receive-pack "200 OK" &&
POST git-receive-pack 0000 "200 OK"
'
test_expect_success 'http.receivepack false' '
- log_div "receivepack false"
+ log_div "receivepack false" &&
config http.receivepack false &&
GET info/refs?service=git-receive-pack "403 Forbidden" &&
POST git-receive-pack 0000 "403 Forbidden"
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 8abb71a..151ea53 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -31,7 +31,7 @@ test_expect_success 'clone with excess parameters (2)' '
'
-test_expect_success 'output from clone' '
+test_expect_success C_LOCALE_OUTPUT 'output from clone' '
rm -fr dst &&
git clone -n "file://$(pwd)/src" dst >output &&
test $(grep Clon output | wc -l) = 1
@@ -163,10 +163,7 @@ test_expect_success 'clone a void' '
test_expect_success 'clone respects global branch.autosetuprebase' '
(
- HOME=$(pwd) &&
- export HOME &&
test_config="$HOME/.gitconfig" &&
- unset GIT_CONFIG_NOGLOBAL &&
git config -f "$test_config" branch.autosetuprebase remote &&
rm -fr dst &&
git clone src dst &&
@@ -178,8 +175,14 @@ test_expect_success 'clone respects global branch.autosetuprebase' '
test_expect_success 'respect url-encoding of file://' '
git init x+y &&
- test_must_fail git clone "file://$PWD/x+y" xy-url &&
- git clone "file://$PWD/x%2By" xy-url
+ git clone "file://$PWD/x+y" xy-url-1 &&
+ git clone "file://$PWD/x%2By" xy-url-2
+'
+
+test_expect_success 'do not query-string-decode + in URLs' '
+ rm -rf x+y &&
+ git init "x y" &&
+ test_must_fail git clone "file://$PWD/x+y" xy-no-plus
'
test_expect_success 'do not respect url-encoding of non-url path' '
@@ -188,4 +191,20 @@ test_expect_success 'do not respect url-encoding of non-url path' '
git clone x+y xy-regular
'
+test_expect_success 'clone separate gitdir' '
+ rm -rf dst &&
+ git clone --separate-git-dir realgitdir src dst &&
+ test -d realgitdir/refs
+'
+
+test_expect_success 'clone separate gitdir: output' '
+ echo "gitdir: `pwd`/realgitdir" >expected &&
+ test_cmp expected dst/.git
+'
+
+test_expect_success 'clone separate gitdir where target already exists' '
+ rm -rf dst &&
+ test_must_fail git clone --separate-git-dir realgitdir src dst
+'
+
test_done
diff --git a/t/t5602-clone-remote-exec.sh b/t/t5602-clone-remote-exec.sh
index deffdae..3f353d9 100755
--- a/t/t5602-clone-remote-exec.sh
+++ b/t/t5602-clone-remote-exec.sh
@@ -5,21 +5,29 @@ test_description=clone
. ./test-lib.sh
test_expect_success setup '
- echo "#!/bin/sh" > not_ssh
- echo "echo \"\$*\" > not_ssh_output" >> not_ssh
- echo "exit 1" >> not_ssh
+ echo "#!/bin/sh" > not_ssh &&
+ echo "echo \"\$*\" > not_ssh_output" >> not_ssh &&
+ echo "exit 1" >> not_ssh &&
chmod +x not_ssh
'
test_expect_success 'clone calls git upload-pack unqualified with no -u option' '
- GIT_SSH=./not_ssh git clone localhost:/path/to/repo junk
- echo "localhost git-upload-pack '\''/path/to/repo'\''" >expected
+ (
+ GIT_SSH=./not_ssh &&
+ export GIT_SSH &&
+ test_must_fail git clone localhost:/path/to/repo junk
+ ) &&
+ echo "localhost git-upload-pack '\''/path/to/repo'\''" >expected &&
test_cmp expected not_ssh_output
'
test_expect_success 'clone calls specified git upload-pack with -u option' '
- GIT_SSH=./not_ssh git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk
- echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected
+ (
+ GIT_SSH=./not_ssh &&
+ export GIT_SSH &&
+ test_must_fail git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk
+ ) &&
+ echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected &&
test_cmp expected not_ssh_output
'
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index 8b4c356..6972258 100755
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
@@ -10,11 +10,11 @@ test_expect_success 'preparing origin repository' '
git clone --bare . a.git &&
git clone --bare . x &&
test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true &&
- test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true
+ test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true &&
git bundle create b1.bundle --all &&
git bundle create b2.bundle master &&
mkdir dir &&
- cp b1.bundle dir/b3
+ cp b1.bundle dir/b3 &&
cp b1.bundle b4
'
@@ -112,7 +112,7 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
cd "$D" &&
git clone b2.bundle b2 &&
cd b2 &&
- git fetch
+ git fetch &&
test ! -e .git/refs/heads/master
'
@@ -144,4 +144,17 @@ test_expect_success 'clone empty repository, and then push should not segfault.'
test_must_fail git push)
'
+test_expect_success 'cloning non-existent directory fails' '
+ cd "$D" &&
+ rm -rf does-not-exist &&
+ test_must_fail git clone does-not-exist
+'
+
+test_expect_success 'cloning non-git directory fails' '
+ cd "$D" &&
+ rm -rf not-a-git-repo not-a-git-repo-clone &&
+ mkdir not-a-git-repo &&
+ test_must_fail git clone not-a-git-repo not-a-git-repo-clone
+'
+
test_done
diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh
index ddc3dc5..728ccd8 100755
--- a/t/t5704-bundle.sh
+++ b/t/t5704-bundle.sh
@@ -30,6 +30,13 @@ test_expect_success 'tags can be excluded by rev-list options' '
'
+test_expect_success 'die if bundle file cannot be created' '
+
+ mkdir adir &&
+ test_must_fail git bundle create adir --all
+
+'
+
test_expect_failure 'bundle --stdin' '
echo master | git bundle create stdin-bundle.bdl --stdin &&
diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh
index 8afbdd4..e9783c3 100755
--- a/t/t5705-clone-2gb.sh
+++ b/t/t5705-clone-2gb.sh
@@ -3,12 +3,14 @@
test_description='Test cloning a repository larger than 2 gigabyte'
. ./test-lib.sh
-test -z "$GIT_TEST_CLONE_2GB" &&
-say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" &&
-test_done &&
-exit
+if test -z "$GIT_TEST_CLONE_2GB"
+then
+ say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
+else
+ test_set_prereq CLONE_2GB
+fi
-test_expect_success 'setup' '
+test_expect_success CLONE_2GB 'setup' '
git config pack.compression 0 &&
git config pack.depth 0 &&
@@ -36,13 +38,13 @@ test_expect_success 'setup' '
'
-test_expect_success 'clone - bare' '
+test_expect_success CLONE_2GB 'clone - bare' '
git clone --bare --no-hardlinks . clone-bare
'
-test_expect_success 'clone - with worktree, file:// protocol' '
+test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
git clone file://. clone-wt
diff --git a/t/t5708-clone-config.sh b/t/t5708-clone-config.sh
new file mode 100755
index 0000000..27d730c
--- /dev/null
+++ b/t/t5708-clone-config.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='tests for git clone -c key=value'
+. ./test-lib.sh
+
+test_expect_success 'clone -c sets config in cloned repo' '
+ rm -rf child &&
+ git clone -c core.foo=bar . child &&
+ echo bar >expect &&
+ git --git-dir=child/.git config core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c can set multi-keys' '
+ rm -rf child &&
+ git clone -c core.foo=bar -c core.foo=baz . child &&
+ { echo bar; echo baz; } >expect &&
+ git --git-dir=child/.git config --get-all core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c without a value is boolean true' '
+ rm -rf child &&
+ git clone -c core.foo . child &&
+ echo true >expect &&
+ git --git-dir=child/.git config --bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c config is available during clone' '
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+ rm -rf child &&
+ git clone -c core.autocrlf . child &&
+ printf "content\\r\\n" >expect &&
+ test_cmp expect child/file
+'
+
+test_done
diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh
index 4ee7b65..1fb6380 100755
--- a/t/t5800-remote-helpers.sh
+++ b/t/t5800-remote-helpers.sh
@@ -13,13 +13,11 @@ if sys.hexversion < 0x02040000:
sys.exit(1)
'
then
- :
-else
- say 'skipping git remote-testgit tests: requires Python 2.4 or newer'
- test_done
+ # Requires Python 2.4 or newer
+ test_set_prereq PYTHON_24
fi
-test_expect_success 'setup repository' '
+test_expect_success PYTHON_24 'setup repository' '
git init --bare server/.git &&
git clone server public &&
(cd public &&
@@ -29,34 +27,34 @@ test_expect_success 'setup repository' '
git push origin master)
'
-test_expect_success 'cloning from local repo' '
+test_expect_success PYTHON_24 'cloning from local repo' '
git clone "testgit::${PWD}/server" localclone &&
test_cmp public/file localclone/file
'
-test_expect_success 'cloning from remote repo' '
+test_expect_success PYTHON_24 'cloning from remote repo' '
git clone "testgit::file://${PWD}/server" clone &&
test_cmp public/file clone/file
'
-test_expect_success 'create new commit on remote' '
+test_expect_success PYTHON_24 'create new commit on remote' '
(cd public &&
echo content >>file &&
git commit -a -m two &&
git push)
'
-test_expect_success 'pulling from local repo' '
+test_expect_success PYTHON_24 'pulling from local repo' '
(cd localclone && git pull) &&
test_cmp public/file localclone/file
'
-test_expect_success 'pulling from remote remote' '
+test_expect_success PYTHON_24 'pulling from remote remote' '
(cd clone && git pull) &&
test_cmp public/file clone/file
'
-test_expect_success 'pushing to local repo' '
+test_expect_success PYTHON_24 'pushing to local repo' '
(cd localclone &&
echo content >>file &&
git commit -a -m three &&
@@ -65,12 +63,12 @@ test_expect_success 'pushing to local repo' '
test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD)
'
-test_expect_success 'synch with changes from localclone' '
+test_expect_success PYTHON_24 'synch with changes from localclone' '
(cd clone &&
git pull)
'
-test_expect_success 'pushing remote local repo' '
+test_expect_success PYTHON_24 'pushing remote local repo' '
(cd clone &&
echo content >>file &&
git commit -a -m four &&
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
new file mode 100755
index 0000000..b10685a
--- /dev/null
+++ b/t/t6000-rev-list-misc.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='miscellaneous rev-list tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo content1 >wanted_file &&
+ echo content2 >unwanted_file &&
+ git add wanted_file unwanted_file &&
+ git commit -m one
+'
+
+test_expect_success 'rev-list --objects heeds pathspecs' '
+ git rev-list --objects HEAD -- wanted_file >output &&
+ grep wanted_file output &&
+ ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and deeper paths' '
+ mkdir foo &&
+ >foo/file &&
+ git add foo/file &&
+ git commit -m two &&
+
+ git rev-list --objects HEAD -- foo >output &&
+ grep foo/file output &&
+
+ git rev-list --objects HEAD -- foo/file >output &&
+ grep foo/file output &&
+ ! grep unwanted_file output
+'
+
+test_expect_success 'rev-list --objects with pathspecs and copied files' '
+ git checkout --orphan junio-testcase &&
+ git rm -rf . &&
+
+ mkdir two &&
+ echo frotz >one &&
+ cp one two/three &&
+ git add one two/three &&
+ test_tick &&
+ git commit -m that &&
+
+ ONE=$(git rev-parse HEAD:one)
+ git rev-list --objects HEAD two >output &&
+ grep "$ONE two/three" output &&
+ ! grep one output
+'
+
+test_done
diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh
index fc57e7d..8efcd13 100755
--- a/t/t6001-rev-list-graft.sh
+++ b/t/t6001-rev-list-graft.sh
@@ -90,22 +90,22 @@ check () {
for type in basic parents parents-raw
do
test_expect_success 'without grafts' "
- rm -f .git/info/grafts
+ rm -f .git/info/grafts &&
check $type $B2 -- $B2 $B1 $B0
"
test_expect_success 'with grafts' "
- echo '$B0 $A2' >.git/info/grafts
+ echo '$B0 $A2' >.git/info/grafts &&
check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0
"
test_expect_success 'without grafts, with pathlimit' "
- rm -f .git/info/grafts
+ rm -f .git/info/grafts &&
check $type $B2 subdir -- $B2 $B0
"
test_expect_success 'with grafts, with pathlimit' "
- echo '$B0 $A2' >.git/info/grafts
+ echo '$B0 $A2' >.git/info/grafts &&
check $type $B2 subdir -- $B2 $B0 $A2 $A0
"
diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh
index 5dabf1c..3e8c42e 100755
--- a/t/t6004-rev-list-path-optim.sh
+++ b/t/t6004-rev-list-path-optim.sh
@@ -1,51 +1,96 @@
#!/bin/sh
-test_description='git rev-list trivial path optimization test'
+test_description='git rev-list trivial path optimization test
+
+ d/z1
+ b0 b1
+ o------------------------*----o master
+ / /
+ o---------o----o----o----o side
+ a0 c0 c1 a1 c2
+ d/f0 d/f1
+ d/z0
+
+'
. ./test-lib.sh
test_expect_success setup '
-echo Hello > a &&
-git add a &&
-git commit -m "Initial commit" a &&
-initial=$(git rev-parse --verify HEAD)
+ echo Hello >a &&
+ mkdir d &&
+ echo World >d/f &&
+ echo World >d/z &&
+ git add a d &&
+ test_tick &&
+ git commit -m "Initial commit" &&
+ git rev-parse --verify HEAD &&
+ git tag initial
'
test_expect_success path-optimization '
- commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
- test $(git rev-list $commit | wc -l) = 2 &&
- test $(git rev-list $commit -- . | wc -l) = 1
+ test_tick &&
+ commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
+ test $(git rev-list $commit | wc -l) = 2 &&
+ test $(git rev-list $commit -- . | wc -l) = 1
'
test_expect_success 'further setup' '
git checkout -b side &&
echo Irrelevant >c &&
- git add c &&
+ echo Irrelevant >d/f &&
+ git add c d/f &&
+ test_tick &&
git commit -m "Side makes an irrelevant commit" &&
+ git tag side_c0 &&
echo "More Irrelevancy" >c &&
git add c &&
+ test_tick &&
git commit -m "Side makes another irrelevant commit" &&
echo Bye >a &&
git add a &&
+ test_tick &&
git commit -m "Side touches a" &&
- side=$(git rev-parse --verify HEAD) &&
+ git tag side_a1 &&
echo "Yet more Irrelevancy" >c &&
git add c &&
+ test_tick &&
git commit -m "Side makes yet another irrelevant commit" &&
git checkout master &&
echo Another >b &&
- git add b &&
+ echo Munged >d/z &&
+ git add b d/z &&
+ test_tick &&
git commit -m "Master touches b" &&
+ git tag master_b0 &&
git merge side &&
echo Touched >b &&
git add b &&
+ test_tick &&
git commit -m "Master touches b again"
'
test_expect_success 'path optimization 2' '
- ( echo "$side"; echo "$initial" ) >expected &&
+ git rev-parse side_a1 initial >expected &&
git rev-list HEAD -- a >actual &&
test_cmp expected actual
'
+test_expect_success 'pathspec with leading path' '
+ git rev-parse master^ master_b0 side_c0 initial >expected &&
+ git rev-list HEAD -- d >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (1)' '
+ git rev-parse master^ master_b0 side_c0 initial >expected &&
+ git rev-list HEAD -- "d/*" >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'pathspec with glob (2)' '
+ git rev-parse side_c0 initial >expected &&
+ git rev-list HEAD -- "d/[a-m]*" >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index cccacd4..d918cc0 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -162,6 +162,14 @@ commit 131a310eb913d107dd3c09a65d1651175898735d
commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
EOF
+test_expect_success '%x00 shows NUL' '
+ echo >expect commit f58db70b055c5718631e5c61528b28b12090cdea &&
+ echo >>expect fooQbar &&
+ git rev-list -1 --format=foo%x00bar HEAD >actual.nul &&
+ nul_to_q <actual.nul >actual &&
+ test_cmp expect actual
+'
+
test_expect_success '%ad respects --date=' '
echo 2005-04-07 >expect.ad-short &&
git log -1 --date=short --pretty=tformat:%ad >output.ad-short master &&
diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh
index b565638..28d4f6b 100755
--- a/t/t6007-rev-list-cherry-pick-file.sh
+++ b/t/t6007-rev-list-cherry-pick-file.sh
@@ -4,13 +4,14 @@ test_description='test git rev-list --cherry-pick -- file'
. ./test-lib.sh
-# A---B
+# A---B---D---F
# \
# \
-# C
+# C---E
#
# B changes a file foo.c, adding a line of text. C changes foo.c as
# well as bar.c, but the change in foo.c was identical to change B.
+# D and C change bar in the same way, E and F differently.
test_expect_success setup '
echo Hallo > foo &&
@@ -25,11 +26,26 @@ test_expect_success setup '
test_tick &&
git commit -m "C" &&
git tag C &&
+ echo Dello > bar &&
+ git add bar &&
+ test_tick &&
+ git commit -m "E" &&
+ git tag E &&
git checkout master &&
git checkout branch foo &&
test_tick &&
git commit -m "B" &&
- git tag B
+ git tag B &&
+ echo Cello > bar &&
+ git add bar &&
+ test_tick &&
+ git commit -m "D" &&
+ git tag D &&
+ echo Nello > bar &&
+ git add bar &&
+ test_tick &&
+ git commit -m "F" &&
+ git tag F
'
cat >expect <<EOF
@@ -53,8 +69,119 @@ test_expect_success '--cherry-pick foo comes up empty' '
test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
'
+cat >expect <<EOF
+>tags/C
+EOF
+
test_expect_success '--cherry-pick bar does not come up empty' '
- ! test -z "$(git rev-list --left-right --cherry-pick B...C -- bar)"
+ git rev-list --left-right --cherry-pick B...C -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+test_expect_success 'bar does not come up empty' '
+ git rev-list --left-right B...C -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+<tags/F
+>tags/E
+EOF
+
+test_expect_success '--cherry-pick bar does not come up empty (II)' '
+ git rev-list --left-right --cherry-pick F...E -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
++tags/F
+=tags/D
++tags/E
+=tags/C
+EOF
+
+test_expect_success '--cherry-mark' '
+ git rev-list --cherry-mark F...E -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+<tags/F
+=tags/D
+>tags/E
+=tags/C
+EOF
+
+test_expect_success '--cherry-mark --left-right' '
+ git rev-list --cherry-mark --left-right F...E -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+tags/E
+EOF
+
+test_expect_success '--cherry-pick --right-only' '
+ git rev-list --cherry-pick --right-only F...E -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+test_expect_success '--cherry-pick --left-only' '
+ git rev-list --cherry-pick --left-only E...F -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
++tags/E
+=tags/C
+EOF
+
+test_expect_success '--cherry' '
+ git rev-list --cherry F...E -- bar > actual &&
+ git name-rev --stdin --name-only --refs="*tags/*" \
+ < actual > actual.named &&
+ test_cmp actual.named expect
+'
+
+cat >expect <<EOF
+1 1
+EOF
+
+test_expect_success '--cherry --count' '
+ git rev-list --cherry --count F...E -- bar > actual &&
+ test_cmp actual expect
+'
+
+cat >expect <<EOF
+2 2
+EOF
+
+test_expect_success '--cherry-mark --count' '
+ git rev-list --cherry-mark --count F...E -- bar > actual &&
+ test_cmp actual expect
+'
+
+cat >expect <<EOF
+1 1 2
+EOF
+
+test_expect_success '--cherry-mark --left-right --count' '
+ git rev-list --cherry-mark --left-right --count F...E -- bar > actual &&
+ test_cmp actual expect
'
test_expect_success '--cherry-pick with independent, but identical branches' '
@@ -75,11 +202,8 @@ cat >expect <<EOF
1 2
EOF
-# Insert an extra commit to break the symmetry
test_expect_success '--count --left-right' '
- git checkout branch &&
- test_commit D &&
- git rev-list --count --left-right B...D > actual &&
+ git rev-list --count --left-right C...D > actual &&
test_cmp expect actual
'
diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh
index c8a96a9..3050740 100755
--- a/t/t6009-rev-list-parent.sh
+++ b/t/t6009-rev-list-parent.sh
@@ -1,14 +1,15 @@
#!/bin/sh
-test_description='properly cull all ancestors'
+test_description='ancestor culling and limiting by parent number'
. ./test-lib.sh
-commit () {
- test_tick &&
- echo $1 >file &&
- git commit -a -m $1 &&
- git tag $1
+check_revlist () {
+ rev_list_args="$1" &&
+ shift &&
+ git rev-parse "$@" >expect &&
+ git rev-list $rev_list_args --all >actual &&
+ test_cmp expect actual
}
test_expect_success setup '
@@ -16,13 +17,13 @@ test_expect_success setup '
touch file &&
git add file &&
- commit one &&
+ test_commit one &&
- test_tick=$(($test_tick - 2400))
+ test_tick=$(($test_tick - 2400)) &&
- commit two &&
- commit three &&
- commit four &&
+ test_commit two &&
+ test_commit three &&
+ test_commit four &&
git log --pretty=oneline --abbrev-commit
'
@@ -35,4 +36,101 @@ test_expect_success 'one is ancestor of others and should not be shown' '
'
+test_expect_success 'setup roots, merges and octopuses' '
+
+ git checkout --orphan newroot &&
+ test_commit five &&
+ git checkout -b sidebranch two &&
+ test_commit six &&
+ git checkout -b anotherbranch three &&
+ test_commit seven &&
+ git checkout -b yetanotherbranch four &&
+ test_commit eight &&
+ git checkout master &&
+ test_merge normalmerge newroot &&
+ test_tick &&
+ git merge -m tripus sidebranch anotherbranch &&
+ git tag tripus &&
+ git checkout -b tetrabranch normalmerge &&
+ test_tick &&
+ git merge -m tetrapus sidebranch anotherbranch yetanotherbranch &&
+ git tag tetrapus &&
+ git checkout master
+'
+
+test_expect_success 'rev-list roots' '
+
+ check_revlist "--max-parents=0" one five
+'
+
+test_expect_success 'rev-list no merges' '
+
+ check_revlist "--max-parents=1" one eight seven six five four three two &&
+ check_revlist "--no-merges" one eight seven six five four three two
+'
+
+test_expect_success 'rev-list no octopuses' '
+
+ check_revlist "--max-parents=2" one normalmerge eight seven six five four three two
+'
+
+test_expect_success 'rev-list no roots' '
+
+ check_revlist "--min-parents=1" tetrapus tripus normalmerge eight seven six four three two
+'
+
+test_expect_success 'rev-list merges' '
+
+ check_revlist "--min-parents=2" tetrapus tripus normalmerge &&
+ check_revlist "--merges" tetrapus tripus normalmerge
+'
+
+test_expect_success 'rev-list octopus' '
+
+ check_revlist "--min-parents=3" tetrapus tripus
+'
+
+test_expect_success 'rev-list ordinary commits' '
+
+ check_revlist "--min-parents=1 --max-parents=1" eight seven six four three two
+'
+
+test_expect_success 'rev-list --merges --no-merges yields empty set' '
+
+ check_revlist "--min-parents=2 --no-merges" &&
+ check_revlist "--merges --no-merges" &&
+ check_revlist "--no-merges --merges"
+'
+
+test_expect_success 'rev-list override and infinities' '
+
+ check_revlist "--min-parents=2 --max-parents=1 --max-parents=3" tripus normalmerge &&
+ check_revlist "--min-parents=1 --min-parents=2 --max-parents=7" tetrapus tripus normalmerge &&
+ check_revlist "--min-parents=2 --max-parents=8" tetrapus tripus normalmerge &&
+ check_revlist "--min-parents=2 --max-parents=-1" tetrapus tripus normalmerge &&
+ check_revlist "--min-parents=2 --no-max-parents" tetrapus tripus normalmerge &&
+ check_revlist "--max-parents=0 --min-parents=1 --no-min-parents" one five
+'
+
+test_expect_success 'dodecapus' '
+
+ roots= &&
+ for i in 1 2 3 4 5 6 7 8 9 10 11
+ do
+ git checkout -b root$i five &&
+ test_commit $i &&
+ roots="$roots root$i" ||
+ return
+ done &&
+ git checkout master &&
+ test_tick &&
+ git merge -m dodecapus $roots &&
+ git tag dodecapus &&
+
+ check_revlist "--min-parents=4" dodecapus tetrapus &&
+ check_revlist "--min-parents=8" dodecapus &&
+ check_revlist "--min-parents=12" dodecapus &&
+ check_revlist "--min-parents=13" &&
+ check_revlist "--min-parents=4 --max-parents=11" tetrapus
+'
test_done
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index 0144d9e..f80bba8 100755
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
@@ -3,13 +3,11 @@
# Copyright (c) 2005 Junio C Hamano
#
-test_description='Merge base computation.
+test_description='Merge base and parent list computation.
'
. ./test-lib.sh
-T=$(git write-tree)
-
M=1130000000
Z=+0000
@@ -19,159 +17,217 @@ GIT_AUTHOR_NAME='A U Thor'
GIT_AUTHOR_EMAIL=git@au.thor.xz
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-doit() {
- OFFSET=$1; shift
- NAME=$1; shift
- PARENTS=
+doit () {
+ OFFSET=$1 &&
+ NAME=$2 &&
+ shift 2 &&
+
+ PARENTS= &&
for P
do
PARENTS="${PARENTS}-p $P "
- done
- GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
- GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
- export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
- commit=$(echo $NAME | git commit-tree $T $PARENTS)
- echo $commit >.git/refs/tags/$NAME
+ done &&
+
+ GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" &&
+ GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE &&
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE &&
+
+ commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
+
+ echo $commit >.git/refs/tags/$NAME &&
echo $commit
}
-# E---D---C---B---A
-# \'-_ \ \
-# \ `---------G \
-# \ \
-# F----------------H
-
-# Setup...
-E=$(doit 5 E)
-D=$(doit 4 D $E)
-F=$(doit 6 F $E)
-C=$(doit 3 C $D)
-B=$(doit 2 B $C)
-A=$(doit 1 A $B)
-G=$(doit 7 G $B $E)
-H=$(doit 8 H $A $F)
-
-test_expect_success 'compute merge-base (single)' \
- 'MB=$(git merge-base G H) &&
- expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-test_expect_success 'compute merge-base (all)' \
- 'MB=$(git merge-base --all G H) &&
- expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-test_expect_success 'compute merge-base with show-branch' \
- 'MB=$(git show-branch --merge-base G H) &&
- expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-# Setup for second test to demonstrate that relying on timestamps in a
-# distributed SCM to provide a _consistent_ partial ordering of commits
-# leads to insanity.
-#
-# Relative
-# Structure timestamps
-#
-# PL PR +4 +4
-# / \/ \ / \/ \
-# L2 C2 R2 +3 -1 +3
-# | | | | | |
-# L1 C1 R1 +2 -2 +2
-# | | | | | |
-# L0 C0 R0 +1 -3 +1
-# \ | / \ | /
-# S 0
-#
-# The left and right chains of commits can be of any length and complexity as
-# long as all of the timestamps are greater than that of S.
+test_expect_success 'setup' '
+ T=$(git mktree </dev/null)
+'
+
+test_expect_success 'set up G and H' '
+ # E---D---C---B---A
+ # \"-_ \ \
+ # \ `---------G \
+ # \ \
+ # F----------------H
+ E=$(doit 5 E) &&
+ D=$(doit 4 D $E) &&
+ F=$(doit 6 F $E) &&
+ C=$(doit 3 C $D) &&
+ B=$(doit 2 B $C) &&
+ A=$(doit 1 A $B) &&
+ G=$(doit 7 G $B $E) &&
+ H=$(doit 8 H $A $F)
+'
-S=$(doit 0 S)
+test_expect_success 'merge-base G H' '
+ git name-rev $B >expected &&
-C0=$(doit -3 C0 $S)
-C1=$(doit -2 C1 $C0)
-C2=$(doit -1 C2 $C1)
+ MB=$(git merge-base G H) &&
+ git name-rev "$MB" >actual.single &&
-L0=$(doit 1 L0 $S)
-L1=$(doit 2 L1 $L0)
-L2=$(doit 3 L2 $L1)
+ MB=$(git merge-base --all G H) &&
+ git name-rev "$MB" >actual.all &&
-R0=$(doit 1 R0 $S)
-R1=$(doit 2 R1 $R0)
-R2=$(doit 3 R2 $R1)
+ MB=$(git show-branch --merge-base G H) &&
+ git name-rev "$MB" >actual.sb &&
-PL=$(doit 4 PL $L2 $C2)
-PR=$(doit 4 PR $C2 $R2)
+ test_cmp expected actual.single &&
+ test_cmp expected actual.all &&
+ test_cmp expected actual.sb
+'
-test_expect_success 'compute merge-base (single)' \
- 'MB=$(git merge-base PL PR) &&
- expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+test_expect_success 'merge-base/show-branch --independent' '
+ git name-rev "$H" >expected1 &&
+ git name-rev "$H" "$G" >expected2 &&
-test_expect_success 'compute merge-base (all)' \
- 'MB=$(git merge-base --all PL PR) &&
- expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+ parents=$(git merge-base --independent H) &&
+ git name-rev $parents >actual1.mb &&
+ parents=$(git merge-base --independent A H G) &&
+ git name-rev $parents >actual2.mb &&
-# Another set to demonstrate base between one commit and a merge
-# in the documentation.
-#
-# * C (MMC) * B (MMB) * A (MMA)
-# * o * o * o
-# * o * o * o
-# * o * o * o
-# * o | _______/
-# | |/
-# | * 1 (MM1)
-# | _______/
-# |/
-# * root (MMR)
+ parents=$(git show-branch --independent H) &&
+ git name-rev $parents >actual1.sb &&
+ parents=$(git show-branch --independent A H G) &&
+ git name-rev $parents >actual2.sb &&
+
+ test_cmp expected1 actual1.mb &&
+ test_cmp expected2 actual2.mb &&
+ test_cmp expected1 actual1.sb &&
+ test_cmp expected2 actual2.sb
+'
+test_expect_success 'unsynchronized clocks' '
+ # This test is to demonstrate that relying on timestamps in a distributed
+ # SCM to provide a _consistent_ partial ordering of commits leads to
+ # insanity.
+ #
+ # Relative
+ # Structure timestamps
+ #
+ # PL PR +4 +4
+ # / \/ \ / \/ \
+ # L2 C2 R2 +3 -1 +3
+ # | | | | | |
+ # L1 C1 R1 +2 -2 +2
+ # | | | | | |
+ # L0 C0 R0 +1 -3 +1
+ # \ | / \ | /
+ # S 0
+ #
+ # The left and right chains of commits can be of any length and complexity as
+ # long as all of the timestamps are greater than that of S.
+
+ S=$(doit 0 S) &&
+
+ C0=$(doit -3 C0 $S) &&
+ C1=$(doit -2 C1 $C0) &&
+ C2=$(doit -1 C2 $C1) &&
+
+ L0=$(doit 1 L0 $S) &&
+ L1=$(doit 2 L1 $L0) &&
+ L2=$(doit 3 L2 $L1) &&
+
+ R0=$(doit 1 R0 $S) &&
+ R1=$(doit 2 R1 $R0) &&
+ R2=$(doit 3 R2 $R1) &&
+
+ PL=$(doit 4 PL $L2 $C2) &&
+ PR=$(doit 4 PR $C2 $R2) &&
+
+ git name-rev $C2 >expected &&
+
+ MB=$(git merge-base PL PR) &&
+ git name-rev "$MB" >actual.single &&
+
+ MB=$(git merge-base --all PL PR) &&
+ git name-rev "$MB" >actual.all &&
+
+ test_cmp expected actual.single &&
+ test_cmp expected actual.all
+'
+
+test_expect_success '--independent with unsynchronized clocks' '
+ IB=$(doit 0 IB) &&
+ I1=$(doit -10 I1 $IB) &&
+ I2=$(doit -9 I2 $I1) &&
+ I3=$(doit -8 I3 $I2) &&
+ I4=$(doit -7 I4 $I3) &&
+ I5=$(doit -6 I5 $I4) &&
+ I6=$(doit -5 I6 $I5) &&
+ I7=$(doit -4 I7 $I6) &&
+ I8=$(doit -3 I8 $I7) &&
+ IH=$(doit -2 IH $I8) &&
+
+ echo $IH >expected &&
+ git merge-base --independent IB IH >actual &&
+ test_cmp expected actual
+'
test_expect_success 'merge-base for octopus-step (setup)' '
- test_tick && git commit --allow-empty -m root && git tag MMR &&
- test_tick && git commit --allow-empty -m 1 && git tag MM1 &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m A && git tag MMA &&
+ # Another set to demonstrate base between one commit and a merge
+ # in the documentation.
+ #
+ # * C (MMC) * B (MMB) * A (MMA)
+ # * o * o * o
+ # * o * o * o
+ # * o * o * o
+ # * o | _______/
+ # | |/
+ # | * 1 (MM1)
+ # | _______/
+ # |/
+ # * root (MMR)
+
+ test_commit MMR &&
+ test_commit MM1 &&
+ test_commit MM-o &&
+ test_commit MM-p &&
+ test_commit MM-q &&
+ test_commit MMA &&
git checkout MM1 &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m B && git tag MMB &&
+ test_commit MM-r &&
+ test_commit MM-s &&
+ test_commit MM-t &&
+ test_commit MMB &&
git checkout MMR &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m C && git tag MMC
+ test_commit MM-u &&
+ test_commit MM-v &&
+ test_commit MM-w &&
+ test_commit MM-x &&
+ test_commit MMC
'
test_expect_success 'merge-base A B C' '
- MB=$(git merge-base --all MMA MMB MMC) &&
- MM1=$(git rev-parse --verify MM1) &&
- test "$MM1" = "$MB"
-'
+ git rev-parse --verify MM1 >expected &&
+ git rev-parse --verify MMR >expected.sb &&
+
+ git merge-base --all MMA MMB MMC >actual &&
+ git merge-base --all --octopus MMA MMB MMC >actual.common &&
+ git show-branch --merge-base MMA MMB MMC >actual.sb &&
-test_expect_success 'merge-base A B C using show-branch' '
- MB=$(git show-branch --merge-base MMA MMB MMC) &&
- MMR=$(git rev-parse --verify MMR) &&
- test "$MMR" = "$MB"
+ test_cmp expected actual &&
+ test_cmp expected.sb actual.common &&
+ test_cmp expected.sb actual.sb
'
-test_expect_success 'criss-cross merge-base for octopus-step (setup)' '
+test_expect_success 'criss-cross merge-base for octopus-step' '
git reset --hard MMR &&
- test_tick && git commit --allow-empty -m 1 && git tag CC1 &&
+ test_commit CC1 &&
git reset --hard E &&
- test_tick && git commit --allow-empty -m 2 && git tag CC2 &&
- test_tick && git merge -s ours CC1 &&
- test_tick && git commit --allow-empty -m o &&
- test_tick && git commit --allow-empty -m B && git tag CCB &&
+ test_commit CC2 &&
+ test_tick &&
+ git merge -s ours CC1 &&
+ test_commit CC-o &&
+ test_commit CCB &&
git reset --hard CC1 &&
- test_tick && git merge -s ours CC2 &&
- test_tick && git commit --allow-empty -m A && git tag CCA
-'
+ git merge -s ours CC2 &&
+ test_commit CCA &&
+
+ git rev-parse CC1 CC2 >expected &&
+ git merge-base --all CCB CCA^^ CCA^^2 >actual &&
-test_expect_success 'merge-base B A^^ A^^2' '
- MB0=$(git merge-base --all CCB CCA^^ CCA^^2 | sort) &&
- MB1=$(git rev-parse CC1 CC2 | sort) &&
- test "$MB0" = "$MB1"
+ sort expected >expected.sorted &&
+ sort actual >actual.sorted &&
+ test_cmp expected.sorted actual.sorted
'
test_done
diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh
index 27fd52b..f7181d1 100755
--- a/t/t6016-rev-list-graph-simplify-history.sh
+++ b/t/t6016-rev-list-graph-simplify-history.sh
@@ -29,7 +29,7 @@ test_expect_success 'set up rev-list --graph test' '
# Octopus merge B and C into branch A
git checkout A &&
git merge B C &&
- git tag A4
+ git tag A4 &&
test_commit A5 bar.txt &&
@@ -39,7 +39,7 @@ test_expect_success 'set up rev-list --graph test' '
test_commit C4 bar.txt &&
git checkout A &&
git merge -s ours C &&
- git tag A6
+ git tag A6 &&
test_commit A7 bar.txt &&
@@ -90,7 +90,7 @@ test_expect_success '--graph --all' '
# that undecorated merges are interesting, even with --simplify-by-decoration
test_expect_success '--graph --simplify-by-decoration' '
rm -f expected &&
- git tag -d A4
+ git tag -d A4 &&
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
@@ -116,12 +116,15 @@ test_expect_success '--graph --simplify-by-decoration' '
test_cmp expected actual
'
-# Get rid of all decorations on branch B, and graph with it simplified away
+test_expect_success 'setup: get rid of decorations on B' '
+ git tag -d B2 &&
+ git tag -d B1 &&
+ git branch -d B
+'
+
+# Graph with branch B simplified away
test_expect_success '--graph --simplify-by-decoration prune branch B' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
@@ -143,9 +146,6 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '
test_expect_success '--graph --full-history -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
@@ -163,9 +163,6 @@ test_expect_success '--graph --full-history -- bar.txt' '
test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "|\\ " >> expected &&
@@ -181,9 +178,6 @@ test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
test_expect_success '--graph -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A5" >> expected &&
echo "* $A3" >> expected &&
@@ -196,9 +190,6 @@ test_expect_success '--graph -- bar.txt' '
test_expect_success '--graph --sparse -- bar.txt' '
rm -f expected &&
- git tag -d B2
- git tag -d B1
- git branch -d B
echo "* $A7" >> expected &&
echo "* $A6" >> expected &&
echo "* $A5" >> expected &&
diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh
index f1c32db..667b375 100755
--- a/t/t6017-rev-list-stdin.sh
+++ b/t/t6017-rev-list-stdin.sh
@@ -58,4 +58,21 @@ check side-3 ^side-4 -- file-3
check side-3 ^side-2
check side-3 ^side-2 -- file-1
+test_expect_success 'not only --stdin' '
+ cat >expect <<-EOF &&
+ 7
+
+ file-1
+ file-2
+ EOF
+ cat >input <<-EOF &&
+ ^master^
+ --
+ file-2
+ EOF
+ git log --pretty=tformat:%s --name-only --stdin master -- file-1 \
+ <input >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 58428d9..f00cebf 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -69,6 +69,18 @@ test_expect_success 'rev-parse --glob=heads/subspace' '
'
+test_expect_failure 'rev-parse accepts --glob as detached option' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob heads/subspace"
+
+'
+
+test_expect_failure 'rev-parse is not confused by option-like glob' '
+
+ compare rev-parse "master" "--glob --symbolic master"
+
+'
+
test_expect_success 'rev-parse --branches=subspace/*' '
compare rev-parse "subspace/one subspace/two" "--branches=subspace/*"
@@ -123,6 +135,18 @@ test_expect_success 'rev-list --glob=refs/heads/subspace/*' '
'
+test_expect_success 'rev-list --glob refs/heads/subspace/*' '
+
+ compare rev-list "subspace/one subspace/two" "--glob refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list not confused by option-like --glob arg' '
+
+ compare rev-list "master" "--glob -0 master"
+
+'
+
test_expect_success 'rev-list --glob=heads/subspace/*' '
compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*"
@@ -207,4 +231,36 @@ test_expect_success 'rev-list --remotes=foo' '
'
+test_expect_success 'shortlog accepts --glob/--tags/--remotes' '
+
+ compare shortlog "subspace/one subspace/two" --branches=subspace &&
+ compare shortlog \
+ "master subspace-x someref other/three subspace/one subspace/two" \
+ --branches &&
+ compare shortlog master "--glob=heads/someref/* master" &&
+ compare shortlog "subspace/one subspace/two other/three" \
+ "--glob=heads/subspace/* --glob=heads/other/*" &&
+ compare shortlog \
+ "master other/three someref subspace-x subspace/one subspace/two" \
+ "--glob=heads/*" &&
+ compare shortlog foo/bar --tags=foo &&
+ compare shortlog foo/bar --tags &&
+ compare shortlog foo/baz --remotes=foo
+
+'
+
+test_expect_failure 'shortlog accepts --glob as detached option' '
+
+ compare shortlog \
+ "master other/three someref subspace-x subspace/one subspace/two" \
+ "--glob heads/*"
+
+'
+
+test_expect_failure 'shortlog --glob is not confused by option-like argument' '
+
+ compare shortlog master "--glob -e master"
+
+'
+
test_done
diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh
index e71c687..eec8f4e 100755
--- a/t/t6020-merge-df.sh
+++ b/t/t6020-merge-df.sh
@@ -6,23 +6,28 @@
test_description='Test merge with directory/file conflicts'
. ./test-lib.sh
-test_expect_success 'prepare repository' \
-'echo "Hello" > init &&
-git add init &&
-git commit -m "Initial commit" &&
-git branch B &&
-mkdir dir &&
-echo "foo" > dir/foo &&
-git add dir/foo &&
-git commit -m "File: dir/foo" &&
-git checkout B &&
-echo "file dir" > dir &&
-git add dir &&
-git commit -m "File: dir"'
-
-test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
-
-test_expect_failure 'F/D conflict' '
+test_expect_success 'prepare repository' '
+ echo Hello >init &&
+ git add init &&
+ git commit -m initial &&
+
+ git branch B &&
+ mkdir dir &&
+ echo foo >dir/foo &&
+ git add dir/foo &&
+ git commit -m "File: dir/foo" &&
+
+ git checkout B &&
+ echo file dir >dir &&
+ git add dir &&
+ git commit -m "File: dir"
+'
+
+test_expect_success 'Merge with d/f conflicts' '
+ test_expect_code 1 git merge "merge msg" B master
+'
+
+test_expect_success 'F/D conflict' '
git reset --hard &&
git checkout master &&
rm .git/index &&
@@ -45,4 +50,51 @@ test_expect_failure 'F/D conflict' '
git merge master
'
+test_expect_success 'setup modify/delete + directory/file conflict' '
+ git checkout --orphan modify &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ printf "a\nb\nc\nd\ne\nf\ng\nh\n" >letters &&
+ git add letters &&
+ git commit -m initial &&
+
+ echo i >>letters &&
+ git add letters &&
+ git commit -m modified &&
+
+ git checkout -b delete HEAD^ &&
+ git rm letters &&
+ mkdir letters &&
+ >letters/file &&
+ git add letters &&
+ git commit -m deleted
+'
+
+test_expect_success 'modify/delete + directory/file conflict' '
+ git checkout delete^0 &&
+ test_must_fail git merge modify &&
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 1 = $(git ls-files -o | wc -l) &&
+
+ test -f letters/file &&
+ test -f letters~modify
+'
+
+test_expect_success 'modify/delete + directory/file conflict; other way' '
+ git reset --hard &&
+ git clean -f &&
+ git checkout modify^0 &&
+ test_must_fail git merge delete &&
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 2 = $(git ls-files -u | wc -l) &&
+ test 1 = $(git ls-files -o | wc -l) &&
+
+ test -f letters/file &&
+ test -f letters~HEAD
+'
+
test_done
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index b66544b..1ed259d 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -3,6 +3,11 @@
test_description='Merge-recursive merging renames'
. ./test-lib.sh
+modify () {
+ sed -e "$1" <"$2" >"$2.x" &&
+ mv "$2.x" "$2"
+}
+
test_expect_success setup \
'
cat >A <<\EOF &&
@@ -94,245 +99,147 @@ git checkout master'
test_expect_success 'pull renaming branch into unrenaming one' \
'
- git show-branch
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- git ls-files -s
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages for B"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git show-branch &&
+ test_expect_code 1 git pull . white &&
+ git ls-files -s &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep master || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep master &&
+ git diff --exit-code white N
'
test_expect_success 'pull renaming branch into another renaming one' \
'
- rm -f B
- git reset --hard
- git checkout red
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ rm -f B &&
+ git reset --hard &&
+ git checkout red &&
+ test_expect_code 1 git pull . white &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'pull unrenaming branch into renaming one' \
'
- git reset --hard
- git show-branch
- git pull . master && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 3 || {
- echo "BAD: should have left stages"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ test_expect_code 1 git pull . master &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 3 b.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'pull conflicting renames' \
'
- git reset --hard
- git show-branch
- git pull . blue && {
- echo "BAD: should have conflicted"
- return 1
- }
- test "$(git ls-files -u A | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -u B | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -u C | wc -l)" -eq 1 || {
- echo "BAD: should have left a stage"
- return 1
- }
- test "$(git ls-files -s N | wc -l)" -eq 1 || {
- echo "BAD: should have merged N"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ test_expect_code 1 git pull . blue &&
+ git ls-files -u A >a.stages &&
+ test_line_count = 1 a.stages &&
+ git ls-files -u B >b.stages &&
+ test_line_count = 1 b.stages &&
+ git ls-files -u C >c.stages &&
+ test_line_count = 1 c.stages &&
+ git ls-files -s N >n.stages &&
+ test_line_count = 1 n.stages &&
sed -ne "/^g/{
p
q
- }" B | grep red || {
- echo "BAD: should have listed our change first"
- return 1
- }
- test "$(git diff white N | wc -l)" -eq 0 || {
- echo "BAD: should have taken colored branch"
- return 1
- }
+ }" B | grep red &&
+ git diff --exit-code white N
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- git show-branch
- echo >A this file should not matter
- git pull . white && {
- echo "BAD: should have conflicted"
- return 1
- }
- test -f A || {
- echo "BAD: should have left A intact"
- return 1
- }
+ git reset --hard &&
+ git show-branch &&
+ echo >A this file should not matter &&
+ test_expect_code 1 git pull . white &&
+ test_path_is_file A
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- git checkout white
- git show-branch
- rm -f A
- echo >A this file should not matter
- git pull . red && {
- echo "BAD: should have conflicted"
- return 1
- }
- test -f A || {
- echo "BAD: should have left A intact"
- return 1
- }
+ git reset --hard &&
+ git checkout white &&
+ git show-branch &&
+ rm -f A &&
+ echo >A this file should not matter &&
+ test_expect_code 1 git pull . red &&
+ test_path_is_file A
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- git pull . yellow || {
- echo "BAD: should have cleanly merged"
- return 1
- }
- test -f M && {
- echo "BAD: should have removed M"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ git pull . yellow &&
+ test_path_is_missing M &&
git reset --hard anchor
'
test_expect_success 'updated working tree file should prevent the merge' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- echo >>M one line addition
- cat M >M.saved
- git pull . yellow && {
- echo "BAD: should have complained"
- return 1
- }
- test_cmp M M.saved || {
- echo "BAD: should have left M intact"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >>M one line addition &&
+ cat M >M.saved &&
+ test_expect_code 128 git pull . yellow &&
+ test_cmp M M.saved &&
rm -f M.saved
'
test_expect_success 'updated working tree file should prevent the merge' '
-
- git reset --hard
- rm -f A M
- git checkout -f master
- git tag -f anchor
- git show-branch
- echo >>M one line addition
- cat M >M.saved
- git update-index M
- git pull . yellow && {
- echo "BAD: should have complained"
- return 1
- }
- test_cmp M M.saved || {
- echo "BAD: should have left M intact"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f master &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >>M one line addition &&
+ cat M >M.saved &&
+ git update-index M &&
+ test_expect_code 128 git pull . yellow &&
+ test_cmp M M.saved &&
rm -f M.saved
'
test_expect_success 'interference with untracked working tree file' '
-
- git reset --hard
- rm -f A M
- git checkout -f yellow
- git tag -f anchor
- git show-branch
- echo >M this file should not matter
- git pull . master || {
- echo "BAD: should have cleanly merged"
- return 1
- }
- test -f M || {
- echo "BAD: should have left M intact"
- return 1
- }
- git ls-files -s | grep M && {
- echo "BAD: M must be untracked in the result"
- return 1
- }
+ git reset --hard &&
+ rm -f A M &&
+ git checkout -f yellow &&
+ git tag -f anchor &&
+ git show-branch &&
+ echo >M this file should not matter &&
+ git pull . master &&
+ test_path_is_file M &&
+ ! {
+ git ls-files -s |
+ grep M
+ } &&
git reset --hard anchor
'
test_expect_success 'merge of identical changes in a renamed file' '
- rm -f A M N
+ rm -f A M N &&
git reset --hard &&
git checkout change+rename &&
GIT_MERGE_VERBOSITY=3 git merge change | grep "^Skipped B" &&
@@ -341,4 +248,365 @@ test_expect_success 'merge of identical changes in a renamed file' '
GIT_MERGE_VERBOSITY=3 git merge change+rename | grep "^Skipped B"
'
+test_expect_success 'setup for rename + d/f conflicts' '
+ git reset --hard &&
+ git checkout --orphan dir-in-way &&
+ git rm -rf . &&
+
+ mkdir sub &&
+ mkdir dir &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file &&
+ echo foo >dir/file-in-the-way &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ echo 11 >>sub/file &&
+ echo more >>dir/file-in-the-way &&
+ git add -u &&
+ git commit -m "Commit to merge, with dir in the way" &&
+
+ git checkout -b dir-not-in-way &&
+ git reset --soft HEAD^ &&
+ git rm -rf dir &&
+ git commit -m "Commit to merge, with dir removed" -- dir sub/file &&
+
+ git checkout -b renamed-file-has-no-conflicts dir-in-way~1 &&
+ git rm -rf dir &&
+ git rm sub/file &&
+ printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir &&
+ git add dir &&
+ git commit -m "Independent change" &&
+
+ git checkout -b renamed-file-has-conflicts dir-in-way~1 &&
+ git rm -rf dir &&
+ git mv sub/file dir &&
+ echo 12 >>dir &&
+ git add dir &&
+ git commit -m "Conflicting change"
+'
+
+printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n11\n" >expected
+
+test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' '
+ git reset --hard &&
+ git checkout -q renamed-file-has-no-conflicts^0 &&
+ git merge --strategy=recursive dir-not-in-way &&
+ git diff --quiet &&
+ test -f dir &&
+ test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-no-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-in-way >output &&
+
+ grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "Auto-merging dir" output &&
+ grep "Adding as dir~HEAD instead" output &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~HEAD &&
+ test_cmp expected dir~HEAD
+'
+
+test_expect_success 'Same as previous, but merged other way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q dir-in-way^0 &&
+ test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
+
+ ! grep "error: refusing to lose untracked file at" errors &&
+ grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
+ grep "Auto-merging dir" output &&
+ grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~renamed-file-has-no-conflicts &&
+ test_cmp expected dir~renamed-file-has-no-conflicts
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+12
+=======
+11
+>>>>>>> dir-not-in-way
+EOF
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-not-in-way &&
+
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir &&
+ test_cmp expected dir
+'
+
+test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '
+ modify s/dir-not-in-way/dir-in-way/ expected &&
+
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q renamed-file-has-conflicts^0 &&
+ test_must_fail git merge --strategy=recursive dir-in-way &&
+
+ test 5 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~HEAD &&
+ test_cmp expected dir~HEAD
+'
+
+cat >expected <<\EOF &&
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+<<<<<<< HEAD
+11
+=======
+12
+>>>>>>> renamed-file-has-conflicts
+EOF
+
+test_expect_success 'Same as previous, but merged other way' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q dir-in-way^0 &&
+ test_must_fail git merge --strategy=recursive renamed-file-has-conflicts &&
+
+ test 5 -eq "$(git ls-files -u | wc -l)" &&
+ test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" &&
+ test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+ test_must_fail git diff --cached --quiet &&
+
+ test -f dir/file-in-the-way &&
+ test -f dir~renamed-file-has-conflicts &&
+ test_cmp expected dir~renamed-file-has-conflicts
+'
+
+test_expect_success 'setup both rename source and destination involved in D/F conflict' '
+ git reset --hard &&
+ git checkout --orphan rename-dest &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ mkdir one &&
+ echo stuff >one/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git mv one/file destdir &&
+ git commit -m "Renamed to destdir" &&
+
+ git checkout -b source-conflict HEAD~1 &&
+ git rm -rf one &&
+ mkdir destdir &&
+ touch one destdir/foo &&
+ git add -A &&
+ git commit -m "Conflicts in the way"
+'
+
+test_expect_success 'both rename source and destination involved in D/F conflict' '
+ git reset --hard &&
+ rm -rf dir~* &&
+ git checkout -q rename-dest^0 &&
+ test_must_fail git merge --strategy=recursive source-conflict &&
+
+ test 1 -eq "$(git ls-files -u | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test -f destdir/foo &&
+ test -f one &&
+ test -f destdir~HEAD &&
+ test "stuff" = "$(cat destdir~HEAD)"
+'
+
+test_expect_success 'setup pair rename to parent of other (D/F conflicts)' '
+ git reset --hard &&
+ git checkout --orphan rename-two &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ mkdir one &&
+ mkdir two &&
+ echo stuff >one/file &&
+ echo other >two/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf one &&
+ git mv two/file one &&
+ git commit -m "Rename two/file -> one" &&
+
+ git checkout -b rename-one HEAD~1 &&
+ git rm -rf two &&
+ git mv one/file two &&
+ rm -r one &&
+ git commit -m "Rename one/file -> two"
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '
+ git checkout -q rename-one^0 &&
+ mkdir one &&
+ test_must_fail git merge --strategy=recursive rename-two &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test 4 -eq $(find . | grep -v .git | wc -l) &&
+
+ test -d one &&
+ test -f one~rename-two &&
+ test -f two &&
+ test "other" = $(cat one~rename-two) &&
+ test "stuff" = $(cat two)
+'
+
+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' '
+ git reset --hard &&
+ git clean -fdqx &&
+ test_must_fail git merge --strategy=recursive rename-two &&
+
+ test 2 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+
+ test_must_fail git diff --quiet &&
+
+ test 3 -eq $(find . | grep -v .git | wc -l) &&
+
+ test -f one &&
+ test -f two &&
+ test "other" = $(cat one) &&
+ test "stuff" = $(cat two)
+'
+
+test_expect_success 'setup rename of one file to two, with directories in the way' '
+ git reset --hard &&
+ git checkout --orphan first-rename &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ echo stuff >original &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ mkdir two &&
+ >two/file &&
+ git add two/file &&
+ git mv original one &&
+ git commit -m "Put two/file in the way, rename to one" &&
+
+ git checkout -b second-rename HEAD~1 &&
+ mkdir one &&
+ >one/file &&
+ git add one/file &&
+ git mv original two &&
+ git commit -m "Put one/file in the way, rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+ git checkout -q first-rename^0 &&
+ test_must_fail git merge --strategy=recursive second-rename &&
+
+ test 5 -eq "$(git ls-files -s | wc -l)" &&
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+ test 1 -eq "$(git ls-files -u original | wc -l)" &&
+ test 2 -eq "$(git ls-files -o | wc -l)" &&
+
+ test -f one/file &&
+ test -f two/file &&
+ test -f one~HEAD &&
+ test -f two~second-rename &&
+ ! test -f original
+'
+
+test_expect_success 'setup rename one file to two; directories moving out of the way' '
+ git reset --hard &&
+ git checkout --orphan first-rename-redo &&
+ git rm -rf . &&
+ git clean -fdqx &&
+
+ echo stuff >original &&
+ mkdir one two &&
+ touch one/file two/file &&
+ git add -A &&
+ git commit -m "Common commmit" &&
+
+ git rm -rf one &&
+ git mv original one &&
+ git commit -m "Rename to one" &&
+
+ git checkout -b second-rename-redo HEAD~1 &&
+ git rm -rf two &&
+ git mv original two &&
+ git commit -m "Rename to two"
+'
+
+test_expect_success 'check handling of differently renamed file with D/F conflicts' '
+ git checkout -q first-rename-redo^0 &&
+ test_must_fail git merge --strategy=recursive second-rename-redo &&
+
+ test 3 -eq "$(git ls-files -u | wc -l)" &&
+ test 1 -eq "$(git ls-files -u one | wc -l)" &&
+ test 1 -eq "$(git ls-files -u two | wc -l)" &&
+ test 1 -eq "$(git ls-files -u original | wc -l)" &&
+ test 0 -eq "$(git ls-files -o | wc -l)" &&
+
+ test -f one &&
+ test -f two &&
+ ! test -f original
+'
+
test_done
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
index d486d73..d9f3439 100755
--- a/t/t6023-merge-file.sh
+++ b/t/t6023-merge-file.sh
@@ -64,6 +64,14 @@ cp new1.txt test.txt
test_expect_success "merge without conflict" \
"git merge-file test.txt orig.txt new2.txt"
+test_expect_success 'works in subdirectory' '
+ mkdir dir &&
+ cp new1.txt dir/a.txt &&
+ cp orig.txt dir/o.txt &&
+ cp new2.txt dir/b.txt &&
+ ( cd dir && git merge-file a.txt o.txt b.txt )
+'
+
cp new1.txt test.txt
test_expect_success "merge without conflict (--quiet)" \
"git merge-file --quiet test.txt orig.txt new2.txt"
diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh
index b3fbf65..755d30c 100755
--- a/t/t6024-recursive-merge.sh
+++ b/t/t6024-recursive-merge.sh
@@ -104,7 +104,7 @@ test_expect_success 'mark rename/delete as unmerged' '
test_tick &&
git commit -m delete &&
git checkout -b rename HEAD^ &&
- git mv a1 a2
+ git mv a1 a2 &&
test_tick &&
git commit -m rename &&
test_must_fail git merge delete &&
diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh
index 3900d9f..73fc240 100755
--- a/t/t6029-merge-subtree.sh
+++ b/t/t6029-merge-subtree.sh
@@ -6,7 +6,7 @@ test_description='subtree merge strategy'
test_expect_success setup '
- s="1 2 3 4 5 6 7 8"
+ s="1 2 3 4 5 6 7 8" &&
for i in $s; do echo $i; done >hello &&
git add hello &&
git commit -m initial &&
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 3b042aa..b5063b6 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -517,13 +517,13 @@ test_expect_success '"parallel" side branch creation' '
add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 &&
PARA_HASH2=$(git rev-parse --verify HEAD) &&
add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 &&
- PARA_HASH3=$(git rev-parse --verify HEAD)
+ PARA_HASH3=$(git rev-parse --verify HEAD) &&
git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" &&
- PARA_HASH4=$(git rev-parse --verify HEAD)
+ PARA_HASH4=$(git rev-parse --verify HEAD) &&
add_line_into_file "5(para): add line on parallel branch" dir1/file1 &&
- PARA_HASH5=$(git rev-parse --verify HEAD)
+ PARA_HASH5=$(git rev-parse --verify HEAD) &&
add_line_into_file "6(para): add line on parallel branch" dir2/file2 &&
- PARA_HASH6=$(git rev-parse --verify HEAD)
+ PARA_HASH6=$(git rev-parse --verify HEAD) &&
git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" &&
PARA_HASH7=$(git rev-parse --verify HEAD)
'
diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh
index 8a3304f..1cd649e 100755
--- a/t/t6031-merge-recursive.sh
+++ b/t/t6031-merge-recursive.sh
@@ -2,11 +2,7 @@
test_description='merge-recursive: handle file mode'
. ./test-lib.sh
-
-if ! test "$(git config --bool core.filemode)" = false
-then
- test_set_prereq FILEMODE
-fi
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
test_expect_success 'mode change in one branch: keep changed version' '
: >file1 &&
@@ -57,4 +53,35 @@ test_expect_success FILEMODE 'verify executable bit on file' '
test -x file2
'
+test_expect_success 'merging with triple rename across D/F conflict' '
+ git reset --hard HEAD &&
+ git checkout -b main &&
+ git rm -rf . &&
+
+ echo "just a file" >sub1 &&
+ mkdir -p sub2 &&
+ echo content1 >sub2/file1 &&
+ echo content2 >sub2/file2 &&
+ echo content3 >sub2/file3 &&
+ mkdir simple &&
+ echo base >simple/bar &&
+ git add -A &&
+ test_tick &&
+ git commit -m base &&
+
+ git checkout -b other &&
+ echo more >>simple/bar &&
+ test_tick &&
+ git commit -a -m changesimplefile &&
+
+ git checkout main &&
+ git rm sub1 &&
+ git mv sub2 sub1 &&
+ test_tick &&
+ git commit -m changefiletodir &&
+
+ test_tick &&
+ git merge other
+'
+
test_done
diff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh
index eac5eba..fdb6c25 100755
--- a/t/t6032-merge-large-rename.sh
+++ b/t/t6032-merge-large-rename.sh
@@ -70,4 +70,34 @@ test_expect_success 'set merge.renamelimit to 5' '
test_rename 5 ok
test_rename 6 fail
+test_expect_success 'setup large simple rename' '
+ git config --unset merge.renamelimit &&
+ git config --unset diff.renamelimit &&
+
+ git reset --hard initial &&
+ for i in $(count 200); do
+ make_text foo bar baz >$i
+ done &&
+ git add . &&
+ git commit -m create-files &&
+
+ git branch simple-change &&
+ git checkout -b simple-rename &&
+
+ mkdir builtin &&
+ git mv [0-9]* builtin/ &&
+ git commit -m renamed &&
+
+ git checkout simple-change &&
+ >unrelated-change &&
+ git add unrelated-change &&
+ git commit -m unrelated-change
+'
+
+test_expect_success 'massive simple rename does not spam added files' '
+ unset GIT_MERGE_VERBOSITY &&
+ git merge --no-stat simple-rename | grep -v Removing >output &&
+ test 5 -gt "$(wc -l < output)"
+'
+
test_done
diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh
index 3202e1d..2599ae5 100755
--- a/t/t6035-merge-dir-to-symlink.sh
+++ b/t/t6035-merge-dir-to-symlink.sh
@@ -3,13 +3,7 @@
test_description='merging when a directory was replaced with a symlink'
. ./test-lib.sh
-if ! test_have_prereq SYMLINKS
-then
- say 'Symbolic links not supported, skipping tests.'
- test_done
-fi
-
-test_expect_success 'create a commit where dir a/b changed to symlink' '
+test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' '
mkdir -p a/b/c a/b-2/c &&
> a/b/c/d &&
> a/b-2/c/d &&
@@ -23,23 +17,31 @@ test_expect_success 'create a commit where dir a/b changed to symlink' '
git commit -m "dir to symlink"
'
-test_expect_success 'keep a/b-2/c/d across checkout' '
+test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' '
git checkout HEAD^0 &&
git reset --hard master &&
git rm --cached a/b &&
git commit -m "untracked symlink remains" &&
- git checkout start^0 &&
- test -f a/b-2/c/d
+ test_must_fail git checkout start^0
'
-test_expect_success 'checkout should not have deleted a/b-2/c/d' '
+test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' '
+ git checkout HEAD^0 &&
+ git reset --hard master &&
+ git rm --cached a/b &&
+ git commit -m "untracked symlink remains" &&
+ git checkout -f start^0 &&
+ test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' '
git checkout HEAD^0 &&
git reset --hard master &&
git checkout start^0 &&
test -f a/b-2/c/d
'
-test_expect_success 'setup for merge test' '
+test_expect_success SYMLINKS 'setup for merge test' '
git reset --hard &&
test -f a/b-2/c/d &&
echo x > a/x &&
@@ -48,7 +50,7 @@ test_expect_success 'setup for merge test' '
git tag baseline
'
-test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve master &&
@@ -56,7 +58,7 @@ test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' '
test -f a/b-2/c/d
'
-test_expect_failure 'do not lose a/b-2/c/d in merge (recursive)' '
+test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s recursive master &&
@@ -64,7 +66,55 @@ test_expect_failure 'do not lose a/b-2/c/d in merge (recursive)' '
test -f a/b-2/c/d
'
-test_expect_success 'setup a merge where dir a/b-2 changed to symlink' '
+test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' '
+ git reset --hard &&
+ git checkout master^0 &&
+ git merge -s resolve baseline^0 &&
+ test -h a/b &&
+ test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' '
+ git reset --hard &&
+ git checkout master^0 &&
+ git merge -s recursive baseline^0 &&
+ test -h a/b &&
+ test -f a/b-2/c/d
+'
+
+test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' '
+ git reset --hard &&
+ git checkout baseline^0 &&
+ >a/b/c/e &&
+ test_must_fail git merge -s resolve master &&
+ test -f a/b/c/e &&
+ test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' '
+ git reset --hard &&
+ git checkout baseline^0 &&
+ >a/b/c/e &&
+ test_must_fail git merge -s recursive master &&
+ test -f a/b/c/e &&
+ test -f a/b-2/c/d
+'
+
+test_expect_success SYMLINKS 'do not lose modifications in merge (resolve)' '
+ git reset --hard &&
+ git checkout baseline^0 &&
+ echo more content >>a/b/c/d &&
+ test_must_fail git merge -s resolve master
+'
+
+test_expect_success SYMLINKS 'do not lose modifications in merge (recursive)' '
+ git reset --hard &&
+ git checkout baseline^0 &&
+ echo more content >>a/b/c/d &&
+ test_must_fail git merge -s recursive master
+'
+
+test_expect_success SYMLINKS 'setup a merge where dir a/b-2 changed to symlink' '
git reset --hard &&
git checkout start^0 &&
rm -rf a/b-2 &&
@@ -74,7 +124,7 @@ test_expect_success 'setup a merge where dir a/b-2 changed to symlink' '
git tag test2
'
-test_expect_success 'merge should not have conflicts (resolve)' '
+test_expect_success SYMLINKS 'merge should not have D/F conflicts (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve test2 &&
@@ -82,7 +132,7 @@ test_expect_success 'merge should not have conflicts (resolve)' '
test -f a/b/c/d
'
-test_expect_failure 'merge should not have conflicts (recursive)' '
+test_expect_success SYMLINKS 'merge should not have D/F conflicts (recursive)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s recursive test2 &&
@@ -90,4 +140,12 @@ test_expect_failure 'merge should not have conflicts (recursive)' '
test -f a/b/c/d
'
+test_expect_success SYMLINKS 'merge should not have F/D conflicts (recursive)' '
+ git reset --hard &&
+ git checkout -b foo test2 &&
+ git merge -s recursive baseline^0 &&
+ test -h a/b-2 &&
+ test -f a/b/c/d
+'
+
test_done
diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
index b874141..871577d 100755
--- a/t/t6036-recursive-corner-cases.sh
+++ b/t/t6036-recursive-corner-cases.sh
@@ -14,7 +14,80 @@ test_description='recursive merge corner cases'
# R1 R2
#
-test_expect_success setup '
+test_expect_success 'setup basic criss-cross + rename with no modifications' '
+ ten="0 1 2 3 4 5 6 7 8 9" &&
+ for i in $ten
+ do
+ echo line $i in a sample file
+ done >one &&
+ for i in $ten
+ do
+ echo line $i in another sample file
+ done >two &&
+ git add one two &&
+ test_tick && git commit -m initial &&
+
+ git branch L1 &&
+ git checkout -b R1 &&
+ git mv one three &&
+ test_tick && git commit -m R1 &&
+
+ git checkout L1 &&
+ git mv two three &&
+ test_tick && git commit -m L1 &&
+
+ git checkout L1^0 &&
+ test_tick && git merge -s ours R1 &&
+ git tag L2 &&
+
+ git checkout R1^0 &&
+ test_tick && git merge -s ours L1 &&
+ git tag R2
+'
+
+test_expect_success 'merge simple rename+criss-cross with no modifications' '
+ git reset --hard &&
+ git checkout L2^0 &&
+
+ test_must_fail git merge -s recursive R2^0 &&
+
+ test 5 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
+ test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
+ test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
+ test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+
+ cp two merged &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty one &&
+ test $(git rev-parse :1:three) = $(git hash-object merged)
+'
+
+#
+# Same as before, but modify L1 slightly:
+#
+# L1m L2
+# o---o
+# / \ / \
+# o X ?
+# \ / \ /
+# o---o
+# R1 R2
+#
+
+test_expect_success 'setup criss-cross + rename merges with basic modification' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
ten="0 1 2 3 4 5 6 7 8 9"
for i in $ten
do
@@ -30,6 +103,8 @@ test_expect_success setup '
git branch L1 &&
git checkout -b R1 &&
git mv one three &&
+ echo more >>two &&
+ git add two &&
test_tick && git commit -m R1 &&
git checkout L1 &&
@@ -45,11 +120,115 @@ test_expect_success setup '
git tag R2
'
-test_expect_success merge '
+test_expect_success 'merge criss-cross + rename merges with basic modification' '
git reset --hard &&
git checkout L2^0 &&
- test_must_fail git merge -s recursive R2^0
+ test_must_fail git merge -s recursive R2^0 &&
+
+ test 5 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
+ test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
+ test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
+ test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+
+ head -n 10 two >merged &&
+ cp one merge-me &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty merge-me &&
+ test $(git rev-parse :1:three) = $(git hash-object merged)
+'
+
+#
+# For the next test, we start with three commits in two lines of development
+# which setup a rename/add conflict:
+# Commit A: File 'a' exists
+# Commit B: Rename 'a' -> 'new_a'
+# Commit C: Modify 'a', create different 'new_a'
+# Later, two different people merge and resolve differently:
+# Commit D: Merge B & C, ignoring separately created 'new_a'
+# Commit E: Merge B & C making use of some piece of secondary 'new_a'
+# Finally, someone goes to merge D & E. Does git detect the conflict?
+#
+# B D
+# o---o
+# / \ / \
+# A o X ? F
+# \ / \ /
+# o---o
+# C E
+#
+
+test_expect_success 'setup differently handled merges of rename/add conflict' '
+ git rm -rf . &&
+ git clean -fdqx &&
+ rm -rf .git &&
+ git init &&
+
+ printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a &&
+ git add a &&
+ test_tick && git commit -m A &&
+
+ git branch B &&
+ git checkout -b C &&
+ echo 10 >>a &&
+ echo "other content" >>new_a &&
+ git add a new_a &&
+ test_tick && git commit -m C &&
+
+ git checkout B &&
+ git mv a new_a &&
+ test_tick && git commit -m B &&
+
+ git checkout B^0 &&
+ test_must_fail git merge C &&
+ git clean -f &&
+ test_tick && git commit -m D &&
+ git tag D &&
+
+ git checkout C^0 &&
+ test_must_fail git merge B &&
+ rm new_a~HEAD new_a &&
+ printf "Incorrectly merged content" >>new_a &&
+ git add -u &&
+ test_tick && git commit -m E &&
+ git tag E
+'
+
+test_expect_success 'git detects differently handled merges conflict' '
+ git reset --hard &&
+ git checkout D^0 &&
+
+ git merge -s recursive E^0 && {
+ echo "BAD: should have conflicted"
+ test "Incorrectly merged content" = "$(cat new_a)" &&
+ echo "BAD: Silently accepted wrong content"
+ return 1
+ }
+
+ test 3 = $(git ls-files -s | wc -l) &&
+ test 3 = $(git ls-files -u | wc -l) &&
+ test 0 = $(git ls-files -o | wc -l) &&
+
+ test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
+ test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
+
+ git cat-file -p B:new_a >>merged &&
+ git cat-file -p C:new_a >>merge-me &&
+ >empty &&
+ test_must_fail git merge-file \
+ -L "Temporary merge branch 2" \
+ -L "" \
+ -L "Temporary merge branch 1" \
+ merged empty merge-me &&
+ test $(git rev-parse :1:new_a) = $(git hash-object merged)
'
test_done
diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh
index 8ab3d61..2cf42c7 100755
--- a/t/t6037-merge-ours-theirs.sh
+++ b/t/t6037-merge-ours-theirs.sh
@@ -58,7 +58,7 @@ test_expect_success 'pull with -X' '
git reset --hard master && git pull -s recursive -X ours . side &&
git reset --hard master && git pull -s recursive -Xtheirs . side &&
git reset --hard master && git pull -s recursive -X theirs . side &&
- git reset --hard master && ! git pull -s recursive -X bork . side
+ git reset --hard master && test_must_fail git pull -s recursive -X bork . side
'
test_done
diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh
new file mode 100755
index 0000000..d9c2d38
--- /dev/null
+++ b/t/t6038-merge-text-auto.sh
@@ -0,0 +1,191 @@
+#!/bin/sh
+
+test_description='CRLF merge conflict across text=auto change
+
+* [master] remove .gitattributes
+ ! [side] add line from b
+--
+ + [side] add line from b
+* [master] remove .gitattributes
+* [master^] add line from a
+* [master~2] normalize file
+*+ [side^] Initial
+'
+
+. ./test-lib.sh
+
+test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
+
+test_expect_success setup '
+ git config core.autocrlf false &&
+
+ echo first line | append_cr >file &&
+ echo first line >control_file &&
+ echo only line >inert_file &&
+
+ git add file control_file inert_file &&
+ test_tick &&
+ git commit -m "Initial" &&
+ git tag initial &&
+ git branch side &&
+
+ echo "* text=auto" >.gitattributes &&
+ touch file &&
+ git add .gitattributes file &&
+ test_tick &&
+ git commit -m "normalize file" &&
+
+ echo same line | append_cr >>file &&
+ echo same line >>control_file &&
+ git add file control_file &&
+ test_tick &&
+ git commit -m "add line from a" &&
+ git tag a &&
+
+ git rm .gitattributes &&
+ rm file &&
+ git checkout file &&
+ test_tick &&
+ git commit -m "remove .gitattributes" &&
+ git tag c &&
+
+ git checkout side &&
+ echo same line | append_cr >>file &&
+ echo same line >>control_file &&
+ git add file control_file &&
+ test_tick &&
+ git commit -m "add line from b" &&
+ git tag b &&
+
+ git checkout master
+'
+
+test_expect_success 'set up fuzz_conflict() helper' '
+ fuzz_conflict() {
+ sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@"
+ }
+'
+
+test_expect_success 'Merge after setting text=auto' '
+ cat <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ rm -f .gitattributes &&
+ git reset --hard a &&
+ git merge b &&
+ test_cmp expected file
+'
+
+test_expect_success 'Merge addition of text=auto' '
+ cat <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ rm -f .gitattributes &&
+ git reset --hard b &&
+ git merge a &&
+ test_cmp expected file
+'
+
+test_expect_success 'Detect CRLF/LF conflict after setting text=auto' '
+ q_to_cr <<-\EOF >expected &&
+ <<<<<<<
+ first line
+ same line
+ =======
+ first lineQ
+ same lineQ
+ >>>>>>>
+ EOF
+
+ git config merge.renormalize false &&
+ rm -f .gitattributes &&
+ git reset --hard a &&
+ test_must_fail git merge b &&
+ fuzz_conflict file >file.fuzzy &&
+ test_cmp expected file.fuzzy
+'
+
+test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' '
+ q_to_cr <<-\EOF >expected &&
+ <<<<<<<
+ first lineQ
+ same lineQ
+ =======
+ first line
+ same line
+ >>>>>>>
+ EOF
+
+ git config merge.renormalize false &&
+ rm -f .gitattributes &&
+ git reset --hard b &&
+ test_must_fail git merge a &&
+ fuzz_conflict file >file.fuzzy &&
+ test_cmp expected file.fuzzy
+'
+
+test_expect_failure 'checkout -m after setting text=auto' '
+ cat <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ rm -f .gitattributes &&
+ git reset --hard initial &&
+ git checkout a -- . &&
+ git checkout -m b &&
+ test_cmp expected file
+'
+
+test_expect_failure 'checkout -m addition of text=auto' '
+ cat <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ rm -f .gitattributes file &&
+ git reset --hard initial &&
+ git checkout b -- . &&
+ git checkout -m a &&
+ test_cmp expected file
+'
+
+test_expect_failure 'cherry-pick patch from after text=auto was added' '
+ append_cr <<-\EOF >expected &&
+ first line
+ same line
+ EOF
+
+ git config merge.renormalize true &&
+ git rm -fr . &&
+ git reset --hard b &&
+ test_must_fail git cherry-pick a >err 2>&1 &&
+ grep "[Nn]othing added" err &&
+ test_cmp expected file
+'
+
+test_expect_success 'Test delete/normalize conflict' '
+ git checkout -f side &&
+ git rm -fr . &&
+ rm -f .gitattributes &&
+ git reset --hard initial &&
+ git rm file &&
+ git commit -m "remove file" &&
+ git checkout master &&
+ git reset --hard a^ &&
+ git merge side
+'
+
+test_done
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 1785e17..a9b0ac1 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -48,7 +48,7 @@ test_expect_success 'branch -v' '
git branch -v
) |
sed -n -e "$script" >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success 'checkout' '
@@ -60,7 +60,7 @@ test_expect_success 'checkout' '
test_expect_success 'checkout with local tracked branch' '
git checkout master &&
- git checkout follower >actual
+ git checkout follower >actual &&
grep "is ahead of" actual
'
@@ -74,20 +74,20 @@ test_expect_success 'status' '
grep "have 1 and 1 different" actual
'
-test_expect_success 'status when tracking lightweight tags' '
+test_expect_success 'fail to track lightweight tags' '
git checkout master &&
git tag light &&
- git branch --track lighttrack light >actual &&
- grep "set up to track" actual &&
- git checkout lighttrack
+ test_must_fail git branch --track lighttrack light >actual &&
+ test_must_fail grep "set up to track" actual &&
+ test_must_fail git checkout lighttrack
'
-test_expect_success 'status when tracking annotated tags' '
+test_expect_success 'fail to track annotated tags' '
git checkout master &&
git tag -m heavy heavy &&
- git branch --track heavytrack heavy >actual &&
- grep "set up to track" actual &&
- git checkout heavytrack
+ test_must_fail git branch --track heavytrack heavy >actual &&
+ test_must_fail grep "set up to track" actual &&
+ test_must_fail git checkout heavytrack
'
test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index 203ffdb..5c87f28 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -53,7 +53,7 @@ test_expect_success 'set up buggy branch' '
echo "line 12" >> hello &&
echo "line 13" >> hello &&
add_and_commit_file hello "2 more lines" &&
- HASH6=$(git rev-parse --verify HEAD)
+ HASH6=$(git rev-parse --verify HEAD) &&
echo "line 14" >> hello &&
echo "line 15" >> hello &&
echo "line 16" >> hello &&
@@ -104,17 +104,18 @@ test_expect_success '"git fsck" works' '
test_expect_success 'repack, clone and fetch work' '
git repack -a -d &&
git clone --no-hardlinks . clone_dir &&
- cd clone_dir &&
- git show HEAD~5 | grep "A U Thor" &&
- git show $HASH2 | grep "A U Thor" &&
- git cat-file commit $R &&
- git repack -a -d &&
- test_must_fail git cat-file commit $R &&
- git fetch ../ "refs/replace/*:refs/replace/*" &&
- git show HEAD~5 | grep "O Thor" &&
- git show $HASH2 | grep "O Thor" &&
- git cat-file commit $R &&
- cd ..
+ (
+ cd clone_dir &&
+ git show HEAD~5 | grep "A U Thor" &&
+ git show $HASH2 | grep "A U Thor" &&
+ git cat-file commit $R &&
+ git repack -a -d &&
+ test_must_fail git cat-file commit $R &&
+ git fetch ../ "refs/replace/*:refs/replace/*" &&
+ git show HEAD~5 | grep "O Thor" &&
+ git show $HASH2 | grep "O Thor" &&
+ git cat-file commit $R
+ )
'
test_expect_success '"git replace" listing and deleting' '
@@ -177,10 +178,11 @@ test_expect_success 'create parallel branch without the bug' '
test_expect_success 'push to cloned repo' '
git push cloned $HASH6^:refs/heads/parallel &&
- cd clone_dir &&
- git checkout parallel &&
- git log --pretty=oneline | grep $PARA2 &&
- cd ..
+ (
+ cd clone_dir &&
+ git checkout parallel &&
+ git log --pretty=oneline | grep $PARA2
+ )
'
test_expect_success 'push branch with replacement' '
@@ -191,25 +193,34 @@ test_expect_success 'push branch with replacement' '
git show $HASH6~2 | grep "O Thor" &&
git show $PARA3 | grep "O Thor" &&
git push cloned $HASH6^:refs/heads/parallel2 &&
- cd clone_dir &&
- git checkout parallel2 &&
- git log --pretty=oneline | grep $PARA3 &&
- git show $PARA3 | grep "A U Thor" &&
- cd ..
+ (
+ cd clone_dir &&
+ git checkout parallel2 &&
+ git log --pretty=oneline | grep $PARA3 &&
+ git show $PARA3 | grep "A U Thor"
+ )
'
test_expect_success 'fetch branch with replacement' '
git branch tofetch $HASH6 &&
- cd clone_dir &&
- git fetch origin refs/heads/tofetch:refs/heads/parallel3
- git log --pretty=oneline parallel3 | grep $PARA3
- git show $PARA3 | grep "A U Thor"
- cd ..
+ (
+ cd clone_dir &&
+ git fetch origin refs/heads/tofetch:refs/heads/parallel3 &&
+ git log --pretty=oneline parallel3 > output.txt &&
+ ! grep $PARA3 output.txt &&
+ git show $PARA3 > para3.txt &&
+ grep "A U Thor" para3.txt &&
+ git fetch origin "refs/replace/*:refs/replace/*" &&
+ git log --pretty=oneline parallel3 > output.txt &&
+ grep $PARA3 output.txt &&
+ git show $PARA3 > para3.txt &&
+ grep "O Thor" para3.txt
+ )
'
test_expect_success 'bisect and replacements' '
git bisect start $HASH7 $HASH1 &&
- test "$S" = "$(git rev-parse --verify HEAD)" &&
+ test "$PARA3" = "$(git rev-parse --verify HEAD)" &&
git bisect reset &&
GIT_NO_REPLACE_OBJECTS=1 git bisect start $HASH7 $HASH1 &&
test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
@@ -219,6 +230,26 @@ test_expect_success 'bisect and replacements' '
git bisect reset
'
-#
-#
+test_expect_success 'index-pack and replacements' '
+ git --no-replace-objects rev-list --objects HEAD |
+ git --no-replace-objects pack-objects test- &&
+ git index-pack test-*.pack
+'
+
+test_expect_success 'not just commits' '
+ echo replaced >file &&
+ git add file &&
+ REPLACED=$(git rev-parse :file) &&
+ mv file file.replaced &&
+
+ echo original >file &&
+ git add file &&
+ ORIGINAL=$(git rev-parse :file) &&
+ git update-ref refs/replace/$ORIGINAL $REPLACED &&
+ mv file file.original &&
+
+ git checkout file &&
+ test_cmp file.replaced file
+'
+
test_done
diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh
new file mode 100755
index 0000000..debadbd
--- /dev/null
+++ b/t/t6060-merge-index.sh
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='basic git merge-index / git-merge-one-file tests'
+. ./test-lib.sh
+
+test_expect_success 'setup diverging branches' '
+ for i in 1 2 3 4 5 6 7 8 9 10; do
+ echo $i
+ done >file &&
+ git add file &&
+ git commit -m base &&
+ git tag base &&
+ sed s/2/two/ <file >tmp &&
+ mv tmp file &&
+ git commit -a -m two &&
+ git tag two &&
+ git checkout -b other HEAD^ &&
+ sed s/10/ten/ <file >tmp &&
+ mv tmp file &&
+ git commit -a -m ten &&
+ git tag ten
+'
+
+cat >expect-merged <<'EOF'
+1
+two
+3
+4
+5
+6
+7
+8
+9
+ten
+EOF
+
+test_expect_success 'read-tree does not resolve content merge' '
+ git read-tree -i -m base ten two &&
+ echo file >expect &&
+ git diff-files --name-only --diff-filter=U >unmerged &&
+ test_cmp expect unmerged
+'
+
+test_expect_success 'git merge-index git-merge-one-file resolves' '
+ git merge-index git-merge-one-file -a &&
+ git diff-files --name-only --diff-filter=U >unmerged &&
+ >expect &&
+ test_cmp expect unmerged &&
+ test_cmp expect-merged file &&
+ git cat-file blob :file >file-index &&
+ test_cmp expect-merged file-index
+'
+
+test_expect_success 'setup bare merge' '
+ git clone --bare . bare.git &&
+ (cd bare.git &&
+ GIT_INDEX_FILE=$PWD/merge.index &&
+ export GIT_INDEX_FILE &&
+ git read-tree -i -m base ten two
+ )
+'
+
+test_expect_success 'merge-one-file fails without a work tree' '
+ (cd bare.git &&
+ GIT_INDEX_FILE=$PWD/merge.index &&
+ export GIT_INDEX_FILE &&
+ test_must_fail git merge-index git-merge-one-file -a
+ )
+'
+
+test_expect_success 'merge-one-file respects GIT_WORK_TREE' '
+ (cd bare.git &&
+ mkdir work &&
+ GIT_WORK_TREE=$PWD/work &&
+ export GIT_WORK_TREE &&
+ GIT_INDEX_FILE=$PWD/merge.index &&
+ export GIT_INDEX_FILE &&
+ git merge-index git-merge-one-file -a &&
+ git cat-file blob :file >work/file-index
+ ) &&
+ test_cmp expect-merged bare.git/work/file &&
+ test_cmp expect-merged bare.git/work/file-index
+'
+
+test_expect_success 'merge-one-file respects core.worktree' '
+ mkdir subdir &&
+ git clone . subdir/child &&
+ (cd subdir &&
+ GIT_DIR=$PWD/child/.git &&
+ export GIT_DIR &&
+ git config core.worktree "$PWD/child" &&
+ git read-tree -i -m base ten two &&
+ git merge-index git-merge-one-file -a &&
+ git cat-file blob :file >file-index
+ ) &&
+ test_cmp expect-merged subdir/child/file &&
+ test_cmp expect-merged subdir/file-index
+'
+
+test_done
diff --git a/t/t6110-rev-list-sparse.sh b/t/t6110-rev-list-sparse.sh
new file mode 100755
index 0000000..656ac7f
--- /dev/null
+++ b/t/t6110-rev-list-sparse.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='operations that cull histories in unusual ways'
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit A &&
+ test_commit B &&
+ test_commit C &&
+ git checkout -b side HEAD^ &&
+ test_commit D &&
+ test_commit E &&
+ git merge master
+'
+
+test_expect_success 'rev-list --first-parent --boundary' '
+ git rev-list --first-parent --boundary HEAD^..
+'
+
+test_done
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 876d1ab..f67aa6f 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -124,7 +124,7 @@ warning: tag 'A' is really 'Q' here
EOF
check_describe A-* HEAD
test_expect_success 'warning was displayed for Q' '
- test_cmp err.expect err.actual
+ test_i18ncmp err.expect err.actual
'
test_expect_success 'rename tag Q back to A' '
mv .git/refs/tags/Q .git/refs/tags/A
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index 42f8ece..9a16806 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -70,14 +70,13 @@ test_expect_success setup '
i=$(($i+1))
done &&
- git show-branch
-'
+ git show-branch &&
-cat >expected <<\EOF
-Merge branch 'left'
-EOF
+ apos="'\''"
+'
-test_expect_success 'merge-msg test #1' '
+test_expect_success 'message for merging local branch' '
+ echo "Merge branch ${apos}left${apos}" >expected &&
git checkout master &&
git fetch . left &&
@@ -86,11 +85,8 @@ test_expect_success 'merge-msg test #1' '
test_cmp expected actual
'
-cat >expected <<EOF
-Merge branch 'left' of $(pwd)
-EOF
-
-test_expect_success 'merge-msg test #2' '
+test_expect_success 'message for merging external branch' '
+ echo "Merge branch ${apos}left${apos} of $(pwd)" >expected &&
git checkout master &&
git fetch "$(pwd)" left &&
@@ -99,139 +95,231 @@ test_expect_success 'merge-msg test #2' '
test_cmp expected actual
'
-cat >expected <<\EOF
-Merge branch 'left'
+test_expect_success '[merge] summary/log configuration' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
-* left:
- Left #5
- Left #4
- Left #3
- Common #2
- Common #1
-EOF
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
-test_expect_success 'merge-msg test #3-1' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
git config merge.log true &&
+ test_might_fail git config --unset-all merge.summary &&
git checkout master &&
test_tick &&
git fetch . left &&
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
- test_cmp expected actual
-'
-
-test_expect_success 'merge-msg test #3-2' '
+ git fmt-merge-msg <.git/FETCH_HEAD >actual1 &&
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary true &&
git checkout master &&
test_tick &&
git fetch . left &&
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ git fmt-merge-msg <.git/FETCH_HEAD >actual2 &&
+
+ test_cmp expected actual1 &&
+ test_cmp expected actual2
+'
+
+test_expect_success 'setup: clear [merge] configuration' '
+ test_might_fail git config --unset-all merge.log &&
+ test_might_fail git config --unset-all merge.summary
+'
+
+test_expect_success 'setup FETCH_HEAD' '
+ git checkout master &&
+ test_tick &&
+ git fetch . left
+'
+
+test_expect_success 'merge.log=3 limits shortlog length' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
+
+ * left: (5 commits)
+ Left #5
+ Left #4
+ Left #3
+ ...
+ EOF
+
+ git -c merge.log=3 fmt-merge-msg <.git/FETCH_HEAD >actual &&
test_cmp expected actual
'
-cat >expected <<\EOF
-Merge branches 'left' and 'right'
+test_expect_success 'merge.log=5 shows all 5 commits' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
-* left:
- Left #5
- Left #4
- Left #3
- Common #2
- Common #1
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
-* right:
- Right #5
- Right #4
- Right #3
- Common #2
- Common #1
-EOF
+ git -c merge.log=5 fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
-test_expect_success 'merge-msg test #4-1' '
+test_expect_success 'merge.log=0 disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected
+ git -c merge.log=0 fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
- git config --unset-all merge.log
- git config --unset-all merge.summary
- git config merge.log true &&
+test_expect_success '--log=3 limits shortlog length' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
- git checkout master &&
- test_tick &&
- git fetch . left right &&
+ * left: (5 commits)
+ Left #5
+ Left #4
+ Left #3
+ ...
+ EOF
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+ git fmt-merge-msg --log=3 <.git/FETCH_HEAD >actual &&
test_cmp expected actual
'
-test_expect_success 'merge-msg test #4-2' '
+test_expect_success '--log=5 shows all 5 commits' '
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}
- git config --unset-all merge.log
- git config --unset-all merge.summary
- git config merge.summary true &&
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
- git checkout master &&
- test_tick &&
- git fetch . left right &&
+ git fmt-merge-msg --log=5 <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+test_expect_success '--no-log disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected &&
+ git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual &&
test_cmp expected actual
'
-test_expect_success 'merge-msg test #5-1' '
+test_expect_success '--log=0 disables shortlog' '
+ echo "Merge branch ${apos}left${apos}" >expected &&
+ git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+'
- git config --unset-all merge.log
- git config --unset-all merge.summary
- git config merge.log yes &&
+test_expect_success 'fmt-merge-msg -m' '
+ echo "Sync with left" >expected &&
+ cat >expected.log <<-EOF &&
+ Sync with left
+
+ * ${apos}left${apos} of $(pwd):
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
+
+ test_might_fail git config --unset merge.log &&
+ test_might_fail git config --unset merge.summary &&
+ git checkout master &&
+ git fetch "$(pwd)" left &&
+ git fmt-merge-msg -m "Sync with left" <.git/FETCH_HEAD >actual &&
+ git fmt-merge-msg --log -m "Sync with left" \
+ <.git/FETCH_HEAD >actual.log &&
+ git config merge.log true &&
+ git fmt-merge-msg -m "Sync with left" \
+ <.git/FETCH_HEAD >actual.log-config &&
+ git fmt-merge-msg --no-log -m "Sync with left" \
+ <.git/FETCH_HEAD >actual.nolog &&
+
+ test_cmp expected actual &&
+ test_cmp expected.log actual.log &&
+ test_cmp expected.log actual.log-config &&
+ test_cmp expected actual.nolog
+'
+
+test_expect_success 'setup: expected shortlog for two branches' '
+ cat >expected <<-EOF
+ Merge branches ${apos}left${apos} and ${apos}right${apos}
+
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+
+ * right:
+ Right #5
+ Right #4
+ Right #3
+ Common #2
+ Common #1
+ EOF
+'
+test_expect_success 'shortlog for two branches' '
+ git config merge.log true &&
+ test_might_fail git config --unset-all merge.summary &&
git checkout master &&
test_tick &&
git fetch . left right &&
+ git fmt-merge-msg <.git/FETCH_HEAD >actual1 &&
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
- test_cmp expected actual
-'
+ test_might_fail git config --unset-all merge.log &&
+ git config merge.summary true &&
+ git checkout master &&
+ test_tick &&
+ git fetch . left right &&
+ git fmt-merge-msg <.git/FETCH_HEAD >actual2 &&
-test_expect_success 'merge-msg test #5-2' '
+ git config merge.log yes &&
+ test_might_fail git config --unset-all merge.summary &&
+ git checkout master &&
+ test_tick &&
+ git fetch . left right &&
+ git fmt-merge-msg <.git/FETCH_HEAD >actual3 &&
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
-
git checkout master &&
test_tick &&
git fetch . left right &&
+ git fmt-merge-msg <.git/FETCH_HEAD >actual4 &&
- git fmt-merge-msg <.git/FETCH_HEAD >actual &&
- test_cmp expected actual
+ test_cmp expected actual1 &&
+ test_cmp expected actual2 &&
+ test_cmp expected actual3 &&
+ test_cmp expected actual4
'
test_expect_success 'merge-msg -F' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
-
git checkout master &&
test_tick &&
git fetch . left right &&
-
git fmt-merge-msg -F .git/FETCH_HEAD >actual &&
test_cmp expected actual
'
test_expect_success 'merge-msg -F in subdirectory' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
-
git checkout master &&
test_tick &&
git fetch . left right &&
@@ -245,11 +333,11 @@ test_expect_success 'merge-msg -F in subdirectory' '
'
test_expect_success 'merge-msg with nothing to merge' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
+ >empty &&
+
(
cd remote &&
git checkout -b unrelated &&
@@ -258,22 +346,20 @@ test_expect_success 'merge-msg with nothing to merge' '
git fmt-merge-msg <.git/FETCH_HEAD >../actual
) &&
- test_cmp /dev/null actual
+ test_cmp empty actual
'
-cat >expected <<\EOF
-Merge tag 'tag-r3'
-
-* tag 'tag-r3':
- Right #3
- Common #2
- Common #1
-EOF
-
test_expect_success 'merge-msg tag' '
+ cat >expected <<-EOF &&
+ Merge tag ${apos}tag-r3${apos}
+
+ * tag ${apos}tag-r3${apos}:
+ Right #3
+ Common #2
+ Common #1
+ EOF
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
git checkout master &&
@@ -284,26 +370,24 @@ test_expect_success 'merge-msg tag' '
test_cmp expected actual
'
-cat >expected <<\EOF
-Merge tags 'tag-r3' and 'tag-l5'
-
-* tag 'tag-r3':
- Right #3
- Common #2
- Common #1
-
-* tag 'tag-l5':
- Left #5
- Left #4
- Left #3
- Common #2
- Common #1
-EOF
-
test_expect_success 'merge-msg two tags' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ cat >expected <<-EOF &&
+ Merge tags ${apos}tag-r3${apos} and ${apos}tag-l5${apos}
+
+ * tag ${apos}tag-r3${apos}:
+ Right #3
+ Common #2
+ Common #1
+
+ * tag ${apos}tag-l5${apos}:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
+
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
git checkout master &&
@@ -314,26 +398,24 @@ test_expect_success 'merge-msg two tags' '
test_cmp expected actual
'
-cat >expected <<\EOF
-Merge branch 'left', tag 'tag-r3'
-
-* tag 'tag-r3':
- Right #3
- Common #2
- Common #1
-
-* left:
- Left #5
- Left #4
- Left #3
- Common #2
- Common #1
-EOF
-
test_expect_success 'merge-msg tag and branch' '
-
- git config --unset-all merge.log
- git config --unset-all merge.summary
+ cat >expected <<-EOF &&
+ Merge branch ${apos}left${apos}, tag ${apos}tag-r3${apos}
+
+ * tag ${apos}tag-r3${apos}:
+ Right #3
+ Common #2
+ Common #1
+
+ * left:
+ Left #5
+ Left #4
+ Left #3
+ Common #2
+ Common #1
+ EOF
+
+ test_might_fail git config --unset-all merge.log &&
git config merge.summary yes &&
git checkout master &&
@@ -344,26 +426,27 @@ test_expect_success 'merge-msg tag and branch' '
test_cmp expected actual
'
-cat >expected <<\EOF
-Merge branch 'long'
-
-* long: (35 commits)
-EOF
-
test_expect_success 'merge-msg lots of commits' '
+ {
+ cat <<-EOF &&
+ Merge branch ${apos}long${apos}
+
+ * long: (35 commits)
+ EOF
+
+ i=29 &&
+ while test $i -gt 9
+ do
+ echo " $i" &&
+ i=$(($i-1))
+ done &&
+ echo " ..."
+ } >expected &&
git checkout master &&
test_tick &&
git fetch . long &&
- i=29 &&
- while test $i -gt 9
- do
- echo " $i" &&
- i=$(($i-1))
- done >>expected &&
- echo " ..." >>expected
-
git fmt-merge-msg <.git/FETCH_HEAD >actual &&
test_cmp expected actual
'
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
new file mode 100755
index 0000000..82f3639
--- /dev/null
+++ b/t/t6500-gc.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='basic git gc tests
+'
+
+. ./test-lib.sh
+
+test_expect_success 'gc empty repository' '
+ git gc
+'
+
+test_expect_success 'gc --gobbledegook' '
+ test_expect_code 129 git gc --nonsense 2>err &&
+ grep "[Uu]sage: git gc" err
+'
+
+test_expect_success 'gc -h with invalid configuration' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ echo "[gc] pruneexpire = CORRUPT" >>.git/config &&
+ test_expect_code 129 git gc -h >usage 2>&1
+ ) &&
+ grep "[Uu]sage" broken/usage
+'
+
+test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 65a35d9..a845b15 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -61,7 +61,7 @@ test_expect_success \
test_expect_success \
'checking -f on untracked file with existing target' \
'touch path0/untracked1 &&
- git mv -f untracked1 path0
+ test_must_fail git mv -f untracked1 path0 &&
test ! -f .git/index.lock &&
test -f untracked1 &&
test -f path0/untracked1'
@@ -207,7 +207,7 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' '
git init &&
echo 1 >dirty &&
git add dirty &&
- entry="$(git ls-files --stage dirty | cut -f 1)"
+ entry="$(git ls-files --stage dirty | cut -f 1)" &&
git mv dirty dirty2 &&
[ "$entry" = "$(git ls-files --stage dirty2 | cut -f 1)" ] &&
echo 2 >dirty2 &&
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 0da13a8..e022773 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -3,31 +3,34 @@
test_description='git filter-branch'
. ./test-lib.sh
-make_commit () {
- lower=$(echo $1 | tr '[A-Z]' '[a-z]')
- echo $lower > $lower
- git add $lower
- test_tick
- git commit -m $1
- git tag $1
-}
-
test_expect_success 'setup' '
- make_commit A
- make_commit B
- git checkout -b branch B
- make_commit D
- mkdir dir
- make_commit dir/D
- make_commit E
- git checkout master
- make_commit C
- git checkout branch
- git merge C
- git tag F
- make_commit G
- make_commit H
-'
+ test_commit A &&
+ test_commit B &&
+ git checkout -b branch B &&
+ test_commit D &&
+ mkdir dir &&
+ test_commit dir/D &&
+ test_commit E &&
+ git checkout master &&
+ test_commit C &&
+ git checkout branch &&
+ git merge C &&
+ git tag F &&
+ test_commit G &&
+ test_commit H
+'
+# * (HEAD, branch) H
+# * G
+# * Merge commit 'C' into branch
+# |\
+# | * (master) C
+# * | E
+# * | dir/D
+# * | D
+# |/
+# * B
+# * A
+
H=$(git rev-parse H)
@@ -65,14 +68,14 @@ test_expect_success 'Fail if commit filter fails' '
'
test_expect_success 'rewrite, renaming a specific file' '
- git filter-branch -f --tree-filter "mv d doh || :" HEAD
+ git filter-branch -f --tree-filter "mv D.t doh || :" HEAD
'
test_expect_success 'test that the file was renamed' '
- test d = "$(git show HEAD:doh --)" &&
- ! test -f d &&
+ test D = "$(git show HEAD:doh --)" &&
+ ! test -f D.t &&
test -f doh &&
- test d = "$(cat doh)"
+ test D = "$(cat doh)"
'
test_expect_success 'rewrite, renaming a specific directory' '
@@ -80,18 +83,18 @@ test_expect_success 'rewrite, renaming a specific directory' '
'
test_expect_success 'test that the directory was renamed' '
- test dir/d = "$(git show HEAD:diroh/d --)" &&
+ test dir/D = "$(git show HEAD:diroh/D.t --)" &&
! test -d dir &&
test -d diroh &&
! test -d diroh/dir &&
- test -f diroh/d &&
- test dir/d = "$(cat diroh/d)"
+ test -f diroh/D.t &&
+ test dir/D = "$(cat diroh/D.t)"
'
git tag oldD HEAD~4
test_expect_success 'rewrite one branch, keeping a side branch' '
git branch modD oldD &&
- git filter-branch -f --tree-filter "mv b boh || :" D..modD
+ git filter-branch -f --tree-filter "mv B.t boh || :" D..modD
'
test_expect_success 'common ancestor is still common (unchanged)' '
@@ -104,13 +107,13 @@ test_expect_success 'filter subdirectory only' '
git add subdir/new &&
test_tick &&
git commit -m "subdir" &&
- echo H > a &&
+ echo H > A.t &&
test_tick &&
- git commit -m "not subdir" a &&
+ git commit -m "not subdir" A.t &&
echo A > subdir/new &&
test_tick &&
git commit -m "again subdir" subdir/new &&
- git rm a &&
+ git rm A.t &&
test_tick &&
git commit -m "again not subdir" &&
git branch sub &&
@@ -134,7 +137,7 @@ test_expect_success 'more setup' '
git add subdir/new &&
test_tick &&
git commit -m "subdir on master" subdir/new &&
- git rm a &&
+ git rm A.t &&
test_tick &&
git commit -m "again subdir on master" &&
git merge branch
@@ -143,11 +146,12 @@ test_expect_success 'more setup' '
test_expect_success 'use index-filter to move into a subdirectory' '
git branch directorymoved &&
git filter-branch -f --index-filter \
- "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+ "git ls-files -s | sed \"s- -&newsubdir/-\" |
GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv \"\$GIT_INDEX_FILE.new\" \"\$GIT_INDEX_FILE\"" directorymoved &&
- test -z "$(git diff HEAD directorymoved:newsubdir)"'
+ git diff --exit-code HEAD directorymoved:newsubdir
+'
test_expect_success 'stops when msg filter fails' '
old=$(git rev-parse HEAD) &&
@@ -282,8 +286,8 @@ test_expect_success 'Tag name filtering allows slashes in tag names' '
test_expect_success 'Prune empty commits' '
git rev-list HEAD > expect &&
- make_commit to_remove &&
- git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD &&
+ test_commit to_remove &&
+ git filter-branch -f --index-filter "git update-index --remove to_remove.t" --prune-empty HEAD &&
git rev-list HEAD > actual &&
test_cmp expect actual
'
@@ -306,6 +310,24 @@ test_expect_success '--remap-to-ancestor with filename filters' '
test $orig_invariant = $(git rev-parse invariant)
'
+test_expect_success 'automatic remapping to ancestor with filename filters' '
+ git checkout master &&
+ git reset --hard A &&
+ test_commit add-foo2 foo 1 &&
+ git branch moved-foo2 &&
+ test_commit add-bar2 bar a &&
+ git branch invariant2 &&
+ orig_invariant=$(git rev-parse invariant2) &&
+ git branch moved-bar2 &&
+ test_commit change-foo2 foo 2 &&
+ git filter-branch -f \
+ moved-foo2 moved-bar2 A..master \
+ -- -- foo &&
+ test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) &&
+ test $(git rev-parse moved-foo2) = $(git rev-parse master^) &&
+ test $orig_invariant = $(git rev-parse invariant2)
+'
+
test_expect_success 'setup submodule' '
rm -fr ?* .git &&
git init &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 73dbc43..097ce2b 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -257,6 +257,11 @@ test_expect_success \
test_cmp expect actual
'
+test_expect_success 'tag -l can accept multiple patterns' '
+ git tag -l "v1*" "v0*" >actual &&
+ test_cmp expect actual
+'
+
# creating and verifying lightweight tags:
test_expect_success \
@@ -583,7 +588,7 @@ test_expect_success \
# subsequent tests require gpg; check if it is available
gpg --version >/dev/null 2>/dev/null
if [ $? -eq 127 ]; then
- say "gpg not found - skipping tag signing and verification tests"
+ say "# gpg not found - skipping tag signing and verification tests"
else
# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
# the gpg version 1.0.6 didn't parse trust packets correctly, so for
@@ -1030,6 +1035,72 @@ test_expect_success GPG \
test_cmp expect actual
'
+# usage with rfc1991 signatures
+echo "rfc1991" > gpghome/gpg.conf
+get_tag_header rfc1991-signed-tag $commit commit $time >expect
+echo "RFC1991 signed tag" >>expect
+echo '-----BEGIN PGP MESSAGE-----' >>expect
+test_expect_success GPG \
+ 'creating a signed tag with rfc1991' '
+ git tag -s -m "RFC1991 signed tag" rfc1991-signed-tag $commit &&
+ get_tag_msg rfc1991-signed-tag >actual &&
+ test_cmp expect actual
+'
+
+cat >fakeeditor <<'EOF'
+#!/bin/sh
+cp "$1" actual
+EOF
+chmod +x fakeeditor
+
+test_expect_success GPG \
+ 'reediting a signed tag body omits signature' '
+ echo "RFC1991 signed tag" >expect &&
+ GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
+ test_cmp expect actual
+'
+
+test_expect_success GPG \
+ 'verifying rfc1991 signature' '
+ git tag -v rfc1991-signed-tag
+'
+
+test_expect_success GPG \
+ 'list tag with rfc1991 signature' '
+ echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
+ git tag -l -n1 rfc1991-signed-tag >actual &&
+ test_cmp expect actual &&
+ git tag -l -n2 rfc1991-signed-tag >actual &&
+ test_cmp expect actual &&
+ git tag -l -n999 rfc1991-signed-tag >actual &&
+ test_cmp expect actual
+'
+
+rm -f gpghome/gpg.conf
+
+test_expect_success GPG \
+ 'verifying rfc1991 signature without --rfc1991' '
+ git tag -v rfc1991-signed-tag
+'
+
+test_expect_success GPG \
+ 'list tag with rfc1991 signature without --rfc1991' '
+ echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
+ git tag -l -n1 rfc1991-signed-tag >actual &&
+ test_cmp expect actual &&
+ git tag -l -n2 rfc1991-signed-tag >actual &&
+ test_cmp expect actual &&
+ git tag -l -n999 rfc1991-signed-tag >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success GPG \
+ 'reediting a signed tag body omits signature' '
+ echo "RFC1991 signed tag" >expect &&
+ GIT_EDITOR=./fakeeditor git tag -f -s rfc1991-signed-tag $commit &&
+ test_cmp expect actual
+'
+
# try to sign with bad user.signingkey
git config user.signingkey BobTheMouse
test_expect_success GPG \
@@ -1051,13 +1122,22 @@ test_expect_success \
test_expect_success \
'message in editor has initial comment' '
- GIT_EDITOR=cat git tag -a initial-comment > actual
+ ! (GIT_EDITOR=cat git tag -a initial-comment > actual)
+'
+
+test_expect_success 'message in editor has initial comment: first line' '
# check the first line --- should be empty
- first=$(sed -e 1q <actual) &&
- test -z "$first" &&
+ echo >first.expect &&
+ sed -e 1q <actual >first.actual &&
+ test_i18ncmp first.expect first.actual
+'
+
+test_expect_success \
+ 'message in editor has initial comment: remainder' '
# remove commented lines from the remainder -- should be empty
- rest=$(sed -e 1d -e '/^#/d' <actual) &&
- test -z "$rest"
+ >rest.expect
+ sed -e 1d -e '/^#/d' <actual >rest.actual &&
+ test_cmp rest.expect rest.actual
'
get_tag_header reuse $commit commit $time >expect
@@ -1097,7 +1177,7 @@ hash1=$(git rev-parse HEAD)
test_expect_success 'creating second commit and tag' '
echo foo-2.0 >foo &&
git add foo &&
- git commit -m second
+ git commit -m second &&
git tag v2.0
'
@@ -1122,18 +1202,18 @@ v2.0
EOF
test_expect_success 'checking that first commit is in all tags (hash)' "
- git tag -l --contains $hash1 v* >actual
+ git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
"
# other ways of specifying the commit
test_expect_success 'checking that first commit is in all tags (tag)' "
- git tag -l --contains v1.0 v* >actual
+ git tag -l --contains v1.0 v* >actual &&
test_cmp expected actual
"
test_expect_success 'checking that first commit is in all tags (relative)' "
- git tag -l --contains HEAD~2 v* >actual
+ git tag -l --contains HEAD~2 v* >actual &&
test_cmp expected actual
"
@@ -1142,7 +1222,7 @@ v2.0
EOF
test_expect_success 'checking that second commit only has one tag' "
- git tag -l --contains $hash2 v* >actual
+ git tag -l --contains $hash2 v* >actual &&
test_cmp expected actual
"
@@ -1151,7 +1231,7 @@ cat > expected <<EOF
EOF
test_expect_success 'checking that third commit has no tags' "
- git tag -l --contains $hash3 v* >actual
+ git tag -l --contains $hash3 v* >actual &&
test_cmp expected actual
"
@@ -1161,7 +1241,7 @@ test_expect_success 'creating simple branch' '
git branch stable v2.0 &&
git checkout stable &&
echo foo-3.0 > foo &&
- git commit foo -m fourth
+ git commit foo -m fourth &&
git tag v3.0
'
@@ -1172,7 +1252,7 @@ v3.0
EOF
test_expect_success 'checking that branch head only has one tag' "
- git tag -l --contains $hash4 v* >actual
+ git tag -l --contains $hash4 v* >actual &&
test_cmp expected actual
"
@@ -1186,7 +1266,7 @@ v4.0
EOF
test_expect_success 'checking that original branch head has one tag now' "
- git tag -l --contains $hash3 v* >actual
+ git tag -l --contains $hash3 v* >actual &&
test_cmp expected actual
"
@@ -1201,18 +1281,18 @@ v4.0
EOF
test_expect_success 'checking that initial commit is in all tags' "
- git tag -l --contains $hash1 v* >actual
+ git tag -l --contains $hash1 v* >actual &&
test_cmp expected actual
"
# mixing modes and options:
test_expect_success 'mixing incompatibles modes and options is forbidden' '
- test_must_fail git tag -a
- test_must_fail git tag -l -v
- test_must_fail git tag -n 100
- test_must_fail git tag -l -m msg
- test_must_fail git tag -l -F some file
+ test_must_fail git tag -a &&
+ test_must_fail git tag -l -v &&
+ test_must_fail git tag -n 100 &&
+ test_must_fail git tag -l -m msg &&
+ test_must_fail git tag -l -F some file &&
test_must_fail git tag -v -s
'
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index fe60d69..1b530b5 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -111,13 +111,13 @@ do
'
done
-if ! echo 'echo space > "$1"' > "e space.sh"
+if echo 'echo space > "$1"' > "e space.sh"
then
- say "Skipping; FS does not support spaces in filenames"
- test_done
+ # FS supports spaces in filenames
+ test_set_prereq SPACES_IN_FILENAMES
fi
-test_expect_success 'editor with a space' '
+test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
chmod a+x "e space.sh" &&
GIT_EDITOR="./e\ space.sh" git commit --amend &&
@@ -126,7 +126,7 @@ test_expect_success 'editor with a space' '
'
unset GIT_EDITOR
-test_expect_success 'core.editor with a space' '
+test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' '
git config core.editor \"./e\ space.sh\" &&
git commit --amend &&
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index c2a3c8e..ed7575d 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -4,44 +4,15 @@ test_description='Test automatic use of a pager.'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-pager.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
cleanup_fail() {
echo >&2 cleanup failed
(exit 1)
}
-test_expect_success 'set up terminal for tests' '
- rm -f stdout_is_tty ||
- cleanup_fail &&
-
- if test -t 1
- then
- >stdout_is_tty
- elif
- test_have_prereq PERL &&
- "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \
- sh -c "test -t 1"
- then
- >test_terminal_works
- fi
-'
-
-if test -e stdout_is_tty
-then
- test_terminal() { "$@"; }
- test_set_prereq TTY
-elif test -e test_terminal_works
-then
- test_terminal() {
- "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl "$@"
- }
- test_set_prereq TTY
-else
- say no usable terminal, so skipping some tests
-fi
-
test_expect_success 'setup' '
- unset GIT_PAGER GIT_PAGER_IN_USE;
+ sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
test_might_fail git config --unset core.pager &&
PAGER="cat >paginated.out" &&
@@ -58,6 +29,21 @@ test_expect_success TTY 'some commands use a pager' '
test -e paginated.out
'
+test_expect_failure TTY 'pager runs from subdir' '
+ echo subdir/paginated.out >expected &&
+ mkdir -p subdir &&
+ rm -f paginated.out subdir/paginated.out &&
+ (
+ cd subdir &&
+ test_terminal git log
+ ) &&
+ {
+ ls paginated.out subdir/paginated.out ||
+ :
+ } >actual &&
+ test_cmp expected actual
+'
+
test_expect_success TTY 'some commands do not use a pager' '
rm -f paginated.out ||
cleanup_fail &&
@@ -106,6 +92,45 @@ test_expect_success TTY 'no pager with --no-pager' '
! test -e paginated.out
'
+test_expect_success TTY 'configuration can disable pager' '
+ rm -f paginated.out &&
+ test_might_fail git config --unset pager.grep &&
+ test_terminal git grep initial &&
+ test -e paginated.out &&
+
+ rm -f paginated.out &&
+ git config pager.grep false &&
+ test_when_finished "git config --unset pager.grep" &&
+ test_terminal git grep initial &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config uses a pager if configured to' '
+ rm -f paginated.out &&
+ git config pager.config true &&
+ test_when_finished "git config --unset pager.config" &&
+ test_terminal git config --list &&
+ test -e paginated.out
+'
+
+test_expect_success TTY 'configuration can enable pager (from subdir)' '
+ rm -f paginated.out &&
+ mkdir -p subdir &&
+ git config pager.bundle true &&
+ test_when_finished "git config --unset pager.bundle" &&
+
+ git bundle create test.bundle --all &&
+ rm -f paginated.out subdir/paginated.out &&
+ (
+ cd subdir &&
+ test_terminal git bundle unbundle ../test.bundle
+ ) &&
+ {
+ test -e paginated.out ||
+ test -e subdir/paginated.out
+ }
+'
+
# A colored commit log will begin with an appropriate ANSI escape
# for the first color; the text "commit" comes later.
colorful() {
@@ -159,63 +184,250 @@ test_expect_success 'color when writing to a file intended for a pager' '
colorful colorful.log
'
-if test_have_prereq SIMPLEPAGER && test_have_prereq TTY
-then
- test_set_prereq SIMPLEPAGERTTY
-fi
+# Use this helper to make it easy for the caller of your
+# terminal-using function to specify whether it should fail.
+# If you write
+#
+# your_test() {
+# parse_args "$@"
+#
+# $test_expectation "$cmd - behaves well" "
+# ...
+# $full_command &&
+# ...
+# "
+# }
+#
+# then your test can be used like this:
+#
+# your_test expect_(success|failure) [test_must_fail] 'git foo'
+#
+parse_args() {
+ test_expectation="test_$1"
+ shift
+ if test "$1" = test_must_fail
+ then
+ full_command="test_must_fail test_terminal "
+ shift
+ else
+ full_command="test_terminal "
+ fi
+ cmd=$1
+ full_command="$full_command $1"
+}
-test_expect_success SIMPLEPAGERTTY 'default pager is used by default' '
- unset PAGER GIT_PAGER;
- test_might_fail git config --unset core.pager &&
- rm -f default_pager_used ||
- cleanup_fail &&
+test_default_pager() {
+ parse_args "$@"
+
+ $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
+ sane_unset PAGER GIT_PAGER &&
+ test_might_fail git config --unset core.pager &&
+ rm -f default_pager_used ||
+ cleanup_fail &&
+
+ cat >\$less <<-\EOF &&
+ #!/bin/sh
+ wc >default_pager_used
+ EOF
+ chmod +x \$less &&
+ (
+ PATH=.:\$PATH &&
+ export PATH &&
+ $full_command
+ ) &&
+ test -e default_pager_used
+ "
+}
- cat >$less <<-\EOF &&
- #!/bin/sh
- wc >default_pager_used
- EOF
- chmod +x $less &&
- (
- PATH=.:$PATH &&
- export PATH &&
- test_terminal git log
- ) &&
- test -e default_pager_used
-'
+test_PAGER_overrides() {
+ parse_args "$@"
-test_expect_success TTY 'PAGER overrides default pager' '
- unset GIT_PAGER;
- test_might_fail git config --unset core.pager &&
- rm -f PAGER_used ||
- cleanup_fail &&
+ $test_expectation TTY "$cmd - PAGER overrides default pager" "
+ sane_unset GIT_PAGER &&
+ test_might_fail git config --unset core.pager &&
+ rm -f PAGER_used ||
+ cleanup_fail &&
- PAGER="wc >PAGER_used" &&
- export PAGER &&
- test_terminal git log &&
- test -e PAGER_used
+ PAGER='wc >PAGER_used' &&
+ export PAGER &&
+ $full_command &&
+ test -e PAGER_used
+ "
+}
+
+test_core_pager_overrides() {
+ if_local_config=
+ used_if_wanted='overrides PAGER'
+ test_core_pager "$@"
+}
+
+test_local_config_ignored() {
+ if_local_config='! '
+ used_if_wanted='is not used'
+ test_core_pager "$@"
+}
+
+test_core_pager() {
+ parse_args "$@"
+
+ $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
+ sane_unset GIT_PAGER &&
+ rm -f core.pager_used ||
+ cleanup_fail &&
+
+ PAGER=wc &&
+ export PAGER &&
+ git config core.pager 'wc >core.pager_used' &&
+ $full_command &&
+ ${if_local_config}test -e core.pager_used
+ "
+}
+
+test_core_pager_subdir() {
+ if_local_config=
+ used_if_wanted='overrides PAGER'
+ test_pager_subdir_helper "$@"
+}
+
+test_no_local_config_subdir() {
+ if_local_config='! '
+ used_if_wanted='is not used'
+ test_pager_subdir_helper "$@"
+}
+
+test_pager_subdir_helper() {
+ parse_args "$@"
+
+ $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
+ sane_unset GIT_PAGER &&
+ rm -f core.pager_used &&
+ rm -fr sub ||
+ cleanup_fail &&
+
+ PAGER=wc &&
+ stampname=\$(pwd)/core.pager_used &&
+ export PAGER stampname &&
+ git config core.pager 'wc >\"\$stampname\"' &&
+ mkdir sub &&
+ (
+ cd sub &&
+ $full_command
+ ) &&
+ ${if_local_config}test -e core.pager_used
+ "
+}
+
+test_GIT_PAGER_overrides() {
+ parse_args "$@"
+
+ $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" "
+ rm -f GIT_PAGER_used ||
+ cleanup_fail &&
+
+ git config core.pager wc &&
+ GIT_PAGER='wc >GIT_PAGER_used' &&
+ export GIT_PAGER &&
+ $full_command &&
+ test -e GIT_PAGER_used
+ "
+}
+
+test_doesnt_paginate() {
+ parse_args "$@"
+
+ $test_expectation TTY "no pager for '$cmd'" "
+ rm -f GIT_PAGER_used ||
+ cleanup_fail &&
+
+ GIT_PAGER='wc >GIT_PAGER_used' &&
+ export GIT_PAGER &&
+ $full_command &&
+ ! test -e GIT_PAGER_used
+ "
+}
+
+test_pager_choices() {
+ test_default_pager expect_success "$@"
+ test_PAGER_overrides expect_success "$@"
+ test_core_pager_overrides expect_success "$@"
+ test_core_pager_subdir expect_success "$@"
+ test_GIT_PAGER_overrides expect_success "$@"
+}
+
+test_expect_success 'setup: some aliases' '
+ git config alias.aliasedlog log &&
+ git config alias.true "!true"
'
-test_expect_success TTY 'core.pager overrides PAGER' '
- unset GIT_PAGER;
- rm -f core.pager_used ||
- cleanup_fail &&
+test_pager_choices 'git log'
+test_pager_choices 'git -p log'
+test_pager_choices 'git aliasedlog'
+
+test_default_pager expect_success 'git -p aliasedlog'
+test_PAGER_overrides expect_success 'git -p aliasedlog'
+test_core_pager_overrides expect_success 'git -p aliasedlog'
+test_core_pager_subdir expect_failure 'git -p aliasedlog'
+test_GIT_PAGER_overrides expect_success 'git -p aliasedlog'
+
+test_default_pager expect_success 'git -p true'
+test_PAGER_overrides expect_success 'git -p true'
+test_core_pager_overrides expect_success 'git -p true'
+test_core_pager_subdir expect_failure 'git -p true'
+test_GIT_PAGER_overrides expect_success 'git -p true'
+
+test_default_pager expect_success test_must_fail 'git -p request-pull'
+test_PAGER_overrides expect_success test_must_fail 'git -p request-pull'
+test_core_pager_overrides expect_success test_must_fail 'git -p request-pull'
+test_core_pager_subdir expect_failure test_must_fail 'git -p request-pull'
+test_GIT_PAGER_overrides expect_success test_must_fail 'git -p request-pull'
+
+test_default_pager expect_success test_must_fail 'git -p'
+test_PAGER_overrides expect_success test_must_fail 'git -p'
+test_local_config_ignored expect_failure test_must_fail 'git -p'
+test_no_local_config_subdir expect_success test_must_fail 'git -p'
+test_GIT_PAGER_overrides expect_success test_must_fail 'git -p'
+
+test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense'
+
+test_pager_choices 'git shortlog'
+test_expect_success 'setup: configure shortlog not to paginate' '
+ git config pager.shortlog false
+'
+test_doesnt_paginate expect_success 'git shortlog'
+test_no_local_config_subdir expect_success 'git shortlog'
+test_default_pager expect_success 'git -p shortlog'
+test_core_pager_subdir expect_success 'git -p shortlog'
- PAGER=wc &&
- export PAGER &&
- git config core.pager "wc >core.pager_used" &&
- test_terminal git log &&
- test -e core.pager_used
+test_core_pager_subdir expect_success test_must_fail \
+ 'git -p apply </dev/null'
+
+test_expect_success TTY 'command-specific pager' '
+ unset PAGER GIT_PAGER;
+ echo "foo:initial" >expect &&
+ >actual &&
+ git config --unset core.pager &&
+ git config pager.log "sed s/^/foo:/ >actual" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
'
-test_expect_success TTY 'GIT_PAGER overrides core.pager' '
- rm -f GIT_PAGER_used ||
- cleanup_fail &&
+test_expect_success TTY 'command-specific pager overrides core.pager' '
+ unset PAGER GIT_PAGER;
+ echo "foo:initial" >expect &&
+ >actual &&
+ git config core.pager "exit 1"
+ git config pager.log "sed s/^/foo:/ >actual" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
+'
- git config core.pager wc &&
- GIT_PAGER="wc >GIT_PAGER_used" &&
- export GIT_PAGER &&
- test_terminal git log &&
- test -e GIT_PAGER_used
+test_expect_success TTY 'command-specific pager overridden by environment' '
+ GIT_PAGER="sed s/^/foo:/ >actual" && export GIT_PAGER &&
+ >actual &&
+ echo "foo:initial" >expect &&
+ git config pager.log "exit 1" &&
+ test_terminal git log --format=%s -1 &&
+ test_cmp expect actual
'
test_done
diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh
index eb8ca88..e058d18 100755
--- a/t/t7008-grep-binary.sh
+++ b/t/t7008-grep-binary.sh
@@ -5,7 +5,7 @@ test_description='git grep in binary files'
. ./test-lib.sh
test_expect_success 'setup' "
- printf 'binary\000file\n' >a &&
+ echo 'binaryQfile' | q_to_nul >a &&
git add a &&
git commit -m.
"
@@ -61,7 +61,7 @@ test_expect_success 'git grep -Fi iLE a' '
# This test actually passes on platforms where regexec() supports the
# flag REG_STARTEND.
-test_expect_failure 'git grep ile a' '
+test_expect_success 'git grep ile a' '
git grep ile a
'
@@ -70,32 +70,32 @@ test_expect_failure 'git grep .fi a' '
'
test_expect_success 'git grep -F y<NUL>f a' "
- printf 'y\000f' >f &&
+ printf 'yQf' | q_to_nul >f &&
git grep -f f -F a
"
test_expect_success 'git grep -F y<NUL>x a' "
- printf 'y\000x' >f &&
+ printf 'yQx' | q_to_nul >f &&
test_must_fail git grep -f f -F a
"
test_expect_success 'git grep -Fi Y<NUL>f a' "
- printf 'Y\000f' >f &&
+ printf 'YQf' | q_to_nul >f &&
git grep -f f -Fi a
"
test_expect_failure 'git grep -Fi Y<NUL>x a' "
- printf 'Y\000x' >f &&
+ printf 'YQx' | q_to_nul >f &&
test_must_fail git grep -f f -Fi a
"
test_expect_success 'git grep y<NUL>f a' "
- printf 'y\000f' >f &&
+ printf 'yQf' | q_to_nul >f &&
git grep -f f a
"
test_expect_failure 'git grep y<NUL>x a' "
- printf 'y\000x' >f &&
+ printf 'yQx' | q_to_nul >f &&
test_must_fail git grep -f f a
"
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
index bb4066f..8f3b54d 100755
--- a/t/t7011-skip-worktree-reading.sh
+++ b/t/t7011-skip-worktree-reading.sh
@@ -24,7 +24,7 @@ H sub/2
EOF
NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-ZERO_SHA0=0000000000000000000000000000000000000000
+
setup_absent() {
test -f 1 && rm 1
git update-index --remove 1 &&
@@ -120,7 +120,7 @@ test_expect_success 'grep with skip-worktree file' '
test "$(git grep --no-ext-grep test)" = "1:test"
'
-echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected
+echo ":000000 100644 $_z40 $NULL_SHA1 A 1" > expected
test_expect_success 'diff-index does not examine skip-worktree absent entries' '
setup_absent &&
git diff-index HEAD -- 1 > result &&
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
index 582d0b5..9ceaa40 100755
--- a/t/t7012-skip-worktree-writing.sh
+++ b/t/t7012-skip-worktree-writing.sh
@@ -54,7 +54,7 @@ test_expect_success 'read-tree removes worktree, dirty case' '
'
NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-ZERO_SHA0=0000000000000000000000000000000000000000
+
setup_absent() {
test -f 1 && rm 1
git update-index --remove 1 &&
@@ -127,13 +127,13 @@ EOF
test_expect_success 'git-clean, absent case' '
setup_absent &&
git clean -n > result &&
- test_cmp expected result
+ test_i18ncmp expected result
'
test_expect_success 'git-clean, dirty case' '
setup_dirty &&
git clean -n > result &&
- test_cmp expected result
+ test_i18ncmp expected result
'
#TODO test_expect_failure 'git-apply adds file' false
diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh
index fcac472..b8cb490 100755
--- a/t/t7060-wtstatus.sh
+++ b/t/t7060-wtstatus.sh
@@ -50,10 +50,72 @@ test_expect_success 'M/D conflict does not segfault' '
git commit -m delete &&
test_must_fail git merge master &&
test_must_fail git commit --dry-run >../actual &&
- test_cmp ../expect ../actual &&
+ test_i18ncmp ../expect ../actual &&
git status >../actual &&
- test_cmp ../expect ../actual
+ test_i18ncmp ../expect ../actual
)
'
+test_expect_success 'rename & unmerged setup' '
+ git rm -f -r . &&
+ cat "$TEST_DIRECTORY/README" >ONE &&
+ git add ONE &&
+ test_tick &&
+ git commit -m "One commit with ONE" &&
+
+ echo Modified >TWO &&
+ cat ONE >>TWO &&
+ cat ONE >>THREE &&
+ git add TWO THREE &&
+ sha1=$(git rev-parse :ONE) &&
+ git rm --cached ONE &&
+ (
+ echo "100644 $sha1 1 ONE" &&
+ echo "100644 $sha1 2 ONE" &&
+ echo "100644 $sha1 3 ONE"
+ ) | git update-index --index-info &&
+ echo Further >>THREE
+'
+
+test_expect_success 'rename & unmerged status' '
+ git status -suno >actual &&
+ cat >expect <<-EOF &&
+ UU ONE
+ AM THREE
+ A TWO
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'git diff-index --cached shows 2 added + 1 unmerged' '
+ cat >expected <<-EOF &&
+ U ONE
+ A THREE
+ A TWO
+ EOF
+ git diff-index --cached --name-status HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git diff-index --cached -M shows 2 added + 1 unmerged' '
+ cat >expected <<-EOF &&
+ U ONE
+ A THREE
+ A TWO
+ EOF
+ git diff-index --cached --name-status HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git diff-index --cached -C shows 2 copies + 1 unmerged' '
+ cat >expected <<-EOF &&
+ U ONE
+ C ONE THREE
+ C ONE TWO
+ EOF
+ git diff-index --cached -C --name-status HEAD |
+ sed "s/^C[0-9]*/C/g" >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index b8cf260..f1cfc9a 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -426,7 +426,7 @@ EOF
test_expect_success '--mixed refreshes the index' '
echo 123 >> file2 &&
git reset --mixed HEAD > output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
test_expect_success 'disambiguation (1)' '
diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh
index c1f4fc3..95fab20 100755
--- a/t/t7105-reset-patch.sh
+++ b/t/t7105-reset-patch.sh
@@ -3,7 +3,7 @@
test_description='git reset --patch'
. ./lib-patch-mode.sh
-test_expect_success 'setup' '
+test_expect_success PERL 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
@@ -17,20 +17,20 @@ test_expect_success 'setup' '
# note: bar sorts before foo, so the first 'n' is always to skip 'bar'
-test_expect_success 'saying "n" does nothing' '
- set_and_save_state dir/foo work work
+test_expect_success PERL 'saying "n" does nothing' '
+ set_and_save_state dir/foo work work &&
(echo n; echo n) | git reset -p &&
verify_saved_state dir/foo &&
verify_saved_state bar
'
-test_expect_success 'git reset -p' '
+test_expect_success PERL 'git reset -p' '
(echo n; echo y) | git reset -p &&
verify_state dir/foo work head &&
verify_saved_state bar
'
-test_expect_success 'git reset -p HEAD^' '
+test_expect_success PERL 'git reset -p HEAD^' '
(echo n; echo y) | git reset -p HEAD^ &&
verify_state dir/foo work parent &&
verify_saved_state bar
@@ -41,27 +41,27 @@ test_expect_success 'git reset -p HEAD^' '
# dir/foo. There's always an extra 'n' to reject edits to dir/foo in
# the failure case (and thus get out of the loop).
-test_expect_success 'git reset -p dir' '
- set_state dir/foo work work
+test_expect_success PERL 'git reset -p dir' '
+ set_state dir/foo work work &&
(echo y; echo n) | git reset -p dir &&
verify_state dir/foo work head &&
verify_saved_state bar
'
-test_expect_success 'git reset -p -- foo (inside dir)' '
- set_state dir/foo work work
+test_expect_success PERL 'git reset -p -- foo (inside dir)' '
+ set_state dir/foo work work &&
(echo y; echo n) | (cd dir && git reset -p -- foo) &&
verify_state dir/foo work head &&
verify_saved_state bar
'
-test_expect_success 'git reset -p HEAD^ -- dir' '
+test_expect_success PERL 'git reset -p HEAD^ -- dir' '
(echo y; echo n) | git reset -p HEAD^ -- dir &&
verify_state dir/foo work parent &&
verify_saved_state bar
'
-test_expect_success 'none of this moved HEAD' '
+test_expect_success PERL 'none of this moved HEAD' '
verify_saved_head
'
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
index 70cdd8e..a82a07a 100755
--- a/t/t7110-reset-merge.sh
+++ b/t/t7110-reset-merge.sh
@@ -237,7 +237,7 @@ test_expect_success '"reset --keep HEAD^" fails with pending merge' '
git reset --hard third &&
test_must_fail git merge branch1 &&
test_must_fail git reset --keep HEAD^ 2>err.log &&
- grep "middle of a merge" err.log
+ test_i18ngrep "middle of a merge" err.log
'
# The next test will test the following:
@@ -263,7 +263,7 @@ test_expect_success '"reset --keep HEAD" fails with pending merge' '
git reset --hard third &&
test_must_fail git merge branch1 &&
test_must_fail git reset --keep HEAD 2>err.log &&
- grep "middle of a merge" err.log
+ test_i18ngrep "middle of a merge" err.log
'
test_expect_success '--merge is ok with added/deleted merge' '
@@ -289,7 +289,7 @@ test_expect_success '--keep fails with added/deleted merge' '
git diff --exit-code file3 &&
git diff --exit-code branch3 file3 &&
test_must_fail git reset --keep HEAD 2>err.log &&
- grep "middle of a merge" err.log
+ test_i18ngrep "middle of a merge" err.log
'
test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 1337fa5..07fb53a 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -228,7 +228,7 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' '
git config advice.detachedHead false &&
git checkout -f renamer && git clean -f &&
git checkout renamer^ 2>messages &&
- grep "HEAD is now at 7329388" messages &&
+ test_i18ngrep "HEAD is now at 7329388" messages &&
test 1 -eq $(wc -l <messages) &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
@@ -246,7 +246,7 @@ test_expect_success 'checkout to detach HEAD' '
git config advice.detachedHead true &&
git checkout -f renamer && git clean -f &&
git checkout renamer^ 2>messages &&
- grep "HEAD is now at 7329388" messages &&
+ test_i18ngrep "HEAD is now at 7329388" messages &&
test 1 -lt $(wc -l <messages) &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
@@ -408,6 +408,15 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' '
test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
'
+test_expect_success 'checkout w/--track from tag fails' '
+ git checkout master^0 &&
+ test_must_fail git symbolic-ref HEAD &&
+ test_must_fail git checkout --track -b track frotz &&
+ test_must_fail git rev-parse --verify track &&
+ test_must_fail git symbolic-ref HEAD &&
+ test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
+'
+
test_expect_success 'detach a symbolic link HEAD' '
git checkout master &&
git config --bool core.prefersymlinkrefs yes &&
@@ -423,7 +432,6 @@ test_expect_success 'detach a symbolic link HEAD' '
test_expect_success \
'checkout with --track fakes a sensible -b <name>' '
git update-ref refs/remotes/origin/koala/bear renamer &&
- git update-ref refs/new/koala/bear renamer &&
git checkout --track origin/koala/bear &&
test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
@@ -439,12 +447,6 @@ test_expect_success \
git checkout --track remotes/origin/koala/bear &&
test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
- test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
-
- git checkout master && git branch -D koala/bear &&
-
- git checkout --track refs/new/koala/bear &&
- test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
'
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 7d8ed68..800b536 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -110,7 +110,7 @@ test_expect_success 'git clean with prefix' '
'
-test_expect_success 'git clean with relative prefix' '
+test_expect_success C_LOCALE_OUTPUT 'git clean with relative prefix' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
@@ -125,7 +125,7 @@ test_expect_success 'git clean with relative prefix' '
}
'
-test_expect_success 'git clean with absolute path' '
+test_expect_success C_LOCALE_OUTPUT 'git clean with absolute path' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
@@ -179,11 +179,11 @@ test_expect_success 'git clean -d with prefix and path' '
'
-test_expect_success 'git clean symbolic link' '
+test_expect_success SYMLINKS 'git clean symbolic link' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
- ln -s docs/manual.txt src/part4.c
+ ln -s docs/manual.txt src/part4.c &&
git clean &&
test -f Makefile &&
test -f README &&
@@ -377,7 +377,7 @@ test_expect_success 'clean.requireForce and -f' '
'
-test_expect_success 'core.excludesfile' '
+test_expect_success C_LOCALE_OUTPUT 'core.excludesfile' '
echo excludes >excludes &&
echo included >included &&
@@ -388,16 +388,15 @@ test_expect_success 'core.excludesfile' '
'
-test_expect_success 'removal failure' '
+test_expect_success SANITY 'removal failure' '
mkdir foo &&
touch foo/bar &&
(exec <foo/bar &&
chmod 0 foo &&
- test_must_fail git clean -f -d)
-
+ test_must_fail git clean -f -d &&
+ chmod 755 foo)
'
-chmod 755 foo
test_expect_success 'nested git work tree' '
rm -fr foo bar &&
@@ -438,4 +437,27 @@ test_expect_success 'force removal of nested git work tree' '
! test -d bar
'
+test_expect_success 'git clean -e' '
+ rm -fr repo &&
+ mkdir repo &&
+ (
+ cd repo &&
+ git init &&
+ touch known 1 2 3 &&
+ git add known &&
+ git clean -f -e 1 -e 2 &&
+ test -e 1 &&
+ test -e 2 &&
+ ! (test -e 3) &&
+ test -e known
+ )
+'
+
+test_expect_success SANITY 'git clean -d with an unreadable empty directory' '
+ mkdir foo &&
+ chmod a= foo &&
+ git clean -dfx foo &&
+ ! test -d foo
+'
+
test_done
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 97ff074..5afe6cc 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -42,7 +42,8 @@ test_expect_success 'setup - hide init subdirectory' '
'
test_expect_success 'setup - repository to add submodules to' '
- git init addtest
+ git init addtest &&
+ git init addtest-ignore
'
# The 'submodule add' tests need some repository to add as a submodule.
@@ -85,6 +86,30 @@ test_expect_success 'submodule add' '
test_cmp empty untracked
'
+test_expect_success 'submodule add to .gitignored path fails' '
+ (
+ cd addtest-ignore &&
+ cat <<-\EOF >expect &&
+ The following path is ignored by one of your .gitignore files:
+ submod
+ Use -f if you really want to add it.
+ EOF
+ # Does not use test_commit due to the ignore
+ echo "*" > .gitignore &&
+ git add --force .gitignore &&
+ git commit -m"Ignore everything" &&
+ ! git submodule add "$submodurl" submod >actual 2>&1 &&
+ test_i18ncmp expect actual
+ )
+'
+
+test_expect_success 'submodule add to .gitignored path with --force' '
+ (
+ cd addtest-ignore &&
+ git submodule add --force "$submodurl" submod
+ )
+'
+
test_expect_success 'submodule add --branch' '
echo "refs/heads/initial" >expect-head &&
cat <<-\EOF >expect-heads &&
@@ -332,7 +357,7 @@ test_expect_success 'update --init' '
git submodule update init > update.out &&
cat update.out &&
- grep "not initialized" update.out &&
+ test_i18ngrep "not initialized" update.out &&
! test -d init/.git &&
git submodule update --init init &&
@@ -388,18 +413,85 @@ test_expect_success 'submodule <invalid-path> warns' '
test_expect_success 'add submodules without specifying an explicit path' '
mkdir repo &&
- cd repo &&
- git init &&
- echo r >r &&
- git add r &&
- git commit -m "repo commit 1" &&
- cd .. &&
+ (
+ cd repo &&
+ git init &&
+ echo r >r &&
+ git add r &&
+ git commit -m "repo commit 1"
+ ) &&
git clone --bare repo/ bare.git &&
- cd addtest &&
- git submodule add "$submodurl/repo" &&
- git config -f .gitmodules submodule.repo.path repo &&
- git submodule add "$submodurl/bare.git" &&
- git config -f .gitmodules submodule.bare.path bare
+ (
+ cd addtest &&
+ git submodule add "$submodurl/repo" &&
+ git config -f .gitmodules submodule.repo.path repo &&
+ git submodule add "$submodurl/bare.git" &&
+ git config -f .gitmodules submodule.bare.path bare
+ )
+'
+
+test_expect_success 'add should fail when path is used by a file' '
+ (
+ cd addtest &&
+ touch file &&
+ test_must_fail git submodule add "$submodurl/repo" file
+ )
+'
+
+test_expect_success 'add should fail when path is used by an existing directory' '
+ (
+ cd addtest &&
+ mkdir empty-dir &&
+ test_must_fail git submodule add "$submodurl/repo" empty-dir
+ )
+'
+
+test_expect_success 'use superproject as upstream when path is relative and no url is set there' '
+ (
+ cd addtest &&
+ git submodule add ../repo relative &&
+ test "$(git config -f .gitmodules submodule.relative.url)" = ../repo &&
+ git submodule sync relative &&
+ test "$(git config submodule.relative.url)" = "$submodurl/repo"
+ )
+'
+
+test_expect_success 'set up for relative path tests' '
+ mkdir reltest &&
+ (
+ cd reltest &&
+ git init &&
+ mkdir sub &&
+ (
+ cd sub &&
+ git init &&
+ test_commit foo
+ ) &&
+ git add sub &&
+ git config -f .gitmodules submodule.sub.path sub &&
+ git config -f .gitmodules submodule.sub.url ../subrepo &&
+ cp .git/config pristine-.git-config
+ )
+'
+
+test_expect_success 'relative path works with URL' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ git config remote.origin.url ssh://hostname/repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = ssh://hostname/subrepo
+ )
+'
+
+test_expect_success 'relative path works with user@host:path' '
+ (
+ cd reltest &&
+ cp pristine-.git-config .git/config &&
+ git config remote.origin.url user@host:repo &&
+ git submodule init &&
+ test "$(git config submodule.sub.url)" = user@host:subrepo
+ )
'
test_done
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index cee319d..30b429e 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -37,11 +37,12 @@ head1=$(add_file sm1 foo1 foo2)
test_expect_success 'added submodule' "
git add sm1 &&
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 0000000...$head1 (2):
> Add foo2
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
@@ -49,43 +50,47 @@ head2=$(add_file sm1 foo3)
test_expect_success 'modified submodule(forward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head1...$head2 (1):
> Add foo3
EOF
+ test_cmp expected actual
"
test_expect_success 'modified submodule(forward), --files' "
git submodule summary --files >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head1...$head2 (1):
> Add foo3
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
-cd sm1 &&
-git reset --hard HEAD~2 >/dev/null &&
-head3=$(git rev-parse --verify HEAD | cut -c1-7) &&
-cd ..
+head3=$(
+ cd sm1 &&
+ git reset --hard HEAD~2 >/dev/null &&
+ git rev-parse --verify HEAD | cut -c1-7
+)
test_expect_success 'modified submodule(backward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head3 (2):
< Add foo3
< Add foo2
EOF
+ test_cmp expected actual
"
head4=$(add_file sm1 foo4 foo5) &&
head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD)
test_expect_success 'modified submodule(backward and forward)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
@@ -93,17 +98,19 @@ test_expect_success 'modified submodule(backward and forward)' "
< Add foo2
EOF
+ test_cmp expected actual
"
test_expect_success '--summary-limit' "
git submodule summary -n 3 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head2...$head4 (4):
> Add foo5
> Add foo4
< Add foo3
EOF
+ test_cmp expected actual
"
commit_file sm1 &&
@@ -116,30 +123,33 @@ mv sm1-bak sm1
test_expect_success 'typechanged submodule(submodule->blob), --cached' "
git submodule summary --cached >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob) (3):
< Add foo5
EOF
+ test_i18ncmp actual expected
"
test_expect_success 'typechanged submodule(submodule->blob), --files' "
git submodule summary --files >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head4(submodule) (3):
> Add foo5
EOF
+ test_i18ncmp actual expected
"
rm -rf sm1 &&
git checkout-index sm1
test_expect_success 'typechanged submodule(submodule->blob)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4(submodule)->$head5(blob):
EOF
+ test_i18ncmp actual expected
"
rm -f sm1 &&
@@ -147,31 +157,34 @@ test_create_repo sm1 &&
head6=$(add_file sm1 foo6 foo7)
test_expect_success 'nonexistent commit' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head4...$head6:
Warn: sm1 doesn't contain commit $head4_full
EOF
+ test_i18ncmp actual expected
"
commit_file
test_expect_success 'typechanged submodule(blob->submodule)' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head5(blob)->$head6(submodule) (2):
> Add foo7
EOF
+ test_i18ncmp expected actual
"
commit_file sm1 &&
rm -rf sm1
test_expect_success 'deleted submodule' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
EOF
+ test_cmp expected actual
"
test_create_repo sm2 &&
@@ -180,39 +193,42 @@ git add sm2
test_expect_success 'multiple submodules' "
git submodule summary >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
test_expect_success 'path filter' "
git submodule summary sm2 >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
commit_file sm2
test_expect_success 'given commit' "
git submodule summary HEAD^ >actual &&
- diff actual - <<-EOF
+ cat >expected <<-EOF &&
* sm1 $head6...0000000:
* sm2 0000000...$head7 (2):
> Add foo9
EOF
+ test_cmp expected actual
"
test_expect_success '--for-status' "
git submodule summary --for-status HEAD^ >actual &&
- test_cmp actual - <<EOF
+ test_i18ncmp actual - <<EOF
# Submodule changes to be committed:
#
# * sm1 $head6...0000000:
diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh
index 7538756..95ffe34 100755
--- a/t/t7403-submodule-sync.sh
+++ b/t/t7403-submodule-sync.sh
@@ -14,7 +14,7 @@ test_expect_success setup '
echo file > file &&
git add file &&
test_tick &&
- git commit -m upstream
+ git commit -m upstream &&
git clone . super &&
git clone super submodule &&
(cd super &&
@@ -23,7 +23,10 @@ test_expect_success setup '
git commit -m "submodule"
) &&
git clone super super-clone &&
- (cd super-clone && git submodule update --init)
+ (cd super-clone && git submodule update --init) &&
+ git clone super empty-clone &&
+ (cd empty-clone && git submodule init) &&
+ git clone super top-only-clone
'
test_expect_success 'change submodule' '
@@ -42,7 +45,7 @@ test_expect_success 'change submodule url' '
) &&
mv submodule moved-submodule &&
(cd super &&
- git config -f .gitmodules submodule.submodule.url ../moved-submodule
+ git config -f .gitmodules submodule.submodule.url ../moved-submodule &&
test_tick &&
git commit -a -m moved-submodule
)
@@ -50,7 +53,7 @@ test_expect_success 'change submodule url' '
test_expect_success '"git submodule sync" should update submodule URLs' '
(cd super-clone &&
- git pull &&
+ git pull --no-recurse-submodules &&
git submodule sync
) &&
test -d "$(git config -f super-clone/submodule/.git/config \
@@ -58,6 +61,27 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
(cd super-clone/submodule &&
git checkout master &&
git pull
+ ) &&
+ (cd super-clone &&
+ test -d "$(git config submodule.submodule.url)"
+ )
+'
+
+test_expect_success '"git submodule sync" should update known submodule URLs' '
+ (cd empty-clone &&
+ git pull &&
+ git submodule sync &&
+ test -d "$(git config submodule.submodule.url)"
+ )
+'
+
+test_expect_success '"git submodule sync" should not vivify uninteresting submodule' '
+ (cd top-only-clone &&
+ git pull &&
+ git submodule sync &&
+ test -z "$(git config submodule.submodule.url)" &&
+ git submodule sync submodule &&
+ test -z "$(git config submodule.submodule.url)"
)
'
diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
index 9a21f78..a8fb30b 100755
--- a/t/t7405-submodule-merge.sh
+++ b/t/t7405-submodule-merge.sh
@@ -45,7 +45,7 @@ test_expect_success setup '
git commit -m sub-b) &&
git add sub &&
test_tick &&
- git commit -m b
+ git commit -m b &&
git checkout -b c a &&
git merge -s ours b &&
@@ -54,21 +54,178 @@ test_expect_success setup '
git merge -s ours a
'
-test_expect_success 'merging with modify/modify conflict' '
+# History setup
+#
+# b
+# / \
+# init -- a d
+# \ \ /
+# g c
+#
+# a in the main repository records to sub-a in the submodule and
+# analogous b and c. d should be automatically found by merging c into
+# b in the main repository.
+test_expect_success 'setup for merge search' '
+ mkdir merge-search &&
+ (cd merge-search &&
+ git init &&
+ mkdir sub &&
+ (cd sub &&
+ git init &&
+ echo "file-a" > file-a &&
+ git add file-a &&
+ git commit -m "sub-a" &&
+ git branch sub-a) &&
+ git commit --allow-empty -m init &&
+ git branch init &&
+ git add sub &&
+ git commit -m "a" &&
+ git branch a &&
+
+ git checkout -b b &&
+ (cd sub &&
+ git checkout -b sub-b &&
+ echo "file-b" > file-b &&
+ git add file-b &&
+ git commit -m "sub-b") &&
+ git commit -a -m "b" &&
+
+ git checkout -b c a &&
+ (cd sub &&
+ git checkout -b sub-c sub-a &&
+ echo "file-c" > file-c &&
+ git add file-c &&
+ git commit -m "sub-c") &&
+ git commit -a -m "c" &&
+
+ git checkout -b d a &&
+ (cd sub &&
+ git checkout -b sub-d sub-b &&
+ git merge sub-c) &&
+ git commit -a -m "d" &&
+ git branch test b &&
+
+ git checkout -b g init &&
+ (cd sub &&
+ git checkout -b sub-g sub-c) &&
+ git add sub &&
+ git commit -a -m "g")
+'
+
+test_expect_success 'merge with one side as a fast-forward of the other' '
+ (cd merge-search &&
+ git checkout -b test-forward b &&
+ git merge d &&
+ git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual &&
+ (cd sub &&
+ git rev-parse sub-d > ../expect) &&
+ test_cmp actual expect)
+'
+
+test_expect_success 'merging should conflict for non fast-forward' '
+ (cd merge-search &&
+ git checkout -b test-nonforward b &&
+ (cd sub &&
+ git rev-parse sub-d > ../expect) &&
+ test_must_fail git merge c 2> actual &&
+ grep $(cat expect) actual > /dev/null &&
+ git reset --hard)
+'
+
+test_expect_success 'merging should fail for ambiguous common parent' '
+ (cd merge-search &&
+ git checkout -b test-ambiguous b &&
+ (cd sub &&
+ git checkout -b ambiguous sub-b &&
+ git merge sub-c &&
+ git rev-parse sub-d > ../expect1 &&
+ git rev-parse ambiguous > ../expect2) &&
+ test_must_fail git merge c 2> actual &&
+ grep $(cat expect1) actual > /dev/null &&
+ grep $(cat expect2) actual > /dev/null &&
+ git reset --hard)
+'
+
+# in a situation like this
+#
+# submodule tree:
+#
+# sub-a --- sub-b --- sub-d
+#
+# main tree:
+#
+# e (sub-a)
+# /
+# bb (sub-b)
+# \
+# f (sub-d)
+#
+# A merge between e and f should fail because one of the submodule
+# commits (sub-a) does not descend from the submodule merge-base (sub-b).
+#
+test_expect_success 'merging should fail for changes that are backwards' '
+ (cd merge-search &&
+ git checkout -b bb a &&
+ (cd sub &&
+ git checkout sub-b) &&
+ git commit -a -m "bb" &&
+
+ git checkout -b e bb &&
+ (cd sub &&
+ git checkout sub-a) &&
+ git commit -a -m "e" &&
+
+ git checkout -b f bb &&
+ (cd sub &&
+ git checkout sub-d) &&
+ git commit -a -m "f" &&
+
+ git checkout -b test-backward e &&
+ test_must_fail git merge f)
+'
+
- git checkout -b test1 a &&
+# Check that the conflicting submodule is detected when it is
+# in the common ancestor. status should be 'U00...00"
+test_expect_success 'git submodule status should display the merge conflict properly with merge base' '
+ (cd merge-search &&
+ cat >.gitmodules <<EOF &&
+[submodule "sub"]
+ path = sub
+ url = $TRASH_DIRECTORY/sub
+EOF
+ cat >expect <<EOF &&
+U0000000000000000000000000000000000000000 sub
+EOF
+ git submodule status > actual &&
+ test_cmp expect actual &&
+ git reset --hard)
+'
+
+# Check that the conflicting submodule is detected when it is
+# not in the common ancestor. status should be 'U00...00"
+test_expect_success 'git submodule status should display the merge conflict properly without merge-base' '
+ (cd merge-search &&
+ git checkout -b test-no-merge-base g &&
test_must_fail git merge b &&
- test -f .git/MERGE_MSG &&
- git diff &&
- test -n "$(git ls-files -u)"
+ cat >.gitmodules <<EOF &&
+[submodule "sub"]
+ path = sub
+ url = $TRASH_DIRECTORY/sub
+EOF
+ cat >expect <<EOF &&
+U0000000000000000000000000000000000000000 sub
+EOF
+ git submodule status > actual &&
+ test_cmp expect actual &&
+ git reset --hard)
'
-test_expect_success 'merging with a modify/modify conflict between merge bases' '
+test_expect_success 'merging with a modify/modify conflict between merge bases' '
git reset --hard HEAD &&
git checkout -b test2 c &&
git merge d
-
'
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 1382a8e..c679f36 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -25,7 +25,7 @@ test_expect_success 'setup a submodule tree' '
echo file > file &&
git add file &&
test_tick &&
- git commit -m upstream
+ git commit -m upstream &&
git clone . super &&
git clone super submodule &&
git clone super rebasing &&
@@ -74,6 +74,49 @@ test_expect_success 'submodule update detaching the HEAD ' '
)
'
+apos="'";
+test_expect_success 'submodule update does not fetch already present commits' '
+ (cd submodule &&
+ echo line3 >> file &&
+ git add file &&
+ test_tick &&
+ git commit -m "upstream line3"
+ ) &&
+ (cd super/submodule &&
+ head=$(git rev-parse --verify HEAD) &&
+ echo "Submodule path ${apos}submodule$apos: checked out $apos$head$apos" > ../../expected &&
+ git reset --hard HEAD~1
+ ) &&
+ (cd super &&
+ git submodule update > ../actual 2> ../actual.err
+ ) &&
+ test_i18ncmp expected actual &&
+ ! test -s actual.err
+'
+
+test_expect_success 'submodule update should fail due to local changes' '
+ (cd super/submodule &&
+ git reset --hard HEAD~1 &&
+ echo "local change" > file
+ ) &&
+ (cd super &&
+ (cd submodule &&
+ compare_head
+ ) &&
+ test_must_fail git submodule update submodule
+ )
+'
+test_expect_success 'submodule update should throw away changes with --force ' '
+ (cd super &&
+ (cd submodule &&
+ compare_head
+ ) &&
+ git submodule update --force submodule &&
+ cd submodule &&
+ ! compare_head
+ )
+'
+
test_expect_success 'submodule update --rebase staying on master' '
(cd super/submodule &&
git checkout master
@@ -203,4 +246,200 @@ test_expect_success 'submodule init picks up merge' '
)
'
+test_expect_success 'submodule update --merge - ignores --merge for new submodules' '
+ (cd super &&
+ rm -rf submodule &&
+ git submodule update submodule &&
+ git status -s submodule >expect &&
+ rm -rf submodule &&
+ git submodule update --merge submodule &&
+ git status -s submodule >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' '
+ (cd super &&
+ rm -rf submodule &&
+ git submodule update submodule &&
+ git status -s submodule >expect &&
+ rm -rf submodule &&
+ git submodule update --rebase submodule &&
+ git status -s submodule >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update ignores update=merge config for new submodules' '
+ (cd super &&
+ rm -rf submodule &&
+ git submodule update submodule &&
+ git status -s submodule >expect &&
+ rm -rf submodule &&
+ git config submodule.submodule.update merge &&
+ git submodule update submodule &&
+ git status -s submodule >actual &&
+ git config --unset submodule.submodule.update &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update ignores update=rebase config for new submodules' '
+ (cd super &&
+ rm -rf submodule &&
+ git submodule update submodule &&
+ git status -s submodule >expect &&
+ rm -rf submodule &&
+ git config submodule.submodule.update rebase &&
+ git submodule update submodule &&
+ git status -s submodule >actual &&
+ git config --unset submodule.submodule.update &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update continues after checkout error' '
+ (cd super &&
+ git reset --hard HEAD &&
+ git submodule add ../submodule submodule2 &&
+ git submodule init &&
+ git commit -am "new_submodule" &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ (cd submodule &&
+ test_commit "update_submodule" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ echo "" > file
+ ) &&
+ git checkout HEAD^ &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+test_expect_success 'submodule update continues after recursive checkout error' '
+ (cd super &&
+ git reset --hard HEAD &&
+ git checkout master &&
+ git submodule update &&
+ (cd submodule &&
+ git submodule add ../submodule subsubmodule &&
+ git submodule init &&
+ git commit -m "new_subsubmodule"
+ ) &&
+ git add submodule &&
+ git commit -m "update_submodule" &&
+ (cd submodule &&
+ (cd subsubmodule &&
+ test_commit "update_subsubmodule" file
+ ) &&
+ git add subsubmodule &&
+ test_commit "update_submodule_again" file &&
+ (cd subsubmodule &&
+ test_commit "update_subsubmodule_again" file
+ ) &&
+ test_commit "update_submodule_again_again" file
+ ) &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect &&
+ test_commit "update_submodule2_again" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "new_commits" &&
+ git checkout HEAD^ &&
+ (cd submodule &&
+ git checkout HEAD^ &&
+ (cd subsubmodule &&
+ echo "" > file
+ )
+ ) &&
+ test_must_fail git submodule update --recursive &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'submodule update exit immediately in case of merge conflict' '
+ (cd super &&
+ git checkout master &&
+ git reset --hard HEAD &&
+ (cd submodule &&
+ (cd subsubmodule &&
+ git reset --hard HEAD
+ )
+ ) &&
+ git submodule update --recursive &&
+ (cd submodule &&
+ test_commit "update_submodule_2" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2_2" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ git checkout master &&
+ test_commit "conflict" file &&
+ echo "conflict" > file
+ ) &&
+ git checkout HEAD^ &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ git config submodule.submodule.update merge &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
+test_expect_success 'submodule update exit immediately after recursive rebase error' '
+ (cd super &&
+ git checkout master &&
+ git reset --hard HEAD &&
+ (cd submodule &&
+ git reset --hard HEAD &&
+ git submodule update --recursive
+ ) &&
+ (cd submodule &&
+ test_commit "update_submodule_3" file
+ ) &&
+ (cd submodule2 &&
+ test_commit "update_submodule2_3" file
+ ) &&
+ git add submodule &&
+ git add submodule2 &&
+ git commit -m "two_new_submodule_commits" &&
+ (cd submodule &&
+ git checkout master &&
+ test_commit "conflict2" file &&
+ echo "conflict" > file
+ ) &&
+ git checkout HEAD^ &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../expect
+ ) &&
+ git config submodule.submodule.update rebase &&
+ test_must_fail git submodule update &&
+ (cd submodule2 &&
+ git rev-parse --max-count=1 HEAD > ../actual
+ ) &&
+ test_cmp expect actual
+ )
+'
test_done
diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
index db9365b..be745fb 100755
--- a/t/t7407-submodule-foreach.sh
+++ b/t/t7407-submodule-foreach.sh
@@ -16,7 +16,7 @@ test_expect_success 'setup a submodule tree' '
echo file > file &&
git add file &&
test_tick &&
- git commit -m upstream
+ git commit -m upstream &&
git clone . super &&
git clone super submodule &&
(
@@ -30,7 +30,7 @@ test_expect_success 'setup a submodule tree' '
submodule.sub2 submodule.foo2 &&
git config -f .gitmodules --rename-section \
submodule.sub3 submodule.foo3 &&
- git add .gitmodules
+ git add .gitmodules &&
test_tick &&
git commit -m "submodules" &&
git submodule init sub1 &&
@@ -77,7 +77,7 @@ test_expect_success 'test basic "submodule foreach" usage' '
git config foo.bar zar &&
git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar"
) &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success 'setup nested submodules' '
@@ -158,7 +158,7 @@ test_expect_success 'test messages from "foreach --recursive"' '
cd clone2 &&
git submodule foreach --recursive "true" > ../actual
) &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
cat > expect <<EOF
@@ -226,6 +226,25 @@ test_expect_success 'test "status --recursive"' '
test_cmp expect actual
'
+sed -e "/nested1 /s/.*/+$nested1sha1 nested1 (file2~1)/;/sub[1-3]/d" < expect > expect2
+mv -f expect2 expect
+
+test_expect_success 'ensure "status --cached --recursive" preserves the --cached flag' '
+ (
+ cd clone3 &&
+ (
+ cd nested1 &&
+ test_commit file2
+ ) &&
+ git submodule status --cached --recursive -- nested1 > ../actual
+ ) &&
+ if test_have_prereq MINGW
+ then
+ dos2unix actual
+ fi &&
+ test_cmp expect actual
+'
+
test_expect_success 'use "git clone --recursive" to checkout all submodules' '
git clone --recursive super clone4 &&
test -d clone4/.git &&
@@ -238,4 +257,57 @@ test_expect_success 'use "git clone --recursive" to checkout all submodules' '
test -d clone4/nested1/nested2/nested3/submodule/.git
'
+test_expect_success 'test "update --recursive" with a flag with spaces' '
+ git clone super "common objects" &&
+ git clone super clone5 &&
+ (
+ cd clone5 &&
+ test ! -d nested1/.git &&
+ git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" &&
+ test -d nested1/.git &&
+ test -d nested1/nested2/.git &&
+ test -d nested1/nested2/nested3/.git &&
+ test -f nested1/.git/objects/info/alternates &&
+ test -f nested1/nested2/.git/objects/info/alternates &&
+ test -f nested1/nested2/nested3/.git/objects/info/alternates
+ )
+'
+
+test_expect_success 'use "update --recursive nested1" to checkout all submodules rooted in nested1' '
+ git clone super clone6 &&
+ (
+ cd clone6 &&
+ test ! -d sub1/.git &&
+ test ! -d sub2/.git &&
+ test ! -d sub3/.git &&
+ test ! -d nested1/.git &&
+ git submodule update --init --recursive -- nested1 &&
+ test ! -d sub1/.git &&
+ test ! -d sub2/.git &&
+ test ! -d sub3/.git &&
+ test -d nested1/.git &&
+ test -d nested1/nested2/.git &&
+ test -d nested1/nested2/nested3/.git &&
+ test -d nested1/nested2/nested3/submodule/.git
+ )
+'
+
+test_expect_success 'command passed to foreach retains notion of stdin' '
+ (
+ cd super &&
+ git submodule foreach echo success >../expected &&
+ yes | git submodule foreach "read y && test \"x\$y\" = xy && echo success" >../actual
+ ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'command passed to foreach --recursive retains notion of stdin' '
+ (
+ cd clone2 &&
+ git submodule foreach --recursive echo success >../expected &&
+ yes | git submodule foreach --recursive "read y && test \"x\$y\" = xy && echo success" >../actual
+ ) &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index aa9c577..1c908f4 100755
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
@@ -10,7 +10,12 @@ Tests for selected commit options.'
. ./test-lib.sh
commit_msg_is () {
- test "`git log --pretty=format:%s%b -1`" = "$1"
+ expect=commit_msg_is.expect
+ actual=commit_msg_is.actual
+
+ printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect &&
+ printf "%s" "$1" >$actual &&
+ test_i18ncmp $expect $actual
}
# A sanity check to see if commit is working at all.
@@ -23,13 +28,21 @@ test_expect_success 'a basic commit in an empty tree should succeed' '
test_expect_success 'nonexistent template file should return error' '
echo changes >> foo &&
git add foo &&
- test_must_fail git commit --template "$PWD"/notexist
+ (
+ GIT_EDITOR="echo hello >\"\$1\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit --template "$PWD"/notexist
+ )
'
test_expect_success 'nonexistent template file in config should return error' '
git config commit.template "$PWD"/notexist &&
- test_must_fail git commit &&
- git config --unset commit.template
+ test_when_finished "git config --unset commit.template" &&
+ (
+ GIT_EDITOR="echo hello >\"\$1\"" &&
+ export GIT_EDITOR &&
+ test_must_fail git commit
+ )
'
# From now on we'll use a template file that exists.
@@ -110,6 +123,20 @@ test_expect_success 'commit message from file should override template' '
commit_msg_is "standard input msg"
'
+cat >"$TEMPLATE" <<\EOF
+
+
+### template
+
+EOF
+test_expect_success 'commit message from template with whitespace issue' '
+ echo "content galore" >>foo &&
+ git add foo &&
+ GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \
+ --template "$TEMPLATE" &&
+ commit_msg_is "commit message"
+'
+
test_expect_success 'using alternate GIT_INDEX_FILE (1)' '
cp .git/index saved-index &&
@@ -215,4 +242,84 @@ test_expect_success 'Commit a message with --allow-empty-message' '
commit_msg_is "hello there"
'
+commit_for_rebase_autosquash_setup () {
+ echo "first content line" >>foo &&
+ git add foo &&
+ cat >log <<EOF &&
+target message subject line
+
+target message body line 1
+target message body line 2
+EOF
+ git commit -F log &&
+ echo "second content line" >>foo &&
+ git add foo &&
+ git commit -m "intermediate commit" &&
+ echo "third content line" >>foo &&
+ git add foo
+}
+
+test_expect_success 'commit --fixup provides correct one-line commit message' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --fixup HEAD~1 &&
+ commit_msg_is "fixup! target message subject line"
+'
+
+test_expect_success 'commit --squash works with -F' '
+ commit_for_rebase_autosquash_setup &&
+ echo "log message from file" >msgfile &&
+ git commit --squash HEAD~1 -F msgfile &&
+ commit_msg_is "squash! target message subject linelog message from file"
+'
+
+test_expect_success 'commit --squash works with -m' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -m "foo bar\nbaz" &&
+ commit_msg_is "squash! target message subject linefoo bar\nbaz"
+'
+
+test_expect_success 'commit --squash works with -C' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD~1 -C HEAD &&
+ commit_msg_is "squash! target message subject lineintermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD~1 -c HEAD &&
+ commit_msg_is "squash! target message subject lineedited commit"
+'
+
+test_expect_success 'commit --squash works with -C for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ git commit --squash HEAD -C HEAD &&
+ commit_msg_is "squash! intermediate commit"
+'
+
+test_expect_success 'commit --squash works with -c for same commit' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/edit-content &&
+ git commit --squash HEAD -c HEAD &&
+ commit_msg_is "squash! edited commit"
+'
+
+test_expect_success 'commit --squash works with editor' '
+ commit_for_rebase_autosquash_setup &&
+ test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
+ git commit --squash HEAD~1 &&
+ commit_msg_is "squash! target message subject linecommit message"
+'
+
+test_expect_success 'invalid message options when using --fixup' '
+ echo changes >>foo &&
+ echo "message" >log &&
+ git add foo &&
+ test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
+ test_must_fail git commit --fixup HEAD~1 -m "cmdline message" &&
+ test_must_fail git commit --fixup HEAD~1 -F log
+'
+
test_done
diff --git a/t/t7500/add-whitespaced-content b/t/t7500/add-whitespaced-content
new file mode 100755
index 0000000..ccf07c6
--- /dev/null
+++ b/t/t7500/add-whitespaced-content
@@ -0,0 +1,8 @@
+#!/bin/sh
+sed -e 's/|$//' >>"$1" <<\EOF
+
+ |
+commit message |
+
+EOF
+exit 0
diff --git a/t/t7500/edit-content b/t/t7500/edit-content
new file mode 100755
index 0000000..08db9fd
--- /dev/null
+++ b/t/t7500/edit-content
@@ -0,0 +1,4 @@
+#!/bin/sh
+sed -e "s/intermediate/edited/g" <"$1" >"$1-"
+mv "$1-" "$1"
+exit 0
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 8297cb4..3ad0436 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -14,8 +14,12 @@ test_tick
test_expect_success \
"initial status" \
"echo 'bongo bongo' >file &&
- git add file && \
- git status | grep 'Initial commit'"
+ git add file"
+
+test_expect_success "Constructing initial commit" '
+ git status >actual &&
+ test_i18ngrep "Initial commit" actual
+'
test_expect_success \
"fail initial amend" \
@@ -38,10 +42,13 @@ test_expect_success \
"echo King of the bongo >file &&
test_must_fail git commit -m foo -a file"
-test_expect_success PERL \
- "using paths with --interactive" \
- "echo bong-o-bong >file &&
- ! (echo 7 | git commit -m foo --interactive file)"
+test_expect_success PERL 'can use paths with --interactive' '
+ echo bong-o-bong >file &&
+ # 2: update, 1:st path, that is all, 7: quit
+ ( echo 2; echo 1; echo; echo 7 ) |
+ git commit -m foo --interactive file &&
+ git reset --hard HEAD^
+'
test_expect_success \
"using invalid commit with -C" \
@@ -127,6 +134,16 @@ test_expect_success PERL \
"interactive add" \
"echo 7 | git commit --interactive | grep 'What now'"
+test_expect_success PERL \
+ "commit --interactive doesn't change index if editor aborts" \
+ "echo zoo >file &&
+ test_must_fail git diff --exit-code >diff1 &&
+ (echo u ; echo '*' ; echo q) |
+ (EDITOR=: && export EDITOR &&
+ test_must_fail git commit --interactive) &&
+ git diff >diff2 &&
+ test_cmp diff1 diff2"
+
test_expect_success \
"showing committed revisions" \
"git rev-list HEAD >current"
@@ -230,6 +247,10 @@ test_expect_success 'amend commit to fix date' '
'
+test_expect_success 'commit complains about bogus date' '
+ test_must_fail git commit --amend --date=10.11.2010
+'
+
test_expect_success 'sign off (1)' '
echo 1 >positive &&
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index ac2e187..3f3adc3 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -22,7 +22,7 @@ check_summary_oneline() {
SUMMARY_POSTFIX="$(git log -1 --pretty='format:%h')"
echo "[$SUMMARY_PREFIX $SUMMARY_POSTFIX] $2" >exp &&
- test_cmp exp act
+ test_i18ncmp exp act
}
test_expect_success 'output summary format' '
@@ -32,7 +32,10 @@ test_expect_success 'output summary format' '
check_summary_oneline "root-commit" "initial" &&
echo change >>file1 &&
- git add file1 &&
+ git add file1
+'
+
+test_expect_success 'output summary format: root-commit' '
check_summary_oneline "" "a change"
'
@@ -215,19 +218,21 @@ test_expect_success 'cleanup commit messages (strip,-F)' '
'
-echo "sample
-
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit." >expect
-
test_expect_success 'cleanup commit messages (strip,-F,-e)' '
echo >>negative &&
{ echo;echo sample;echo; } >text &&
git commit -e -F text -a &&
- head -n 4 .git/COMMIT_EDITMSG >actual &&
- test_cmp expect actual
+ head -n 4 .git/COMMIT_EDITMSG >actual
+'
+echo "sample
+
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit." >expect
+
+test_expect_success 'cleanup commit messages (strip,-F,-e): output' '
+ test_i18ncmp expect actual
'
echo "#
@@ -235,11 +240,10 @@ echo "#
#" >> expect
test_expect_success 'author different from committer' '
-
echo >>negative &&
- git commit -e -m "sample"
+ test_might_fail git commit -e -m "sample" &&
head -n 7 .git/COMMIT_EDITMSG >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
mv expect expect.tmp
@@ -252,14 +256,14 @@ test_expect_success 'committer is automatic' '
echo >>negative &&
(
- unset GIT_COMMITTER_EMAIL
- unset GIT_COMMITTER_NAME
+ sane_unset GIT_COMMITTER_EMAIL &&
+ sane_unset GIT_COMMITTER_NAME &&
# must fail because there is no change
test_must_fail git commit -e -m "sample"
) &&
head -n 8 .git/COMMIT_EDITMSG | \
- sed "s/^# Committer: .*/# Committer:/" >actual &&
- test_cmp expect actual
+ sed "s/^# Committer: .*/# Committer:/" >actual
+ test_i18ncmp expect actual
'
pwd=`pwd`
@@ -362,9 +366,9 @@ try_commit () {
GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
case "$use_template" in
'')
- ! grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+ test_i18ngrep ! "^## Custom template" .git/COMMIT_EDITMSG ;;
*)
- grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+ test_i18ngrep "^## Custom template" .git/COMMIT_EDITMSG ;;
esac
}
@@ -373,67 +377,67 @@ try_commit_status_combo () {
test_expect_success 'commit' '
clear_config commit.status &&
try_commit "" &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit' '
clear_config commit.status &&
try_commit "" &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status' '
clear_config commit.status &&
try_commit --status &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status' '
clear_config commit.status &&
- try_commit --no-status
- ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ try_commit --no-status &&
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit with commit.status = yes' '
clear_config commit.status &&
git config commit.status yes &&
try_commit "" &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit with commit.status = no' '
clear_config commit.status &&
git config commit.status no &&
try_commit "" &&
- ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status with commit.status = yes' '
clear_config commit.status &&
git config commit.status yes &&
try_commit --status &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status with commit.status = yes' '
clear_config commit.status &&
git config commit.status yes &&
try_commit --no-status &&
- ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status with commit.status = no' '
clear_config commit.status &&
git config commit.status no &&
try_commit --status &&
- grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status with commit.status = no' '
clear_config commit.status &&
git config commit.status no &&
try_commit --no-status &&
- ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
}
diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh
index 8528f64..ee7f0cd 100755
--- a/t/t7503-pre-commit-hook.sh
+++ b/t/t7503-pre-commit-hook.sh
@@ -84,5 +84,38 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' '
git commit --no-verify -m "more content"
'
+chmod +x "$HOOK"
+
+# a hook that checks $GIT_PREFIX and succeeds inside the
+# success/ subdirectory only
+cat > "$HOOK" <<EOF
+#!/bin/sh
+test \$GIT_PREFIX = success/
+EOF
+
+test_expect_success 'with hook requiring GIT_PREFIX' '
+
+ echo "more content" >> file &&
+ git add file &&
+ mkdir success &&
+ (
+ cd success &&
+ git commit -m "hook requires GIT_PREFIX = success/"
+ ) &&
+ rmdir success
+'
+
+test_expect_success 'with failing hook requiring GIT_PREFIX' '
+
+ echo "more content" >> file &&
+ git add file &&
+ mkdir fail &&
+ (
+ cd fail &&
+ test_must_fail git commit -m "hook must fail"
+ ) &&
+ rmdir fail &&
+ git checkout -- file
+'
test_done
diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh
index ff18962..5b4b694 100755
--- a/t/t7505-prepare-commit-msg-hook.sh
+++ b/t/t7505-prepare-commit-msg-hook.sh
@@ -132,6 +132,18 @@ test_expect_success 'with hook (-c)' '
'
+test_expect_success 'with hook (merge)' '
+
+ head=`git rev-parse HEAD` &&
+ git checkout -b other HEAD@{1} &&
+ echo "more" >> file &&
+ git add file &&
+ git commit -m other &&
+ git checkout - &&
+ git merge other &&
+ test "`git log -1 --pretty=format:%s`" = merge
+'
+
cat > "$HOOK" <<'EOF'
#!/bin/sh
exit 1
diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
index 3d4f85d..d31b34d 100755
--- a/t/t7506-status-submodule.sh
+++ b/t/t7506-status-submodule.sh
@@ -4,17 +4,21 @@ test_description='git status for submodule'
. ./test-lib.sh
-test_expect_success 'setup' '
- test_create_repo sub &&
+test_create_repo_with_commit () {
+ test_create_repo "$1" &&
(
- cd sub &&
+ cd "$1" &&
: >bar &&
git add bar &&
git commit -m " Add bar" &&
: >foo &&
git add foo &&
git commit -m " Add foo"
- ) &&
+ )
+}
+
+test_expect_success 'setup' '
+ test_create_repo_with_commit sub &&
echo output > .gitignore &&
git add sub .gitignore &&
git commit -m "Add submodule sub"
@@ -22,19 +26,19 @@ test_expect_success 'setup' '
test_expect_success 'status clean' '
git status >output &&
- grep "nothing to commit" output
+ test_i18ngrep "nothing to commit" output
'
test_expect_success 'commit --dry-run -a clean' '
test_must_fail git commit --dry-run -a >output &&
- grep "nothing to commit" output
+ test_i18ngrep "nothing to commit" output
'
test_expect_success 'status with modified file in submodule' '
(cd sub && git reset --hard) &&
echo "changed" >sub/foo &&
git status >output &&
- grep "modified: sub (modified content)" output
+ test_i18ngrep "modified: sub (modified content)" output
'
test_expect_success 'status with modified file in submodule (porcelain)' '
@@ -49,7 +53,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' '
test_expect_success 'status with added file in submodule' '
(cd sub && git reset --hard && echo >foo && git add foo) &&
git status >output &&
- grep "modified: sub (modified content)" output
+ test_i18ngrep "modified: sub (modified content)" output
'
test_expect_success 'status with added file in submodule (porcelain)' '
@@ -64,12 +68,12 @@ test_expect_success 'status with untracked file in submodule' '
(cd sub && git reset --hard) &&
echo "content" >sub/new-file &&
git status >output &&
- grep "modified: sub (untracked content)" output
+ test_i18ngrep "modified: sub (untracked content)" output
'
test_expect_success 'status -uno with untracked file in submodule' '
git status -uno >output &&
- grep "^nothing to commit" output
+ test_i18ngrep "^nothing to commit" output
'
test_expect_success 'status with untracked file in submodule (porcelain)' '
@@ -83,7 +87,7 @@ test_expect_success 'status with added and untracked file in submodule' '
(cd sub && git reset --hard && echo >foo && git add foo) &&
echo "content" >sub/new-file &&
git status >output &&
- grep "modified: sub (modified content, untracked content)" output
+ test_i18ngrep "modified: sub (modified content, untracked content)" output
'
test_expect_success 'status with added and untracked file in submodule (porcelain)' '
@@ -101,7 +105,7 @@ test_expect_success 'status with modified file in modified submodule' '
(cd sub && echo "next change" >foo && git commit -m "next change" foo) &&
echo "changed" >sub/foo &&
git status >output &&
- grep "modified: sub (new commits, modified content)" output
+ test_i18ngrep "modified: sub (new commits, modified content)" output
'
test_expect_success 'status with modified file in modified submodule (porcelain)' '
@@ -116,7 +120,7 @@ test_expect_success 'status with modified file in modified submodule (porcelain)
test_expect_success 'status with added file in modified submodule' '
(cd sub && git reset --hard && echo >foo && git add foo) &&
git status >output &&
- grep "modified: sub (new commits, modified content)" output
+ test_i18ngrep "modified: sub (new commits, modified content)" output
'
test_expect_success 'status with added file in modified submodule (porcelain)' '
@@ -131,7 +135,7 @@ test_expect_success 'status with untracked file in modified submodule' '
(cd sub && git reset --hard) &&
echo "content" >sub/new-file &&
git status >output &&
- grep "modified: sub (new commits, untracked content)" output
+ test_i18ngrep "modified: sub (new commits, untracked content)" output
'
test_expect_success 'status with untracked file in modified submodule (porcelain)' '
@@ -145,7 +149,7 @@ test_expect_success 'status with added and untracked file in modified submodule'
(cd sub && git reset --hard && echo >foo && git add foo) &&
echo "content" >sub/new-file &&
git status >output &&
- grep "modified: sub (new commits, modified content, untracked content)" output
+ test_i18ngrep "modified: sub (new commits, modified content, untracked content)" output
'
test_expect_success 'status with added and untracked file in modified submodule (porcelain)' '
@@ -170,7 +174,7 @@ test_expect_success 'setup .git file for sub' '
test_expect_success 'status with added file in modified submodule with .git file' '
(cd sub && git reset --hard && echo >foo && git add foo) &&
git status >output &&
- grep "modified: sub (new commits, modified content)" output
+ test_i18ngrep "modified: sub (new commits, modified content)" output
'
test_expect_success 'rm submodule contents' '
@@ -179,12 +183,92 @@ test_expect_success 'rm submodule contents' '
test_expect_success 'status clean (empty submodule dir)' '
git status >output &&
- grep "nothing to commit" output
+ test_i18ngrep "nothing to commit" output
'
test_expect_success 'status -a clean (empty submodule dir)' '
test_must_fail git commit --dry-run -a >output &&
- grep "nothing to commit" output
+ test_i18ngrep "nothing to commit" output
+'
+
+cat >status_expect <<\EOF
+AA .gitmodules
+A sub1
+EOF
+
+test_expect_success 'status with merge conflict in .gitmodules' '
+ git clone . super &&
+ test_create_repo_with_commit sub1 &&
+ test_tick &&
+ test_create_repo_with_commit sub2 &&
+ (
+ cd super &&
+ prev=$(git rev-parse HEAD) &&
+ git checkout -b add_sub1 &&
+ git submodule add ../sub1 &&
+ git commit -m "add sub1" &&
+ git checkout -b add_sub2 $prev &&
+ git submodule add ../sub2 &&
+ git commit -m "add sub2" &&
+ git checkout -b merge_conflict_gitmodules &&
+ test_must_fail git merge add_sub1 &&
+ git status -s >../status_actual 2>&1
+ ) &&
+ test_cmp status_actual status_expect
+'
+
+sha1_merge_sub1=$(cd sub1 && git rev-parse HEAD)
+sha1_merge_sub2=$(cd sub2 && git rev-parse HEAD)
+short_sha1_merge_sub1=$(cd sub1 && git rev-parse --short HEAD)
+short_sha1_merge_sub2=$(cd sub2 && git rev-parse --short HEAD)
+cat >diff_expect <<\EOF
+diff --cc .gitmodules
+index badaa4c,44f999a..0000000
+--- a/.gitmodules
++++ b/.gitmodules
+@@@ -1,3 -1,3 +1,9 @@@
+++<<<<<<< HEAD
+ +[submodule "sub2"]
+ + path = sub2
+ + url = ../sub2
+++=======
++ [submodule "sub1"]
++ path = sub1
++ url = ../sub1
+++>>>>>>> add_sub1
+EOF
+
+cat >diff_submodule_expect <<\EOF
+diff --cc .gitmodules
+index badaa4c,44f999a..0000000
+--- a/.gitmodules
++++ b/.gitmodules
+@@@ -1,3 -1,3 +1,9 @@@
+++<<<<<<< HEAD
+ +[submodule "sub2"]
+ + path = sub2
+ + url = ../sub2
+++=======
++ [submodule "sub1"]
++ path = sub1
++ url = ../sub1
+++>>>>>>> add_sub1
+EOF
+
+test_expect_success 'diff with merge conflict in .gitmodules' '
+ (
+ cd super &&
+ git diff >../diff_actual 2>&1
+ ) &&
+ test_cmp diff_actual diff_expect
+'
+
+test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
+ (
+ cd super &&
+ git diff --submodule >../diff_submodule_actual 2>&1
+ ) &&
+ test_cmp diff_submodule_actual diff_submodule_expect
'
test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index a72fe3a..905255a 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -7,6 +7,30 @@ test_description='git status'
. ./test-lib.sh
+test_expect_success 'status -h in broken repository' '
+ mkdir broken &&
+ test_when_finished "rm -fr broken" &&
+ (
+ cd broken &&
+ git init &&
+ echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+ test_expect_code 129 git status -h >usage 2>&1
+ ) &&
+ test_i18ngrep "[Uu]sage" broken/usage
+'
+
+test_expect_success 'commit -h in broken repository' '
+ mkdir broken &&
+ test_when_finished "rm -fr broken" &&
+ (
+ cd broken &&
+ git init &&
+ echo "[status] showuntrackedfiles = CORRUPT" >>.git/config &&
+ test_expect_code 129 git commit -h >usage 2>&1
+ ) &&
+ test_i18ngrep "[Uu]sage" broken/usage
+'
+
test_expect_success 'setup' '
: >tracked &&
: >modified &&
@@ -32,9 +56,7 @@ test_expect_success 'setup' '
'
test_expect_success 'status (1)' '
-
- grep "use \"git rm --cached <file>\.\.\.\" to unstage" output
-
+ test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
'
cat >expect <<\EOF
@@ -44,7 +66,7 @@ cat >expect <<\EOF
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -62,10 +84,8 @@ cat >expect <<\EOF
EOF
test_expect_success 'status (2)' '
-
git status >output &&
- test_cmp expect output
-
+ test_i18ncmp expect output
'
cat >expect <<\EOF
@@ -73,7 +93,7 @@ cat >expect <<\EOF
# Changes to be committed:
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# modified: dir1/modified
#
# Untracked files:
@@ -85,17 +105,14 @@ cat >expect <<\EOF
# untracked
EOF
-git config advice.statusHints false
-
test_expect_success 'status (advice.statusHints false)' '
-
+ test_when_finished "git config --unset advice.statusHints" &&
+ git config advice.statusHints false &&
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
-git config --unset advice.statusHints
-
cat >expect <<\EOF
M dir1/modified
A dir2/added
@@ -114,6 +131,127 @@ test_expect_success 'status -s' '
'
+test_expect_success 'status with gitignore' '
+ {
+ echo ".gitignore" &&
+ echo "expect" &&
+ echo "output" &&
+ echo "untracked"
+ } >.gitignore &&
+
+ cat >expect <<-\EOF &&
+ M dir1/modified
+ A dir2/added
+ ?? dir2/modified
+ EOF
+ git status -s >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-\EOF &&
+ M dir1/modified
+ A dir2/added
+ ?? dir2/modified
+ !! .gitignore
+ !! dir1/untracked
+ !! dir2/untracked
+ !! expect
+ !! output
+ !! untracked
+ EOF
+ git status -s --ignored >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-\EOF &&
+ # On branch master
+ # Changes to be committed:
+ # (use "git reset HEAD <file>..." to unstage)
+ #
+ # new file: dir2/added
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: dir1/modified
+ #
+ # Untracked files:
+ # (use "git add <file>..." to include in what will be committed)
+ #
+ # dir2/modified
+ # Ignored files:
+ # (use "git add -f <file>..." to include in what will be committed)
+ #
+ # .gitignore
+ # dir1/untracked
+ # dir2/untracked
+ # expect
+ # output
+ # untracked
+ EOF
+ git status --ignored >output &&
+ test_cmp expect output
+'
+
+test_expect_success 'status with gitignore (nothing untracked)' '
+ {
+ echo ".gitignore" &&
+ echo "expect" &&
+ echo "dir2/modified" &&
+ echo "output" &&
+ echo "untracked"
+ } >.gitignore &&
+
+ cat >expect <<-\EOF &&
+ M dir1/modified
+ A dir2/added
+ EOF
+ git status -s >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-\EOF &&
+ M dir1/modified
+ A dir2/added
+ !! .gitignore
+ !! dir1/untracked
+ !! dir2/modified
+ !! dir2/untracked
+ !! expect
+ !! output
+ !! untracked
+ EOF
+ git status -s --ignored >output &&
+ test_cmp expect output &&
+
+ cat >expect <<-\EOF &&
+ # On branch master
+ # Changes to be committed:
+ # (use "git reset HEAD <file>..." to unstage)
+ #
+ # new file: dir2/added
+ #
+ # Changes not staged for commit:
+ # (use "git add <file>..." to update what will be committed)
+ # (use "git checkout -- <file>..." to discard changes in working directory)
+ #
+ # modified: dir1/modified
+ #
+ # Ignored files:
+ # (use "git add -f <file>..." to include in what will be committed)
+ #
+ # .gitignore
+ # dir1/untracked
+ # dir2/modified
+ # dir2/untracked
+ # expect
+ # output
+ # untracked
+ EOF
+ git status --ignored >output &&
+ test_cmp expect output
+'
+
+rm -f .gitignore
+
cat >expect <<\EOF
## master
M dir1/modified
@@ -133,6 +271,12 @@ test_expect_success 'status -s -b' '
'
+test_expect_success 'setup dir3' '
+ mkdir dir3 &&
+ : >dir3/untracked1 &&
+ : >dir3/untracked2
+'
+
cat >expect <<EOF
# On branch master
# Changes to be committed:
@@ -140,7 +284,7 @@ cat >expect <<EOF
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -149,17 +293,15 @@ cat >expect <<EOF
# Untracked files not listed (use -u option to show untracked files)
EOF
test_expect_success 'status -uno' '
- mkdir dir3 &&
- : >dir3/untracked1 &&
- : >dir3/untracked2 &&
git status -uno >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
test_expect_success 'status (status.showUntrackedFiles no)' '
git config status.showuntrackedfiles no
+ test_when_finished "git config --unset status.showuntrackedfiles" &&
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -167,7 +309,7 @@ cat >expect <<EOF
# Changes to be committed:
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# modified: dir1/modified
#
# Untracked files not listed
@@ -175,7 +317,7 @@ EOF
git config advice.statusHints false
test_expect_success 'status -uno (advice.statusHints false)' '
git status -uno >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
git config --unset advice.statusHints
@@ -184,7 +326,6 @@ cat >expect << EOF
A dir2/added
EOF
test_expect_success 'status -s -uno' '
- git config --unset status.showuntrackedfiles
git status -s -uno >output &&
test_cmp expect output
'
@@ -202,7 +343,7 @@ cat >expect <<EOF
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -221,13 +362,14 @@ cat >expect <<EOF
EOF
test_expect_success 'status -unormal' '
git status -unormal >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
test_expect_success 'status (status.showUntrackedFiles normal)' '
git config status.showuntrackedfiles normal
+ test_when_finished "git config --unset status.showuntrackedfiles" &&
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -242,7 +384,6 @@ A dir2/added
?? untracked
EOF
test_expect_success 'status -s -unormal' '
- git config --unset status.showuntrackedfiles
git status -s -unormal >output &&
test_cmp expect output
'
@@ -260,7 +401,7 @@ cat >expect <<EOF
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -280,14 +421,18 @@ cat >expect <<EOF
EOF
test_expect_success 'status -uall' '
git status -uall >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
+
test_expect_success 'status (status.showUntrackedFiles all)' '
git config status.showuntrackedfiles all
+ test_when_finished "git config --unset status.showuntrackedfiles" &&
git status >output &&
- rm -rf dir3 &&
- git config --unset status.showuntrackedfiles &&
- test_cmp expect output
+ test_i18ncmp expect output
+'
+
+test_expect_success 'teardown dir3' '
+ rm -rf dir3
'
cat >expect <<EOF
@@ -320,7 +465,7 @@ cat >expect <<\EOF
#
# new file: ../dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -338,10 +483,8 @@ cat >expect <<\EOF
EOF
test_expect_success 'status with relative paths' '
-
(cd dir1 && git status) >output &&
- test_cmp expect output
-
+ test_i18ncmp expect output
'
cat >expect <<\EOF
@@ -381,18 +524,19 @@ test_expect_success 'status --porcelain ignores relative paths setting' '
test_expect_success 'setup unique colors' '
- git config status.color.untracked blue
+ git config status.color.untracked blue &&
+ git config status.color.branch green
'
cat >expect <<\EOF
-# On branch master
+# On branch <GREEN>master<RESET>
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# <GREEN>new file: dir2/added<RESET>
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -410,20 +554,17 @@ cat >expect <<\EOF
EOF
test_expect_success 'status with color.ui' '
-
git config color.ui always &&
+ test_when_finished "git config --unset color.ui" &&
git status | test_decode_color >output &&
- test_cmp expect output
-
+ test_i18ncmp expect output
'
test_expect_success 'status with color.status' '
-
- git config --unset color.ui &&
git config color.status always &&
+ test_when_finished "git config --unset color.status" &&
git status | test_decode_color >output &&
- test_cmp expect output
-
+ test_i18ncmp expect output
'
cat >expect <<\EOF
@@ -439,7 +580,6 @@ EOF
test_expect_success 'status -s with color.ui' '
- git config --unset color.status &&
git config color.ui always &&
git status -s | test_decode_color >output &&
test_cmp expect output
@@ -521,7 +661,7 @@ cat >expect <<\EOF
#
# new file: dir2/added
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -541,9 +681,10 @@ EOF
test_expect_success 'status without relative paths' '
- git config status.relativePaths false
+ git config status.relativePaths false &&
+ test_when_finished "git config --unset status.relativePaths" &&
(cd dir1 && git status) >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
@@ -560,6 +701,8 @@ EOF
test_expect_success 'status -s without relative paths' '
+ git config status.relativePaths false &&
+ test_when_finished "git config --unset status.relativePaths" &&
(cd dir1 && git status -s) >output &&
test_cmp expect output
@@ -583,7 +726,7 @@ cat <<EOF >expect
EOF
test_expect_success 'dry-run of partial commit excluding new file in index' '
git commit --dry-run dir1/modified >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -614,7 +757,7 @@ cat >expect <<EOF
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -632,13 +775,13 @@ cat >expect <<EOF
EOF
test_expect_success 'status submodule summary is disabled by default' '
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
# we expect the same as the previous test
test_expect_success 'status --untracked-files=all does not show submodule' '
git status --untracked-files=all >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -673,7 +816,7 @@ cat >expect <<EOF
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -697,7 +840,7 @@ EOF
test_expect_success 'status submodule summary' '
git config status.submodulesummary 10 &&
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -718,7 +861,7 @@ test_expect_success 'status -s submodule summary' '
cat >expect <<EOF
# On branch master
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -735,13 +878,13 @@ cat >expect <<EOF
# untracked
no changes added to commit (use "git add" and/or "git commit -a")
EOF
-test_expect_success 'status submodule summary (clean submodule)' '
+test_expect_success 'status submodule summary (clean submodule): commit' '
git commit -m "commit submodule" &&
git config status.submodulesummary 10 &&
test_must_fail git commit --dry-run >output &&
- test_cmp expect output &&
+ test_i18ncmp expect output &&
git status >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<EOF
@@ -758,6 +901,13 @@ test_expect_success 'status -s submodule summary (clean submodule)' '
test_cmp expect output
'
+test_expect_success 'status -z implies porcelain' '
+ git status --porcelain |
+ perl -pe "s/\012/\000/g" >expect &&
+ git status -z >output &&
+ test_cmp expect output
+'
+
cat >expect <<EOF
# On branch master
# Changes to be committed:
@@ -766,7 +916,7 @@ cat >expect <<EOF
# new file: dir2/added
# new file: sm
#
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -790,10 +940,10 @@ EOF
test_expect_success 'commit --dry-run submodule summary (--amend)' '
git config status.submodulesummary 10 &&
git commit --dry-run --amend >output &&
- test_cmp expect output
+ test_i18ncmp expect output
'
-test_expect_success POSIXPERM 'status succeeds in a read-only repository' '
+test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' '
(
chmod a-w .git &&
# make dir1/tracked stat-dirty
@@ -808,46 +958,130 @@ test_expect_success POSIXPERM 'status succeeds in a read-only repository' '
(exit $status)
'
+(cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm
+new_head=$(cd sm && git rev-parse --short=7 --verify HEAD)
+touch .gitmodules
+
cat > expect << EOF
# On branch master
-# Changed but not updated:
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# modified: sm
+#
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: dir1/modified
#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+# > Add bar
+#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
+# .gitmodules
# dir1/untracked
# dir2/modified
# dir2/untracked
# expect
# output
# untracked
-no changes added to commit (use "git add" and/or "git commit -a")
EOF
test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' '
- echo modified > sm/untracked &&
- git status --ignore-submodules=untracked > output &&
- test_cmp expect output
+ echo modified sm/untracked &&
+ git status --ignore-submodules=untracked >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' '
+ git config diff.ignoreSubmodules dirty &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --add -f .gitmodules submodule.subname.ignore untracked &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname &&
+ git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore untracked &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config --remove-section -f .gitmodules submodule.subname
'
test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' '
- git status --ignore-submodules=dirty > output &&
- test_cmp expect output
+ git status --ignore-submodules=dirty >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
+ git config diff.ignoreSubmodules dirty &&
+ git status >output &&
+ ! test -s actual &&
+ git config --add -f .gitmodules submodule.subname.ignore dirty &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname &&
+ git config --unset diff.ignoreSubmodules
+'
+
+test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore dirty &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' '
- echo modified > sm/foo &&
- git status --ignore-submodules=dirty > output &&
- test_cmp expect output
+ echo modified >sm/foo &&
+ git status --ignore-submodules=dirty >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success '.gitmodules ignore=dirty suppresses submodules with modified content' '
+ git config --add -f .gitmodules submodule.subname.ignore dirty &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname
+'
+
+test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore dirty &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
cat > expect << EOF
# On branch master
-# Changed but not updated:
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# modified: sm
+#
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
# (commit or discard the untracked or modified content in submodules)
@@ -855,64 +1089,135 @@ cat > expect << EOF
# modified: dir1/modified
# modified: sm (modified content)
#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+# > Add bar
+#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
+# .gitmodules
# dir1/untracked
# dir2/modified
# dir2/untracked
# expect
# output
# untracked
-no changes added to commit (use "git add" and/or "git commit -a")
EOF
test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" '
git status --ignore-submodules=untracked > output &&
- test_cmp expect output
+ test_i18ncmp expect output
+'
+
+test_expect_success ".gitmodules ignore=untracked doesn't suppress submodules with modified content" '
+ git config --add -f .gitmodules submodule.subname.ignore untracked &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore untracked &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD)
cat > expect << EOF
# On branch master
-# Changed but not updated:
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# modified: sm
+#
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: dir1/modified
# modified: sm (new commits)
#
+# Submodule changes to be committed:
+#
+# * sm $head...$new_head (1):
+# > Add bar
+#
# Submodules changed but not updated:
#
-# * sm $head...$head2 (1):
+# * sm $new_head...$head2 (1):
# > 2nd commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
+# .gitmodules
# dir1/untracked
# dir2/modified
# dir2/untracked
# expect
# output
# untracked
-no changes added to commit (use "git add" and/or "git commit -a")
EOF
test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" '
git status --ignore-submodules=untracked > output &&
- test_cmp expect output
+ test_i18ncmp expect output
+'
+
+test_expect_success ".gitmodules ignore=untracked doesn't suppress submodule summary" '
+ git config --add -f .gitmodules submodule.subname.ignore untracked &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore untracked &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" '
git status --ignore-submodules=dirty > output &&
- test_cmp expect output
+ test_i18ncmp expect output
+'
+test_expect_success ".gitmodules ignore=dirty doesn't suppress submodule summary" '
+ git config --add -f .gitmodules submodule.subname.ignore dirty &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname
+'
+
+test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore dirty &&
+ git config --add submodule.subname.path sm &&
+ git status >output &&
+ test_i18ncmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
cat > expect << EOF
# On branch master
-# Changed but not updated:
+# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
@@ -921,6 +1226,7 @@ cat > expect << EOF
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
+# .gitmodules
# dir1/untracked
# dir2/modified
# dir2/untracked
@@ -932,7 +1238,26 @@ EOF
test_expect_success "--ignore-submodules=all suppresses submodule summary" '
git status --ignore-submodules=all > output &&
- test_cmp expect output
+ test_i18ncmp expect output
+'
+
+test_expect_failure '.gitmodules ignore=all suppresses submodule summary' '
+ git config --add -f .gitmodules submodule.subname.ignore all &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git status > output &&
+ test_cmp expect output &&
+ git config -f .gitmodules --remove-section submodule.subname
+'
+
+test_expect_failure '.git/config ignore=all suppresses submodule summary' '
+ git config --add -f .gitmodules submodule.subname.ignore none &&
+ git config --add -f .gitmodules submodule.subname.path sm &&
+ git config --add submodule.subname.ignore all &&
+ git config --add submodule.subname.path sm &&
+ git status > output &&
+ test_cmp expect output &&
+ git config --remove-section submodule.subname &&
+ git config -f .gitmodules --remove-section submodule.subname
'
test_done
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
index 3ea33db..b61fd3c 100755
--- a/t/t7509-commit.sh
+++ b/t/t7509-commit.sh
@@ -40,7 +40,7 @@ test_expect_success '-C option copies only the message with --reset-author' '
test_tick &&
git commit -a -C Initial --reset-author &&
echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
- author_header HEAD >actual
+ author_header HEAD >actual &&
test_cmp expect actual &&
message_body Initial >expect &&
@@ -111,7 +111,7 @@ test_expect_success '--amend option with empty author' '
test_when_finished "git checkout Initial" &&
echo "Empty author test" >>foo &&
test_tick &&
- ! git commit -a -m "empty author" --amend 2>err &&
+ test_must_fail git commit -a -m "empty author" --amend 2>err &&
grep "empty ident" err
'
@@ -125,7 +125,7 @@ test_expect_success '--amend option with missing author' '
test_when_finished "git checkout Initial" &&
echo "Missing author test" >>foo &&
test_tick &&
- ! git commit -a -m "malformed author" --amend 2>err &&
+ test_must_fail git commit -a -m "malformed author" --amend 2>err &&
grep "empty ident" err
'
@@ -157,4 +157,33 @@ test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
test_must_fail git commit -a --reset-author -m done
'
+test_expect_success 'commit respects CHERRY_PICK_HEAD and MERGE_MSG' '
+ echo "cherry-pick 1a" >>foo &&
+ test_tick &&
+ git commit -am "cherry-pick 1" --author="Cherry <cherry@pick.er>" &&
+ git tag cherry-pick-head &&
+ git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
+ echo "This is a MERGE_MSG" >.git/MERGE_MSG &&
+ echo "cherry-pick 1b" >>foo &&
+ test_tick &&
+ git commit -a &&
+ author_header cherry-pick-head >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ echo "This is a MERGE_MSG" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
+ git rev-parse cherry-pick-head >.git/CHERRY_PICK_HEAD &&
+ echo "cherry-pick 2" >>foo &&
+ test_tick &&
+ git commit -am "cherry-pick 2" --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index cde8390..87aac83 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -5,190 +5,104 @@
test_description='git merge
-Testing basic merge operations/option parsing.'
+Testing basic merge operations/option parsing.
+
+! [c0] commit 0
+ ! [c1] commit 1
+ ! [c2] commit 2
+ ! [c3] commit 3
+ ! [c4] c4
+ ! [c5] c5
+ ! [c6] c6
+ * [master] Merge commit 'c1'
+--------
+ - [master] Merge commit 'c1'
+ + * [c1] commit 1
+ + [c6] c6
+ + [c5] c5
+ ++ [c4] c4
+ ++++ [c3] commit 3
+ + [c2] commit 2
++++++++* [c0] commit 0
+'
. ./test-lib.sh
-cat >file <<EOF
-1
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >file.1 <<EOF
-1 X
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >file.5 <<EOF
-1
-2
-3
-4
-5 X
-6
-7
-8
-9
-EOF
-
-cat >file.9 <<EOF
-1
-2
-3
-4
-5
-6
-7
-8
-9 X
-EOF
-
-cat >result.1 <<EOF
-1 X
-2
-3
-4
-5
-6
-7
-8
-9
-EOF
-
-cat >result.1-5 <<EOF
-1 X
-2
-3
-4
-5 X
-6
-7
-8
-9
-EOF
+printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
+printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
+printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
+printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
+printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
+printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+>empty
-cat >result.1-5-9 <<EOF
-1 X
-2
-3
-4
-5 X
-6
-7
-8
-9 X
-EOF
-
-create_merge_msgs() {
+create_merge_msgs () {
echo "Merge commit 'c2'" >msg.1-5 &&
echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 &&
- echo "Squashed commit of the following:" >squash.1 &&
- echo >>squash.1 &&
- git log --no-merges ^HEAD c1 >>squash.1 &&
- echo "Squashed commit of the following:" >squash.1-5 &&
- echo >>squash.1-5 &&
- git log --no-merges ^HEAD c2 >>squash.1-5 &&
- echo "Squashed commit of the following:" >squash.1-5-9 &&
- echo >>squash.1-5-9 &&
- git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 &&
- echo > msg.nolog &&
- echo "* commit 'c3':" >msg.log &&
- echo " commit 3" >>msg.log &&
- echo >>msg.log
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c1
+ } >squash.1 &&
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c2
+ } >squash.1-5 &&
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c2 c3
+ } >squash.1-5-9 &&
+ echo >msg.nolog &&
+ {
+ echo "* commit 'c3':" &&
+ echo " commit 3" &&
+ echo
+ } >msg.log
}
-verify_diff() {
- if ! test_cmp "$1" "$2"
- then
- echo "$3"
- false
- fi
-}
-
-verify_merge() {
- verify_diff "$2" "$1" "[OOPS] bad merge result" &&
- if test $(git ls-files -u | wc -l) -gt 0
- then
- echo "[OOPS] unmerged files"
- false
- fi &&
- if test_must_fail git diff --exit-code
- then
- echo "[OOPS] working tree != index"
- false
- fi &&
+verify_merge () {
+ test_cmp "$2" "$1" &&
+ git update-index --refresh &&
+ git diff --exit-code &&
if test -n "$3"
then
git show -s --pretty=format:%s HEAD >msg.act &&
- verify_diff "$3" msg.act "[OOPS] bad merge message"
+ test_cmp "$3" msg.act
fi
}
-verify_head() {
- if test "$1" != "$(git rev-parse HEAD)"
- then
- echo "[OOPS] HEAD != $1"
- false
- fi
+verify_head () {
+ echo "$1" >head.expected &&
+ git rev-parse HEAD >head.actual &&
+ test_cmp head.expected head.actual
}
-verify_parents() {
- i=1
- while test $# -gt 0
+verify_parents () {
+ printf '%s\n' "$@" >parents.expected &&
+ >parents.actual &&
+ i=1 &&
+ while test $i -le $#
do
- if test "$1" != "$(git rev-parse HEAD^$i)"
- then
- echo "[OOPS] HEAD^$i != $1"
- return 1
- fi
- i=$(expr $i + 1)
- shift
- done
+ git rev-parse HEAD^$i >>parents.actual &&
+ i=$(expr $i + 1) ||
+ return 1
+ done &&
+ test_must_fail git rev-parse --verify "HEAD^$i" &&
+ test_cmp parents.expected parents.actual
}
-verify_mergeheads() {
- i=1
- if ! test -f .git/MERGE_HEAD
- then
- echo "[OOPS] MERGE_HEAD is missing"
- false
- fi &&
- while test $# -gt 0
- do
- head=$(head -n $i .git/MERGE_HEAD | sed -ne \$p)
- if test "$1" != "$head"
- then
- echo "[OOPS] MERGE_HEAD $i != $1"
- return 1
- fi
- i=$(expr $i + 1)
- shift
- done
+verify_mergeheads () {
+ printf '%s\n' "$@" >mergehead.expected &&
+ test_cmp mergehead.expected .git/MERGE_HEAD
}
-verify_no_mergehead() {
- if test -f .git/MERGE_HEAD
- then
- echo "[OOPS] MERGE_HEAD exists"
- false
- fi
+verify_no_mergehead () {
+ ! test -e .git/MERGE_HEAD
}
-
test_expect_success 'setup' '
git add file &&
test_tick &&
@@ -219,7 +133,7 @@ test_expect_success 'setup' '
create_merge_msgs
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'test option parsing' '
test_must_fail git merge -$ c1 &&
@@ -230,18 +144,35 @@ test_expect_success 'test option parsing' '
test_must_fail git merge
'
+test_expect_success 'merge -h with invalid index' '
+ mkdir broken &&
+ (
+ cd broken &&
+ git init &&
+ >.git/index &&
+ test_expect_code 129 git merge -h 2>usage
+ ) &&
+ grep "[Uu]sage: git merge" broken/usage
+'
+
test_expect_success 'reject non-strategy with a git-merge-foo name' '
test_must_fail git merge -s index c1
'
test_expect_success 'merge c0 with c1' '
+ echo "OBJID HEAD@{0}: merge c1: Fast-forward" >reflog.expected &&
+
git reset --hard c0 &&
git merge c1 &&
verify_merge file result.1 &&
- verify_head "$c1"
+ verify_head "$c1" &&
+
+ git reflog -1 >reflog.actual &&
+ sed "s/$_x05[0-9a-f]*/OBJID/g" reflog.actual >reflog.fuzzy &&
+ test_cmp reflog.expected reflog.fuzzy
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c0 with c1 with --ff-only' '
git reset --hard c0 &&
@@ -251,7 +182,28 @@ test_expect_success 'merge c0 with c1 with --ff-only' '
verify_head "$c1"
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge from unborn branch' '
+ git checkout -f master &&
+ test_might_fail git branch -D kid &&
+
+ echo "OBJID HEAD@{0}: initial pull" >reflog.expected &&
+
+ git checkout --orphan kid &&
+ test_when_finished "git checkout -f master" &&
+ git rm -fr . &&
+ test_tick &&
+ git merge --ff-only c1 &&
+ verify_merge file result.1 &&
+ verify_head "$c1" &&
+
+ git reflog -1 >reflog.actual &&
+ sed "s/$_x05[0-9a-f][0-9a-f]/OBJID/g" reflog.actual >reflog.fuzzy &&
+ test_cmp reflog.expected reflog.fuzzy
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2' '
git reset --hard c1 &&
@@ -261,7 +213,7 @@ test_expect_success 'merge c1 with c2' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 and c3' '
git reset --hard c1 &&
@@ -271,14 +223,30 @@ test_expect_success 'merge c1 with c2 and c3' '
verify_parents $c1 $c2 $c3
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
-test_expect_success 'failing merges with --ff-only' '
+test_expect_success 'merges with --ff-only' '
git reset --hard c1 &&
test_tick &&
test_must_fail git merge --ff-only c2 &&
test_must_fail git merge --ff-only c3 &&
- test_must_fail git merge --ff-only c2 c3
+ test_must_fail git merge --ff-only c2 c3 &&
+ git reset --hard c0 &&
+ git merge c3 &&
+ verify_head $c3
+'
+
+test_expect_success 'merges with merge.ff=only' '
+ git reset --hard c1 &&
+ test_tick &&
+ test_when_finished "git config --unset merge.ff" &&
+ git config merge.ff only &&
+ test_must_fail git merge c2 &&
+ test_must_fail git merge c3 &&
+ test_must_fail git merge c2 c3 &&
+ git reset --hard c0 &&
+ git merge c3 &&
+ verify_head $c3
'
test_expect_success 'merge c0 with c1 (no-commit)' '
@@ -288,7 +256,7 @@ test_expect_success 'merge c0 with c1 (no-commit)' '
verify_head $c1
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (no-commit)' '
git reset --hard c1 &&
@@ -298,7 +266,7 @@ test_expect_success 'merge c1 with c2 (no-commit)' '
verify_mergeheads $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 and c3 (no-commit)' '
git reset --hard c1 &&
@@ -308,7 +276,7 @@ test_expect_success 'merge c1 with c2 and c3 (no-commit)' '
verify_mergeheads $c2 $c3
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c0 with c1 (squash)' '
git reset --hard c0 &&
@@ -316,10 +284,10 @@ test_expect_success 'merge c0 with c1 (squash)' '
verify_merge file result.1 &&
verify_head $c0 &&
verify_no_mergehead &&
- verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message"
+ test_cmp squash.1 .git/SQUASH_MSG
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c0 with c1 (squash, ff-only)' '
git reset --hard c0 &&
@@ -327,10 +295,10 @@ test_expect_success 'merge c0 with c1 (squash, ff-only)' '
verify_merge file result.1 &&
verify_head $c0 &&
verify_no_mergehead &&
- verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message"
+ test_cmp squash.1 .git/SQUASH_MSG
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (squash)' '
git reset --hard c1 &&
@@ -338,17 +306,17 @@ test_expect_success 'merge c1 with c2 (squash)' '
verify_merge file result.1-5 &&
verify_head $c1 &&
verify_no_mergehead &&
- verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message"
+ test_cmp squash.1-5 .git/SQUASH_MSG
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '
git reset --hard c1 &&
test_must_fail git merge --squash --ff-only c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 and c3 (squash)' '
git reset --hard c1 &&
@@ -356,10 +324,10 @@ test_expect_success 'merge c1 with c2 and c3 (squash)' '
verify_merge file result.1-5-9 &&
verify_head $c1 &&
verify_no_mergehead &&
- verify_diff squash.1-5-9 .git/SQUASH_MSG "[OOPS] bad squash message"
+ test_cmp squash.1-5-9 .git/SQUASH_MSG
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (no-commit in config)' '
git reset --hard c1 &&
@@ -370,7 +338,40 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' '
verify_mergeheads $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge c1 with c2 (log in config)' '
+ git config branch.master.mergeoptions "" &&
+ git reset --hard c1 &&
+ git merge --log c2 &&
+ git show -s --pretty=tformat:%s%n%b >expect &&
+
+ git config branch.master.mergeoptions --log &&
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >actual &&
+
+ test_cmp expect actual
+'
+
+test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
+ test_when_finished "git config --remove-section branch.master" &&
+ test_when_finished "git config --remove-section merge" &&
+ test_might_fail git config --remove-section branch.master &&
+ test_might_fail git config --remove-section merge &&
+
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >expect &&
+
+ git config branch.master.mergeoptions "--no-log" &&
+ git config merge.log true &&
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >actual &&
+
+ test_cmp expect actual
+'
test_expect_success 'merge c1 with c2 (squash in config)' '
git reset --hard c1 &&
@@ -379,10 +380,10 @@ test_expect_success 'merge c1 with c2 (squash in config)' '
verify_merge file result.1-5 &&
verify_head $c1 &&
verify_no_mergehead &&
- verify_diff squash.1-5 .git/SQUASH_MSG "[OOPS] bad squash message"
+ test_cmp squash.1-5 .git/SQUASH_MSG
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'override config option -n with --summary' '
git reset --hard c1 &&
@@ -412,7 +413,7 @@ test_expect_success 'override config option -n with --stat' '
fi
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'override config option --stat' '
git reset --hard c1 &&
@@ -428,7 +429,7 @@ test_expect_success 'override config option --stat' '
fi
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (override --no-commit)' '
git reset --hard c1 &&
@@ -439,7 +440,7 @@ test_expect_success 'merge c1 with c2 (override --no-commit)' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (override --squash)' '
git reset --hard c1 &&
@@ -450,7 +451,7 @@ test_expect_success 'merge c1 with c2 (override --squash)' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c0 with c1 (no-ff)' '
git reset --hard c0 &&
@@ -461,9 +462,43 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
verify_parents $c0 $c1
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'merge c0 with c1 (merge.ff=false)' '
+ git reset --hard c0 &&
+ git config merge.ff false &&
+ test_tick &&
+ git merge c1 &&
+ git config --remove-section merge &&
+ verify_merge file result.1 &&
+ verify_parents $c0 $c1
+'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
+ git reset --hard c0 &&
+ git config branch.master.mergeoptions --ff &&
+ git config merge.ff false &&
+ test_tick &&
+ git merge c1 &&
+ git config --remove-section "branch.master" &&
+ git config --remove-section "merge" &&
+ verify_merge file result.1 &&
+ verify_parents "$c0"
+'
+
+test_expect_success 'tolerate unknown values for merge.ff' '
+ git reset --hard c0 &&
+ git config merge.ff something-new &&
+ test_tick &&
+ git merge c1 2>message &&
+ git config --remove-section "merge" &&
+ verify_head "$c1" &&
+ test_cmp empty message
+'
test_expect_success 'combining --squash and --no-ff is refused' '
+ git reset --hard c0 &&
test_must_fail git merge --squash --no-ff c1 &&
test_must_fail git merge --no-ff --squash c1
'
@@ -485,20 +520,20 @@ test_expect_success 'merge log message' '
git reset --hard c0 &&
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
+ test_cmp msg.nolog msg.act &&
git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
+ test_cmp msg.log msg.act &&
git reset --hard HEAD^ &&
git config merge.log yes &&
git merge c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- verify_diff msg.log msg.act "[OOPS] bad merge log message"
+ test_cmp msg.log msg.act
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
@@ -509,7 +544,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
@@ -520,7 +555,7 @@ test_expect_success 'merge c1 with c0, c2, c0, and c1' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c1 and c2' '
git reset --hard c1 &&
@@ -531,7 +566,7 @@ test_expect_success 'merge c1 with c1 and c2' '
verify_parents $c1 $c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge fast-forward in a dirty tree' '
git reset --hard c0 &&
@@ -541,16 +576,16 @@ test_expect_success 'merge fast-forward in a dirty tree' '
git merge c2
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'in-index merge' '
git reset --hard c0 &&
- git merge --no-ff -s resolve c1 > out &&
- grep "Wonderful." out &&
+ git merge --no-ff -s resolve c1 >out &&
+ test_i18ngrep "Wonderful." out &&
verify_parents $c0 $c1
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'refresh the index before merging' '
git reset --hard c1 &&
@@ -558,31 +593,39 @@ test_expect_success 'refresh the index before merging' '
git merge c3
'
-cat >expected <<EOF
-Merge branch 'c5' (early part)
+cat >expected.branch <<\EOF
+Merge branch 'c5-branch' (early part)
+EOF
+cat >expected.tag <<\EOF
+Merge commit 'c5~1'
EOF
test_expect_success 'merge early part of c2' '
git reset --hard c3 &&
- echo c4 > c4.c &&
+ echo c4 >c4.c &&
git add c4.c &&
git commit -m c4 &&
git tag c4 &&
- echo c5 > c5.c &&
+ echo c5 >c5.c &&
git add c5.c &&
git commit -m c5 &&
git tag c5 &&
git reset --hard c3 &&
- echo c6 > c6.c &&
+ echo c6 >c6.c &&
git add c6.c &&
git commit -m c6 &&
git tag c6 &&
+ git branch -f c5-branch c5 &&
+ git merge c5-branch~1 &&
+ git show -s --pretty=format:%s HEAD >actual.branch &&
+ git reset --keep HEAD^ &&
git merge c5~1 &&
- git show -s --pretty=format:%s HEAD > actual &&
- test_cmp actual expected
+ git show -s --pretty=format:%s HEAD >actual.tag &&
+ test_cmp expected.branch actual.branch &&
+ test_cmp expected.tag actual.tag
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge --no-ff --no-commit && commit' '
git reset --hard c0 &&
@@ -591,13 +634,13 @@ test_expect_success 'merge --no-ff --no-commit && commit' '
verify_parents $c0 $c1
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'amending no-ff merge commit' '
EDITOR=: git commit --amend &&
verify_parents $c0 $c1
'
-test_debug 'gitk --all'
+test_debug 'git log --graph --decorate --oneline --all'
test_done
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index 7ba94ea..b44b293 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -114,13 +114,13 @@ test_expect_success 'setup conflicted merge' '
test_expect_success 'merge picks up the best result' '
git config --unset-all pull.twohead &&
git reset --hard c5 &&
- git merge -s resolve c6
+ test_must_fail git merge -s resolve c6 &&
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive c6
+ test_must_fail git merge -s recursive c6 &&
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive -s resolve c6
+ test_must_fail git merge -s recursive -s resolve c6 &&
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
@@ -129,13 +129,13 @@ test_expect_success 'merge picks up the best result' '
test_expect_success 'merge picks up the best result (from config)' '
git config pull.twohead "recursive resolve" &&
git reset --hard c5 &&
- git merge -s resolve c6
+ test_must_fail git merge -s resolve c6 &&
resolve_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge -s recursive c6
+ test_must_fail git merge -s recursive c6 &&
recursive_count=$(conflict_count) &&
git reset --hard c5 &&
- git merge c6
+ test_must_fail git merge c6 &&
auto_count=$(conflict_count) &&
test $auto_count = $recursive_count &&
test $auto_count != $resolve_count
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
index 2746169..0a46795 100755
--- a/t/t7602-merge-octopus-many.sh
+++ b/t/t7602-merge-octopus-many.sh
@@ -31,7 +31,7 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
do
refs="$refs c$i"
i=`expr $i + 1`
- done
+ done &&
git merge $refs &&
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
i=1 &&
diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh
index 52a451d..8e8c4d7 100755
--- a/t/t7606-merge-custom.sh
+++ b/t/t7606-merge-custom.sh
@@ -1,49 +1,93 @@
#!/bin/sh
-test_description='git merge
+test_description="git merge
-Testing a custom strategy.'
+Testing a custom strategy.
+
+* (HEAD, master) Merge commit 'c3'
+|\
+| * (tag: c3) c3
+* | (tag: c1) c1
+|/
+| * tag: c2) c2
+|/
+* (tag: c0) c0
+"
. ./test-lib.sh
-cat >git-merge-theirs <<EOF
-#!$SHELL_PATH
-eval git read-tree --reset -u \\\$\$#
-EOF
-chmod +x git-merge-theirs
-PATH=.:$PATH
-export PATH
+test_expect_success 'set up custom strategy' '
+ cat >git-merge-theirs <<-EOF &&
+ #!$SHELL_PATH
+ eval git read-tree --reset -u \\\$\$#
+ EOF
+
+ chmod +x git-merge-theirs &&
+ PATH=.:$PATH &&
+ export PATH
+'
test_expect_success 'setup' '
- echo c0 >c0.c &&
- git add c0.c &&
- git commit -m c0 &&
- git tag c0 &&
- echo c1 >c1.c &&
- git add c1.c &&
- git commit -m c1 &&
- git tag c1 &&
- git reset --hard c0 &&
+ test_commit c0 c0.c &&
+ test_commit c1 c1.c &&
+ git reset --keep c0 &&
echo c1c1 >c1.c &&
- echo c2 >c2.c &&
- git add c1.c c2.c &&
- git commit -m c2 &&
- git tag c2
+ git add c1.c &&
+ test_commit c2 c2.c &&
+ git reset --keep c0 &&
+ test_commit c3 c3.c
'
test_expect_success 'merge c2 with a custom strategy' '
git reset --hard c1 &&
+
+ git rev-parse c1 >head.old &&
+ git rev-parse c2 >second-parent.expected &&
+ git rev-parse c2^{tree} >tree.expected &&
git merge -s theirs c2 &&
- test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
- test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
- test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
- test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" &&
+
+ git rev-parse HEAD >head.new &&
+ git rev-parse HEAD^1 >first-parent &&
+ git rev-parse HEAD^2 >second-parent &&
+ git rev-parse HEAD^{tree} >tree &&
+ git update-index --refresh &&
git diff --exit-code &&
git diff --exit-code c2 HEAD &&
git diff --exit-code c2 &&
+
+ ! test_cmp head.old head.new &&
+ test_cmp head.old first-parent &&
+ test_cmp second-parent.expected second-parent &&
+ test_cmp tree.expected tree &&
test -f c0.c &&
grep c1c1 c1.c &&
test -f c2.c
'
+test_expect_success 'trivial merge with custom strategy' '
+ git reset --hard c1 &&
+
+ git rev-parse c1 >head.old &&
+ git rev-parse c3 >second-parent.expected &&
+ git rev-parse c3^{tree} >tree.expected &&
+ git merge -s theirs c3 &&
+
+ git rev-parse HEAD >head.new &&
+ git rev-parse HEAD^1 >first-parent &&
+ git rev-parse HEAD^2 >second-parent &&
+ git rev-parse HEAD^{tree} >tree &&
+ git update-index --refresh &&
+ git diff --exit-code &&
+ git diff --exit-code c3 HEAD &&
+ git diff --exit-code c3 &&
+
+ ! test_cmp head.old head.new &&
+ test_cmp head.old first-parent &&
+ test_cmp second-parent.expected second-parent &&
+ test_cmp tree.expected tree &&
+ test -f c0.c &&
+ ! test -e c1.c &&
+ test -f c3.c
+'
+
test_done
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index 49f4e15..72a8731 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -7,48 +7,54 @@ Do not overwrite changes.'
. ./test-lib.sh
test_expect_success 'setup' '
- echo c0 > c0.c &&
- git add c0.c &&
- git commit -m c0 &&
- git tag c0 &&
- echo c1 > c1.c &&
- git add c1.c &&
- git commit -m c1 &&
- git tag c1 &&
+ test_commit c0 c0.c &&
+ test_commit c1 c1.c &&
+ test_commit c1a c1.c "c1 a" &&
git reset --hard c0 &&
- echo c2 > c2.c &&
- git add c2.c &&
- git commit -m c2 &&
- git tag c2 &&
- git reset --hard c1 &&
- echo "c1 a" > c1.c &&
- git add c1.c &&
- git commit -m "c1 a" &&
- git tag c1a &&
+ test_commit c2 c2.c &&
+ git reset --hard c0 &&
+ mkdir sub &&
+ echo "sub/f" > sub/f &&
+ mkdir sub2 &&
+ echo "sub2/f" > sub2/f &&
+ git add sub/f sub2/f &&
+ git commit -m sub &&
+ git tag sub &&
echo "VERY IMPORTANT CHANGES" > important
'
test_expect_success 'will not overwrite untracked file' '
git reset --hard c1 &&
- cat important > c2.c &&
- ! git merge c2 &&
+ cp important c2.c &&
+ test_must_fail git merge c2 &&
+ test_path_is_missing .git/MERGE_HEAD &&
test_cmp important c2.c
'
+test_expect_success 'will overwrite tracked file' '
+ git reset --hard c1 &&
+ cp important c2.c &&
+ git add c2.c &&
+ git commit -m important &&
+ git checkout c2
+'
+
test_expect_success 'will not overwrite new file' '
git reset --hard c1 &&
- cat important > c2.c &&
+ cp important c2.c &&
git add c2.c &&
- ! git merge c2 &&
+ test_must_fail git merge c2 &&
+ test_path_is_missing .git/MERGE_HEAD &&
test_cmp important c2.c
'
test_expect_success 'will not overwrite staged changes' '
git reset --hard c1 &&
- cat important > c2.c &&
+ cp important c2.c &&
git add c2.c &&
rm c2.c &&
- ! git merge c2 &&
+ test_must_fail git merge c2 &&
+ test_path_is_missing .git/MERGE_HEAD &&
git checkout c2.c &&
test_cmp important c2.c
'
@@ -57,8 +63,8 @@ test_expect_success 'will not overwrite removed file' '
git reset --hard c1 &&
git rm c1.c &&
git commit -m "rm c1.c" &&
- cat important > c1.c &&
- ! git merge c1a &&
+ cp important c1.c &&
+ test_must_fail git merge c1a &&
test_cmp important c1.c
'
@@ -66,9 +72,10 @@ test_expect_success 'will not overwrite re-added file' '
git reset --hard c1 &&
git rm c1.c &&
git commit -m "rm c1.c" &&
- cat important > c1.c &&
+ cp important c1.c &&
git add c1.c &&
- ! git merge c1a &&
+ test_must_fail git merge c1a &&
+ test_path_is_missing .git/MERGE_HEAD &&
test_cmp important c1.c
'
@@ -76,12 +83,101 @@ test_expect_success 'will not overwrite removed file with staged changes' '
git reset --hard c1 &&
git rm c1.c &&
git commit -m "rm c1.c" &&
- cat important > c1.c &&
+ cp important c1.c &&
git add c1.c &&
rm c1.c &&
- ! git merge c1a &&
+ test_must_fail git merge c1a &&
+ test_path_is_missing .git/MERGE_HEAD &&
git checkout c1.c &&
test_cmp important c1.c
'
+test_expect_success 'will not overwrite untracked subtree' '
+ git reset --hard c0 &&
+ rm -rf sub &&
+ mkdir -p sub/f &&
+ cp important sub/f/important &&
+ test_must_fail git merge sub &&
+ test_path_is_missing .git/MERGE_HEAD &&
+ test_cmp important sub/f/important
+'
+
+cat >expect <<\EOF
+error: The following untracked working tree files would be overwritten by merge:
+ sub
+ sub2
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'will not overwrite untracked file in leading path' '
+ git reset --hard c0 &&
+ rm -rf sub &&
+ cp important sub &&
+ cp important sub2 &&
+ test_must_fail git merge sub 2>out &&
+ test_cmp out expect &&
+ test_path_is_missing .git/MERGE_HEAD &&
+ test_cmp important sub &&
+ test_cmp important sub2 &&
+ rm -f sub sub2
+'
+
+test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading path' '
+ git reset --hard c0 &&
+ rm -rf sub &&
+ mkdir sub2 &&
+ ln -s sub2 sub &&
+ test_must_fail git merge sub &&
+ test_path_is_missing .git/MERGE_HEAD
+'
+
+test_expect_success SYMLINKS 'will not be confused by symlink in leading path' '
+ git reset --hard c0 &&
+ rm -rf sub &&
+ ln -s sub2 sub &&
+ git add sub &&
+ git commit -m ln &&
+ git checkout sub
+'
+
+cat >expect <<\EOF
+error: Untracked working tree file 'c0.c' would be overwritten by merge.
+fatal: read-tree failed
+EOF
+
+test_expect_success 'will not overwrite untracked file on unborn branch' '
+ git reset --hard c0 &&
+ git rm -fr . &&
+ git checkout --orphan new &&
+ cp important c0.c &&
+ test_must_fail git merge c0 2>out &&
+ test_i18ncmp out expect
+'
+
+test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' '
+ test_when_finished "rm c0.c" &&
+ test_path_is_missing .git/MERGE_HEAD &&
+ test_cmp important c0.c
+'
+
+test_expect_success 'failed merge leaves unborn branch in the womb' '
+ test_must_fail git rev-parse --verify HEAD
+'
+
+test_expect_success 'set up unborn branch and content' '
+ git symbolic-ref HEAD refs/heads/unborn &&
+ rm -f .git/index &&
+ echo foo > tracked-file &&
+ git add tracked-file &&
+ echo bar > untracked-file
+'
+
+test_expect_success 'will not clobber WT/index when merging into unborn' '
+ git merge master &&
+ grep foo tracked-file &&
+ git show :tracked-file >expect &&
+ grep foo expect &&
+ grep bar untracked-file
+'
+
test_done
diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh
index 28d5679..9225fa6 100755
--- a/t/t7608-merge-messages.sh
+++ b/t/t7608-merge-messages.sh
@@ -47,14 +47,14 @@ test_expect_success 'ambiguous tag' '
check_oneline "Merge commit QambiguousQ"
'
-test_expect_success 'remote branch' '
+test_expect_success 'remote-tracking branch' '
git checkout -b remote master &&
test_commit remote-1 &&
git update-ref refs/remotes/origin/master remote &&
git checkout master &&
test_commit master-5 &&
git merge origin/master &&
- check_oneline "Merge remote branch Qorigin/masterQ"
+ check_oneline "Merge remote-tracking branch Qorigin/masterQ"
'
test_done
diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh
new file mode 100755
index 0000000..c994836
--- /dev/null
+++ b/t/t7609-merge-co-error-msgs.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+
+test_description='unpack-trees error messages'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+ echo one >one &&
+ git add one &&
+ git commit -a -m First &&
+
+ git checkout -b branch &&
+ echo two >two &&
+ echo three >three &&
+ echo four >four &&
+ echo five >five &&
+ git add two three four five &&
+ git commit -m Second &&
+
+ git checkout master &&
+ echo other >two &&
+ echo other >three &&
+ echo other >four &&
+ echo other >five
+'
+
+cat >expect <<\EOF
+error: The following untracked working tree files would be overwritten by merge:
+ five
+ four
+ three
+ two
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' '
+ test_must_fail git merge branch 2>out &&
+ test_cmp out expect &&
+ git commit --allow-empty -m empty &&
+ (
+ GIT_MERGE_VERBOSITY=0 &&
+ export GIT_MERGE_VERBOSITY &&
+ test_must_fail git merge branch 2>out2
+ ) &&
+ test_cmp out2 expect &&
+ git reset --hard HEAD^
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by merge:
+ four
+ three
+ two
+Please, commit your changes or stash them before you can merge.
+error: The following untracked working tree files would be overwritten by merge:
+ five
+Please move or remove them before you can merge.
+EOF
+
+test_expect_success 'untracked files or local changes ovewritten by merge' '
+ git add two &&
+ git add three &&
+ git add four &&
+ test_must_fail git merge branch 2>out &&
+ test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by checkout:
+ rep/one
+ rep/two
+Please, commit your changes or stash them before you can switch branches.
+EOF
+
+test_expect_success 'cannot switch branches because of local changes' '
+ git add five &&
+ mkdir rep &&
+ echo one >rep/one &&
+ echo two >rep/two &&
+ git add rep/one rep/two &&
+ git commit -m Fourth &&
+ git checkout master &&
+ echo uno >rep/one &&
+ echo dos >rep/two &&
+ test_must_fail git checkout branch 2>out &&
+ test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Your local changes to the following files would be overwritten by checkout:
+ rep/one
+ rep/two
+Please, commit your changes or stash them before you can switch branches.
+EOF
+
+test_expect_success 'not uptodate file porcelain checkout error' '
+ git add rep/one rep/two &&
+ test_must_fail git checkout branch 2>out &&
+ test_cmp out expect
+'
+
+cat >expect <<\EOF
+error: Updating the following directories would lose untracked files in it:
+ rep
+ rep2
+
+EOF
+
+test_expect_success 'not_uptodate_dir porcelain checkout error' '
+ git init uptodate &&
+ cd uptodate &&
+ mkdir rep &&
+ mkdir rep2 &&
+ touch rep/foo &&
+ touch rep2/foo &&
+ git add rep/foo rep2/foo &&
+ git commit -m init &&
+ git checkout -b branch &&
+ git rm rep -r &&
+ git rm rep2 -r &&
+ >rep &&
+ >rep2 &&
+ git add rep rep2&&
+ git commit -m "added test as a file" &&
+ git checkout master &&
+ >rep/untracked-file &&
+ >rep2/untracked-file &&
+ test_must_fail git checkout branch 2>out &&
+ test_cmp out ../expect
+'
+
+test_done
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index e768c3e..cbc08e3 100755
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
@@ -14,24 +14,59 @@ Testing basic merge tool invocation'
# running mergetool
test_expect_success 'setup' '
+ git config rerere.enabled true &&
echo master >file1 &&
+ echo master file11 >file11 &&
+ echo master file12 >file12 &&
+ echo master file13 >file13 &&
+ echo master file14 >file14 &&
mkdir subdir &&
echo master sub >subdir/file3 &&
- git add file1 subdir/file3 &&
- git commit -m "added file1" &&
+ test_create_repo submod &&
+ (
+ cd submod &&
+ : >foo &&
+ git add foo &&
+ git commit -m "Add foo"
+ ) &&
+ git submodule add git://example.com/submod submod &&
+ git add file1 file1[1-4] subdir/file3 .gitmodules submod &&
+ git commit -m "add initial versions" &&
git checkout -b branch1 master &&
+ git submodule update -N &&
echo branch1 change >file1 &&
echo branch1 newfile >file2 &&
+ echo branch1 change file11 >file11 &&
+ echo branch1 change file13 >file13 &&
echo branch1 sub >subdir/file3 &&
- git add file1 file2 subdir/file3 &&
+ (
+ cd submod &&
+ echo branch1 submodule >bar &&
+ git add bar &&
+ git commit -m "Add bar on branch1" &&
+ git checkout -b submod-branch1
+ ) &&
+ git add file1 file11 file13 file2 subdir/file3 submod &&
+ git rm file12 &&
git commit -m "branch1 changes" &&
git checkout master &&
+ git submodule update -N &&
echo master updated >file1 &&
echo master new >file2 &&
+ echo master updated file12 >file12 &&
+ echo master updated file14 >file14 &&
echo master new sub >subdir/file3 &&
- git add file1 file2 subdir/file3 &&
+ (
+ cd submod &&
+ echo master submodule >bar &&
+ git add bar &&
+ git commit -m "Add bar on master" &&
+ git checkout -b submod-master
+ ) &&
+ git add file1 file12 file14 file2 subdir/file3 submod &&
+ git rm file11 &&
git commit -m "master updates" &&
git config merge.tool mytool &&
@@ -41,49 +76,351 @@ test_expect_success 'setup' '
test_expect_success 'custom mergetool' '
git checkout -b test1 branch1 &&
+ git submodule update -N &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod >/dev/null 2>&1 ) &&
test "$(cat file1)" = "master updated" &&
test "$(cat file2)" = "master new" &&
test "$(cat subdir/file3)" = "master new sub" &&
+ test "$(cat submod/bar)" = "branch1 submodule" &&
git commit -m "branch1 resolved with mergetool"
'
test_expect_success 'mergetool crlf' '
git config core.autocrlf true &&
- git checkout -b test2 branch1
+ git checkout -b test2 branch1 &&
test_must_fail git merge master >/dev/null 2>&1 &&
( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
+ ( yes "r" | git mergetool submod >/dev/null 2>&1 ) &&
test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
git commit -m "branch1 resolved with mergetool - autocrlf" &&
git config core.autocrlf false &&
git reset --hard
'
test_expect_success 'mergetool in subdir' '
- git checkout -b test3 branch1
- cd subdir && (
+ git checkout -b test3 branch1 &&
+ git submodule update -N &&
+ (
+ cd subdir &&
+ test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
+ test "$(cat file3)" = "master new sub"
+ )
+'
+
+test_expect_success 'mergetool on file in parent dir' '
+ (
+ cd subdir &&
+ ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) &&
+ test "$(cat ../file1)" = "master updated" &&
+ test "$(cat ../file2)" = "master new" &&
+ test "$(cat ../submod/bar)" = "branch1 submodule" &&
+ git commit -m "branch1 resolved with mergetool - subdir"
+ )
+'
+
+test_expect_success 'mergetool skips autoresolved' '
+ git checkout -b test4 branch1 &&
+ git submodule update -N &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod >/dev/null 2>&1 ) &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git reset --hard
+'
+
+test_expect_success 'mergetool merges all from subdir' '
+ (
+ cd subdir &&
+ git config rerere.enabled false &&
+ test_must_fail git merge master &&
+ ( yes "r" | git mergetool ../submod ) &&
+ ( yes "d" "d" | git mergetool --no-prompt ) &&
+ test "$(cat ../file1)" = "master updated" &&
+ test "$(cat ../file2)" = "master new" &&
+ test "$(cat file3)" = "master new sub" &&
+ ( cd .. && git submodule update -N ) &&
+ test "$(cat ../submod/bar)" = "master submodule" &&
+ git commit -m "branch2 resolved by mergetool from subdir"
+ )
+'
+
+test_expect_success 'mergetool skips resolved paths when rerere is active' '
+ git config rerere.enabled true &&
+ rm -rf .git/rr-cache &&
+ git checkout -b test5 branch1
+ git submodule update -N &&
test_must_fail git merge master >/dev/null 2>&1 &&
- ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
- test "$(cat file3)" = "master new sub" )
+ ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) &&
+ ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) &&
+ git submodule update -N &&
+ output="$(yes "n" | git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git reset --hard
'
-# We can't merge files from parent directories when running mergetool
-# from a subdir. Is this a bug?
-#
-#test_expect_failure 'mergetool in subdir' '
-# cd subdir && (
-# ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
-# ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
-# test "$(cat ../file1)" = "master updated" &&
-# test "$(cat ../file2)" = "master new" &&
-# git commit -m "branch1 resolved with mergetool - subdir" )
-#'
+test_expect_success 'deleted vs modified submodule' '
+ git checkout -b test6 branch1 &&
+ git submodule update -N &&
+ mv submod submod-movedaside &&
+ git rm submod &&
+ git commit -m "Submodule deleted from branch" &&
+ git checkout -b test6.a test6 &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "r" | git mergetool submod ) &&
+ rmdir submod && mv submod-movedaside submod &&
+ test "$(cat submod/bar)" = "branch1 submodule" &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping module" &&
+
+ mv submod submod-movedaside &&
+ git checkout -b test6.b test6 &&
+ git submodule update -N &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod ) &&
+ test ! -e submod &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by deleting module" &&
+
+ mv submod-movedaside submod &&
+ git checkout -b test6.c master &&
+ git submodule update -N &&
+ test_must_fail git merge test6 &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "r" | git mergetool submod ) &&
+ test ! -e submod &&
+ test -d submod.orig &&
+ git submodule update -N &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by deleting module" &&
+ mv submod.orig submod &&
+
+ git checkout -b test6.d master &&
+ git submodule update -N &&
+ test_must_fail git merge test6 &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod ) &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping module" &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'file vs modified submodule' '
+ git checkout -b test7 branch1 &&
+ git submodule update -N &&
+ mv submod submod-movedaside &&
+ git rm submod &&
+ echo not a submodule >submod &&
+ git add submod &&
+ git commit -m "Submodule path becomes file" &&
+ git checkout -b test7.a branch1 &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "r" | git mergetool submod ) &&
+ rmdir submod && mv submod-movedaside submod &&
+ test "$(cat submod/bar)" = "branch1 submodule" &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping module" &&
+
+ mv submod submod-movedaside &&
+ git checkout -b test7.b test7 &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod ) &&
+ git submodule update -N &&
+ test "$(cat submod)" = "not a submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping file" &&
+
+ git checkout -b test7.c master &&
+ rmdir submod && mv submod-movedaside submod &&
+ test ! -e submod.orig &&
+ git submodule update -N &&
+ test_must_fail git merge test7 &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "r" | git mergetool submod ) &&
+ test -d submod.orig &&
+ git submodule update -N &&
+ test "$(cat submod)" = "not a submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping file" &&
+
+ git checkout -b test7.d master &&
+ rmdir submod && mv submod.orig submod &&
+ git submodule update -N &&
+ test_must_fail git merge test7 &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+ ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
+ ( yes "l" | git mergetool submod ) &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging" &&
+ git commit -m "Merge resolved by keeping module"
+'
+
+test_expect_success 'submodule in subdirectory' '
+ git checkout -b test10 branch1 &&
+ git submodule update -N &&
+ (
+ cd subdir &&
+ test_create_repo subdir_module &&
+ (
+ cd subdir_module &&
+ : >file15 &&
+ git add file15 &&
+ git commit -m "add initial versions"
+ )
+ ) &&
+ git submodule add git://example.com/subsubmodule subdir/subdir_module &&
+ git add subdir/subdir_module &&
+ git commit -m "add submodule in subdirectory" &&
+
+ git checkout -b test10.a test10 &&
+ git submodule update -N &&
+ (
+ cd subdir/subdir_module &&
+ git checkout -b super10.a &&
+ echo test10.a >file15 &&
+ git add file15 &&
+ git commit -m "on branch 10.a"
+ ) &&
+ git add subdir/subdir_module &&
+ git commit -m "change submodule in subdirectory on test10.a" &&
+
+ git checkout -b test10.b test10 &&
+ git submodule update -N &&
+ (
+ cd subdir/subdir_module &&
+ git checkout -b super10.b &&
+ echo test10.b >file15 &&
+ git add file15 &&
+ git commit -m "on branch 10.b"
+ ) &&
+ git add subdir/subdir_module &&
+ git commit -m "change submodule in subdirectory on test10.b" &&
+
+ test_must_fail git merge test10.a >/dev/null 2>&1 &&
+ (
+ cd subdir &&
+ ( yes "l" | git mergetool subdir_module )
+ ) &&
+ test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ git submodule update -N &&
+ test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ git reset --hard &&
+ git submodule update -N &&
+
+ test_must_fail git merge test10.a >/dev/null 2>&1 &&
+ ( yes "r" | git mergetool subdir/subdir_module ) &&
+ test "$(cat subdir/subdir_module/file15)" = "test10.b" &&
+ git submodule update -N &&
+ test "$(cat subdir/subdir_module/file15)" = "test10.a" &&
+ git commit -m "branch1 resolved with mergetool" &&
+ rm -rf subdir/subdir_module
+'
+
+test_expect_success 'directory vs modified submodule' '
+ git checkout -b test11 branch1 &&
+ mv submod submod-movedaside &&
+ git rm submod &&
+ mkdir submod &&
+ echo not a submodule >submod/file16 &&
+ git add submod/file16 &&
+ git commit -m "Submodule path becomes directory" &&
+
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "l" | git mergetool submod ) &&
+ test "$(cat submod/file16)" = "not a submodule" &&
+ rm -rf submod.orig &&
+
+ git reset --hard &&
+ test_must_fail git merge master &&
+ test -n "$(git ls-files -u)" &&
+ test ! -e submod.orig &&
+ ( yes "r" | git mergetool submod ) &&
+ test -d submod.orig &&
+ test "$(cat submod.orig/file16)" = "not a submodule" &&
+ rm -r submod.orig &&
+ mv submod-movedaside/.git submod &&
+ ( cd submod && git clean -f && git reset --hard ) &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+ git reset --hard && rm -rf submod-movedaside &&
+
+ git checkout -b test11.c master &&
+ git submodule update -N &&
+ test_must_fail git merge test11 &&
+ test -n "$(git ls-files -u)" &&
+ ( yes "l" | git mergetool submod ) &&
+ git submodule update -N &&
+ test "$(cat submod/bar)" = "master submodule" &&
+
+ git reset --hard &&
+ git submodule update -N &&
+ test_must_fail git merge test11 &&
+ test -n "$(git ls-files -u)" &&
+ test ! -e submod.orig &&
+ ( yes "r" | git mergetool submod ) &&
+ test "$(cat submod/file16)" = "not a submodule" &&
+
+ git reset --hard master &&
+ ( cd submod && git clean -f && git reset --hard ) &&
+ git submodule update -N
+'
test_done
diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh
new file mode 100755
index 0000000..7b4798e
--- /dev/null
+++ b/t/t7611-merge-abort.sh
@@ -0,0 +1,319 @@
+#!/bin/sh
+
+test_description='test aborting in-progress merges
+
+Set up repo with conflicting and non-conflicting branches:
+
+There are three files foo/bar/baz, and the following graph illustrates the
+content of these files in each commit:
+
+# foo/bar/baz --- foo/bar/bazz <-- master
+# \
+# --- foo/barf/bazf <-- conflict_branch
+# \
+# --- foo/bart/baz <-- clean_branch
+
+Next, test git merge --abort with the following variables:
+- before/after successful merge (should fail when not in merge context)
+- with/without conflicts
+- clean/dirty index before merge
+- clean/dirty worktree before merge
+- dirty index before merge matches contents on remote branch
+- changed/unchanged worktree after merge
+- changed/unchanged index after merge
+'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ # Create the above repo
+ echo foo > foo &&
+ echo bar > bar &&
+ echo baz > baz &&
+ git add foo bar baz &&
+ git commit -m initial &&
+ echo bazz > baz &&
+ git commit -a -m "second" &&
+ git checkout -b conflict_branch HEAD^ &&
+ echo barf > bar &&
+ echo bazf > baz &&
+ git commit -a -m "conflict" &&
+ git checkout -b clean_branch HEAD^ &&
+ echo bart > bar &&
+ git commit -a -m "clean" &&
+ git checkout master
+'
+
+pre_merge_head="$(git rev-parse HEAD)"
+
+test_expect_success 'fails without MERGE_HEAD (unstarted merge)' '
+ test_must_fail git merge --abort 2>output &&
+ test_i18ngrep MERGE_HEAD output
+'
+
+test_expect_success 'fails without MERGE_HEAD (unstarted merge): .git/MERGE_HEAD sanity' '
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'fails without MERGE_HEAD (completed merge)' '
+ git merge clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ # Merge successfully completed
+ post_merge_head="$(git rev-parse HEAD)" &&
+ test_must_fail git merge --abort 2>output &&
+ test_i18ngrep MERGE_HEAD output
+'
+
+test_expect_success 'fails without MERGE_HEAD (completed merge): .git/MERGE_HEAD sanity' '
+ test ! -f .git/MERGE_HEAD &&
+ test "$post_merge_head" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'Forget previous merge' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort after --no-commit' '
+ # Redo merge, but stop before creating merge commit
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort non-conflicting merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Abort after conflicts' '
+ # Create conflicting merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort conflicting merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ test -z "$(git diff --staged)"
+'
+
+test_expect_success 'Clean merge with dirty index fails' '
+ echo xyzzy >> foo &&
+ git add foo &&
+ git diff --staged > expect &&
+ test_must_fail git merge clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Conflicting merge with dirty index fails' '
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset index (but preserve worktree changes)' '
+ git reset "$pre_merge_head" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with non-conflicting dirty worktree' '
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' '
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with conflicting dirty worktree' '
+ echo xyzzy >> bar &&
+ git diff > expect &&
+ test_must_fail git merge --no-commit clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Fail conflicting merge with conflicting dirty worktree' '
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail clean merge with matching dirty worktree' '
+ echo bart > bar &&
+ git diff > expect &&
+ test_must_fail git merge --no-commit clean_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort clean merge with matching dirty index' '
+ git add bar &&
+ git diff --staged > expect &&
+ git merge --no-commit clean_branch &&
+ test -f .git/MERGE_HEAD &&
+ ### When aborting the merge, git will discard all staged changes,
+ ### including those that were staged pre-merge. In other words,
+ ### --abort will LOSE any staged changes (the staged changes that
+ ### are lost must match the merge result, or the merge would not
+ ### have been allowed to start). Change expectations accordingly:
+ rm expect &&
+ touch expect &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual &&
+ test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Fail conflicting merge with matching dirty worktree' '
+ echo barf > bar &&
+ git diff > expect &&
+ test_must_fail git merge conflict_branch &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ test -z "$(git diff --staged)" &&
+ git diff > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'Abort conflicting merge with matching dirty index' '
+ git add bar &&
+ git diff --staged > expect &&
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ ### When aborting the merge, git will discard all staged changes,
+ ### including those that were staged pre-merge. In other words,
+ ### --abort will LOSE any staged changes (the staged changes that
+ ### are lost must match the merge result, or the merge would not
+ ### have been allowed to start). Change expectations accordingly:
+ rm expect &&
+ touch expect &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff --staged > actual &&
+ test_cmp expect actual &&
+ test -z "$(git diff)"
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge worktree changes' '
+ # Pre-merge worktree changes
+ echo xyzzy > foo &&
+ echo barf > bar &&
+ git add bar &&
+ git diff > expect &&
+ git diff --staged > expect-staged &&
+ # Perform merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Post-merge worktree changes
+ echo yzxxz > foo &&
+ echo blech > baz &&
+ ### When aborting the merge, git will discard staged changes (bar)
+ ### and unmerged changes (baz). Other changes that are neither
+ ### staged nor marked as unmerged (foo), will be preserved. For
+ ### these changed, git cannot tell pre-merge changes apart from
+ ### post-merge changes, so the post-merge changes will be
+ ### preserved. Change expectations accordingly:
+ git diff -- foo > expect &&
+ rm expect-staged &&
+ touch expect-staged &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff > actual &&
+ test_cmp expect actual &&
+ git diff --staged > actual-staged &&
+ test_cmp expect-staged actual-staged
+'
+
+test_expect_success 'Reset worktree changes' '
+ git reset --hard "$pre_merge_head"
+'
+
+test_expect_success 'Abort merge with pre- and post-merge index changes' '
+ # Pre-merge worktree changes
+ echo xyzzy > foo &&
+ echo barf > bar &&
+ git add bar &&
+ git diff > expect &&
+ git diff --staged > expect-staged &&
+ # Perform merge
+ test_must_fail git merge conflict_branch &&
+ test -f .git/MERGE_HEAD &&
+ # Post-merge worktree changes
+ echo yzxxz > foo &&
+ echo blech > baz &&
+ git add foo bar &&
+ ### When aborting the merge, git will discard all staged changes
+ ### (foo, bar and baz), and no changes will be preserved. Whether
+ ### the changes were staged pre- or post-merge does not matter
+ ### (except for not preventing starting the merge).
+ ### Change expectations accordingly:
+ rm expect expect-staged &&
+ touch expect &&
+ touch expect-staged &&
+ # Abort merge
+ git merge --abort &&
+ test ! -f .git/MERGE_HEAD &&
+ test "$pre_merge_head" = "$(git rev-parse HEAD)" &&
+ git diff > actual &&
+ test_cmp expect actual &&
+ git diff --staged > actual-staged &&
+ test_cmp expect-staged actual-staged
+'
+
+test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index c2f66ff..d954b84 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -56,7 +56,7 @@ test_expect_success 'loose objects in alternate ODB are not repacked' '
'
test_expect_success 'packed obs in alt ODB are repacked even when local repo is packless' '
- mkdir alt_objects/pack
+ mkdir alt_objects/pack &&
mv .git/objects/pack/* alt_objects/pack &&
git repack -a &&
myidx=$(ls -1 .git/objects/pack/*.idx) &&
@@ -95,14 +95,14 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' '
# swap the .keep so the commit object is in the pack with .keep
for p in alt_objects/pack/*.pack
do
- base_name=$(basename $p .pack)
+ base_name=$(basename $p .pack) &&
if test -f alt_objects/pack/$base_name.keep
then
rm alt_objects/pack/$base_name.keep
else
touch alt_objects/pack/$base_name.keep
fi
- done
+ done &&
git repack -a -d &&
myidx=$(ls -1 .git/objects/pack/*.idx) &&
test -f "$myidx" &&
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 1de83ef..4048d10 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -10,11 +10,6 @@ Testing basic diff tool invocation
. ./test-lib.sh
-if ! test_have_prereq PERL; then
- say 'skipping difftool tests, perl not available'
- test_done
-fi
-
LF='
'
@@ -50,7 +45,7 @@ prompt_given()
}
# Create a file on master and change it on branch
-test_expect_success 'setup' '
+test_expect_success PERL 'setup' '
echo master >file &&
git add file &&
git commit -m "added file" &&
@@ -62,7 +57,7 @@ test_expect_success 'setup' '
'
# Configure a custom difftool.<tool>.cmd and use it
-test_expect_success 'custom commands' '
+test_expect_success PERL 'custom commands' '
restore_test_defaults &&
git config difftool.test-tool.cmd "cat \$REMOTE" &&
@@ -75,13 +70,13 @@ test_expect_success 'custom commands' '
'
# Ensures that git-difftool ignores bogus --tool values
-test_expect_success 'difftool ignores bad --tool values' '
+test_expect_success PERL 'difftool ignores bad --tool values' '
diff=$(git difftool --no-prompt --tool=bad-tool branch)
test "$?" = 1 &&
test "$diff" = ""
'
-test_expect_success 'difftool honors --gui' '
+test_expect_success PERL 'difftool honors --gui' '
git config merge.tool bogus-tool &&
git config diff.tool bogus-tool &&
git config diff.guitool test-tool &&
@@ -92,7 +87,7 @@ test_expect_success 'difftool honors --gui' '
restore_test_defaults
'
-test_expect_success 'difftool --gui works without configured diff.guitool' '
+test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
git config diff.tool test-tool &&
diff=$(git difftool --no-prompt --gui branch) &&
@@ -102,8 +97,8 @@ test_expect_success 'difftool --gui works without configured diff.guitool' '
'
# Specify the diff tool using $GIT_DIFF_TOOL
-test_expect_success 'GIT_DIFF_TOOL variable' '
- git config --unset diff.tool
+test_expect_success PERL 'GIT_DIFF_TOOL variable' '
+ test_might_fail git config --unset diff.tool &&
GIT_DIFF_TOOL=test-tool &&
export GIT_DIFF_TOOL &&
@@ -115,7 +110,7 @@ test_expect_success 'GIT_DIFF_TOOL variable' '
# Test the $GIT_*_TOOL variables and ensure
# that $GIT_DIFF_TOOL always wins unless --tool is specified
-test_expect_success 'GIT_DIFF_TOOL overrides' '
+test_expect_success PERL 'GIT_DIFF_TOOL overrides' '
git config diff.tool bogus-tool &&
git config merge.tool bogus-tool &&
@@ -136,7 +131,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' '
# Test that we don't have to pass --no-prompt to difftool
# when $GIT_DIFFTOOL_NO_PROMPT is true
-test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
+test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' '
GIT_DIFFTOOL_NO_PROMPT=true &&
export GIT_DIFFTOOL_NO_PROMPT &&
@@ -148,7 +143,7 @@ test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
# git-difftool supports the difftool.prompt variable.
# Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
-test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
+test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' '
git config difftool.prompt false &&
GIT_DIFFTOOL_PROMPT=true &&
export GIT_DIFFTOOL_PROMPT &&
@@ -160,7 +155,7 @@ test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
'
# Test that we don't have to pass --no-prompt when difftool.prompt is false
-test_expect_success 'difftool.prompt config variable is false' '
+test_expect_success PERL 'difftool.prompt config variable is false' '
git config difftool.prompt false &&
diff=$(git difftool branch) &&
@@ -170,8 +165,8 @@ test_expect_success 'difftool.prompt config variable is false' '
'
# Test that we don't have to pass --no-prompt when mergetool.prompt is false
-test_expect_success 'difftool merge.prompt = false' '
- git config --unset difftool.prompt
+test_expect_success PERL 'difftool merge.prompt = false' '
+ test_might_fail git config --unset difftool.prompt &&
git config mergetool.prompt false &&
diff=$(git difftool branch) &&
@@ -181,7 +176,7 @@ test_expect_success 'difftool merge.prompt = false' '
'
# Test that the -y flag can override difftool.prompt = true
-test_expect_success 'difftool.prompt can overridden with -y' '
+test_expect_success PERL 'difftool.prompt can overridden with -y' '
git config difftool.prompt true &&
diff=$(git difftool -y branch) &&
@@ -191,7 +186,7 @@ test_expect_success 'difftool.prompt can overridden with -y' '
'
# Test that the --prompt flag can override difftool.prompt = false
-test_expect_success 'difftool.prompt can overridden with --prompt' '
+test_expect_success PERL 'difftool.prompt can overridden with --prompt' '
git config difftool.prompt false &&
prompt=$(echo | git difftool --prompt branch | tail -1) &&
@@ -201,7 +196,7 @@ test_expect_success 'difftool.prompt can overridden with --prompt' '
'
# Test that the last flag passed on the command-line wins
-test_expect_success 'difftool last flag wins' '
+test_expect_success PERL 'difftool last flag wins' '
diff=$(git difftool --prompt --no-prompt branch) &&
test "$diff" = "branch" &&
@@ -215,8 +210,8 @@ test_expect_success 'difftool last flag wins' '
# git-difftool falls back to git-mergetool config variables
# so test that behavior here
-test_expect_success 'difftool + mergetool config variables' '
- remove_config_vars
+test_expect_success PERL 'difftool + mergetool config variables' '
+ remove_config_vars &&
git config merge.tool test-tool &&
git config mergetool.test-tool.cmd "cat \$LOCAL" &&
@@ -233,7 +228,7 @@ test_expect_success 'difftool + mergetool config variables' '
restore_test_defaults
'
-test_expect_success 'difftool.<tool>.path' '
+test_expect_success PERL 'difftool.<tool>.path' '
git config difftool.tkdiff.path echo &&
diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
git config --unset difftool.tkdiff.path &&
@@ -243,33 +238,33 @@ test_expect_success 'difftool.<tool>.path' '
restore_test_defaults
'
-test_expect_success 'difftool --extcmd=cat' '
+test_expect_success PERL 'difftool --extcmd=cat' '
diff=$(git difftool --no-prompt --extcmd=cat branch) &&
test "$diff" = branch"$LF"master
'
-test_expect_success 'difftool --extcmd cat' '
+test_expect_success PERL 'difftool --extcmd cat' '
diff=$(git difftool --no-prompt --extcmd cat branch) &&
test "$diff" = branch"$LF"master
'
-test_expect_success 'difftool -x cat' '
+test_expect_success PERL 'difftool -x cat' '
diff=$(git difftool --no-prompt -x cat branch) &&
test "$diff" = branch"$LF"master
'
-test_expect_success 'difftool --extcmd echo arg1' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch)
+test_expect_success PERL 'difftool --extcmd echo arg1' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) &&
test "$diff" = file
'
-test_expect_success 'difftool --extcmd cat arg1' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch)
+test_expect_success PERL 'difftool --extcmd cat arg1' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) &&
test "$diff" = master
'
-test_expect_success 'difftool --extcmd cat arg2' '
- diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch)
+test_expect_success PERL 'difftool --extcmd cat arg2' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) &&
test "$diff" = branch
'
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 8a63227..a29ae45 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -26,6 +26,17 @@ test_expect_success setup '
echo foo mmap bar_mmap
echo foo_mmap bar mmap baz
} >file &&
+ {
+ echo Hello world
+ echo HeLLo world
+ echo Hello_world
+ echo HeLLo_world
+ } >hello_world &&
+ {
+ echo "a+b*c"
+ echo "a+bc"
+ echo "abc"
+ } >ab &&
echo vvv >v &&
echo ww w >w &&
echo x x xx x >x &&
@@ -59,13 +70,35 @@ do
echo ${HC}file:4:foo mmap bar_mmap
echo ${HC}file:5:foo_mmap bar mmap baz
} >expected &&
- git grep -n -w -e mmap $H >actual &&
+ git -c grep.linenumber=false grep -n -w -e mmap $H >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep -w $L" '
+ {
+ echo ${HC}file:1:foo mmap bar
+ echo ${HC}file:3:foo_mmap bar mmap
+ echo ${HC}file:4:foo mmap bar_mmap
+ echo ${HC}file:5:foo_mmap bar mmap baz
+ } >expected &&
+ git -c grep.linenumber=true grep -w -e mmap $H >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep -w $L" '
+ {
+ echo ${HC}file:foo mmap bar
+ echo ${HC}file:foo_mmap bar mmap
+ echo ${HC}file:foo mmap bar_mmap
+ echo ${HC}file:foo_mmap bar mmap baz
+ } >expected &&
+ git -c grep.linenumber=true grep --no-line-number -w -e mmap $H >actual &&
test_cmp expected actual
'
test_expect_success "grep -w $L (w)" '
: >expected &&
- ! git grep -n -w -e "^w" >actual &&
+ test_must_fail git grep -n -w -e "^w" >actual &&
test_cmp expected actual
'
@@ -182,6 +215,34 @@ do
test_cmp expected actual
'
+ test_expect_success "grep --max-depth 0 -- . t $L" '
+ {
+ echo ${HC}t/v:1:vvv
+ echo ${HC}v:1:vvv
+ } >expected &&
+ git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep --max-depth 0 -- t . $L" '
+ {
+ echo ${HC}t/v:1:vvv
+ echo ${HC}v:1:vvv
+ } >expected &&
+ git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
+ test_cmp expected actual
+ '
+ test_expect_success "grep $L with grep.extendedRegexp=false" '
+ echo "ab:a+bc" >expected &&
+ git -c grep.extendedRegexp=false grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep $L with grep.extendedRegexp=true" '
+ echo "ab:abc" >expected &&
+ git -c grep.extendedRegexp=true grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
done
cat >expected <<EOF
@@ -285,6 +346,11 @@ test_expect_success 'grep -f, ignore empty lines' '
test_cmp expected actual
'
+test_expect_success 'grep -f, ignore empty lines, read patterns from stdin' '
+ git grep -f - <patterns >actual &&
+ test_cmp expected actual
+'
+
cat >expected <<EOF
y:y yy
--
@@ -324,8 +390,13 @@ test_expect_success 'log grep setup' '
echo a >>file &&
test_tick &&
- git commit -a -m "third"
+ git commit -a -m "third" &&
+ echo a >>file &&
+ test_tick &&
+ GIT_AUTHOR_NAME="Night Fall" \
+ GIT_AUTHOR_EMAIL="nitfol@frobozz.com" \
+ git commit -a -m "fourth"
'
test_expect_success 'log grep (1)' '
@@ -372,6 +443,28 @@ test_expect_success 'log --grep --author implicitly uses all-match' '
test_cmp expect actual
'
+test_expect_success 'log with multiple --author uses union' '
+ git log --author="Thor" --author="Aster" --format=%s >actual &&
+ {
+ echo third && echo second && echo initial
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log with --grep and multiple --author uses all-match' '
+ git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
+ {
+ echo third && echo initial
+ } >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log with --grep and multiple --author uses all-match' '
+ git log --author="Thor" --author="Night" --grep=q --format=%s >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'grep with CE_VALID file' '
git update-index --assume-unchanged t/t &&
rm t/t &&
@@ -452,7 +545,7 @@ test_expect_success 'outside of git repository' '
echo file1:hello &&
echo sub/file2:world
} >non/expect.full &&
- echo file2:world >non/expect.sub
+ echo file2:world >non/expect.sub &&
(
GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
export GIT_CEILING_DIRECTORIES &&
@@ -478,7 +571,7 @@ test_expect_success 'inside git repository but with --no-index' '
echo sub/file2:world
} >is/expect.full &&
: >is/expect.empty &&
- echo file2:world >is/expect.sub
+ echo file2:world >is/expect.sub &&
(
cd is/git &&
git init &&
@@ -527,4 +620,195 @@ test_expect_success 'grep -e -- -- path' '
test_cmp expected actual
'
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c: printf("Hello world.\n");
+EOF
+
+test_expect_success LIBPCRE 'grep --perl-regexp pattern' '
+ git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P pattern' '
+ git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep pattern with grep.extendedRegexp=true' '
+ >empty &&
+ test_must_fail git -c grep.extendedregexp=true \
+ grep "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' '
+ git -c grep.extendedregexp=true \
+ grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -v pattern' '
+ {
+ echo "ab:a+b*c"
+ echo "ab:a+bc"
+ } >expected &&
+ git grep -P -v "abc" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -i pattern' '
+ cat >expected <<-EOF &&
+ hello.c: printf("Hello world.\n");
+ EOF
+ git grep -P -i "PRINTF\([^\d]+\)" hello.c >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P -w pattern' '
+ {
+ echo "hello_world:Hello world"
+ echo "hello_world:HeLLo world"
+ } >expected &&
+ git grep -P -w "He((?i)ll)o" hello_world >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -G invalidpattern properly dies ' '
+ test_must_fail git grep -G "a["
+'
+
+test_expect_success 'grep -E invalidpattern properly dies ' '
+ test_must_fail git grep -E "a["
+'
+
+test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' '
+ test_must_fail git grep -P "a["
+'
+
+test_expect_success 'grep -G -E -F pattern' '
+ echo "ab:a+b*c" >expected &&
+ git grep -G -E -F "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -E -F -G pattern' '
+ echo "ab:a+bc" >expected &&
+ git grep -E -F -G "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -F -G -E pattern' '
+ echo "ab:abc" >expected &&
+ git grep -F -G -E "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -G -F -P -E pattern' '
+ >empty &&
+ test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp empty actual
+'
+
+test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
+ echo "ab:a+b*c" >expected &&
+ git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_config() {
+ git config "$1" "$2" &&
+ test_when_finished "git config --unset $1"
+}
+
+cat >expected <<EOF
+hello.c<RED>:<RESET>int main(int argc, const char **argv)
+hello.c<RED>-<RESET>{
+<RED>--<RESET>
+hello.c<RED>:<RESET> /* char ?? */
+hello.c<RED>-<RESET>}
+<RED>--<RESET>
+hello_world<RED>:<RESET>Hello_world
+hello_world<RED>-<RESET>HeLLo_world
+EOF
+
+test_expect_success 'grep --color, separator' '
+ test_config color.grep.context normal &&
+ test_config color.grep.filename normal &&
+ test_config color.grep.function normal &&
+ test_config color.grep.linenumber normal &&
+ test_config color.grep.match normal &&
+ test_config color.grep.selected normal &&
+ test_config color.grep.separator red &&
+
+ git grep --color=always -A1 -e char -e lo_w hello.c hello_world |
+ test_decode_color >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c: /* char ?? */
+
+hello_world:Hello_world
+EOF
+
+test_expect_success 'grep --break' '
+ git grep --break -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+hello.c-{
+--
+hello.c: /* char ?? */
+hello.c-}
+
+hello_world:Hello_world
+hello_world-HeLLo_world
+EOF
+
+test_expect_success 'grep --break with context' '
+ git grep --break -A1 -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+ /* char ?? */
+hello_world
+Hello_world
+EOF
+
+test_expect_success 'grep --heading' '
+ git grep --heading -e char -e lo_w hello.c hello_world >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+<BOLD;GREEN>hello.c<RESET>
+2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
+6: /* <BLACK;BYELLOW>char<RESET> ?? */
+
+<BOLD;GREEN>hello_world<RESET>
+3:Hel<BLACK;BYELLOW>lo_w<RESET>orld
+EOF
+
+test_expect_success 'mimic ack-grep --group' '
+ test_config color.grep.context normal &&
+ test_config color.grep.filename "bold green" &&
+ test_config color.grep.function normal &&
+ test_config color.grep.linenumber normal &&
+ test_config color.grep.match "black yellow" &&
+ test_config color.grep.selected normal &&
+ test_config color.grep.separator normal &&
+
+ git grep --break --heading -n --color \
+ -e char -e lo_w hello.c hello_world |
+ test_decode_color >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh
index c110441..a895778 100755
--- a/t/t7811-grep-open.sh
+++ b/t/t7811-grep-open.sh
@@ -63,7 +63,7 @@ test_expect_success SIMPLEPAGER 'git grep -O' '
test_expect_success 'git grep -O --cached' '
test_must_fail git grep --cached -O GREP_PATTERN >out 2>msg &&
- grep open-files-in-pager msg
+ test_i18ngrep open-files-in-pager msg
'
test_expect_success 'git grep -O --no-index' '
@@ -125,6 +125,21 @@ test_expect_success 'modified file' '
test_cmp empty out
'
+test_config() {
+ git config "$1" "$2" &&
+ test_when_finished "git config --unset $1"
+}
+
+test_expect_success 'copes with color settings' '
+ rm -f actual &&
+ echo grep.h >expect &&
+ test_config color.grep always &&
+ test_config color.grep.filename yellow &&
+ test_config color.grep.separator green &&
+ git grep -O'\''printf "%s\n" >actual'\'' GREP_AND &&
+ test_cmp expect actual
+'
+
test_expect_success 'run from subdir' '
rm -f actual &&
echo grep.c >expect &&
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
index 45cb60e..41962f0 100755
--- a/t/t8001-annotate.sh
+++ b/t/t8001-annotate.sh
@@ -6,10 +6,11 @@ test_description='git annotate'
PROG='git annotate'
. "$TEST_DIRECTORY"/annotate-tests.sh
-test_expect_success \
- 'Annotating an old revision works' \
- '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \
- [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]'
-
+test_expect_success 'Annotating an old revision works' '
+ git annotate file master >result &&
+ awk "{ print \$3; }" <result >authors &&
+ test 2 = $(grep A <authors | wc -l) &&
+ test 2 = $(grep B <authors | wc -l)
+'
test_done
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
index 597cf04..e2896cf 100755
--- a/t/t8002-blame.sh
+++ b/t/t8002-blame.sh
@@ -6,4 +6,9 @@ test_description='git blame'
PROG='git blame -c'
. "$TEST_DIRECTORY"/annotate-tests.sh
+PROG='git blame -c -e'
+test_expect_success 'Blame --show-email works' '
+ check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1 "<E at test dot git>" 1
+'
+
test_done
diff --git a/t/t8003-blame.sh b/t/t8003-blame-corner-cases.sh
index 230143c..230143c 100755
--- a/t/t8003-blame.sh
+++ b/t/t8003-blame-corner-cases.sh
diff --git a/t/t8004-blame.sh b/t/t8004-blame-with-conflicts.sh
index ba19ac1..ba19ac1 100755
--- a/t/t8004-blame.sh
+++ b/t/t8004-blame-with-conflicts.sh
diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh
index 9ad96d4..32ec82a 100755
--- a/t/t8006-blame-textconv.sh
+++ b/t/t8006-blame-textconv.sh
@@ -9,22 +9,30 @@ find_blame() {
cat >helper <<'EOF'
#!/bin/sh
-sed 's/^/converted: /' "$@"
+grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; }
+sed 's/^bin: /converted: /' "$1"
EOF
chmod +x helper
test_expect_success 'setup ' '
- echo test 1 >one.bin &&
- echo test number 2 >two.bin &&
+ echo "bin: test 1" >one.bin &&
+ echo "bin: test number 2" >two.bin &&
+ if test_have_prereq SYMLINKS; then
+ ln -s one.bin symlink.bin
+ fi &&
git add . &&
GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
- echo test 1 version 2 >one.bin &&
- echo test number 2 version 2 >>two.bin &&
+ echo "bin: test 1 version 2" >one.bin &&
+ echo "bin: test number 2 version 2" >>two.bin &&
+ if test_have_prereq SYMLINKS; then
+ rm symlink.bin &&
+ ln -s two.bin symlink.bin
+ fi &&
GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
'
cat >expected <<EOF
-(Number2 2010-01-01 20:00:00 +0000 1) test 1 version 2
+(Number2 2010-01-01 20:00:00 +0000 1) bin: test 1 version 2
EOF
test_expect_success 'no filter specified' '
@@ -66,8 +74,29 @@ test_expect_success 'blame --textconv going through revisions' '
test_cmp expected result
'
+test_expect_success 'setup +cachetextconv' '
+ git config diff.test.cachetextconv true
+'
+
+cat >expected_one <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) converted: test 1 version 2
+EOF
+
+test_expect_success 'blame --textconv works with textconvcache' '
+ git blame --textconv two.bin >blame &&
+ find_blame <blame >result &&
+ test_cmp expected result &&
+ git blame --textconv one.bin >blame &&
+ find_blame <blame >result &&
+ test_cmp expected_one result
+'
+
+test_expect_success 'setup -cachetextconv' '
+ git config diff.test.cachetextconv false
+'
+
test_expect_success 'make a new commit' '
- echo "test number 2 version 3" >>two.bin &&
+ echo "bin: test number 2 version 3" >>two.bin &&
GIT_AUTHOR_NAME=Number3 git commit -a -m Third --date="2010-01-01 22:00:00"
'
@@ -77,4 +106,45 @@ test_expect_success 'blame from previous revision' '
test_cmp expected result
'
+cat >expected <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) two.bin
+EOF
+
+test_expect_success SYMLINKS 'blame with --no-textconv (on symlink)' '
+ git blame --no-textconv symlink.bin >blame &&
+ find_blame <blame >result &&
+ test_cmp expected result
+'
+
+test_expect_success SYMLINKS 'blame --textconv (on symlink)' '
+ git blame --textconv symlink.bin >blame &&
+ find_blame <blame >result &&
+ test_cmp expected result
+'
+
+# cp two.bin three.bin and make small tweak
+# (this will direct blame -C -C three.bin to consider two.bin and symlink.bin)
+test_expect_success SYMLINKS 'make another new commit' '
+ cat >three.bin <<\EOF &&
+bin: test number 2
+bin: test number 2 version 2
+bin: test number 2 version 3
+bin: test number 3
+EOF
+ git add three.bin &&
+ GIT_AUTHOR_NAME=Number4 git commit -a -m Fourth --date="2010-01-01 23:00:00"
+'
+
+test_expect_success SYMLINKS 'blame on last commit (-C -C, symlink)' '
+ git blame -C -C three.bin >blame &&
+ find_blame <blame >result &&
+ cat >expected <<\EOF &&
+(Number1 2010-01-01 18:00:00 +0000 1) converted: test number 2
+(Number2 2010-01-01 20:00:00 +0000 2) converted: test number 2 version 2
+(Number3 2010-01-01 22:00:00 +0000 3) converted: test number 2 version 3
+(Number4 2010-01-01 23:00:00 +0000 4) converted: test number 3
+EOF
+ test_cmp expected result
+'
+
test_done
diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh
index 38ac05e..78a0085 100755
--- a/t/t8007-cat-file-textconv.sh
+++ b/t/t8007-cat-file-textconv.sh
@@ -5,15 +5,19 @@ test_description='git cat-file textconv support'
cat >helper <<'EOF'
#!/bin/sh
-sed 's/^/converted: /' "$@"
+grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; }
+sed 's/^bin: /converted: /' "$1"
EOF
chmod +x helper
test_expect_success 'setup ' '
- echo test >one.bin &&
+ echo "bin: test" >one.bin &&
+ if test_have_prereq SYMLINKS; then
+ ln -s one.bin symlink.bin
+ fi &&
git add . &&
GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
- echo test version 2 >one.bin &&
+ echo "bin: test version 2" >one.bin &&
GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
'
@@ -33,7 +37,7 @@ test_expect_success 'setup textconv filters' '
'
cat >expected <<EOF
-test version 2
+bin: test version 2
EOF
test_expect_success 'cat-file without --textconv' '
@@ -42,7 +46,7 @@ test_expect_success 'cat-file without --textconv' '
'
cat >expected <<EOF
-test
+bin: test
EOF
test_expect_success 'cat-file without --textconv on previous commit' '
@@ -67,4 +71,28 @@ test_expect_success 'cat-file --textconv on previous commit' '
git cat-file --textconv HEAD^:one.bin >result &&
test_cmp expected result
'
+
+test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' '
+ git cat-file blob :symlink.bin >result &&
+ printf "%s" "one.bin" >expected
+ test_cmp expected result
+'
+
+
+test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' '
+ ! git cat-file --textconv :symlink.bin 2>result &&
+ cat >expected <<\EOF &&
+fatal: git cat-file --textconv: unable to run textconv on :symlink.bin
+EOF
+ test_cmp expected result
+'
+
+test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' '
+ ! git cat-file --textconv HEAD:symlink.bin 2>result &&
+ cat >expected <<EOF &&
+fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin
+EOF
+ test_cmp expected result
+'
+
test_done
diff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh
new file mode 100755
index 0000000..d15f8b3
--- /dev/null
+++ b/t/t8008-blame-formats.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description='blame output in various formats on a simple case'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo a >file &&
+ git add file
+ test_tick &&
+ git commit -m one &&
+ echo b >>file &&
+ echo c >>file &&
+ echo d >>file &&
+ test_tick &&
+ git commit -a -m two
+'
+
+cat >expect <<'EOF'
+^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d
+EOF
+test_expect_success 'normal blame output' '
+ git blame file >actual &&
+ test_cmp expect actual
+'
+
+ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc
+COMMIT1='author A U Thor
+author-mail <author@example.com>
+author-time 1112911993
+author-tz -0700
+committer C O Mitter
+committer-mail <committer@example.com>
+committer-time 1112911993
+committer-tz -0700
+summary one
+boundary
+filename file'
+ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec
+COMMIT2='author A U Thor
+author-mail <author@example.com>
+author-time 1112912053
+author-tz -0700
+committer C O Mitter
+committer-mail <committer@example.com>
+committer-time 1112912053
+committer-tz -0700
+summary two
+previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file
+filename file'
+
+cat >expect <<EOF
+$ID1 1 1 1
+$COMMIT1
+ a
+$ID2 2 2 3
+$COMMIT2
+ b
+$ID2 3 3
+ c
+$ID2 4 4
+ d
+EOF
+test_expect_success 'blame --porcelain output' '
+ git blame --porcelain file >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<EOF
+$ID1 1 1 1
+$COMMIT1
+ a
+$ID2 2 2 3
+$COMMIT2
+ b
+$ID2 3 3
+$COMMIT2
+ c
+$ID2 4 4
+$COMMIT2
+ d
+EOF
+test_expect_success 'blame --line-porcelain output' '
+ git blame --line-porcelain file >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 382ab6c..579ddb7 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -3,20 +3,17 @@
test_description='git send-email'
. ./test-lib.sh
-if ! test_have_prereq PERL; then
- say 'skipping git send-email tests, perl not available'
- test_done
-fi
+# May be altered later in the test
+PREREQ="PERL"
-PROG='git send-email'
-test_expect_success \
+test_expect_success $PREREQ \
'prepare reference tree' \
'echo "1A quick brown fox jumps over the" >file &&
echo "lazy dog" >>file &&
git add file &&
GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
-test_expect_success \
+test_expect_success $PREREQ \
'Setup helper tool' \
'(echo "#!$SHELL_PATH"
echo shift
@@ -36,7 +33,7 @@ clean_fake_sendmail() {
rm -f commandline* msgtxt*
}
-test_expect_success 'Extract patches' '
+test_expect_success $PREREQ 'Extract patches' '
patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
'
@@ -57,49 +54,56 @@ test_no_confirm () {
# Exit immediately to prevent hang if a no-confirm test fails
check_no_confirm () {
- test -f no_confirm_okay || {
- say 'No confirm test failed; skipping remaining tests to prevent hanging'
- test_done
- }
+ if ! test -f no_confirm_okay
+ then
+ say 'confirm test failed; skipping remaining tests to prevent hanging'
+ PREREQ="$PREREQ,CHECK_NO_CONFIRM"
+ fi
+ return 0
}
-test_expect_success 'No confirm with --suppress-cc' '
- test_no_confirm --suppress-cc=sob
+test_expect_success $PREREQ 'No confirm with --suppress-cc' '
+ test_no_confirm --suppress-cc=sob &&
+ check_no_confirm
'
-check_no_confirm
-test_expect_success 'No confirm with --confirm=never' '
- test_no_confirm --confirm=never
+
+test_expect_success $PREREQ 'No confirm with --confirm=never' '
+ test_no_confirm --confirm=never &&
+ check_no_confirm
'
-check_no_confirm
# leave sendemail.confirm set to never after this so that none of the
# remaining tests prompt unintentionally.
-test_expect_success 'No confirm with sendemail.confirm=never' '
+test_expect_success $PREREQ 'No confirm with sendemail.confirm=never' '
git config sendemail.confirm never &&
- test_no_confirm --compose --subject=foo
+ test_no_confirm --compose --subject=foo &&
+ check_no_confirm
'
-check_no_confirm
-test_expect_success 'Send patches' '
+test_expect_success $PREREQ 'Send patches' '
git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
'
+test_expect_success $PREREQ 'setup expect' '
cat >expected <<\EOF
!nobody@example.com!
!author@example.com!
!one@example.com!
!two@example.com!
EOF
-test_expect_success \
+'
+
+test_expect_success $PREREQ \
'Verify commandline' \
'test_cmp expected commandline1'
-test_expect_success 'Send patches with --envelope-sender' '
+test_expect_success $PREREQ 'Send patches with --envelope-sender' '
clean_fake_sendmail &&
git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
'
+test_expect_success $PREREQ 'setup expect' '
cat >expected <<\EOF
!patch@example.com!
!-i!
@@ -108,15 +112,18 @@ cat >expected <<\EOF
!one@example.com!
!two@example.com!
EOF
-test_expect_success \
+'
+
+test_expect_success $PREREQ \
'Verify commandline' \
'test_cmp expected commandline1'
-test_expect_success 'Send patches with --envelope-sender=auto' '
+test_expect_success $PREREQ 'Send patches with --envelope-sender=auto' '
clean_fake_sendmail &&
git send-email --envelope-sender=auto --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
'
+test_expect_success $PREREQ 'setup expect' '
cat >expected <<\EOF
!nobody@example.com!
!-i!
@@ -125,10 +132,13 @@ cat >expected <<\EOF
!one@example.com!
!two@example.com!
EOF
-test_expect_success \
+'
+
+test_expect_success $PREREQ \
'Verify commandline' \
'test_cmp expected commandline1'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-show-all-headers <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -158,8 +168,9 @@ References: <unique-message-id@example.com>
Result: OK
EOF
+"
-test_expect_success 'Show all headers' '
+test_expect_success $PREREQ 'Show all headers' '
git send-email \
--dry-run \
--suppress-cc=sob \
@@ -177,7 +188,7 @@ test_expect_success 'Show all headers' '
test_cmp expected-show-all-headers actual-show-all-headers
'
-test_expect_success 'Prompting works' '
+test_expect_success $PREREQ 'Prompting works' '
clean_fake_sendmail &&
(echo "Example <from@example.com>"
echo "to@example.com"
@@ -190,10 +201,28 @@ test_expect_success 'Prompting works' '
grep "^To: to@example.com\$" msgtxt1
'
-test_expect_success 'cccmd works' '
+test_expect_success $PREREQ 'tocmd works' '
+ clean_fake_sendmail &&
+ cp $patches tocmd.patch &&
+ echo tocmd--tocmd@example.com >>tocmd.patch &&
+ {
+ echo "#!$SHELL_PATH"
+ echo sed -n -e s/^tocmd--//p \"\$1\"
+ } > tocmd-sed &&
+ chmod +x tocmd-sed &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to-cmd=./tocmd-sed \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ tocmd.patch \
+ &&
+ grep "^To: tocmd@example.com" msgtxt1
+'
+
+test_expect_success $PREREQ 'cccmd works' '
clean_fake_sendmail &&
cp $patches cccmd.patch &&
- echo cccmd--cccmd@example.com >>cccmd.patch &&
+ echo "cccmd-- cccmd@example.com" >>cccmd.patch &&
{
echo "#!$SHELL_PATH"
echo sed -n -e s/^cccmd--//p \"\$1\"
@@ -209,10 +238,10 @@ test_expect_success 'cccmd works' '
grep "^ cccmd@example.com" msgtxt1
'
-z8=zzzzzzzz
-z64=$z8$z8$z8$z8$z8$z8$z8$z8
-z512=$z64$z64$z64$z64$z64$z64$z64$z64
-test_expect_success 'reject long lines' '
+test_expect_success $PREREQ 'reject long lines' '
+ z8=zzzzzzzz &&
+ z64=$z8$z8$z8$z8$z8$z8$z8$z8 &&
+ z512=$z64$z64$z64$z64$z64$z64$z64$z64 &&
clean_fake_sendmail &&
cp $patches longline.patch &&
echo $z512$z512 >>longline.patch &&
@@ -225,33 +254,33 @@ test_expect_success 'reject long lines' '
grep longline.patch errors
'
-test_expect_success 'no patch was sent' '
+test_expect_success $PREREQ 'no patch was sent' '
! test -e commandline1
'
-test_expect_success 'Author From: in message body' '
+test_expect_success $PREREQ 'Author From: in message body' '
clean_fake_sendmail &&
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^\$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1 &&
grep "From: A <author@example.com>" msgbody1
'
-test_expect_success 'Author From: not in message body' '
+test_expect_success $PREREQ 'Author From: not in message body' '
clean_fake_sendmail &&
git send-email \
--from="A <author@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^\$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1 &&
! grep "From: A <author@example.com>" msgbody1
'
-test_expect_success 'allow long lines with --no-validate' '
+test_expect_success $PREREQ 'allow long lines with --no-validate' '
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
@@ -261,19 +290,19 @@ test_expect_success 'allow long lines with --no-validate' '
2>errors
'
-test_expect_success 'Invalid In-Reply-To' '
+test_expect_success $PREREQ 'Invalid In-Reply-To' '
clean_fake_sendmail &&
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--in-reply-to=" " \
--smtp-server="$(pwd)/fake.sendmail" \
- $patches
- 2>errors
+ $patches \
+ 2>errors &&
! grep "^In-Reply-To: < *>" msgtxt1
'
-test_expect_success 'Valid In-Reply-To when prompting' '
+test_expect_success $PREREQ 'Valid In-Reply-To when prompting' '
clean_fake_sendmail &&
(echo "From Example <from@example.com>"
echo "To Example <to@example.com>"
@@ -284,7 +313,50 @@ test_expect_success 'Valid In-Reply-To when prompting' '
! grep "^In-Reply-To: < *>" msgtxt1
'
-test_expect_success 'setup fake editor' '
+test_expect_success $PREREQ 'In-Reply-To without --chain-reply-to' '
+ clean_fake_sendmail &&
+ echo "<unique-message-id@example.com>" >expect &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --nochain-reply-to \
+ --in-reply-to="$(cat expect)" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches $patches $patches \
+ 2>errors &&
+ # The first message is a reply to --in-reply-to
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
+ test_cmp expect actual &&
+ # Second and subsequent messages are replies to the first one
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success $PREREQ 'In-Reply-To with --chain-reply-to' '
+ clean_fake_sendmail &&
+ echo "<unique-message-id@example.com>" >expect &&
+ git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --chain-reply-to \
+ --in-reply-to="$(cat expect)" \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches $patches $patches \
+ 2>errors &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
+ test_cmp expect actual &&
+ sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt2 >expect &&
+ sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success $PREREQ 'setup fake editor' '
(echo "#!$SHELL_PATH" &&
echo "echo fake edit >>\"\$1\""
) >fake-editor &&
@@ -293,7 +365,7 @@ test_expect_success 'setup fake editor' '
test_set_editor "$(pwd)/fake-editor"
-test_expect_success '--compose works' '
+test_expect_success $PREREQ '--compose works' '
clean_fake_sendmail &&
git send-email \
--compose --subject foo \
@@ -304,14 +376,15 @@ test_expect_success '--compose works' '
2>errors
'
-test_expect_success 'first message is compose text' '
+test_expect_success $PREREQ 'first message is compose text' '
grep "^fake edit" msgtxt1
'
-test_expect_success 'second message is patch' '
+test_expect_success $PREREQ 'second message is patch' '
grep "Subject:.*Second" msgtxt2
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -338,6 +411,7 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
test_suppression () {
git send-email \
@@ -354,11 +428,12 @@ test_suppression () {
test_cmp expected-suppress-$1${2+"-$2"} actual-suppress-$1${2+"-$2"}
}
-test_expect_success 'sendemail.cc set' '
+test_expect_success $PREREQ 'sendemail.cc set' '
git config sendemail.cc cc@example.com &&
test_suppression sob
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -383,12 +458,14 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success 'sendemail.cc unset' '
+test_expect_success $PREREQ 'sendemail.cc unset' '
git config --unset sendemail.cc &&
test_suppression sob
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-cccmd <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -416,14 +493,16 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success 'sendemail.cccmd' '
+test_expect_success $PREREQ 'sendemail.cccmd' '
echo echo cc-cmd@example.com > cccmd &&
chmod +x cccmd &&
git config sendemail.cccmd ./cccmd &&
test_suppression cccmd
'
+test_expect_success $PREREQ 'setup expect' '
cat >expected-suppress-all <<\EOF
0001-Second.patch
Dry-OK. Log says:
@@ -439,11 +518,13 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+'
-test_expect_success '--suppress-cc=all' '
+test_expect_success $PREREQ '--suppress-cc=all' '
test_suppression all
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-body <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -471,11 +552,13 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success '--suppress-cc=body' '
+test_expect_success $PREREQ '--suppress-cc=body' '
test_suppression body
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-body-cccmd <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -500,11 +583,13 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success '--suppress-cc=body --suppress-cc=cccmd' '
+test_expect_success $PREREQ '--suppress-cc=body --suppress-cc=cccmd' '
test_suppression body cccmd
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -529,12 +614,14 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success '--suppress-cc=sob' '
- git config --unset sendemail.cccmd
+test_expect_success $PREREQ '--suppress-cc=sob' '
+ test_might_fail git config --unset sendemail.cccmd &&
test_suppression sob
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-bodycc <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -562,11 +649,13 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success '--suppress-cc=bodycc' '
+test_expect_success $PREREQ '--suppress-cc=bodycc' '
test_suppression bodycc
'
+test_expect_success $PREREQ 'setup expect' "
cat >expected-suppress-cc <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -588,8 +677,9 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
+"
-test_expect_success '--suppress-cc=cc' '
+test_expect_success $PREREQ '--suppress-cc=cc' '
test_suppression cc
'
@@ -604,23 +694,23 @@ test_confirm () {
grep "Send this email" stdout
}
-test_expect_success '--confirm=always' '
+test_expect_success $PREREQ '--confirm=always' '
test_confirm --confirm=always --suppress-cc=all
'
-test_expect_success '--confirm=auto' '
+test_expect_success $PREREQ '--confirm=auto' '
test_confirm --confirm=auto
'
-test_expect_success '--confirm=cc' '
+test_expect_success $PREREQ '--confirm=cc' '
test_confirm --confirm=cc
'
-test_expect_success '--confirm=compose' '
+test_expect_success $PREREQ '--confirm=compose' '
test_confirm --confirm=compose --compose
'
-test_expect_success 'confirm by default (due to cc)' '
+test_expect_success $PREREQ 'confirm by default (due to cc)' '
CONFIRM=$(git config --get sendemail.confirm) &&
git config --unset sendemail.confirm &&
test_confirm
@@ -629,7 +719,7 @@ test_expect_success 'confirm by default (due to cc)' '
test $ret = "0"
'
-test_expect_success 'confirm by default (due to --compose)' '
+test_expect_success $PREREQ 'confirm by default (due to --compose)' '
CONFIRM=$(git config --get sendemail.confirm) &&
git config --unset sendemail.confirm &&
test_confirm --suppress-cc=all --compose
@@ -638,7 +728,7 @@ test_expect_success 'confirm by default (due to --compose)' '
test $ret = "0"
'
-test_expect_success 'confirm detects EOF (inform assumes y)' '
+test_expect_success $PREREQ 'confirm detects EOF (inform assumes y)' '
CONFIRM=$(git config --get sendemail.confirm) &&
git config --unset sendemail.confirm &&
rm -fr outdir &&
@@ -654,7 +744,7 @@ test_expect_success 'confirm detects EOF (inform assumes y)' '
test $ret = "0"
'
-test_expect_success 'confirm detects EOF (auto causes failure)' '
+test_expect_success $PREREQ 'confirm detects EOF (auto causes failure)' '
CONFIRM=$(git config --get sendemail.confirm) &&
git config sendemail.confirm auto &&
GIT_SEND_EMAIL_NOTTY=1 &&
@@ -669,7 +759,7 @@ test_expect_success 'confirm detects EOF (auto causes failure)' '
test $ret = "0"
'
-test_expect_success 'confirm doesnt loop forever' '
+test_expect_success $PREREQ 'confirm doesnt loop forever' '
CONFIRM=$(git config --get sendemail.confirm) &&
git config sendemail.confirm auto &&
GIT_SEND_EMAIL_NOTTY=1 &&
@@ -684,7 +774,7 @@ test_expect_success 'confirm doesnt loop forever' '
test $ret = "0"
'
-test_expect_success 'utf8 Cc is rfc2047 encoded' '
+test_expect_success $PREREQ 'utf8 Cc is rfc2047 encoded' '
clean_fake_sendmail &&
rm -fr outdir &&
git format-patch -1 -o outdir --cc="àéìöú <utf8@example.com>" &&
@@ -697,7 +787,7 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' '
grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>"
'
-test_expect_success '--compose adds MIME for utf8 body' '
+test_expect_success $PREREQ '--compose adds MIME for utf8 body' '
clean_fake_sendmail &&
(echo "#!$SHELL_PATH" &&
echo "echo utf8 body: àéìöú >>\"\$1\""
@@ -714,7 +804,7 @@ test_expect_success '--compose adds MIME for utf8 body' '
grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1
'
-test_expect_success '--compose respects user mime type' '
+test_expect_success $PREREQ '--compose respects user mime type' '
clean_fake_sendmail &&
(echo "#!$SHELL_PATH" &&
echo "(echo MIME-Version: 1.0"
@@ -737,7 +827,7 @@ test_expect_success '--compose respects user mime type' '
! grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1
'
-test_expect_success '--compose adds MIME for utf8 subject' '
+test_expect_success $PREREQ '--compose adds MIME for utf8 subject' '
clean_fake_sendmail &&
GIT_EDITOR="\"$(pwd)/fake-editor\"" \
git send-email \
@@ -750,7 +840,7 @@ test_expect_success '--compose adds MIME for utf8 subject' '
grep "^Subject: =?UTF-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
'
-test_expect_success 'detects ambiguous reference/file conflict' '
+test_expect_success $PREREQ 'detects ambiguous reference/file conflict' '
echo master > master &&
git add master &&
git commit -m"add master" &&
@@ -758,7 +848,7 @@ test_expect_success 'detects ambiguous reference/file conflict' '
grep disambiguate errors
'
-test_expect_success 'feed two files' '
+test_expect_success $PREREQ 'feed two files' '
rm -fr outdir &&
git format-patch -2 -o outdir &&
git send-email \
@@ -771,7 +861,7 @@ test_expect_success 'feed two files' '
test "z$(sed -n -e 2p subjects)" = "zSubject: [PATCH 2/2] add master"
'
-test_expect_success 'in-reply-to but no threading' '
+test_expect_success $PREREQ 'in-reply-to but no threading' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -782,7 +872,7 @@ test_expect_success 'in-reply-to but no threading' '
grep "In-Reply-To: <in-reply-id@example.com>"
'
-test_expect_success 'no in-reply-to and no threading' '
+test_expect_success $PREREQ 'no in-reply-to and no threading' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -792,7 +882,7 @@ test_expect_success 'no in-reply-to and no threading' '
! grep "In-Reply-To: " stdout
'
-test_expect_success 'threading but no chain-reply-to' '
+test_expect_success $PREREQ 'threading but no chain-reply-to' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -803,7 +893,7 @@ test_expect_success 'threading but no chain-reply-to' '
grep "In-Reply-To: " stdout
'
-test_expect_success 'warning with an implicit --chain-reply-to' '
+test_expect_success $PREREQ 'warning with an implicit --chain-reply-to' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -812,7 +902,7 @@ test_expect_success 'warning with an implicit --chain-reply-to' '
grep "no-chain-reply-to" errors
'
-test_expect_success 'no warning with an explicit --chain-reply-to' '
+test_expect_success $PREREQ 'no warning with an explicit --chain-reply-to' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -822,7 +912,7 @@ test_expect_success 'no warning with an explicit --chain-reply-to' '
! grep "no-chain-reply-to" errors
'
-test_expect_success 'no warning with an explicit --no-chain-reply-to' '
+test_expect_success $PREREQ 'no warning with an explicit --no-chain-reply-to' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -832,7 +922,7 @@ test_expect_success 'no warning with an explicit --no-chain-reply-to' '
! grep "no-chain-reply-to" errors
'
-test_expect_success 'no warning with sendemail.chainreplyto = false' '
+test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = false' '
git config sendemail.chainreplyto false &&
git send-email \
--dry-run \
@@ -842,7 +932,7 @@ test_expect_success 'no warning with sendemail.chainreplyto = false' '
! grep "no-chain-reply-to" errors
'
-test_expect_success 'no warning with sendemail.chainreplyto = true' '
+test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = true' '
git config sendemail.chainreplyto true &&
git send-email \
--dry-run \
@@ -852,7 +942,7 @@ test_expect_success 'no warning with sendemail.chainreplyto = true' '
! grep "no-chain-reply-to" errors
'
-test_expect_success 'sendemail.to works' '
+test_expect_success $PREREQ 'sendemail.to works' '
git config --replace-all sendemail.to "Somebody <somebody@ex.com>" &&
git send-email \
--dry-run \
@@ -861,7 +951,7 @@ test_expect_success 'sendemail.to works' '
grep "To: Somebody <somebody@ex.com>" stdout
'
-test_expect_success '--no-to overrides sendemail.to' '
+test_expect_success $PREREQ '--no-to overrides sendemail.to' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -872,7 +962,7 @@ test_expect_success '--no-to overrides sendemail.to' '
! grep "To: Somebody <somebody@ex.com>" stdout
'
-test_expect_success 'sendemail.cc works' '
+test_expect_success $PREREQ 'sendemail.cc works' '
git config --replace-all sendemail.cc "Somebody <somebody@ex.com>" &&
git send-email \
--dry-run \
@@ -882,7 +972,7 @@ test_expect_success 'sendemail.cc works' '
grep "Cc: Somebody <somebody@ex.com>" stdout
'
-test_expect_success '--no-cc overrides sendemail.cc' '
+test_expect_success $PREREQ '--no-cc overrides sendemail.cc' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -894,7 +984,7 @@ test_expect_success '--no-cc overrides sendemail.cc' '
! grep "Cc: Somebody <somebody@ex.com>" stdout
'
-test_expect_success 'sendemail.bcc works' '
+test_expect_success $PREREQ 'sendemail.bcc works' '
git config --replace-all sendemail.bcc "Other <other@ex.com>" &&
git send-email \
--dry-run \
@@ -905,7 +995,7 @@ test_expect_success 'sendemail.bcc works' '
grep "RCPT TO:<other@ex.com>" stdout
'
-test_expect_success '--no-bcc overrides sendemail.bcc' '
+test_expect_success $PREREQ '--no-bcc overrides sendemail.bcc' '
git send-email \
--dry-run \
--from="Example <nobody@example.com>" \
@@ -918,6 +1008,46 @@ test_expect_success '--no-bcc overrides sendemail.bcc' '
! grep "RCPT TO:<other@ex.com>" stdout
'
+test_expect_success $PREREQ 'patches To headers are used by default' '
+ patch=`git format-patch -1 --to="bodies@example.com"` &&
+ test_when_finished "rm $patch" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --smtp-server relay.example.com \
+ $patch >stdout &&
+ grep "RCPT TO:<bodies@example.com>" stdout
+'
+
+test_expect_success $PREREQ 'patches To headers are appended to' '
+ patch=`git format-patch -1 --to="bodies@example.com"` &&
+ test_when_finished "rm $patch" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server relay.example.com \
+ $patch >stdout &&
+ grep "RCPT TO:<bodies@example.com>" stdout &&
+ grep "RCPT TO:<nobody@example.com>" stdout
+'
+
+test_expect_success $PREREQ 'To headers from files reset each patch' '
+ patch1=`git format-patch -1 --to="bodies@example.com"` &&
+ patch2=`git format-patch -1 --to="other@example.com" HEAD~` &&
+ test_when_finished "rm $patch1 && rm $patch2" &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to="nobody@example.com" \
+ --smtp-server relay.example.com \
+ $patch1 $patch2 >stdout &&
+ test $(grep -c "RCPT TO:<bodies@example.com>" stdout) = 1 &&
+ test $(grep -c "RCPT TO:<nobody@example.com>" stdout) = 2 &&
+ test $(grep -c "RCPT TO:<other@example.com>" stdout) = 1
+'
+
+test_expect_success $PREREQ 'setup expect' '
cat >email-using-8bit <<EOF
From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
Message-Id: <bogus-message-id@example.com>
@@ -927,14 +1057,17 @@ Subject: subject goes here
Dieser deutsche Text enthält einen Umlaut!
EOF
+'
+test_expect_success $PREREQ 'setup expect' '
cat >content-type-decl <<EOF
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
EOF
+'
-test_expect_success 'asks about and fixes 8bit encodings' '
+test_expect_success $PREREQ 'asks about and fixes 8bit encodings' '
clean_fake_sendmail &&
echo |
git send-email --from=author@example.com --to=nobody@example.com \
@@ -947,7 +1080,7 @@ test_expect_success 'asks about and fixes 8bit encodings' '
test_cmp actual content-type-decl
'
-test_expect_success 'sendemail.8bitEncoding works' '
+test_expect_success $PREREQ 'sendemail.8bitEncoding works' '
clean_fake_sendmail &&
git config sendemail.assume8bitEncoding UTF-8 &&
echo bogus |
@@ -958,7 +1091,7 @@ test_expect_success 'sendemail.8bitEncoding works' '
test_cmp actual content-type-decl
'
-test_expect_success '--8bit-encoding overrides sendemail.8bitEncoding' '
+test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' '
clean_fake_sendmail &&
git config sendemail.assume8bitEncoding "bogus too" &&
echo bogus |
@@ -970,6 +1103,7 @@ test_expect_success '--8bit-encoding overrides sendemail.8bitEncoding' '
test_cmp actual content-type-decl
'
+test_expect_success $PREREQ 'setup expect' '
cat >email-using-8bit <<EOF
From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
Message-Id: <bogus-message-id@example.com>
@@ -979,12 +1113,15 @@ Subject: Dieser Betreff enthält auch einen Umlaut!
Nothing to see here.
EOF
+'
+test_expect_success $PREREQ 'setup expect' '
cat >expected <<EOF
Subject: =?UTF-8?q?Dieser=20Betreff=20enth=C3=A4lt=20auch=20einen=20Umlaut!?=
EOF
+'
-test_expect_success '--8bit-encoding also treats subject' '
+test_expect_success $PREREQ '--8bit-encoding also treats subject' '
clean_fake_sendmail &&
echo bogus |
git send-email --from=author@example.com --to=nobody@example.com \
@@ -995,4 +1132,40 @@ test_expect_success '--8bit-encoding also treats subject' '
test_cmp expected actual
'
+# Note that the patches in this test are deliberately out of order; we
+# want to make sure it works even if the cover-letter is not in the
+# first mail.
+test_expect_success $PREREQ 'refusing to send cover letter template' '
+ clean_fake_sendmail &&
+ rm -fr outdir &&
+ git format-patch --cover-letter -2 -o outdir &&
+ test_must_fail git send-email \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ outdir/0002-*.patch \
+ outdir/0000-*.patch \
+ outdir/0001-*.patch \
+ 2>errors >out &&
+ grep "SUBJECT HERE" errors &&
+ test -z "$(ls msgtxt*)"
+'
+
+test_expect_success $PREREQ '--force sends cover letter template anyway' '
+ clean_fake_sendmail &&
+ rm -fr outdir &&
+ git format-patch --cover-letter -2 -o outdir &&
+ git send-email \
+ --force \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ outdir/0002-*.patch \
+ outdir/0000-*.patch \
+ outdir/0001-*.patch \
+ 2>errors >out &&
+ ! grep "SUBJECT HERE" errors &&
+ test -n "$(ls msgtxt*)"
+'
+
test_done
diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh
new file mode 100755
index 0000000..6f6175a
--- /dev/null
+++ b/t/t9010-svn-fe.sh
@@ -0,0 +1,863 @@
+#!/bin/sh
+
+test_description='check svn dumpfile importer'
+
+. ./test-lib.sh
+
+reinit_git () {
+ rm -fr .git &&
+ git init
+}
+
+properties () {
+ while test "$#" -ne 0
+ do
+ property="$1" &&
+ value="$2" &&
+ printf "%s\n" "K ${#property}" &&
+ printf "%s\n" "$property" &&
+ printf "%s\n" "V ${#value}" &&
+ printf "%s\n" "$value" &&
+ shift 2 ||
+ return 1
+ done
+}
+
+text_no_props () {
+ text="$1
+" &&
+ printf "%s\n" "Prop-content-length: 10" &&
+ printf "%s\n" "Text-content-length: ${#text}" &&
+ printf "%s\n" "Content-length: $((${#text} + 10))" &&
+ printf "%s\n" "" "PROPS-END" &&
+ printf "%s\n" "$text"
+}
+
+>empty
+
+test_expect_success 'empty dump' '
+ reinit_git &&
+ echo "SVN-fs-dump-format-version: 2" >input &&
+ test-svn-fe input >stream &&
+ git fast-import <stream
+'
+
+test_expect_success 'v4 dumps not supported' '
+ reinit_git &&
+ echo "SVN-fs-dump-format-version: 4" >v4.dump &&
+ test_must_fail test-svn-fe v4.dump >stream &&
+ test_cmp empty stream
+'
+
+test_expect_failure 'empty revision' '
+ reinit_git &&
+ printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
+ cat >emptyrev.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 0
+ Content-length: 0
+
+ Revision-number: 2
+ Prop-content-length: 0
+ Content-length: 0
+
+ EOF
+ test-svn-fe emptyrev.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'empty properties' '
+ reinit_git &&
+ printf "rev <nobody, nobody@local>: %s\n" "" "" >expect &&
+ cat >emptyprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test-svn-fe emptyprop.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="rev <%an, %ae>: %s" HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'author name and commit message' '
+ reinit_git &&
+ echo "<author@example.com, author@example.com@local>" >expect.author &&
+ cat >message <<-\EOF &&
+ A concise summary of the change
+
+ A detailed description of the change, why it is needed, what
+ was broken and why applying this is the best course of action.
+
+ * file.c
+ Details pertaining to an individual file.
+ EOF
+ {
+ properties \
+ svn:author author@example.com \
+ svn:log "$(cat message)" &&
+ echo PROPS-END
+ } >props &&
+ {
+ echo "SVN-fs-dump-format-version: 3" &&
+ echo &&
+ echo "Revision-number: 1" &&
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props
+ } >log.dump &&
+ test-svn-fe log.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format="%B" HEAD >actual.log &&
+ git log --format="<%an, %ae>" >actual.author &&
+ test_cmp message actual.log &&
+ test_cmp expect.author actual.author
+'
+
+test_expect_success 'unsupported properties are ignored' '
+ reinit_git &&
+ echo author >expect &&
+ cat >extraprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 56
+ Content-length: 56
+
+ K 8
+ nonsense
+ V 1
+ y
+ K 10
+ svn:author
+ V 6
+ author
+ PROPS-END
+ EOF
+ test-svn-fe extraprop.dump >stream &&
+ git fast-import <stream &&
+ git log -p --format=%an HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_failure 'timestamp and empty file' '
+ echo author@example.com >expect.author &&
+ echo 1999-01-01 >expect.date &&
+ echo file >expect.files &&
+ reinit_git &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-01T00:01:002.000000Z" \
+ svn:log "add empty file" &&
+ echo PROPS-END
+ } >props &&
+ {
+ cat <<-EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF
+
+ Node-path: empty-file
+ Node-kind: file
+ Node-action: add
+ Content-length: 0
+
+ EOF
+ } >emptyfile.dump &&
+ test-svn-fe emptyfile.dump >stream &&
+ git fast-import <stream &&
+ git log --format=%an HEAD >actual.author &&
+ git log --date=short --format=%ad HEAD >actual.date &&
+ git ls-tree -r --name-only HEAD >actual.files &&
+ test_cmp expect.author actual.author &&
+ test_cmp expect.date actual.date &&
+ test_cmp expect.files actual.files &&
+ git checkout HEAD empty-file &&
+ test_cmp empty file
+'
+
+test_expect_success 'directory with files' '
+ reinit_git &&
+ printf "%s\n" directory/file1 directory/file2 >expect.files &&
+ echo hi >hi &&
+ echo hello >hello &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-02-01T00:01:002.000000Z" \
+ svn:log "add directory with some files in it" &&
+ echo PROPS-END
+ } >props &&
+ {
+ cat <<-EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF &&
+
+ Node-path: directory
+ Node-kind: dir
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: directory/file1
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hello &&
+ cat <<-\EOF &&
+ Node-path: directory/file2
+ Node-kind: file
+ Node-action: add
+ EOF
+ text_no_props hi
+ } >directory.dump &&
+ test-svn-fe directory.dump >stream &&
+ git fast-import <stream &&
+
+ git ls-tree -r --name-only HEAD >actual.files &&
+ git checkout HEAD directory &&
+ test_cmp expect.files actual.files &&
+ test_cmp hello directory/file1 &&
+ test_cmp hi directory/file2
+'
+
+test_expect_success 'node without action' '
+ cat >inaction.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: directory
+ Node-kind: dir
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test_must_fail test-svn-fe inaction.dump
+'
+
+test_expect_success 'action: add node without text' '
+ cat >textless.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: textless
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test_must_fail test-svn-fe textless.dump
+'
+
+test_expect_failure 'change file mode but keep old content' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T greeting
+ OBJID
+ :100644 120000 OBJID OBJID T greeting
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ echo "link hello" >expect.blob &&
+ echo hello >hello &&
+ cat >filemode.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 33
+ Content-length: 33
+
+ K 11
+ svn:special
+ V 1
+ *
+ PROPS-END
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+ EOF
+ test-svn-fe filemode.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ git show HEAD:greeting >actual.blob &&
+ git show HEAD^:greeting >actual.target &&
+ test_cmp expect actual &&
+ test_cmp expect.blob actual.blob &&
+ test_cmp hello actual.target
+'
+
+test_expect_success 'NUL in property value' '
+ reinit_git &&
+ echo "commit message" >expect.message &&
+ {
+ properties \
+ unimportant "something with a NUL (Q)" \
+ svn:log "commit message"&&
+ echo PROPS-END
+ } |
+ q_to_nul >props &&
+ {
+ cat <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props
+ } >nulprop.dump &&
+ test-svn-fe nulprop.dump >stream &&
+ git fast-import <stream &&
+ git diff-tree --always -s --format=%s HEAD >actual.message &&
+ test_cmp expect.message actual.message
+'
+
+test_expect_success 'NUL in log message, file content, and property name' '
+ # Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the
+ # svn:specialQnotreally example.
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100644 100644 OBJID OBJID M greeting
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ printf "\n%s\n" "something with an ASCII NUL (Q)" >expect.message &&
+ printf "%s\n" "helQo" >expect.hello1 &&
+ printf "%s\n" "link hello" >expect.hello2 &&
+ {
+ properties svn:log "something with an ASCII NUL (Q)" &&
+ echo PROPS-END
+ } |
+ q_to_nul >props &&
+ {
+ q_to_nul <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 6
+ Content-length: 16
+
+ PROPS-END
+ helQo
+
+ Revision-number: 2
+ EOF
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ q_to_nul <<-\EOF
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 43
+ Text-content-length: 11
+ Content-length: 54
+
+ K 21
+ svn:specialQnotreally
+ V 1
+ *
+ PROPS-END
+ link hello
+ EOF
+ } >8bitclean.dump &&
+ test-svn-fe 8bitclean.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ {
+ git cat-file commit HEAD | nul_to_q &&
+ echo
+ } |
+ sed -ne "/^\$/,\$ p" >actual.message &&
+ git cat-file blob HEAD^:greeting | nul_to_q >actual.hello1 &&
+ git cat-file blob HEAD:greeting | nul_to_q >actual.hello2 &&
+ test_cmp expect actual &&
+ test_cmp expect.message actual.message &&
+ test_cmp expect.hello1 actual.hello1 &&
+ test_cmp expect.hello2 actual.hello2
+'
+
+test_expect_success 'change file mode and reiterate content' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T greeting
+ OBJID
+ :100644 120000 OBJID OBJID T greeting
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ echo "link hello" >expect.blob &&
+ echo hello >hello &&
+ cat >filemode.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 33
+ Text-content-length: 11
+ Content-length: 44
+
+ K 11
+ svn:special
+ V 1
+ *
+ PROPS-END
+ link hello
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: change
+ Prop-content-length: 10
+ Text-content-length: 11
+ Content-length: 21
+
+ PROPS-END
+ link hello
+ EOF
+ test-svn-fe filemode.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ git show HEAD:greeting >actual.blob &&
+ git show HEAD^:greeting >actual.target &&
+ test_cmp expect actual &&
+ test_cmp expect.blob actual.blob &&
+ test_cmp hello actual.target
+'
+
+test_expect_success 'deltas not supported' '
+ {
+ # (old) h + (inline) ello + (old) \n
+ printf "SVNQ%b%b%s" "Q\003\006\005\004" "\001Q\0204\001\002" "ello" |
+ q_to_nul
+ } >delta &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-05T00:01:002.000000Z" \
+ svn:log "add greeting" &&
+ echo PROPS-END
+ } >props &&
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-01-06T00:01:002.000000Z" \
+ svn:log "change it" &&
+ echo PROPS-END
+ } >props2 &&
+ {
+ echo SVN-fs-dump-format-version: 3 &&
+ echo &&
+ echo Revision-number: 1 &&
+ echo Prop-content-length: $(wc -c <props) &&
+ echo Content-length: $(wc -c <props) &&
+ echo &&
+ cat props &&
+ cat <<-\EOF &&
+
+ Node-path: hello
+ Node-kind: file
+ Node-action: add
+ Prop-content-length: 10
+ Text-content-length: 3
+ Content-length: 13
+
+ PROPS-END
+ hi
+
+ EOF
+ echo Revision-number: 2 &&
+ echo Prop-content-length: $(wc -c <props2) &&
+ echo Content-length: $(wc -c <props2) &&
+ echo &&
+ cat props2 &&
+ cat <<-\EOF &&
+
+ Node-path: hello
+ Node-kind: file
+ Node-action: change
+ Text-delta: true
+ Prop-content-length: 10
+ EOF
+ echo Text-content-length: $(wc -c <delta) &&
+ echo Content-length: $((10 + $(wc -c <delta))) &&
+ echo &&
+ echo PROPS-END &&
+ cat delta
+ } >delta.dump &&
+ test_must_fail test-svn-fe delta.dump
+'
+
+test_expect_success 'property deltas supported' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :100755 100644 OBJID OBJID M script.sh
+ EOF
+ {
+ properties \
+ svn:author author@example.com \
+ svn:date "1999-03-06T00:01:002.000000Z" \
+ svn:log "make an executable, or chmod -x it" &&
+ echo PROPS-END
+ } >revprops &&
+ {
+ echo SVN-fs-dump-format-version: 3 &&
+ echo &&
+ echo Revision-number: 1 &&
+ echo Prop-content-length: $(wc -c <revprops) &&
+ echo Content-length: $(wc -c <revprops) &&
+ echo &&
+ cat revprops &&
+ echo &&
+ cat <<-\EOF &&
+ Node-path: script.sh
+ Node-kind: file
+ Node-action: add
+ Text-content-length: 0
+ Prop-content-length: 39
+ Content-length: 39
+
+ K 14
+ svn:executable
+ V 4
+ true
+ PROPS-END
+
+ EOF
+ echo Revision-number: 2 &&
+ echo Prop-content-length: $(wc -c <revprops) &&
+ echo Content-length: $(wc -c <revprops) &&
+ echo &&
+ cat revprops &&
+ echo &&
+ cat <<-\EOF
+ Node-path: script.sh
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 30
+ Content-length: 30
+
+ D 14
+ svn:executable
+ PROPS-END
+ EOF
+ } >propdelta.dump &&
+ test-svn-fe propdelta.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'properties on /' '
+ reinit_git &&
+ cat <<-\EOF >expect &&
+ OBJID
+ OBJID
+ :000000 100644 OBJID OBJID A greeting
+ EOF
+ sed -e "s/X$//" <<-\EOF >changeroot.dump &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: greeting
+ Node-kind: file
+ Node-action: add
+ Text-content-length: 0
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: X
+ Node-kind: dir
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 43
+ Content-length: 43
+
+ K 10
+ svn:ignore
+ V 11
+ build-area
+
+ PROPS-END
+ EOF
+ test-svn-fe changeroot.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --always --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'deltas for typechange' '
+ reinit_git &&
+ cat >expect <<-\EOF &&
+ OBJID
+ :120000 100644 OBJID OBJID T test-file
+ OBJID
+ :100755 120000 OBJID OBJID T test-file
+ OBJID
+ :000000 100755 OBJID OBJID A test-file
+ EOF
+ cat >deleteprop.dump <<-\EOF &&
+ SVN-fs-dump-format-version: 3
+
+ Revision-number: 1
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: add
+ Prop-delta: true
+ Prop-content-length: 35
+ Text-content-length: 17
+ Content-length: 52
+
+ K 14
+ svn:executable
+ V 0
+
+ PROPS-END
+ link testing 123
+
+ Revision-number: 2
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 53
+ Text-content-length: 17
+ Content-length: 70
+
+ K 11
+ svn:special
+ V 1
+ *
+ D 14
+ svn:executable
+ PROPS-END
+ link testing 231
+
+ Revision-number: 3
+ Prop-content-length: 10
+ Content-length: 10
+
+ PROPS-END
+
+ Node-path: test-file
+ Node-kind: file
+ Node-action: change
+ Prop-delta: true
+ Prop-content-length: 27
+ Text-content-length: 17
+ Content-length: 44
+
+ D 11
+ svn:special
+ PROPS-END
+ link testing 321
+ EOF
+ test-svn-fe deleteprop.dump >stream &&
+ git fast-import <stream &&
+ {
+ git rev-list HEAD |
+ git diff-tree --root --stdin |
+ sed "s/$_x40/OBJID/g"
+ } >actual &&
+ test_cmp expect actual
+'
+
+
+test_expect_success 'set up svn repo' '
+ svnconf=$PWD/svnconf &&
+ mkdir -p "$svnconf" &&
+
+ if
+ svnadmin -h >/dev/null 2>&1 &&
+ svnadmin create simple-svn &&
+ svnadmin load simple-svn <"$TEST_DIRECTORY/t9135/svn.dump" &&
+ svn export --config-dir "$svnconf" "file://$PWD/simple-svn" simple-svnco
+ then
+ test_set_prereq SVNREPO
+ fi
+'
+
+test_expect_success SVNREPO 't9135/svn.dump' '
+ git init simple-git &&
+ test-svn-fe "$TEST_DIRECTORY/t9135/svn.dump" >simple.fe &&
+ (
+ cd simple-git &&
+ git fast-import <../simple.fe
+ ) &&
+ (
+ cd simple-svnco &&
+ git init &&
+ git add . &&
+ git fetch ../simple-git master &&
+ git diff --exit-code FETCH_HEAD
+ )
+'
+
+test_done
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 570e035..b041516 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -15,24 +15,25 @@ case "$GIT_SVN_LC_ALL" in
test_set_prereq UTF8
;;
*)
- say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
+ say "# UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
;;
esac
test_expect_success \
'initialize git svn' '
mkdir import &&
- cd import &&
- echo foo > foo &&
- ln -s foo foo.link
- mkdir -p dir/a/b/c/d/e &&
- echo "deep dir" > dir/a/b/c/d/e/file &&
- mkdir bar &&
- echo "zzz" > bar/zzz &&
- echo "#!/bin/sh" > exec.sh &&
- chmod +x exec.sh &&
- svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null &&
- cd .. &&
+ (
+ cd import &&
+ echo foo >foo &&
+ ln -s foo foo.link
+ mkdir -p dir/a/b/c/d/e &&
+ echo "deep dir" >dir/a/b/c/d/e/file &&
+ mkdir bar &&
+ echo "zzz" >bar/zzz &&
+ echo "#!/bin/sh" >exec.sh &&
+ chmod +x exec.sh &&
+ svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null
+ ) &&
rm -rf import &&
git svn init "$svnrepo"'
@@ -142,7 +143,7 @@ test_expect_success "$name" '
git svn set-tree --find-copies-harder --rmdir \
${remotes_git_svn}..mybranch5 &&
svn_cmd up "$SVN_TREE" &&
- test -L "$SVN_TREE"/exec.sh'
+ test -h "$SVN_TREE"/exec.sh'
name='new symlink is added to a file that was also just made executable'
@@ -155,7 +156,7 @@ test_expect_success "$name" '
${remotes_git_svn}..mybranch5 &&
svn_cmd up "$SVN_TREE" &&
test -x "$SVN_TREE"/bar/zzz &&
- test -L "$SVN_TREE"/exec-2.sh'
+ test -h "$SVN_TREE"/exec-2.sh'
name='modify a symlink to become a file'
test_expect_success "$name" '
@@ -168,7 +169,7 @@ test_expect_success "$name" '
${remotes_git_svn}..mybranch5 &&
svn_cmd up "$SVN_TREE" &&
test -f "$SVN_TREE"/exec-2.sh &&
- test ! -L "$SVN_TREE"/exec-2.sh &&
+ test ! -h "$SVN_TREE"/exec-2.sh &&
test_cmp help "$SVN_TREE"/exec-2.sh'
name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
@@ -271,6 +272,17 @@ test_expect_success 'able to dcommit to a subdirectory' "
test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
"
+test_expect_success 'dcommit should not fail with a touched file' '
+ test_commit "commit-new-file-foo2" foo2 &&
+ test-chmtime =-60 foo &&
+ git svn dcommit
+'
+
+test_expect_success 'rebase should not fail with a touched file' '
+ test-chmtime =-60 foo &&
+ git svn rebase
+'
+
test_expect_success 'able to set-tree to a subdirectory' "
echo cba > d &&
git update-index d &&
diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh
index 929499e..8869f50 100755
--- a/t/t9101-git-svn-props.sh
+++ b/t/t9101-git-svn-props.sh
@@ -53,8 +53,9 @@ cd ..
rm -rf import
test_expect_success 'checkout working copy from svn' 'svn co "$svnrepo" test_wc'
-test_expect_success 'setup some commits to svn' \
- 'cd test_wc &&
+test_expect_success 'setup some commits to svn' '
+ (
+ cd test_wc &&
echo Greetings >> kw.c &&
poke kw.c &&
svn_cmd commit -m "Not yet an Id" &&
@@ -63,8 +64,9 @@ test_expect_success 'setup some commits to svn' \
svn_cmd commit -m "Modified file, but still not yet an Id" &&
svn_cmd propset svn:keywords Id kw.c &&
poke kw.c &&
- svn_cmd commit -m "Propset Id" &&
- cd ..'
+ svn_cmd commit -m "Propset Id"
+ )
+'
test_expect_success 'initialize git svn' 'git svn init "$svnrepo"'
test_expect_success 'fetch revisions from svn' 'git svn fetch'
@@ -81,13 +83,15 @@ expect='/* $Id$ */'
got="`sed -ne 2p kw.c`"
test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'"
-test_expect_success "propset CR on crlf files" \
- 'cd test_wc &&
+test_expect_success "propset CR on crlf files" '
+ (
+ cd test_wc &&
svn_cmd propset svn:eol-style CR empty &&
svn_cmd propset svn:eol-style CR crlf &&
svn_cmd propset svn:eol-style CR ne_crlf &&
- svn_cmd commit -m "propset CR on crlf files" &&
- cd ..'
+ svn_cmd commit -m "propset CR on crlf files"
+ )
+'
test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
'git svn fetch &&
@@ -137,19 +141,20 @@ cat > show-ignore.expect <<\EOF
EOF
test_expect_success 'test show-ignore' "
- cd test_wc &&
- mkdir -p deeply/nested/directory &&
- touch deeply/nested/directory/.keep &&
- svn_cmd add deeply &&
- svn_cmd up &&
- svn_cmd propset -R svn:ignore '
+ (
+ cd test_wc &&
+ mkdir -p deeply/nested/directory &&
+ touch deeply/nested/directory/.keep &&
+ svn_cmd add deeply &&
+ svn_cmd up &&
+ svn_cmd propset -R svn:ignore '
no-such-file*
' .
- svn_cmd commit -m 'propset svn:ignore'
- cd .. &&
+ svn_cmd commit -m 'propset svn:ignore'
+ ) &&
git svn show-ignore > show-ignore.got &&
cmp show-ignore.expect show-ignore.got
- "
+"
cat >create-ignore.expect <<\EOF
/no-such-file*
diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh
index 028fb19..eb70f48 100755
--- a/t/t9102-git-svn-deep-rmdir.sh
+++ b/t/t9102-git-svn-deep-rmdir.sh
@@ -4,13 +4,14 @@ test_description='git svn rmdir'
test_expect_success 'initialize repo' '
mkdir import &&
- cd import &&
- mkdir -p deeply/nested/directory/number/1 &&
- mkdir -p deeply/nested/directory/number/2 &&
- echo foo > deeply/nested/directory/number/1/file &&
- echo foo > deeply/nested/directory/number/2/another &&
- svn_cmd import -m "import for git svn" . "$svnrepo" &&
- cd ..
+ (
+ cd import &&
+ mkdir -p deeply/nested/directory/number/1 &&
+ mkdir -p deeply/nested/directory/number/2 &&
+ echo foo >deeply/nested/directory/number/1/file &&
+ echo foo >deeply/nested/directory/number/2/another &&
+ svn_cmd import -m "import for git svn" . "$svnrepo"
+ )
'
test_expect_success 'mirror via git svn' '
diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh
index bbfd7f4..13b179e 100755
--- a/t/t9104-git-svn-follow-parent.sh
+++ b/t/t9104-git-svn-follow-parent.sh
@@ -8,22 +8,24 @@ test_description='git svn fetching'
test_expect_success 'initialize repo' '
mkdir import &&
- cd import &&
- mkdir -p trunk &&
- echo hello > trunk/readme &&
- svn_cmd import -m "initial" . "$svnrepo" &&
- cd .. &&
+ (
+ cd import &&
+ mkdir -p trunk &&
+ echo hello >trunk/readme &&
+ svn_cmd import -m "initial" . "$svnrepo"
+ ) &&
svn_cmd co "$svnrepo" wc &&
- cd wc &&
- echo world >> trunk/readme &&
- poke trunk/readme &&
- svn_cmd commit -m "another commit" &&
- svn_cmd up &&
- svn_cmd mv trunk thunk &&
- echo goodbye >> thunk/readme &&
- poke thunk/readme &&
- svn_cmd commit -m "bye now" &&
- cd ..
+ (
+ cd wc &&
+ echo world >>trunk/readme &&
+ poke trunk/readme &&
+ svn_cmd commit -m "another commit" &&
+ svn_cmd up &&
+ svn_cmd mv trunk thunk &&
+ echo goodbye >>thunk/readme &&
+ poke thunk/readme &&
+ svn_cmd commit -m "bye now"
+ )
'
test_expect_success 'init and fetch a moved directory' '
@@ -83,16 +85,17 @@ test_expect_success 'follow larger parent' '
'
test_expect_success 'follow higher-level parent' '
- svn mkdir -m "follow higher-level parent" "$svnrepo"/blob &&
- svn co "$svnrepo"/blob blob &&
- cd blob &&
- echo hi > hi &&
- svn add hi &&
- svn commit -m "hihi" &&
- cd ..
- svn mkdir -m "new glob at top level" "$svnrepo"/glob &&
- svn mv -m "move blob down a level" "$svnrepo"/blob "$svnrepo"/glob/blob &&
- git svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
+ svn mkdir -m "follow higher-level parent" "$svnrepo"/blob &&
+ svn co "$svnrepo"/blob blob &&
+ (
+ cd blob &&
+ echo hi > hi &&
+ svn add hi &&
+ svn commit -m "hihi"
+ ) &&
+ svn mkdir -m "new glob at top level" "$svnrepo"/glob &&
+ svn mv -m "move blob down a level" "$svnrepo"/blob "$svnrepo"/glob/blob &&
+ git svn init --minimize-url -i blob "$svnrepo"/glob/blob &&
git svn fetch -i blob
'
@@ -117,18 +120,23 @@ test_expect_success 'follow-parent avoids deleting relevant info' '
import/trunk/subversion/bindings/swig/perl/t/larger-parent &&
echo "bad delete test 2" > \
import/trunk/subversion/bindings/swig/perl/another-larger &&
- cd import &&
- svn import -m "r9270 test" . "$svnrepo"/r9270 &&
- cd .. &&
+ (
+ cd import &&
+ svn import -m "r9270 test" . "$svnrepo"/r9270
+ ) &&
svn_cmd co "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl r9270 &&
- cd r9270 &&
- svn mkdir native &&
- svn mv t native/t &&
- for i in a b c; do svn mv $i.pm native/$i.pm; done &&
- echo z >> native/t/c.t &&
- poke native/t/c.t &&
- svn commit -m "reorg test" &&
- cd .. &&
+ (
+ cd r9270 &&
+ svn mkdir native &&
+ svn mv t native/t &&
+ for i in a b c
+ do
+ svn mv $i.pm native/$i.pm
+ done &&
+ echo z >>native/t/c.t &&
+ poke native/t/c.t &&
+ svn commit -m "reorg test"
+ ) &&
git svn init --minimize-url -i r9270-t \
"$svnrepo"/r9270/trunk/subversion/bindings/swig/perl/native/t &&
git svn fetch -i r9270-t &&
@@ -182,7 +190,7 @@ test_expect_success "follow-parent is atomic" '
git svn init --minimize-url -i stunk "$svnrepo"/stunk &&
git svn fetch -i stunk &&
git svn init --minimize-url -i flunked "$svnrepo"/flunked &&
- git svn fetch -i flunked
+ git svn fetch -i flunked &&
test "`git rev-parse --verify refs/remotes/flunk@18`" \
= "`git rev-parse --verify refs/remotes/stunk`" &&
test "`git rev-parse --verify refs/remotes/flunk~1`" \
diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh
index dd48e9c..5d0afea 100755
--- a/t/t9105-git-svn-commit-diff.sh
+++ b/t/t9105-git-svn-commit-diff.sh
@@ -6,10 +6,11 @@ test_description='git svn commit-diff'
test_expect_success 'initialize repo' '
mkdir import &&
- cd import &&
- echo hello > readme &&
- svn_cmd import -m "initial" . "$svnrepo" &&
- cd .. &&
+ (
+ cd import &&
+ echo hello >readme &&
+ svn_cmd import -m "initial" . "$svnrepo"
+ ) &&
echo hello > readme &&
git update-index --add readme &&
git commit -a -m "initial" &&
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index 12f21b7..f6d7ac7 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -6,21 +6,23 @@ test_description='git svn commit-diff clobber'
test_expect_success 'initialize repo' '
mkdir import &&
- cd import &&
- echo initial > file &&
- svn_cmd import -m "initial" . "$svnrepo" &&
- cd .. &&
+ (
+ cd import &&
+ echo initial >file &&
+ svn_cmd import -m "initial" . "$svnrepo"
+ ) &&
echo initial > file &&
git update-index --add file &&
git commit -a -m "initial"
'
test_expect_success 'commit change from svn side' '
svn_cmd co "$svnrepo" t.svn &&
- cd t.svn &&
- echo second line from svn >> file &&
- poke file &&
- svn_cmd commit -m "second line from svn" &&
- cd .. &&
+ (
+ cd t.svn &&
+ echo second line from svn >>file &&
+ poke file &&
+ svn_cmd commit -m "second line from svn"
+ ) &&
rm -rf t.svn
'
@@ -44,11 +46,12 @@ test_expect_success 'dcommit fails to commit because of conflict' '
git svn fetch &&
git reset --hard refs/${remotes_git_svn} &&
svn_cmd co "$svnrepo" t.svn &&
- cd t.svn &&
- echo fourth line from svn >> file &&
- poke file &&
- svn_cmd commit -m "fourth line from svn" &&
- cd .. &&
+ (
+ cd t.svn &&
+ echo fourth line from svn >>file &&
+ poke file &&
+ svn_cmd commit -m "fourth line from svn"
+ ) &&
rm -rf t.svn &&
echo "fourth line from git" >> file &&
git commit -a -m "fourth line from git" &&
@@ -68,11 +71,12 @@ test_expect_success 'dcommit does the svn equivalent of an index merge' "
test_expect_success 'commit another change from svn side' '
svn_cmd co "$svnrepo" t.svn &&
- cd t.svn &&
- echo third line from svn >> file &&
+ (
+ cd t.svn &&
+ echo third line from svn >>file &&
poke file &&
- svn_cmd commit -m "third line from svn" &&
- cd .. &&
+ svn_cmd commit -m "third line from svn"
+ ) &&
rm -rf t.svn
'
diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh
index 901b8e0..289fc31 100755
--- a/t/t9107-git-svn-migrate.sh
+++ b/t/t9107-git-svn-migrate.sh
@@ -6,14 +6,16 @@ test_description='git svn metadata migrations from previous versions'
test_expect_success 'setup old-looking metadata' '
cp "$GIT_DIR"/config "$GIT_DIR"/config-old-git-svn &&
mkdir import &&
- cd import &&
- for i in trunk branches/a branches/b \
- tags/0.1 tags/0.2 tags/0.3; do
- mkdir -p $i && \
- echo hello >> $i/README || exit 1
- done && \
+ (
+ cd import &&
+ for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3
+ do
+ mkdir -p $i &&
+ echo hello >>$i/README ||
+ exit 1
+ done &&
svn_cmd import -m test . "$svnrepo"
- cd .. &&
+ ) &&
git svn init "$svnrepo" &&
git svn fetch &&
rm -rf "$GIT_DIR"/svn &&
diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh
index 84f7c9b..3077851 100755
--- a/t/t9114-git-svn-dcommit-merge.sh
+++ b/t/t9114-git-svn-dcommit-merge.sh
@@ -37,11 +37,12 @@ EOF
test_expect_success 'setup svn repository' '
svn_cmd co "$svnrepo" mysvnwork &&
mkdir -p mysvnwork/trunk &&
- cd mysvnwork &&
- big_text_block >> trunk/README &&
+ (
+ cd mysvnwork &&
+ big_text_block >>trunk/README &&
svn_cmd add trunk &&
- svn_cmd ci -m "first commit" trunk &&
- cd ..
+ svn_cmd ci -m "first commit" trunk
+ )
'
test_expect_success 'setup git mirror and merge' '
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
index 767799e..6a48e40 100755
--- a/t/t9115-git-svn-dcommit-funky-renames.sh
+++ b/t/t9115-git-svn-dcommit-funky-renames.sh
@@ -61,11 +61,12 @@ test_expect_success 'add a file with plus signs' '
test_expect_success 'clone the repository to test rebase' '
git svn clone "$svnrepo" test-rebase &&
- cd test-rebase &&
- echo test-rebase > test-rebase &&
+ (
+ cd test-rebase &&
+ echo test-rebase >test-rebase &&
git add test-rebase &&
- git commit -m test-rebase &&
- cd ..
+ git commit -m test-rebase
+ )
'
test_expect_success 'make a commit to test rebase' '
diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh
index 0374a74..cf4c052 100755
--- a/t/t9116-git-svn-log.sh
+++ b/t/t9116-git-svn-log.sh
@@ -8,14 +8,16 @@ test_description='git svn log tests'
test_expect_success 'setup repository and import' '
mkdir import &&
- cd import &&
- for i in trunk branches/a branches/b \
- tags/0.1 tags/0.2 tags/0.3; do
- mkdir -p $i && \
- echo hello >> $i/README || exit 1
- done && \
+ (
+ cd import &&
+ for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3
+ do
+ mkdir -p $i &&
+ echo hello >>$i/README ||
+ exit 1
+ done &&
svn_cmd import -m test . "$svnrepo"
- cd .. &&
+ ) &&
git svn init "$svnrepo" -T trunk -b branches -t tags &&
git svn fetch &&
git reset --hard trunk &&
@@ -58,6 +60,21 @@ test_expect_success 'test ascending revision range' "
git svn log -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 -
"
+test_expect_success 'test ascending revision range with --show-commit' "
+ git reset --hard trunk &&
+ git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 -
+ "
+
+test_expect_success 'test ascending revision range with --show-commit (sha1)' "
+ git svn find-rev r1 >expected-range-r1-r2-r4-sha1 &&
+ git svn find-rev r2 >>expected-range-r1-r2-r4-sha1 &&
+ git svn find-rev r4 >>expected-range-r1-r2-r4-sha1 &&
+ git reset --hard trunk &&
+ git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f2 >out &&
+ git rev-parse \$(cat out) >actual &&
+ test_cmp expected-range-r1-r2-r4-sha1 actual
+ "
+
printf 'r4 \nr2 \nr1 \n' > expected-range-r4-r2-r1
test_expect_success 'test descending revision range' "
diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh
index 7d7acc3..63fc982 100755
--- a/t/t9118-git-svn-funky-branch-names.sh
+++ b/t/t9118-git-svn-funky-branch-names.sh
@@ -28,48 +28,52 @@ test_expect_success 'setup svnrepo' '
svn_cmd cp -m "trailing .lock" "$svnrepo/pr ject/trunk" \
"$svnrepo/pr ject/branches/trailing_dotlock.lock" &&
svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \
- "$svnrepo/pr ject/branches/not-a@{0}reflog" &&
+ "$svnrepo/pr ject/branches/not-a%40{0}reflog" &&
start_httpd
'
test_expect_success 'test clone with funky branch names' '
git svn clone -s "$svnrepo/pr ject" project &&
- cd project &&
+ (
+ cd project &&
git rev-parse "refs/remotes/fun%20plugin" &&
git rev-parse "refs/remotes/more%20fun%20plugin!" &&
git rev-parse "refs/remotes/$scary_ref" &&
git rev-parse "refs/remotes/%2Eleading_dot" &&
git rev-parse "refs/remotes/trailing_dot%2E" &&
git rev-parse "refs/remotes/trailing_dotlock%2Elock" &&
- git rev-parse "refs/remotes/not-a%40{0}reflog" &&
- cd ..
+ git rev-parse "refs/remotes/not-a%40{0}reflog"
+ )
'
test_expect_success 'test dcommit to funky branch' "
- cd project &&
- git reset --hard 'refs/remotes/more%20fun%20plugin!' &&
- echo hello >> foo &&
- git commit -m 'hello' -- foo &&
- git svn dcommit &&
- cd ..
+ (
+ cd project &&
+ git reset --hard 'refs/remotes/more%20fun%20plugin!' &&
+ echo hello >> foo &&
+ git commit -m 'hello' -- foo &&
+ git svn dcommit
+ )
"
test_expect_success 'test dcommit to scary branch' '
- cd project &&
- git reset --hard "refs/remotes/$scary_ref" &&
- echo urls are scary >> foo &&
- git commit -m "eep" -- foo &&
- git svn dcommit &&
- cd ..
+ (
+ cd project &&
+ git reset --hard "refs/remotes/$scary_ref" &&
+ echo urls are scary >> foo &&
+ git commit -m "eep" -- foo &&
+ git svn dcommit
+ )
'
test_expect_success 'test dcommit to trailing_dotlock branch' '
- cd project &&
- git reset --hard "refs/remotes/trailing_dotlock%2Elock" &&
- echo who names branches like this anyway? >> foo &&
- git commit -m "bar" -- foo &&
- git svn dcommit &&
- cd ..
+ (
+ cd project &&
+ git reset --hard "refs/remotes/trailing_dotlock%2Elock" &&
+ echo who names branches like this anyway? >> foo &&
+ git commit -m "bar" -- foo &&
+ git svn dcommit
+ )
'
stop_httpd
diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh
index a9a558d..ff19695 100755
--- a/t/t9119-git-svn-info.sh
+++ b/t/t9119-git-svn-info.sh
@@ -13,63 +13,55 @@ case $v in
1.[456].*)
;;
*)
- say "skipping svn-info test (SVN version: $v not supported)"
+ skip_all="skipping svn-info test (SVN version: $v not supported)"
test_done
;;
esac
-ptouch() {
- perl -w -e '
- use strict;
- use POSIX qw(mktime);
- die "ptouch requires exactly 2 arguments" if @ARGV != 2;
- my $text_last_updated = shift @ARGV;
- my $git_file = shift @ARGV;
- die "\"$git_file\" does not exist" if ! -e $git_file;
- if ($text_last_updated
- =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
- my $mtime = mktime($6, $5, $4, $3, $2 - 1, $1 - 1900);
- my $atime = $mtime;
- utime $atime, $mtime, $git_file;
- }
- ' "`svn_cmd info $2 | grep '^Text Last Updated:'`" "$1"
+# On the "Text Last Updated" line, "git svn info" does not return the
+# same value as "svn info" (i.e. the commit timestamp that touched the
+# path most recently); do not expect that field to match.
+test_cmp_info () {
+ sed -e '/^Text Last Updated:/d' "$1" >tmp.expect
+ sed -e '/^Text Last Updated:/d' "$2" >tmp.actual
+ test_cmp tmp.expect tmp.actual &&
+ rm -f tmp.expect tmp.actual
}
quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')"
test_expect_success 'setup repository and import' '
mkdir info &&
- cd info &&
- echo FIRST > A &&
- echo one > file &&
+ (
+ cd info &&
+ echo FIRST >A &&
+ echo one >file &&
ln -s file symlink-file &&
mkdir directory &&
touch directory/.placeholder &&
ln -s directory symlink-directory &&
- svn_cmd import -m "initial" . "$svnrepo" &&
- cd .. &&
+ svn_cmd import -m "initial" . "$svnrepo"
+ ) &&
svn_cmd co "$svnrepo" svnwc &&
- cd svnwc &&
- echo foo > foo &&
+ (
+ cd svnwc &&
+ echo foo >foo &&
svn_cmd add foo &&
svn_cmd commit -m "change outside directory" &&
- svn_cmd update &&
- cd .. &&
+ svn_cmd update
+ ) &&
mkdir gitwc &&
- cd gitwc &&
+ (
+ cd gitwc &&
git svn init "$svnrepo" &&
- git svn fetch &&
- cd .. &&
- ptouch gitwc/file svnwc/file &&
- ptouch gitwc/directory svnwc/directory &&
- ptouch gitwc/symlink-file svnwc/symlink-file &&
- ptouch gitwc/symlink-directory svnwc/symlink-directory
+ git svn fetch
+ )
'
test_expect_success 'info' "
(cd svnwc; svn info) > expected.info &&
(cd gitwc; git svn info) > actual.info &&
- test_cmp expected.info actual.info
+ test_cmp_info expected.info actual.info
"
test_expect_success 'info --url' '
@@ -79,7 +71,7 @@ test_expect_success 'info --url' '
test_expect_success 'info .' "
(cd svnwc; svn info .) > expected.info-dot &&
(cd gitwc; git svn info .) > actual.info-dot &&
- test_cmp expected.info-dot actual.info-dot
+ test_cmp_info expected.info-dot actual.info-dot
"
test_expect_success 'info --url .' '
@@ -89,7 +81,7 @@ test_expect_success 'info --url .' '
test_expect_success 'info file' "
(cd svnwc; svn info file) > expected.info-file &&
(cd gitwc; git svn info file) > actual.info-file &&
- test_cmp expected.info-file actual.info-file
+ test_cmp_info expected.info-file actual.info-file
"
test_expect_success 'info --url file' '
@@ -99,13 +91,13 @@ test_expect_success 'info --url file' '
test_expect_success 'info directory' "
(cd svnwc; svn info directory) > expected.info-directory &&
(cd gitwc; git svn info directory) > actual.info-directory &&
- test_cmp expected.info-directory actual.info-directory
+ test_cmp_info expected.info-directory actual.info-directory
"
test_expect_success 'info inside directory' "
(cd svnwc/directory; svn info) > expected.info-inside-directory &&
(cd gitwc/directory; git svn info) > actual.info-inside-directory &&
- test_cmp expected.info-inside-directory actual.info-inside-directory
+ test_cmp_info expected.info-inside-directory actual.info-inside-directory
"
test_expect_success 'info --url directory' '
@@ -115,7 +107,7 @@ test_expect_success 'info --url directory' '
test_expect_success 'info symlink-file' "
(cd svnwc; svn info symlink-file) > expected.info-symlink-file &&
(cd gitwc; git svn info symlink-file) > actual.info-symlink-file &&
- test_cmp expected.info-symlink-file actual.info-symlink-file
+ test_cmp_info expected.info-symlink-file actual.info-symlink-file
"
test_expect_success 'info --url symlink-file' '
@@ -128,7 +120,7 @@ test_expect_success 'info symlink-directory' "
> expected.info-symlink-directory &&
(cd gitwc; git svn info symlink-directory) \
> actual.info-symlink-directory &&
- test_cmp expected.info-symlink-directory actual.info-symlink-directory
+ test_cmp_info expected.info-symlink-directory actual.info-symlink-directory
"
test_expect_success 'info --url symlink-directory' '
@@ -138,17 +130,18 @@ test_expect_success 'info --url symlink-directory' '
test_expect_success 'info added-file' "
echo two > gitwc/added-file &&
- cd gitwc &&
- git add added-file &&
- cd .. &&
+ (
+ cd gitwc &&
+ git add added-file
+ ) &&
cp gitwc/added-file svnwc/added-file &&
- ptouch gitwc/added-file svnwc/added-file &&
- cd svnwc &&
- svn_cmd add added-file > /dev/null &&
- cd .. &&
+ (
+ cd svnwc &&
+ svn_cmd add added-file > /dev/null
+ ) &&
(cd svnwc; svn info added-file) > expected.info-added-file &&
(cd gitwc; git svn info added-file) > actual.info-added-file &&
- test_cmp expected.info-added-file actual.info-added-file
+ test_cmp_info expected.info-added-file actual.info-added-file
"
test_expect_success 'info --url added-file' '
@@ -158,19 +151,20 @@ test_expect_success 'info --url added-file' '
test_expect_success 'info added-directory' "
mkdir gitwc/added-directory svnwc/added-directory &&
- ptouch gitwc/added-directory svnwc/added-directory &&
touch gitwc/added-directory/.placeholder &&
- cd svnwc &&
- svn_cmd add added-directory > /dev/null &&
- cd .. &&
- cd gitwc &&
- git add added-directory &&
- cd .. &&
+ (
+ cd svnwc &&
+ svn_cmd add added-directory > /dev/null
+ ) &&
+ (
+ cd gitwc &&
+ git add added-directory
+ ) &&
(cd svnwc; svn info added-directory) \
> expected.info-added-directory &&
(cd gitwc; git svn info added-directory) \
> actual.info-added-directory &&
- test_cmp expected.info-added-directory actual.info-added-directory
+ test_cmp_info expected.info-added-directory actual.info-added-directory
"
test_expect_success 'info --url added-directory' '
@@ -179,21 +173,22 @@ test_expect_success 'info --url added-directory' '
'
test_expect_success 'info added-symlink-file' "
- cd gitwc &&
+ (
+ cd gitwc &&
ln -s added-file added-symlink-file &&
- git add added-symlink-file &&
- cd .. &&
- cd svnwc &&
+ git add added-symlink-file
+ ) &&
+ (
+ cd svnwc &&
ln -s added-file added-symlink-file &&
- svn_cmd add added-symlink-file > /dev/null &&
- cd .. &&
- ptouch gitwc/added-symlink-file svnwc/added-symlink-file &&
+ svn_cmd add added-symlink-file > /dev/null
+ ) &&
(cd svnwc; svn info added-symlink-file) \
> expected.info-added-symlink-file &&
(cd gitwc; git svn info added-symlink-file) \
> actual.info-added-symlink-file &&
- test_cmp expected.info-added-symlink-file \
- actual.info-added-symlink-file
+ test_cmp_info expected.info-added-symlink-file \
+ actual.info-added-symlink-file
"
test_expect_success 'info --url added-symlink-file' '
@@ -202,21 +197,22 @@ test_expect_success 'info --url added-symlink-file' '
'
test_expect_success 'info added-symlink-directory' "
- cd gitwc &&
+ (
+ cd gitwc &&
ln -s added-directory added-symlink-directory &&
- git add added-symlink-directory &&
- cd .. &&
- cd svnwc &&
+ git add added-symlink-directory
+ ) &&
+ (
+ cd svnwc &&
ln -s added-directory added-symlink-directory &&
- svn_cmd add added-symlink-directory > /dev/null &&
- cd .. &&
- ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory &&
+ svn_cmd add added-symlink-directory > /dev/null
+ ) &&
(cd svnwc; svn info added-symlink-directory) \
> expected.info-added-symlink-directory &&
(cd gitwc; git svn info added-symlink-directory) \
> actual.info-added-symlink-directory &&
- test_cmp expected.info-added-symlink-directory \
- actual.info-added-symlink-directory
+ test_cmp_info expected.info-added-symlink-directory \
+ actual.info-added-symlink-directory
"
test_expect_success 'info --url added-symlink-directory' '
@@ -224,25 +220,18 @@ test_expect_success 'info --url added-symlink-directory' '
= "$quoted_svnrepo/added-symlink-directory"
'
-# The next few tests replace the "Text Last Updated" value with a
-# placeholder since git doesn't have a way to know the date that a
-# now-deleted file was last checked out locally. Internally it
-# simply reuses the Last Changed Date.
-
test_expect_success 'info deleted-file' "
- cd gitwc &&
- git rm -f file > /dev/null &&
- cd .. &&
- cd svnwc &&
- svn_cmd rm --force file > /dev/null &&
- cd .. &&
- (cd svnwc; svn info file) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > expected.info-deleted-file &&
- (cd gitwc; git svn info file) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > actual.info-deleted-file &&
- test_cmp expected.info-deleted-file actual.info-deleted-file
+ (
+ cd gitwc &&
+ git rm -f file > /dev/null
+ ) &&
+ (
+ cd svnwc &&
+ svn_cmd rm --force file > /dev/null
+ ) &&
+ (cd svnwc; svn info file) >expected.info-deleted-file &&
+ (cd gitwc; git svn info file) >actual.info-deleted-file &&
+ test_cmp_info expected.info-deleted-file actual.info-deleted-file
"
test_expect_success 'info --url file (deleted)' '
@@ -251,19 +240,17 @@ test_expect_success 'info --url file (deleted)' '
'
test_expect_success 'info deleted-directory' "
- cd gitwc &&
- git rm -r -f directory > /dev/null &&
- cd .. &&
- cd svnwc &&
- svn_cmd rm --force directory > /dev/null &&
- cd .. &&
- (cd svnwc; svn info directory) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > expected.info-deleted-directory &&
- (cd gitwc; git svn info directory) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > actual.info-deleted-directory &&
- test_cmp expected.info-deleted-directory actual.info-deleted-directory
+ (
+ cd gitwc &&
+ git rm -r -f directory > /dev/null
+ ) &&
+ (
+ cd svnwc &&
+ svn_cmd rm --force directory > /dev/null
+ ) &&
+ (cd svnwc; svn info directory) >expected.info-deleted-directory &&
+ (cd gitwc; git svn info directory) >actual.info-deleted-directory &&
+ test_cmp_info expected.info-deleted-directory actual.info-deleted-directory
"
test_expect_success 'info --url directory (deleted)' '
@@ -272,20 +259,17 @@ test_expect_success 'info --url directory (deleted)' '
'
test_expect_success 'info deleted-symlink-file' "
- cd gitwc &&
- git rm -f symlink-file > /dev/null &&
- cd .. &&
- cd svnwc &&
- svn_cmd rm --force symlink-file > /dev/null &&
- cd .. &&
- (cd svnwc; svn info symlink-file) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > expected.info-deleted-symlink-file &&
- (cd gitwc; git svn info symlink-file) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > actual.info-deleted-symlink-file &&
- test_cmp expected.info-deleted-symlink-file \
- actual.info-deleted-symlink-file
+ (
+ cd gitwc &&
+ git rm -f symlink-file > /dev/null
+ ) &&
+ (
+ cd svnwc &&
+ svn_cmd rm --force symlink-file > /dev/null
+ ) &&
+ (cd svnwc; svn info symlink-file) >expected.info-deleted-symlink-file &&
+ (cd gitwc; git svn info symlink-file) >actual.info-deleted-symlink-file &&
+ test_cmp_info expected.info-deleted-symlink-file actual.info-deleted-symlink-file
"
test_expect_success 'info --url symlink-file (deleted)' '
@@ -294,20 +278,17 @@ test_expect_success 'info --url symlink-file (deleted)' '
'
test_expect_success 'info deleted-symlink-directory' "
- cd gitwc &&
- git rm -f symlink-directory > /dev/null &&
- cd .. &&
- cd svnwc &&
- svn_cmd rm --force symlink-directory > /dev/null &&
- cd .. &&
- (cd svnwc; svn info symlink-directory) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > expected.info-deleted-symlink-directory &&
- (cd gitwc; git svn info symlink-directory) |
- sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \
- > actual.info-deleted-symlink-directory &&
- test_cmp expected.info-deleted-symlink-directory \
- actual.info-deleted-symlink-directory
+ (
+ cd gitwc &&
+ git rm -f symlink-directory > /dev/null
+ ) &&
+ (
+ cd svnwc &&
+ svn_cmd rm --force symlink-directory > /dev/null
+ ) &&
+ (cd svnwc; svn info symlink-directory) >expected.info-deleted-symlink-directory &&
+ (cd gitwc; git svn info symlink-directory) >actual.info-deleted-symlink-directory &&
+ test_cmp_info expected.info-deleted-symlink-directory actual.info-deleted-symlink-directory
"
test_expect_success 'info --url symlink-directory (deleted)' '
@@ -346,9 +327,10 @@ test_expect_success 'info --url unknown-directory' '
'
test_expect_success 'info unknown-symlink-file' "
- cd gitwc &&
- ln -s unknown-file unknown-symlink-file &&
- cd .. &&
+ (
+ cd gitwc &&
+ ln -s unknown-file unknown-symlink-file
+ ) &&
(cd gitwc; test_must_fail git svn info unknown-symlink-file) \
2> actual.info-unknown-symlink-file &&
grep unknown-symlink-file actual.info-unknown-symlink-file
@@ -361,9 +343,10 @@ test_expect_success 'info --url unknown-symlink-file' '
'
test_expect_success 'info unknown-symlink-directory' "
- cd gitwc &&
- ln -s unknown-directory unknown-symlink-directory &&
- cd .. &&
+ (
+ cd gitwc &&
+ ln -s unknown-directory unknown-symlink-directory
+ ) &&
(cd gitwc; test_must_fail git svn info unknown-symlink-directory) \
2> actual.info-unknown-symlink-directory &&
grep unknown-symlink-directory actual.info-unknown-symlink-directory
diff --git a/t/t9120-git-svn-clone-with-percent-escapes.sh b/t/t9120-git-svn-clone-with-percent-escapes.sh
index 9d9ebd5..1d92c05 100755
--- a/t/t9120-git-svn-clone-with-percent-escapes.sh
+++ b/t/t9120-git-svn-clone-with-percent-escapes.sh
@@ -20,9 +20,10 @@ test_expect_success 'setup svnrepo' '
test_expect_success 'test clone with percent escapes' '
git svn clone "$svnrepo/pr%20ject" clone &&
- cd clone &&
- git rev-parse refs/${remotes_git_svn} &&
- cd ..
+ (
+ cd clone &&
+ git rev-parse refs/${remotes_git_svn}
+ )
'
# SVN works either way, so should we...
diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
index 0455216..fd81847 100755
--- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh
+++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
@@ -8,15 +8,15 @@ test_description='git svn respects rewriteRoot during rebuild'
. ./lib-git-svn.sh
mkdir import
-cd import
+(cd import
touch foo
svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null
-cd ..
+)
rm -rf import
test_expect_success 'init, fetch and checkout repository' '
git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" &&
- git svn fetch
+ git svn fetch &&
git checkout -b mybranch ${remotes_git_svn}
'
diff --git a/t/t9124-git-svn-dcommit-auto-props.sh b/t/t9124-git-svn-dcommit-auto-props.sh
index d6b076f..aa841e1 100755
--- a/t/t9124-git-svn-dcommit-auto-props.sh
+++ b/t/t9124-git-svn-dcommit-auto-props.sh
@@ -24,7 +24,7 @@ test_expect_success 'initialize git svn' '
svn_cmd import -m "import for git svn" . "$svnrepo"
) &&
rm -rf import &&
- git svn init "$svnrepo"
+ git svn init "$svnrepo" &&
git svn fetch
'
diff --git a/t/t9125-git-svn-multi-glob-branch-names.sh b/t/t9125-git-svn-multi-glob-branch-names.sh
index c194186..096abd1 100755
--- a/t/t9125-git-svn-multi-glob-branch-names.sh
+++ b/t/t9125-git-svn-multi-glob-branch-names.sh
@@ -19,19 +19,19 @@ test_expect_success 'setup svnrepo' '
test_expect_success 'test clone with multi-glob in branch names' '
git svn clone -T trunk -b branches/*/* -t tags \
"$svnrepo/project" project &&
- cd project &&
+ (cd project &&
git rev-parse "refs/remotes/v14.1/beta" &&
- git rev-parse "refs/remotes/v14.1/gold" &&
- cd ..
+ git rev-parse "refs/remotes/v14.1/gold"
+ )
'
test_expect_success 'test dcommit to multi-globbed branch' "
- cd project &&
+ (cd project &&
git reset --hard 'refs/remotes/v14.1/gold' &&
echo hello >> foo &&
git commit -m 'hello' -- foo &&
- git svn dcommit &&
- cd ..
+ git svn dcommit
+ )
"
test_done
diff --git a/t/t9127-git-svn-partial-rebuild.sh b/t/t9127-git-svn-partial-rebuild.sh
index 4aab8ec..2e4789d 100755
--- a/t/t9127-git-svn-partial-rebuild.sh
+++ b/t/t9127-git-svn-partial-rebuild.sh
@@ -9,27 +9,27 @@ test_description='git svn partial-rebuild tests'
test_expect_success 'initialize svnrepo' '
mkdir import &&
(
- cd import &&
+ (cd import &&
mkdir trunk branches tags &&
- cd trunk &&
- echo foo > foo &&
- cd .. &&
+ (cd trunk &&
+ echo foo > foo
+ ) &&
svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null &&
svn_cmd copy "$svnrepo"/trunk "$svnrepo"/branches/a \
- -m "created branch a" &&
- cd .. &&
+ -m "created branch a"
+ ) &&
rm -rf import &&
svn_cmd co "$svnrepo"/trunk trunk &&
- cd trunk &&
+ (cd trunk &&
echo bar >> foo &&
- svn_cmd ci -m "updated trunk" &&
- cd .. &&
+ svn_cmd ci -m "updated trunk"
+ ) &&
svn_cmd co "$svnrepo"/branches/a a &&
- cd a &&
+ (cd a &&
echo baz >> a &&
svn_cmd add a &&
- svn_cmd ci -m "updated a" &&
- cd .. &&
+ svn_cmd ci -m "updated a"
+ ) &&
git svn init --stdlayout "$svnrepo"
)
'
@@ -41,11 +41,11 @@ test_expect_success 'import an early SVN revision into git' '
test_expect_success 'make full git mirror of SVN' '
mkdir mirror &&
(
- cd mirror &&
+ (cd mirror &&
git init &&
git svn init --stdlayout "$svnrepo" &&
- git svn fetch &&
- cd ..
+ git svn fetch
+ )
)
'
diff --git a/t/t9128-git-svn-cmd-branch.sh b/t/t9128-git-svn-cmd-branch.sh
index 807e494..4b034a6 100755
--- a/t/t9128-git-svn-cmd-branch.sh
+++ b/t/t9128-git-svn-cmd-branch.sh
@@ -9,19 +9,19 @@ test_description='git svn partial-rebuild tests'
test_expect_success 'initialize svnrepo' '
mkdir import &&
(
- cd import &&
+ (cd import &&
mkdir trunk branches tags &&
- cd trunk &&
- echo foo > foo &&
- cd .. &&
- svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null &&
- cd .. &&
+ (cd trunk &&
+ echo foo > foo
+ ) &&
+ svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null
+ ) &&
rm -rf import &&
svn_cmd co "$svnrepo"/trunk trunk &&
- cd trunk &&
+ (cd trunk &&
echo bar >> foo &&
- svn_cmd ci -m "updated trunk" &&
- cd .. &&
+ svn_cmd ci -m "updated trunk"
+ ) &&
rm -rf trunk
)
'
diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh
index 1e9a2eb..8cfdfe7 100755
--- a/t/t9129-git-svn-i18n-commitencoding.sh
+++ b/t/t9129-git-svn-i18n-commitencoding.sh
@@ -23,7 +23,7 @@ if test -n "$a_utf8_locale"
then
test_set_prereq UTF8
else
- say "UTF-8 locale not available, some tests are skipped"
+ say "# UTF-8 locale not available, some tests are skipped"
fi
compare_svn_head_with () {
diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh
index 134411e..b324c49 100755
--- a/t/t9130-git-svn-authors-file.sh
+++ b/t/t9130-git-svn-authors-file.sh
@@ -20,7 +20,7 @@ test_expect_success 'setup svnrepo' '
'
test_expect_success 'start import with incomplete authors file' '
- ! git svn clone --authors-file=svn-authors "$svnrepo" x
+ test_must_fail git svn clone --authors-file=svn-authors "$svnrepo" x
'
test_expect_success 'imported 2 revisions successfully' '
@@ -63,7 +63,7 @@ test_expect_success 'authors-file against globs' '
'
test_expect_success 'fetch fails on ee' '
- ( cd aa-work && ! git svn fetch --authors-file=../svn-authors )
+ ( cd aa-work && test_must_fail git svn fetch --authors-file=../svn-authors )
'
tmp_config_get () {
@@ -95,10 +95,7 @@ test_expect_success 'fresh clone with svn.authors-file in config' '
(
rm -r "$GIT_DIR" &&
test x = x"$(git config svn.authorsfile)" &&
- HOME="`pwd`" &&
- export HOME &&
test_config="$HOME"/.gitconfig &&
- unset GIT_CONFIG_NOGLOBAL &&
unset GIT_DIR &&
unset GIT_CONFIG &&
git config --global \
diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh
index 9a24a65..f762038 100755
--- a/t/t9131-git-svn-empty-symlink.sh
+++ b/t/t9131-git-svn-empty-symlink.sh
@@ -88,7 +88,7 @@ test_expect_success 'enable broken symlink workaround' \
test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
test_expect_success 'get "bar" => symlink fix from svn' \
'(cd x && git svn rebase)'
-test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -L x/bar'
+test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar'
test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y'
diff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh
index 636ca0a..d60da63 100755
--- a/t/t9137-git-svn-dcommit-clobber-series.sh
+++ b/t/t9137-git-svn-dcommit-clobber-series.sh
@@ -6,10 +6,10 @@ test_description='git svn dcommit clobber series'
test_expect_success 'initialize repo' '
mkdir import &&
- cd import &&
+ (cd import &&
awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file
- svn_cmd import -m "initial" . "$svnrepo" &&
- cd .. &&
+ svn_cmd import -m "initial" . "$svnrepo"
+ ) &&
git svn init "$svnrepo" &&
git svn fetch &&
test -e file
@@ -19,14 +19,14 @@ test_expect_success '(supposedly) non-conflicting change from SVN' '
test x"`sed -n -e 58p < file`" = x58 &&
test x"`sed -n -e 61p < file`" = x61 &&
svn_cmd co "$svnrepo" tmp &&
- cd tmp &&
+ (cd tmp &&
perl -i.bak -p -e "s/^58$/5588/" file &&
perl -i.bak -p -e "s/^61$/6611/" file &&
poke file &&
test x"`sed -n -e 58p < file`" = x5588 &&
test x"`sed -n -e 61p < file`" = x6611 &&
- svn_cmd commit -m "58 => 5588, 61 => 6611" &&
- cd ..
+ svn_cmd commit -m "58 => 5588, 61 => 6611"
+ )
'
test_expect_success 'some unrelated changes to git' "
diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh
index f337959..22d80b0 100755
--- a/t/t9139-git-svn-non-utf8-commitencoding.sh
+++ b/t/t9139-git-svn-non-utf8-commitencoding.sh
@@ -39,7 +39,7 @@ do
(
cd $H &&
git config --unset i18n.commitencoding &&
- ! git svn dcommit
+ test_must_fail git svn dcommit
)
'
done
diff --git a/t/t9140-git-svn-reset.sh b/t/t9140-git-svn-reset.sh
index 0735526..e855904 100755
--- a/t/t9140-git-svn-reset.sh
+++ b/t/t9140-git-svn-reset.sh
@@ -41,7 +41,7 @@ test_expect_success 'modify hidden file in SVN repo' '
test_expect_success 'fetch fails on modified hidden file' '
( cd g &&
git svn find-rev refs/remotes/git-svn > ../expect &&
- ! git svn fetch 2> ../errors &&
+ test_must_fail git svn fetch 2> ../errors &&
git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
fgrep "not found in commit" errors &&
test_cmp expect expect2
diff --git a/t/t9142-git-svn-shallow-clone.sh b/t/t9142-git-svn-shallow-clone.sh
index 1236acc..e21ee5f 100755
--- a/t/t9142-git-svn-shallow-clone.sh
+++ b/t/t9142-git-svn-shallow-clone.sh
@@ -17,11 +17,10 @@ test_expect_success 'setup test repository' '
> foo &&
svn_cmd add foo &&
svn_cmd commit -m "add foo"
- )
+ ) &&
+ start_httpd
'
-start_httpd
-
test_expect_success 'clone trunk with "-r HEAD"' '
git svn clone -r HEAD "$svnrepo/trunk" g &&
( cd g && git rev-parse --symbolic --verify HEAD )
diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh
index 99f69c6..4594e1a 100755
--- a/t/t9143-git-svn-gc.sh
+++ b/t/t9143-git-svn-gc.sh
@@ -37,13 +37,11 @@ test_expect_success 'git svn gc runs' 'git svn gc'
test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index'
-if perl -MCompress::Zlib -e 0 2>/dev/null
+if test -r .git/svn/refs/remotes/git-svn/unhandled.log.gz
then
test_expect_success 'git svn gc produces a valid gzip file' '
gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz
'
-else
- say "Perl Compress::Zlib unavailable, skipping gunzip test"
fi
test_expect_success 'git svn gc does not change unhandled.log files' '
diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh
index 565365c..6d3130e 100755
--- a/t/t9146-git-svn-empty-dirs.sh
+++ b/t/t9146-git-svn-empty-dirs.sh
@@ -28,12 +28,29 @@ test_expect_success 'empty directories exist' '
)
'
+test_expect_success 'option automkdirs set to false' '
+ (
+ git svn init "$svnrepo" cloned-no-mkdirs &&
+ cd cloned-no-mkdirs &&
+ git config svn-remote.svn.automkdirs false &&
+ git svn fetch &&
+ for i in a b c d d/e d/e/f "weird file name"
+ do
+ if test -d "$i"
+ then
+ echo >&2 "$i exists"
+ exit 1
+ fi
+ done
+ )
+'
+
test_expect_success 'more emptiness' '
svn_cmd mkdir -m "bang bang" "$svnrepo"/"! !"
'
test_expect_success 'git svn rebase creates empty directory' '
- ( cd cloned && git svn rebase )
+ ( cd cloned && git svn rebase ) &&
test -d cloned/"! !"
'
diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh
index 250c651..4f6c06e 100755
--- a/t/t9151-svn-mergeinfo.sh
+++ b/t/t9151-svn-mergeinfo.sh
@@ -18,39 +18,39 @@ test_expect_success 'load svn dump' "
test_expect_success 'all svn merges became git merge commits' '
unmarked=$(git rev-list --parents --all --grep=Merge |
- grep -v " .* " | cut -f1 -d" ")
+ grep -v " .* " | cut -f1 -d" ") &&
[ -z "$unmarked" ]
'
test_expect_success 'cherry picks did not become git merge commits' '
bad_cherries=$(git rev-list --parents --all --grep=Cherry |
- grep " .* " | cut -f1 -d" ")
+ grep " .* " | cut -f1 -d" ") &&
[ -z "$bad_cherries" ]
'
test_expect_success 'svn non-merge merge commits did not become git merge commits' '
bad_non_merges=$(git rev-list --parents --all --grep=non-merge |
- grep " .* " | cut -f1 -d" ")
+ grep " .* " | cut -f1 -d" ") &&
[ -z "$bad_non_merges" ]
'
test_expect_success 'commit made to merged branch is reachable from the merge' '
- before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2")
- merge_commit=$(git rev-list --all --grep="Merge trunk to b2")
- not_reachable=$(git rev-list -1 $before_commit --not $merge_commit)
+ before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2") &&
+ merge_commit=$(git rev-list --all --grep="Merge trunk to b2") &&
+ not_reachable=$(git rev-list -1 $before_commit --not $merge_commit) &&
[ -z "$not_reachable" ]
'
test_expect_success 'merging two branches in one commit is detected correctly' '
- f1_commit=$(git rev-list --all --grep="make f1 branch from trunk")
- f2_commit=$(git rev-list --all --grep="make f2 branch from trunk")
- merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk")
- not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit)
+ f1_commit=$(git rev-list --all --grep="make f1 branch from trunk") &&
+ f2_commit=$(git rev-list --all --grep="make f2 branch from trunk") &&
+ merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk") &&
+ not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit) &&
[ -z "$not_reachable" ]
'
test_expect_failure 'everything got merged in the end' '
- unmerged=$(git rev-list --all --not master)
+ unmerged=$(git rev-list --all --not master) &&
[ -z "$unmerged" ]
'
diff --git a/t/t9155-git-svn-fetch-deleted-tag.sh b/t/t9155-git-svn-fetch-deleted-tag.sh
new file mode 100755
index 0000000..a486a98
--- /dev/null
+++ b/t/t9155-git-svn-fetch-deleted-tag.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='git svn fetch deleted tag'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repo' '
+ mkdir -p import/trunk/subdir &&
+ mkdir -p import/branches &&
+ mkdir -p import/tags &&
+ echo "base" >import/trunk/subdir/file &&
+ svn_cmd import -m "import for git svn" import "$svnrepo" &&
+ rm -rf import &&
+
+ svn_cmd mkdir -m "create mybranch directory" "$svnrepo/branches/mybranch" &&
+ svn_cmd cp -m "create branch mybranch" "$svnrepo/trunk" "$svnrepo/branches/mybranch/trunk" &&
+
+ svn_cmd co "$svnrepo/trunk" svn_project &&
+ (cd svn_project &&
+ echo "trunk change" >>subdir/file &&
+ svn_cmd ci -m "trunk change" subdir/file &&
+
+ svn_cmd switch "$svnrepo/branches/mybranch/trunk" &&
+ echo "branch change" >>subdir/file &&
+ svn_cmd ci -m "branch change" subdir/file
+ ) &&
+
+ svn_cmd cp -m "create mytag attempt 1" -r5 "$svnrepo/trunk/subdir" "$svnrepo/tags/mytag" &&
+ svn_cmd rm -m "delete mytag attempt 1" "$svnrepo/tags/mytag" &&
+ svn_cmd cp -m "create mytag attempt 2" -r5 "$svnrepo/branches/mybranch/trunk/subdir" "$svnrepo/tags/mytag"
+'
+
+test_expect_success 'fetch deleted tags from same revision with checksum error' '
+ git svn init --stdlayout "$svnrepo" git_project &&
+ cd git_project &&
+ git svn fetch &&
+
+ git diff --exit-code mybranch:trunk/subdir/file tags/mytag:file &&
+ git diff --exit-code master:subdir/file tags/mytag^:file
+'
+
+test_done
diff --git a/t/t9156-git-svn-fetch-deleted-tag-2.sh b/t/t9156-git-svn-fetch-deleted-tag-2.sh
new file mode 100755
index 0000000..5ce7e2f
--- /dev/null
+++ b/t/t9156-git-svn-fetch-deleted-tag-2.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+test_description='git svn fetch deleted tag 2'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repo' '
+ mkdir -p import/branches &&
+ mkdir -p import/tags &&
+ mkdir -p import/trunk/subdir1 &&
+ mkdir -p import/trunk/subdir2 &&
+ mkdir -p import/trunk/subdir3 &&
+ echo "file1" >import/trunk/subdir1/file &&
+ echo "file2" >import/trunk/subdir2/file &&
+ echo "file3" >import/trunk/subdir3/file &&
+ svn_cmd import -m "import for git svn" import "$svnrepo" &&
+ rm -rf import &&
+
+ svn_cmd co "$svnrepo/trunk" svn_project &&
+ (cd svn_project &&
+ echo "change1" >>subdir1/file &&
+ echo "change2" >>subdir2/file &&
+ echo "change3" >>subdir3/file &&
+ svn_cmd ci -m "change" .
+ ) &&
+
+ svn_cmd cp -m "create mytag 1" -r2 "$svnrepo/trunk/subdir1" "$svnrepo/tags/mytag" &&
+ svn_cmd rm -m "delete mytag 1" "$svnrepo/tags/mytag" &&
+ svn_cmd cp -m "create mytag 2" -r2 "$svnrepo/trunk/subdir2" "$svnrepo/tags/mytag" &&
+ svn_cmd rm -m "delete mytag 2" "$svnrepo/tags/mytag" &&
+ svn_cmd cp -m "create mytag 3" -r2 "$svnrepo/trunk/subdir3" "$svnrepo/tags/mytag"
+'
+
+test_expect_success 'fetch deleted tags from same revision with no checksum error' '
+ git svn init --stdlayout "$svnrepo" git_project &&
+ cd git_project &&
+ git svn fetch &&
+
+ git diff --exit-code master:subdir3/file tags/mytag:file &&
+ git diff --exit-code master:subdir2/file tags/mytag^:file &&
+ git diff --exit-code master:subdir1/file tags/mytag^^:file
+'
+
+test_done
diff --git a/t/t9157-git-svn-fetch-merge.sh b/t/t9157-git-svn-fetch-merge.sh
new file mode 100755
index 0000000..991d2aa
--- /dev/null
+++ b/t/t9157-git-svn-fetch-merge.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Steven Walter
+#
+
+test_description='git svn merge detection'
+. ./lib-git-svn.sh
+
+svn_ver="$(svn --version --quiet)"
+case $svn_ver in
+0.* | 1.[0-4].*)
+ skip_all="skipping git-svn test - SVN too old ($svn_ver)"
+ test_done
+ ;;
+esac
+
+test_expect_success 'initialize source svn repo' '
+ svn_cmd mkdir -m x "$svnrepo"/trunk &&
+ svn_cmd mkdir -m x "$svnrepo"/branches &&
+ svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+ (
+ cd "$SVN_TREE" &&
+ touch foo &&
+ svn add foo &&
+ svn commit -m "initial commit" &&
+ svn cp -m branch "$svnrepo"/trunk "$svnrepo"/branches/branch1 &&
+ touch bar &&
+ svn add bar &&
+ svn commit -m x &&
+ svn cp -m branch "$svnrepo"/trunk "$svnrepo"/branches/branch2 &&
+ svn switch "$svnrepo"/branches/branch1 &&
+ touch baz &&
+ svn add baz &&
+ svn commit -m x &&
+ svn switch "$svnrepo"/trunk &&
+ svn merge "$svnrepo"/branches/branch1 &&
+ svn commit -m "merge" &&
+ svn switch "$svnrepo"/branches/branch1 &&
+ svn commit -m x &&
+ svn switch "$svnrepo"/branches/branch2 &&
+ svn merge "$svnrepo"/branches/branch1 &&
+ svn commit -m "merge branch1" &&
+ svn switch "$svnrepo"/trunk &&
+ svn merge "$svnrepo"/branches/branch2 &&
+ svn resolved baz &&
+ svn commit -m "merge branch2"
+ ) &&
+ rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo' '
+ git svn init -s "$svnrepo" &&
+ git svn fetch
+'
+
+test_expect_success 'verify merge commit' 'git rev-parse HEAD^2'
+
+test_done
diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh
new file mode 100755
index 0000000..3ab4390
--- /dev/null
+++ b/t/t9158-git-svn-mergeinfo.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Steven Walter
+#
+
+test_description='git svn mergeinfo propagation'
+
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+
+test_expect_success 'initialize source svn repo' '
+ svn_cmd mkdir -m x "$svnrepo"/trunk &&
+ svn_cmd co "$svnrepo"/trunk "$SVN_TREE" &&
+ (
+ cd "$SVN_TREE" &&
+ touch foo &&
+ svn_cmd add foo &&
+ svn_cmd commit -m "initial commit"
+ ) &&
+ rm -rf "$SVN_TREE"
+'
+
+test_expect_success 'clone svn repo' '
+ git svn init "$svnrepo"/trunk &&
+ git svn fetch
+'
+
+test_expect_success 'change svn:mergeinfo' '
+ touch bar &&
+ git add bar &&
+ git commit -m "bar" &&
+ git svn dcommit --mergeinfo="/branches/foo:1-10"
+'
+
+test_expect_success 'verify svn:mergeinfo' '
+ mergeinfo=$(svn_cmd propget svn:mergeinfo "$svnrepo"/trunk)
+ test "$mergeinfo" = "/branches/foo:1-10"
+'
+
+test_done
diff --git a/t/t9159-git-svn-no-parent-mergeinfo.sh b/t/t9159-git-svn-no-parent-mergeinfo.sh
new file mode 100755
index 0000000..85120b7
--- /dev/null
+++ b/t/t9159-git-svn-no-parent-mergeinfo.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+test_description='git svn handling of root commits in merge ranges'
+. ./lib-git-svn.sh
+
+test_expect_success 'test handling of root commits in merge ranges' '
+ mkdir -p init/trunk init/branches init/tags &&
+ echo "r1" > init/trunk/file.txt &&
+ svn_cmd import -m "initial import" init "$svnrepo" &&
+ svn_cmd co "$svnrepo" tmp &&
+ (
+ cd tmp &&
+ echo "r2" > trunk/file.txt &&
+ svn_cmd commit -m "Modify file.txt on trunk" &&
+ svn_cmd cp trunk@1 branches/a &&
+ svn_cmd commit -m "Create branch a from trunk r1" &&
+ svn_cmd propset svn:mergeinfo /trunk:1-2 branches/a &&
+ svn_cmd commit -m "Fake merge of trunk r2 into branch a" &&
+ mkdir branches/b &&
+ echo "r5" > branches/b/file2.txt &&
+ svn_cmd add branches/b &&
+ svn_cmd commit -m "Create branch b from thin air" &&
+ echo "r6" > branches/b/file2.txt &&
+ svn_cmd commit -m "Modify file2.txt on branch b" &&
+ svn_cmd cp branches/b@5 branches/c &&
+ svn_cmd commit -m "Create branch c from branch b r5" &&
+ svn_cmd propset svn:mergeinfo /branches/b:5-6 branches/c &&
+ svn_cmd commit -m "Fake merge of branch b r6 into branch c"
+ ) &&
+ git svn init -s "$svnrepo" &&
+ git svn fetch
+ '
+
+test_done
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 61bcb8f..e5da65b 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -5,16 +5,17 @@
test_description='Test export of commits to CVS'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
if ! test_have_prereq PERL; then
- say 'skipping git cvsexportcommit tests, perl not available'
+ skip_all='skipping git cvsexportcommit tests, perl not available'
test_done
fi
cvs >/dev/null 2>&1
if test $? -ne 1
then
- say 'skipping git cvsexportcommit tests, cvs not found'
+ skip_all='skipping git cvsexportcommit tests, cvs not found'
test_done
fi
@@ -229,11 +230,6 @@ test_expect_success \
test_must_fail git cvsexportcommit -c $id
)'
-if ! test "$(git config --bool core.filemode)" = false
-then
- test_set_prereq FILEMODE
-fi
-
test_expect_success FILEMODE \
'Retain execute bit' \
'mkdir G &&
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 131f032..2a53640 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -7,6 +7,23 @@ test_description='test git fast-import utility'
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
+# Print $1 bytes from stdin to stdout.
+#
+# This could be written as "head -c $1", but IRIX "head" does not
+# support the -c option.
+head_c () {
+ perl -e '
+ my $len = $ARGV[1];
+ while ($len > 0) {
+ my $s;
+ my $nread = sysread(STDIN, $s, $len);
+ die "cannot read: $!" unless defined($nread);
+ print $s;
+ $len -= $nread;
+ }
+ ' - "$1"
+}
+
file2_data='file2
second line of EOF'
@@ -23,11 +40,26 @@ file5_data='an inline file.
file6_data='#!/bin/sh
echo "$@"'
+>empty
+
+test_expect_success 'setup: have pipes?' '
+ rm -f frob &&
+ if mkfifo frob
+ then
+ test_set_prereq PIPE
+ fi
+'
+
###
### series A
###
test_tick
+
+test_expect_success 'empty stream succeeds' '
+ git fast-import </dev/null
+'
+
cat >input <<INPUT_END
blob
mark :2
@@ -166,6 +198,63 @@ test_expect_success \
test `git rev-parse --verify master:file2` \
= `git rev-parse --verify verify--import-marks:copy-of-file2`'
+test_tick
+mt=$(git hash-object --stdin < /dev/null)
+: >input.blob
+: >marks.exp
+: >tree.exp
+
+cat >input.commit <<EOF
+commit refs/heads/verify--dump-marks
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+test the sparse array dumping routines with exponentially growing marks
+COMMIT
+EOF
+
+i=0
+l=4
+m=6
+n=7
+while test "$i" -lt 27; do
+ cat >>input.blob <<EOF
+blob
+mark :$l
+data 0
+blob
+mark :$m
+data 0
+blob
+mark :$n
+data 0
+EOF
+ echo "M 100644 :$l l$i" >>input.commit
+ echo "M 100644 :$m m$i" >>input.commit
+ echo "M 100644 :$n n$i" >>input.commit
+
+ echo ":$l $mt" >>marks.exp
+ echo ":$m $mt" >>marks.exp
+ echo ":$n $mt" >>marks.exp
+
+ printf "100644 blob $mt\tl$i\n" >>tree.exp
+ printf "100644 blob $mt\tm$i\n" >>tree.exp
+ printf "100644 blob $mt\tn$i\n" >>tree.exp
+
+ l=$(($l + $l))
+ m=$(($m + $m))
+ n=$(($l + $n))
+
+ i=$((1 + $i))
+done
+
+sort tree.exp > tree.exp_s
+
+test_expect_success 'A: export marks with large values' '
+ cat input.blob input.commit | git fast-import --export-marks=marks.large &&
+ git ls-tree refs/heads/verify--dump-marks >tree.out &&
+ test_cmp tree.exp_s tree.out &&
+ test_cmp marks.exp marks.large'
+
###
### series B
###
@@ -264,7 +353,7 @@ test_expect_success \
'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
test_expect_success \
'C: validate reuse existing blob' \
- 'test $newf = `git rev-parse --verify branch:file2/newf`
+ 'test $newf = `git rev-parse --verify branch:file2/newf` &&
test $oldf = `git rev-parse --verify branch:file2/oldf`'
cat >expect <<EOF
@@ -796,6 +885,302 @@ test_expect_success \
'git fast-import <input &&
test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`'
+test_expect_success \
+ 'N: copy directory by id' \
+ 'cat >expect <<-\EOF &&
+ :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf
+ :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf
+ EOF
+ subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N4
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy by tree hash
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $subdir file3
+ INPUT_END
+ git fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
+ compare_diff_raw expect actual'
+
+test_expect_success PIPE 'N: read and copy directory' '
+ cat >expect <<-\EOF
+ :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf
+ :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf
+ EOF
+ git update-ref -d refs/heads/N4 &&
+ rm -f backflow &&
+ mkfifo backflow &&
+ (
+ exec <backflow &&
+ cat <<-EOF &&
+ commit refs/heads/N4
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy by tree hash, part 2
+ COMMIT
+
+ from refs/heads/branch^0
+ ls "file2"
+ EOF
+ read mode type tree filename &&
+ echo "M 040000 $tree file3"
+ ) |
+ git fast-import --cat-blob-fd=3 3>backflow &&
+ git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
+ compare_diff_raw expect actual
+'
+
+test_expect_success PIPE 'N: empty directory reads as missing' '
+ cat <<-\EOF >expect &&
+ OBJNAME
+ :000000 100644 OBJNAME OBJNAME A unrelated
+ EOF
+ echo "missing src" >expect.response &&
+ git update-ref -d refs/heads/read-empty &&
+ rm -f backflow &&
+ mkfifo backflow &&
+ (
+ exec <backflow &&
+ cat <<-EOF &&
+ commit refs/heads/read-empty
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ read "empty" (missing) directory
+ COMMIT
+
+ M 100644 inline src/greeting
+ data <<BLOB
+ hello
+ BLOB
+ C src/greeting dst1/non-greeting
+ C src/greeting unrelated
+ # leave behind "empty" src directory
+ D src/greeting
+ ls "src"
+ EOF
+ read -r line &&
+ printf "%s\n" "$line" >response &&
+ cat <<-\EOF
+ D dst1
+ D dst2
+ EOF
+ ) |
+ git fast-import --cat-blob-fd=3 3>backflow &&
+ test_cmp expect.response response &&
+ git rev-list read-empty |
+ git diff-tree -r --root --stdin |
+ sed "s/$_x40/OBJNAME/g" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success \
+ 'N: copy root directory by tree hash' \
+ 'cat >expect <<-\EOF &&
+ :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file3/newf
+ :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file3/oldf
+ EOF
+ root=$(git rev-parse refs/heads/branch^0^{tree}) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N6
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy root directory by tree hash
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $root ""
+ INPUT_END
+ git fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N4 N6 >actual &&
+ compare_diff_raw expect actual'
+
+test_expect_success \
+ 'N: delete directory by copying' \
+ 'cat >expect <<-\EOF &&
+ OBJID
+ :100644 000000 OBJID OBJID D foo/bar/qux
+ OBJID
+ :000000 100644 OBJID OBJID A foo/bar/baz
+ :000000 100644 OBJID OBJID A foo/bar/qux
+ EOF
+ empty_tree=$(git mktree </dev/null) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N-delete
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ collect data to be deleted
+ COMMIT
+
+ deleteall
+ M 100644 inline foo/bar/baz
+ data <<DATA_END
+ hello
+ DATA_END
+ C "foo/bar/baz" "foo/bar/qux"
+ C "foo/bar/baz" "foo/bar/quux/1"
+ C "foo/bar/baz" "foo/bar/quuux"
+ M 040000 $empty_tree foo/bar/quux
+ M 040000 $empty_tree foo/bar/quuux
+
+ commit refs/heads/N-delete
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ delete subdirectory
+ COMMIT
+
+ M 040000 $empty_tree foo/bar/qux
+ INPUT_END
+ git fast-import <input &&
+ git rev-list N-delete |
+ git diff-tree -r --stdin --root --always |
+ sed -e "s/$_x40/OBJID/g" >actual &&
+ test_cmp expect actual'
+
+test_expect_success \
+ 'N: modify copied tree' \
+ 'cat >expect <<-\EOF &&
+ :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5
+ :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf
+ :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf
+ EOF
+ subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N5
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy by tree hash
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $subdir file3
+
+ commit refs/heads/N5
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ modify directory copy
+ COMMIT
+
+ M 644 inline file3/file5
+ data <<EOF
+ $file5_data
+ EOF
+ INPUT_END
+ git fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
+ compare_diff_raw expect actual'
+
+test_expect_success \
+ 'N: reject foo/ syntax' \
+ 'subdir=$(git rev-parse refs/heads/branch^0:file2) &&
+ test_must_fail git fast-import <<-INPUT_END
+ commit refs/heads/N5B
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy with invalid syntax
+ COMMIT
+
+ from refs/heads/branch^0
+ M 040000 $subdir file3/
+ INPUT_END'
+
+test_expect_success \
+ 'N: copy to root by id and modify' \
+ 'echo "hello, world" >expect.foo &&
+ echo hello >expect.bar &&
+ git fast-import <<-SETUP_END &&
+ commit refs/heads/N7
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ hello, tree
+ COMMIT
+
+ deleteall
+ M 644 inline foo/bar
+ data <<EOF
+ hello
+ EOF
+ SETUP_END
+
+ tree=$(git rev-parse --verify N7:) &&
+ git fast-import <<-INPUT_END &&
+ commit refs/heads/N8
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy to root by id and modify
+ COMMIT
+
+ M 040000 $tree ""
+ M 644 inline foo/foo
+ data <<EOF
+ hello, world
+ EOF
+ INPUT_END
+ git show N8:foo/foo >actual.foo &&
+ git show N8:foo/bar >actual.bar &&
+ test_cmp expect.foo actual.foo &&
+ test_cmp expect.bar actual.bar'
+
+test_expect_success \
+ 'N: extract subtree' \
+ 'branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
+ cat >input <<-INPUT_END &&
+ commit refs/heads/N9
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ extract subtree branch:newdir
+ COMMIT
+
+ M 040000 $branch ""
+ C "newdir" ""
+ INPUT_END
+ git fast-import <input &&
+ git diff --exit-code branch:newdir N9'
+
+test_expect_success \
+ 'N: modify subtree, extract it, and modify again' \
+ 'echo hello >expect.baz &&
+ echo hello, world >expect.qux &&
+ git fast-import <<-SETUP_END &&
+ commit refs/heads/N10
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ hello, tree
+ COMMIT
+
+ deleteall
+ M 644 inline foo/bar/baz
+ data <<EOF
+ hello
+ EOF
+ SETUP_END
+
+ tree=$(git rev-parse --verify N10:) &&
+ git fast-import <<-INPUT_END &&
+ commit refs/heads/N11
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy to root by id and modify
+ COMMIT
+
+ M 040000 $tree ""
+ M 100644 inline foo/bar/qux
+ data <<EOF
+ hello, world
+ EOF
+ R "foo" ""
+ C "bar/qux" "bar/quux"
+ INPUT_END
+ git show N11:bar/baz >actual.baz &&
+ git show N11:bar/qux >actual.qux &&
+ git show N11:bar/quux >actual.quux &&
+ test_cmp expect.baz actual.baz &&
+ test_cmp expect.qux actual.qux &&
+ test_cmp expect.qux actual.quux'
+
###
### series O
###
@@ -999,11 +1384,10 @@ test_expect_success \
'P: supermodule & submodule mix' \
'git fast-import <input &&
git checkout subuse1 &&
- rm -rf sub && mkdir sub && cd sub &&
+ rm -rf sub && mkdir sub && (cd sub &&
git init &&
git fetch --update-head-ok .. refs/heads/sub:refs/heads/master &&
- git checkout master &&
- cd .. &&
+ git checkout master) &&
git submodule init &&
git submodule update'
@@ -1443,6 +1827,61 @@ test_expect_success \
'cat input | git fast-import --export-marks=other.marks &&
grep :1 other.marks'
+test_expect_success 'R: catch typo in marks file name' '
+ test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
+ echo "feature import-marks=nonexistent.marks" |
+ test_must_fail git fast-import
+'
+
+test_expect_success 'R: import and output marks can be the same file' '
+ rm -f io.marks &&
+ blob=$(echo hi | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ :1 $blob
+ :2 $blob
+ EOF
+ git fast-import --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+ git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :2
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks
+'
+
+test_expect_success 'R: --import-marks=foo --output-marks=foo to create foo fails' '
+ rm -f io.marks &&
+ test_must_fail git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+'
+
+test_expect_success 'R: --import-marks-if-exists' '
+ rm -f io.marks &&
+ blob=$(echo hi | git hash-object --stdin) &&
+ echo ":1 $blob" >expect &&
+ git fast-import --import-marks-if-exists=io.marks --export-marks=io.marks <<-\EOF &&
+ blob
+ mark :1
+ data 3
+ hi
+
+ EOF
+ test_cmp expect io.marks
+'
+
cat >input << EOF
feature import-marks=marks.out
feature export-marks=marks.new
@@ -1454,7 +1893,7 @@ test_expect_success \
test_cmp marks.out marks.new'
cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
feature export-marks=marks.new
EOF
@@ -1465,7 +1904,7 @@ test_expect_success \
cat >input <<EOF
-feature import-marks=nonexistant.marks
+feature import-marks=nonexistent.marks
feature export-marks=combined.marks
EOF
@@ -1501,6 +1940,250 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
test_cmp marks.new non-relative.out
'
+test_expect_success 'R: feature ls supported' '
+ echo "feature ls" |
+ git fast-import
+'
+
+test_expect_success 'R: feature cat-blob supported' '
+ echo "feature cat-blob" |
+ git fast-import
+'
+
+test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
+ test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
+'
+
+test_expect_success 'R: print old blob' '
+ blob=$(echo "yes it can" | git hash-object -w --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 11
+ yes it can
+
+ EOF
+ echo "cat-blob $blob" |
+ git fast-import --cat-blob-fd=6 6>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'R: in-stream cat-blob-fd not respected' '
+ echo hello >greeting &&
+ blob=$(git hash-object -w greeting) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 6
+ hello
+
+ EOF
+ git fast-import --cat-blob-fd=3 3>actual.3 >actual.1 <<-EOF &&
+ cat-blob $blob
+ EOF
+ test_cmp expect actual.3 &&
+ test_cmp empty actual.1 &&
+ git fast-import 3>actual.3 >actual.1 <<-EOF &&
+ option cat-blob-fd=3
+ cat-blob $blob
+ EOF
+ test_cmp empty actual.3 &&
+ test_cmp expect actual.1
+'
+
+test_expect_success 'R: print new blob' '
+ blob=$(echo "yep yep yep" | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 12
+ yep yep yep
+
+ EOF
+ git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
+ blob
+ mark :1
+ data <<BLOB_END
+ yep yep yep
+ BLOB_END
+ cat-blob :1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'R: print new blob by sha1' '
+ blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
+ cat >expect <<-EOF &&
+ ${blob} blob 25
+ a new blob named by sha1
+
+ EOF
+ git fast-import --cat-blob-fd=6 6>actual <<-EOF &&
+ blob
+ data <<BLOB_END
+ a new blob named by sha1
+ BLOB_END
+ cat-blob $blob
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup: big file' '
+ (
+ echo "the quick brown fox jumps over the lazy dog" >big &&
+ for i in 1 2 3
+ do
+ cat big big big big >bigger &&
+ cat bigger bigger bigger bigger >big ||
+ exit
+ done
+ )
+'
+
+test_expect_success 'R: print two blobs to stdout' '
+ blob1=$(git hash-object big) &&
+ blob1_len=$(wc -c <big) &&
+ blob2=$(echo hello | git hash-object --stdin) &&
+ {
+ echo ${blob1} blob $blob1_len &&
+ cat big &&
+ cat <<-EOF
+
+ ${blob2} blob 6
+ hello
+
+ EOF
+ } >expect &&
+ {
+ cat <<-\END_PART1 &&
+ blob
+ mark :1
+ data <<data_end
+ END_PART1
+ cat big &&
+ cat <<-\EOF
+ data_end
+ blob
+ mark :2
+ data <<data_end
+ hello
+ data_end
+ cat-blob :1
+ cat-blob :2
+ EOF
+ } |
+ git fast-import >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: copy using cat-file' '
+ expect_id=$(git hash-object big) &&
+ expect_len=$(wc -c <big) &&
+ echo $expect_id blob $expect_len >expect.response &&
+
+ rm -f blobs &&
+ cat >frontend <<-\FRONTEND_END &&
+ #!/bin/sh
+ FRONTEND_END
+
+ mkfifo blobs &&
+ (
+ export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE &&
+ cat <<-\EOF &&
+ feature cat-blob
+ blob
+ mark :1
+ data <<BLOB
+ EOF
+ cat big &&
+ cat <<-\EOF &&
+ BLOB
+ cat-blob :1
+ EOF
+
+ read blob_id type size <&3 &&
+ echo "$blob_id $type $size" >response &&
+ head_c $size >blob <&3 &&
+ read newline <&3 &&
+
+ cat <<-EOF &&
+ commit refs/heads/copied
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ copy big file as file3
+ COMMIT
+ M 644 inline file3
+ data <<BLOB
+ EOF
+ cat blob &&
+ echo BLOB
+ ) 3<blobs |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ git show copied:file3 >actual &&
+ test_cmp expect.response response &&
+ test_cmp big actual
+'
+
+test_expect_success PIPE 'R: print blob mid-commit' '
+ rm -f blobs &&
+ echo "A blob from _before_ the commit." >expect &&
+ mkfifo blobs &&
+ (
+ exec 3<blobs &&
+ cat <<-EOF &&
+ feature cat-blob
+ blob
+ mark :1
+ data <<BLOB
+ A blob from _before_ the commit.
+ BLOB
+ commit refs/heads/temporary
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ Empty commit
+ COMMIT
+ cat-blob :1
+ EOF
+
+ read blob_id type size <&3 &&
+ head_c $size >actual <&3 &&
+ read newline <&3 &&
+
+ echo
+ ) |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ test_cmp expect actual
+'
+
+test_expect_success PIPE 'R: print staged blob within commit' '
+ rm -f blobs &&
+ echo "A blob from _within_ the commit." >expect &&
+ mkfifo blobs &&
+ (
+ exec 3<blobs &&
+ cat <<-EOF &&
+ feature cat-blob
+ commit refs/heads/within
+ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+ data <<COMMIT
+ Empty commit
+ COMMIT
+ M 644 inline within
+ data <<BLOB
+ A blob from _within_ the commit.
+ BLOB
+ EOF
+
+ to_get=$(
+ echo "A blob from _within_ the commit." |
+ git hash-object --stdin
+ ) &&
+ echo "cat-blob $to_get" &&
+
+ read blob_id type size <&3 &&
+ head_c $size >actual <&3 &&
+ read newline <&3 &&
+
+ echo deleteall
+ ) |
+ git fast-import --cat-blob-fd=3 3>blobs &&
+ test_cmp expect actual
+'
+
cat >input << EOF
option git quiet
blob
@@ -1509,8 +2192,6 @@ hi
EOF
-touch empty
-
test_expect_success 'R: quiet option results in no stats being output' '
cat input | git fast-import 2> output &&
test_cmp empty output
@@ -1528,6 +2209,14 @@ test_expect_success 'R: unknown commandline options are rejected' '\
test_must_fail git fast-import --non-existing-option < /dev/null
'
+test_expect_success 'R: die on invalid option argument' '
+ echo "option git active-branches=-5" |
+ test_must_fail git fast-import &&
+ echo "option git depth=" |
+ test_must_fail git fast-import &&
+ test_must_fail git fast-import --depth="5 elephants" </dev/null
+'
+
cat >input <<EOF
option non-existing-vcs non-existing-option
EOF
diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh
index a5c99d8..463254c 100755
--- a/t/t9301-fast-import-notes.sh
+++ b/t/t9301-fast-import-notes.sh
@@ -120,6 +120,7 @@ test_expect_success 'add notes with simple M command' '
test_tick
cat >input <<INPUT_END
+feature notes
commit refs/notes/test
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT
@@ -255,13 +256,18 @@ EOF
INPUT_END
+whitespace=" "
+
cat >expect <<EXPECT_END
fourth commit
pre-prefix of note for fourth commit
+$whitespace
prefix of note for fourth commit
+$whitespace
third note for fourth commit
third commit
prefix of note for third commit
+$whitespace
third note for third commit
second commit
third note for second commit
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index d43f37c..f823c05 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -26,7 +26,7 @@ test_expect_success 'setup' '
test_tick &&
git tag rein &&
git checkout -b wer HEAD^ &&
- echo lange > file2
+ echo lange > file2 &&
test_tick &&
git commit -m sitzt file2 &&
test_tick &&
@@ -355,6 +355,20 @@ test_expect_failure 'no exact-ref revisions included' '
)
'
+test_expect_success 'path limiting with import-marks does not lose unmodified files' '
+ git checkout -b simple marks~2 &&
+ git fast-export --export-marks=marks simple -- file > /dev/null &&
+ echo more content >> file &&
+ test_tick &&
+ git commit -mnext file &&
+ git fast-export --import-marks=marks simple -- file file0 | grep file0
+'
+
+test_expect_success 'full-tree re-shows unmodified files' '
+ git checkout -f simple &&
+ test $(git fast-export --full-tree simple | grep -c file0) -eq 3
+'
+
test_expect_success 'set-up a few more tags for tag export tests' '
git checkout -f master &&
HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
@@ -376,4 +390,28 @@ test_expect_success 'tree_tag-obj' 'git fast-export tree_tag-obj'
test_expect_success 'tag-obj_tag' 'git fast-export tag-obj_tag'
test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
+test_expect_success SYMLINKS 'directory becomes symlink' '
+ git init dirtosymlink &&
+ git init result &&
+ (
+ cd dirtosymlink &&
+ mkdir foo &&
+ mkdir bar &&
+ echo hello > foo/world &&
+ echo hello > bar/world &&
+ git add foo/world bar/world &&
+ git commit -q -mone &&
+ git rm -r foo &&
+ ln -s bar foo &&
+ git add foo &&
+ git commit -q -mtwo
+ ) &&
+ (
+ cd dirtosymlink &&
+ git fast-export master -- foo |
+ (cd ../result && git fast-import --quiet)
+ ) &&
+ (cd result && git show master:foo)
+'
+
test_done
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 8639506..9199550 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -11,17 +11,17 @@ cvs CLI client via git-cvsserver server'
. ./test-lib.sh
if ! test_have_prereq PERL; then
- say 'skipping git cvsserver tests, perl not available'
+ skip_all='skipping git cvsserver tests, perl not available'
test_done
fi
cvs >/dev/null 2>&1
if test $? -ne 1
then
- say 'skipping git-cvsserver tests, cvs not found'
+ skip_all='skipping git-cvsserver tests, cvs not found'
test_done
fi
"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
- say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
+ skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done
}
@@ -57,7 +57,7 @@ test_expect_success 'setup' '
# as argument to co -d
test_expect_success 'basic checkout' \
'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master &&
- test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | head -n 1))" = "empty/1.1/"
+ test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | head -n 1))" = "empty/1.1/" &&
test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5 | sed -ne \$p))" = "secondrootfile/1.1/"'
#------------------------
diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh
index ed7b513..ff6d6fb 100755
--- a/t/t9401-git-cvsserver-crlf.sh
+++ b/t/t9401-git-cvsserver-crlf.sh
@@ -41,16 +41,16 @@ not_present() {
cvs >/dev/null 2>&1
if test $? -ne 1
then
- say 'skipping git-cvsserver tests, cvs not found'
+ skip_all='skipping git-cvsserver tests, cvs not found'
test_done
fi
if ! test_have_prereq PERL
then
- say 'skipping git-cvsserver tests, perl not available'
+ skip_all='skipping git-cvsserver tests, perl not available'
test_done
fi
"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
- say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
+ skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done
}
@@ -70,7 +70,7 @@ test_expect_success 'setup' '
mkdir subdir &&
echo "Another text file" > subdir/file.h &&
echo "Another binary: Q (this time CR)" | q_to_cr > subdir/withCr.bin &&
- echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c
+ echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c &&
echo "Unspecified" > subdir/unspecified.other &&
echo "/*.bin -crlf" > .gitattributes &&
echo "/*.c crlf" >> .gitattributes &&
@@ -129,21 +129,22 @@ test_expect_success 'cvs co (use attributes)' '
'
test_expect_success 'adding files' '
- cd cvswork/subdir &&
+ (cd cvswork &&
+ (cd subdir &&
echo "more text" > src.c &&
GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 &&
marked_as . src.c "" &&
- echo "psuedo-binary" > temp.bin &&
- cd .. &&
+ echo "psuedo-binary" > temp.bin
+ ) &&
GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 &&
marked_as subdir temp.bin "-kb" &&
cd subdir &&
GIT_CONFIG="$git_config" cvs -Q ci -m "adding files" >cvs.log 2>&1 &&
marked_as . temp.bin "-kb" &&
marked_as . src.c ""
+ )
'
-cd "$WORKDIR"
test_expect_success 'updating' '
git pull gitcvs.git &&
echo 'hi' > subdir/newfile.bin &&
@@ -153,9 +154,9 @@ test_expect_success 'updating' '
git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin &&
git commit -q -m "Add and change some files" &&
git push gitcvs.git >/dev/null &&
- cd cvswork &&
- GIT_CONFIG="$git_config" cvs -Q update &&
- cd .. &&
+ (cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q update
+ ) &&
marked_as cvswork textfile.c "" &&
marked_as cvswork binfile.bin -kb &&
marked_as cvswork .gitattributes "" &&
@@ -233,35 +234,35 @@ test_expect_success 'cvs co another copy (guess)' '
'
test_expect_success 'add text (guess)' '
- cd cvswork &&
+ (cd cvswork &&
echo "simpleText" > simpleText.c &&
- GIT_CONFIG="$git_config" cvs -Q add simpleText.c &&
- cd .. &&
+ GIT_CONFIG="$git_config" cvs -Q add simpleText.c
+ ) &&
marked_as cvswork simpleText.c ""
'
test_expect_success 'add bin (guess)' '
- cd cvswork &&
+ (cd cvswork &&
echo "simpleBin: NUL: Q <- there" | q_to_nul > simpleBin.bin &&
- GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin &&
- cd .. &&
+ GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin
+ ) &&
marked_as cvswork simpleBin.bin -kb
'
test_expect_success 'remove files (guess)' '
- cd cvswork &&
+ (cd cvswork &&
GIT_CONFIG="$git_config" cvs -Q rm -f subdir/file.h &&
- cd subdir &&
- GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin &&
- cd ../.. &&
+ (cd subdir &&
+ GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin
+ )) &&
marked_as cvswork/subdir withCr.bin -kb &&
marked_as cvswork/subdir file.h ""
'
test_expect_success 'cvs ci (guess)' '
- cd cvswork &&
- GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1 &&
- cd .. &&
+ (cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1
+ ) &&
marked_as cvswork textfile.c "" &&
marked_as cvswork binfile.bin -kb &&
marked_as cvswork .gitattributes "" &&
@@ -278,9 +279,9 @@ test_expect_success 'cvs ci (guess)' '
'
test_expect_success 'update subdir of other copy (guess)' '
- cd cvswork2/subdir &&
- GIT_CONFIG="$git_config" cvs -Q update &&
- cd ../.. &&
+ (cd cvswork2/subdir &&
+ GIT_CONFIG="$git_config" cvs -Q update
+ ) &&
marked_as cvswork2 textfile.c "" &&
marked_as cvswork2 binfile.bin -kb &&
marked_as cvswork2 .gitattributes "" &&
@@ -304,11 +305,11 @@ test_expect_success 'update/merge full other copy (guess)' '
git add multilineTxt.c &&
git commit -q -m "modify multiline file" >> "${WORKDIR}/marked.log" &&
git push gitcvs.git >/dev/null &&
- cd cvswork2 &&
+ (cd cvswork2 &&
sed "s/1/replaced_1/" < multilineTxt.c > ml.temp &&
mv ml.temp multilineTxt.c &&
- GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1 &&
- cd .. &&
+ GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1
+ ) &&
marked_as cvswork2 textfile.c "" &&
marked_as cvswork2 binfile.bin -kb &&
marked_as cvswork2 .gitattributes "" &&
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index 4f2b9b0..5329715 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -18,42 +18,34 @@ or warnings to log.'
test_expect_success \
'no commits: projects_list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: projects_index' \
'gitweb_run "a=project_index"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git summary (implicit)' \
'gitweb_run "p=.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git commit (implicit HEAD)' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git commitdiff (implicit HEAD)' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git tree (implicit HEAD)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git heads' \
'gitweb_run "p=.git;a=heads"'
-test_debug 'cat gitweb.log'
test_expect_success \
'no commits: .git tags' \
'gitweb_run "p=.git;a=tags"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
@@ -69,52 +61,42 @@ test_expect_success \
test_expect_success \
'projects_list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'projects_index' \
'gitweb_run "a=project_index"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git summary (implicit)' \
'gitweb_run "p=.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commit (implicit HEAD)' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (implicit HEAD, root commit)' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff_plain (implicit HEAD, root commit)' \
'gitweb_run "p=.git;a=commitdiff_plain"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commit (HEAD)' \
'gitweb_run "p=.git;a=commit;h=HEAD"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tree (implicit HEAD)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob (file)' \
'gitweb_run "p=.git;a=blob;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob_plain (file)' \
'gitweb_run "p=.git;a=blob_plain;f=file"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# nonexistent objects
@@ -122,37 +104,30 @@ test_debug 'cat gitweb.log'
test_expect_success \
'.git commit (non-existent)' \
'gitweb_run "p=.git;a=commit;h=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (non-existent)' \
'gitweb_run "p=.git;a=commitdiff;h=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git commitdiff (non-existent vs HEAD)' \
'gitweb_run "p=.git;a=commitdiff;hp=non-existent;h=HEAD"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tree (0000000000000000000000000000000000000000)' \
'gitweb_run "p=.git;a=tree;h=0000000000000000000000000000000000000000"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git tag (0000000000000000000000000000000000000000)' \
'gitweb_run "p=.git;a=tag;h=0000000000000000000000000000000000000000"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob (non-existent)' \
'gitweb_run "p=.git;a=blob;f=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'.git blob_plain (non-existent)' \
'gitweb_run "p=.git;a=blob_plain;f=non-existent"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
@@ -161,7 +136,6 @@ test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): root' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file added' \
@@ -169,21 +143,18 @@ test_expect_success \
git add new_file &&
git commit -a -m "File added." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): mode change' \
'test_chmod +x new_file &&
git commit -a -m "Mode changed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file renamed' \
'git mv new_file renamed_file &&
git commit -a -m "File renamed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success SYMLINKS \
'commitdiff(0): file to symlink' \
@@ -191,7 +162,6 @@ test_expect_success SYMLINKS \
ln -s file renamed_file &&
git commit -a -m "File to symlink." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file deleted' \
@@ -199,7 +169,6 @@ test_expect_success \
rm -f renamed_file &&
git commit -a -m "File removed." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): file copied / new file' \
@@ -207,7 +176,6 @@ test_expect_success \
git add file2 &&
git commit -a -m "File copied." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): mode change and modified' \
@@ -215,7 +183,6 @@ test_expect_success \
test_chmod +x file2 &&
git commit -a -m "Mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): renamed and modified' \
@@ -233,7 +200,6 @@ EOF
echo "Propter nomen suum." >> file3 &&
git commit -a -m "File rename and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): renamed, mode change and modified' \
@@ -242,7 +208,6 @@ test_expect_success \
test_chmod +x file2 &&
git commit -a -m "File rename, mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# commitdiff testing (taken from t4114-apply-typechange.sh)
@@ -279,42 +244,34 @@ test_expect_success SYMLINKS 'setup typechange commits' '
test_expect_success \
'commitdiff(2): file renamed from foo to foo/baz' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-baz-renamed-from-foo"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file renamed from foo/baz to foo' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-baz-renamed-from-foo;h=initial"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): directory becomes file' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=initial"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file becomes directory' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-becomes-a-directory"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): file becomes symlink' \
'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-symlinked-to-bar"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): symlink becomes file' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-back-to-file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): symlink becomes directory' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-becomes-a-directory"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(2): directory becomes symlink' \
'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# commit, commitdiff: merge, large
@@ -330,12 +287,10 @@ test_expect_success \
test_expect_success \
'commit(0): merge commit' \
'gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(0): merge commit' \
'gitweb_run "p=.git;a=commitdiff"'
-test_debug 'cat gitweb.log'
test_expect_success \
'Prepare large commit' \
@@ -371,12 +326,10 @@ test_expect_success \
test_expect_success \
'commit(1): large commit' \
'gitweb_run "p=.git;a=commit;h=b"'
-test_debug 'cat gitweb.log'
test_expect_success \
'commitdiff(1): large commit' \
'gitweb_run "p=.git;a=commitdiff;h=b"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# tags testing
@@ -394,17 +347,14 @@ test_expect_success \
git tag lightweight/tag-tree HEAD^{tree} &&
git tag lightweight/tag-blob HEAD:file &&
gitweb_run "p=.git;a=tags"'
-test_debug 'cat gitweb.log'
test_expect_success \
'tag: Tag to commit object' \
'gitweb_run "p=.git;a=tag;h=tag-commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'tag: on lightweight tag (invalid)' \
'gitweb_run "p=.git;a=tag;h=lightweight/tag-commit"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# logs
@@ -412,22 +362,18 @@ test_debug 'cat gitweb.log'
test_expect_success \
'logs: log (implicit HEAD)' \
'gitweb_run "p=.git;a=log"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: shortlog (implicit HEAD)' \
'gitweb_run "p=.git;a=shortlog"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, file)' \
'gitweb_run "p=.git;a=history;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, non-existent file)' \
'gitweb_run "p=.git;a=history;f=non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'logs: history (implicit HEAD, deleted file)' \
@@ -438,55 +384,45 @@ test_expect_success \
git rm deleted_file &&
git commit -m "Delete file" &&
gitweb_run "p=.git;a=history;f=deleted_file"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# path_info links
test_expect_success \
'path_info: project' \
'gitweb_run "" "/.git"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch' \
'gitweb_run "" "/.git/b"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:file' \
'gitweb_run "" "/.git/master:file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:dir/' \
'gitweb_run "" "/.git/master:foo/"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:file (non-existent)' \
'gitweb_run "" "/.git/master:non-existent"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:dir/ (non-existent)' \
'gitweb_run "" "/.git/master:non-existent/"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/branch:/file' \
'gitweb_run "" "/.git/master:/file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/:/file (implicit HEAD)' \
'gitweb_run "" "/.git/:/file"'
-test_debug 'cat gitweb.log'
test_expect_success \
'path_info: project/:/ (implicit HEAD, top tree)' \
'gitweb_run "" "/.git/:/"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
@@ -495,17 +431,14 @@ test_debug 'cat gitweb.log'
test_expect_success \
'feeds: OPML' \
'gitweb_run "a=opml"'
-test_debug 'cat gitweb.log'
test_expect_success \
'feed: RSS' \
'gitweb_run "p=.git;a=rss"'
-test_debug 'cat gitweb.log'
test_expect_success \
'feed: Atom' \
'gitweb_run "p=.git;a=atom"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# encoding/decoding
@@ -513,27 +446,28 @@ test_debug 'cat gitweb.log'
test_expect_success \
'encode(commit): utf8' \
'. "$TEST_DIRECTORY"/t3901-utf8.txt &&
+ test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
echo "UTF-8" >> file &&
git add file &&
git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'encode(commit): iso-8859-1' \
'. "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+ test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
+ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
echo "ISO-8859-1" >> file &&
git add file &&
git config i18n.commitencoding ISO-8859-1 &&
+ test_when_finished "git config --unset i18n.commitencoding" &&
git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
- git config --unset i18n.commitencoding &&
gitweb_run "p=.git;a=commit"'
-test_debug 'cat gitweb.log'
test_expect_success \
'encode(log): utf-8 and iso-8859-1' \
'gitweb_run "p=.git;a=log"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# extra options
@@ -541,27 +475,22 @@ test_debug 'cat gitweb.log'
test_expect_success \
'opt: log --no-merges' \
'gitweb_run "p=.git;a=log;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: atom --no-merges' \
'gitweb_run "p=.git;a=log;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: "file" history --no-merges' \
'gitweb_run "p=.git;a=history;f=file;opt=--no-merges"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: log --no-such-option (invalid option)' \
'gitweb_run "p=.git;a=log;opt=--no-such-option"'
-test_debug 'cat gitweb.log'
test_expect_success \
'opt: tree --no-merges (invalid option for action)' \
'gitweb_run "p=.git;a=tree;opt=--no-merges"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# testing config_to_multi / cloneurl
@@ -569,14 +498,12 @@ test_debug 'cat gitweb.log'
test_expect_success \
'URL: no project URLs, no base URL' \
'gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
test_expect_success \
'URL: project URLs via gitweb.url' \
'git config --add gitweb.url git://example.com/git/trash.git &&
git config --add gitweb.url http://example.com/git/trash.git &&
gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
cat >.git/cloneurl <<\EOF
git://example.com/git/trash.git
@@ -586,7 +513,6 @@ EOF
test_expect_success \
'URL: project URLs via cloneurl file' \
'gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# gitweb config and repo config
@@ -604,12 +530,10 @@ EOF
test_expect_success \
'config override: projects list (implicit)' \
'gitweb_run'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features not overridden in repo config' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features disabled in repo config' \
@@ -617,14 +541,12 @@ test_expect_success \
git config gitweb.snapshot none &&
git config gitweb.avatar gravatar &&
gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
test_expect_success \
'config override: tree view, features enabled in repo config (1)' \
'git config gitweb.blame yes &&
git config gitweb.snapshot "zip,tgz, tbz2" &&
gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
cat >.git/config <<\EOF
# testing noval and alternate separator
@@ -635,7 +557,6 @@ EOF
test_expect_success \
'config override: tree view, features enabled in repo config (2)' \
'gitweb_run "p=.git;a=tree"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# non-ASCII in README.html
@@ -645,35 +566,98 @@ test_expect_success \
'echo "<b>UTF-8 example:</b><br />" > .git/README.html &&
cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html &&
gitweb_run "p=.git;a=summary"'
-test_debug 'cat gitweb.log'
# ----------------------------------------------------------------------
# syntax highlighting
-cat >>gitweb_config.perl <<\EOF
-$feature{'highlight'}{'override'} = 1;
-EOF
highlight --version >/dev/null 2>&1
if [ $? -eq 127 ]; then
say "Skipping syntax highlighting test, because 'highlight' was not found"
else
test_set_prereq HIGHLIGHT
+ cat >>gitweb_config.perl <<-\EOF
+ our $highlight_bin = "highlight";
+ $feature{'highlight'}{'override'} = 1;
+ EOF
fi
test_expect_success HIGHLIGHT \
- 'syntax highlighting (no highlight)' \
+ 'syntax highlighting (no highlight, unknown syntax)' \
'git config gitweb.highlight yes &&
gitweb_run "p=.git;a=blob;f=file"'
-test_debug 'cat gitweb.log'
test_expect_success HIGHLIGHT \
- 'syntax highlighting (highlighted)' \
+ 'syntax highlighting (highlighted, shell script)' \
'git config gitweb.highlight yes &&
echo "#!/usr/bin/sh" > test.sh &&
git add test.sh &&
git commit -m "Add test.sh" &&
gitweb_run "p=.git;a=blob;f=test.sh"'
-test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# forks of projects
+
+cat >>gitweb_config.perl <<\EOF &&
+$feature{'forks'}{'default'} = [1];
+EOF
+
+test_expect_success \
+ 'forks: prepare' \
+ 'git init --bare foo.git &&
+ git --git-dir=foo.git --work-tree=. add file &&
+ git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
+ echo "foo" > foo.git/description &&
+ mkdir -p foo &&
+ (cd foo &&
+ git clone --shared --bare ../foo.git foo-forked.git &&
+ echo "fork of foo" > foo-forked.git/description)'
+
+test_expect_success \
+ 'forks: projects list' \
+ 'gitweb_run'
+
+test_expect_success \
+ 'forks: forks action' \
+ 'gitweb_run "p=foo.git;a=forks"'
+
+# ----------------------------------------------------------------------
+# content tags (tag cloud)
+
+cat >>gitweb_config.perl <<-\EOF &&
+# we don't test _setting_ content tags, so any true value is good
+$feature{'ctags'}{'default'} = ['ctags_script.cgi'];
+EOF
+
+test_expect_success \
+ 'ctags: tag cloud in projects list' \
+ 'mkdir .git/ctags &&
+ echo "2" > .git/ctags/foo &&
+ echo "1" > .git/ctags/bar &&
+ gitweb_run'
+
+test_expect_success \
+ 'ctags: search projects by existing tag' \
+ 'gitweb_run "by_tag=foo"'
+
+test_expect_success \
+ 'ctags: search projects by non existent tag' \
+ 'gitweb_run "by_tag=non-existent"'
+
+test_expect_success \
+ 'ctags: malformed tag weights' \
+ 'mkdir -p .git/ctags &&
+ echo "not-a-number" > .git/ctags/nan &&
+ echo "not-a-number-2" > .git/ctags/nan2 &&
+ echo "0.1" >.git/ctags/floating-point &&
+ gitweb_run'
+
+# ----------------------------------------------------------------------
+# categories
+
+test_expect_success \
+ 'categories: projects list, only default category' \
+ 'echo "\$projects_list_group_categories = 1;" >>gitweb_config.perl &&
+ gitweb_run'
test_done
diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh
index 2487da1..26102ee 100755
--- a/t/t9501-gitweb-standalone-http-status.sh
+++ b/t/t9501-gitweb-standalone-http-status.sh
@@ -16,7 +16,7 @@ code and message.'
# snapshot settings
test_expect_success 'setup' "
- test_commit 'SnapshotTests' 'i can has snapshot?'
+ test_commit 'SnapshotTests' 'i can has snapshot'
"
@@ -126,7 +126,6 @@ test_expect_success 'load checking: load too high (default action)' '
grep "Status: 503 Service Unavailable" gitweb.headers &&
grep "503 - The load average on the server is too high" gitweb.body
'
-test_debug 'cat gitweb.log' # just in case
test_debug 'cat gitweb.headers'
# turn off load checking
diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
index dd83890..731e64c 100755
--- a/t/t9502-gitweb-standalone-parse-output.sh
+++ b/t/t9502-gitweb-standalone-parse-output.sh
@@ -112,4 +112,78 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' '
'
test_debug 'cat gitweb.headers'
+# ----------------------------------------------------------------------
+# forks of projects
+
+test_expect_success 'forks: setup' '
+ git init --bare foo.git &&
+ echo file > file &&
+ git --git-dir=foo.git --work-tree=. add file &&
+ git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
+ echo "foo" > foo.git/description &&
+ git clone --bare foo.git foo.bar.git &&
+ echo "foo.bar" > foo.bar.git/description &&
+ git clone --bare foo.git foo_baz.git &&
+ echo "foo_baz" > foo_baz.git/description &&
+ rm -fr foo &&
+ mkdir -p foo &&
+ (
+ cd foo &&
+ git clone --shared --bare ../foo.git foo-forked.git &&
+ echo "fork of foo" > foo-forked.git/description
+ )
+'
+
+test_expect_success 'forks: not skipped unless "forks" feature enabled' '
+ gitweb_run "a=project_list" &&
+ grep -q ">\\.git<" gitweb.body &&
+ grep -q ">foo\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo\\.bar\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -q ">fork of .*<" gitweb.body
+'
+
+cat >>gitweb_config.perl <<\EOF &&
+$feature{'forks'}{'default'} = [1];
+EOF
+
+test_expect_success 'forks: forks skipped if "forks" feature enabled' '
+ gitweb_run "a=project_list" &&
+ grep -q ">\\.git<" gitweb.body &&
+ grep -q ">foo\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo\\.bar\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -v ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -v ">fork of .*<" gitweb.body
+'
+
+test_expect_success 'forks: "forks" action for forked repository' '
+ gitweb_run "p=foo.git;a=forks" &&
+ grep -q ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -q ">fork of foo<" gitweb.body
+'
+
+test_expect_success 'forks: can access forked repository' '
+ gitweb_run "p=foo/foo-forked.git;a=summary" &&
+ grep -q "200 OK" gitweb.headers &&
+ grep -q ">fork of foo<" gitweb.body
+'
+
+test_expect_success 'forks: project_index lists all projects (incl. forks)' '
+ cat >expected <<-\EOF
+ .git
+ foo.bar.git
+ foo.git
+ foo/foo-forked.git
+ foo_baz.git
+ EOF
+ gitweb_run "a=project_index" &&
+ sed -e "s/ .*//" <gitweb.body | sort >actual &&
+ test_cmp expected actual
+'
+
+
test_done
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
index b572ce3..4c384ff 100755
--- a/t/t9600-cvsimport.sh
+++ b/t/t9600-cvsimport.sh
@@ -3,21 +3,18 @@
test_description='git cvsimport basic tests'
. ./lib-cvs.sh
-if ! test_have_prereq PERL; then
- say 'skipping git cvsimport tests, perl not available'
- test_done
-fi
-
-CVSROOT=$(pwd)/cvsroot
-export CVSROOT
+test_expect_success PERL 'setup cvsroot environment' '
+ CVSROOT=$(pwd)/cvsroot &&
+ export CVSROOT
+'
-test_expect_success 'setup cvsroot' '$CVS init'
+test_expect_success PERL 'setup cvsroot' '$CVS init'
-test_expect_success 'setup a cvs module' '
+test_expect_success PERL 'setup a cvs module' '
mkdir "$CVSROOT/module" &&
$CVS co -d module-cvs module &&
- cd module-cvs &&
+ (cd module-cvs &&
cat <<EOF >o_fortuna &&
O Fortuna
velut luna
@@ -41,29 +38,28 @@ add "O Fortuna" lyrics
These public domain lyrics make an excellent sample text.
EOF
- $CVS commit -F message &&
- cd ..
+ $CVS commit -F message
+ )
'
-test_expect_success 'import a trivial module' '
+test_expect_success PERL 'import a trivial module' '
git cvsimport -a -R -z 0 -C module-git module &&
test_cmp module-cvs/o_fortuna module-git/o_fortuna
'
-test_expect_success 'pack refs' 'cd module-git && git gc && cd ..'
+test_expect_success PERL 'pack refs' '(cd module-git && git gc)'
-test_expect_success 'initial import has correct .git/cvs-revisions' '
+test_expect_success PERL 'initial import has correct .git/cvs-revisions' '
(cd module-git &&
git log --format="o_fortuna 1.1 %H" -1) > expected &&
test_cmp expected module-git/.git/cvs-revisions
'
-test_expect_success 'update cvs module' '
-
- cd module-cvs &&
+test_expect_success PERL 'update cvs module' '
+ (cd module-cvs &&
cat <<EOF >o_fortuna &&
O Fortune,
like the moon
@@ -86,21 +82,22 @@ translate to English
My Latin is terrible.
EOF
- $CVS commit -F message &&
- cd ..
+ $CVS commit -F message
+ )
'
-test_expect_success 'update git module' '
+test_expect_success PERL 'update git module' '
- cd module-git &&
- git cvsimport -a -R -z 0 module &&
- git merge origin &&
- cd .. &&
+ (cd module-git &&
+ git config cvsimport.trackRevisions true &&
+ git cvsimport -a -z 0 module &&
+ git merge origin
+ ) &&
test_cmp module-cvs/o_fortuna module-git/o_fortuna
'
-test_expect_success 'update has correct .git/cvs-revisions' '
+test_expect_success PERL 'update has correct .git/cvs-revisions' '
(cd module-git &&
git log --format="o_fortuna 1.1 %H" -1 HEAD^ &&
@@ -108,28 +105,28 @@ test_expect_success 'update has correct .git/cvs-revisions' '
test_cmp expected module-git/.git/cvs-revisions
'
-test_expect_success 'update cvs module' '
+test_expect_success PERL 'update cvs module' '
- cd module-cvs &&
+ (cd module-cvs &&
echo 1 >tick &&
$CVS add tick &&
$CVS commit -m 1
- cd ..
-
+ )
'
-test_expect_success 'cvsimport.module config works' '
+test_expect_success PERL 'cvsimport.module config works' '
- cd module-git &&
+ (cd module-git &&
git config cvsimport.module module &&
- git cvsimport -a -R -z0 &&
- git merge origin &&
- cd .. &&
+ git config cvsimport.trackRevisions true &&
+ git cvsimport -a -z0 &&
+ git merge origin
+ ) &&
test_cmp module-cvs/tick module-git/tick
'
-test_expect_success 'second update has correct .git/cvs-revisions' '
+test_expect_success PERL 'second update has correct .git/cvs-revisions' '
(cd module-git &&
git log --format="o_fortuna 1.1 %H" -1 HEAD^^ &&
@@ -138,24 +135,25 @@ test_expect_success 'second update has correct .git/cvs-revisions' '
test_cmp expected module-git/.git/cvs-revisions
'
-test_expect_success 'import from a CVS working tree' '
+test_expect_success PERL 'import from a CVS working tree' '
$CVS co -d import-from-wt module &&
- cd import-from-wt &&
+ (cd import-from-wt &&
+ git config cvsimport.trackRevisions false &&
git cvsimport -a -z0 &&
echo 1 >expect &&
git log -1 --pretty=format:%s%n >actual &&
- test_cmp actual expect &&
- cd ..
+ test_cmp actual expect
+ )
'
-test_expect_success 'no .git/cvs-revisions created by default' '
+test_expect_success PERL 'no .git/cvs-revisions created by default' '
! test -e import-from-wt/.git/cvs-revisions
'
-test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master'
+test_expect_success PERL 'test entire HEAD' 'test_cmp_branch_tree master'
test_done
diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh
index 3afaf56..827d39f 100755
--- a/t/t9601-cvsimport-vendor-branch.sh
+++ b/t/t9601-cvsimport-vendor-branch.sh
@@ -34,50 +34,49 @@
test_description='git cvsimport handling of vendor branches'
. ./lib-cvs.sh
-CVSROOT="$TEST_DIRECTORY"/t9601/cvsroot
-export CVSROOT
+setup_cvs_test_repository t9601
-test_expect_success 'import a module with a vendor branch' '
+test_expect_success PERL 'import a module with a vendor branch' '
git cvsimport -C module-git module
'
-test_expect_success 'check HEAD out of cvs repository' 'test_cvs_co master'
+test_expect_success PERL 'check HEAD out of cvs repository' 'test_cvs_co master'
-test_expect_success 'check master out of git repository' 'test_git_co master'
+test_expect_success PERL 'check master out of git repository' 'test_git_co master'
-test_expect_success 'check a file that was imported once' '
+test_expect_success PERL 'check a file that was imported once' '
test_cmp_branch_file master imported-once.txt
'
-test_expect_failure 'check a file that was imported twice' '
+test_expect_failure PERL 'check a file that was imported twice' '
test_cmp_branch_file master imported-twice.txt
'
-test_expect_success 'check a file that was imported then modified on HEAD' '
+test_expect_success PERL 'check a file that was imported then modified on HEAD' '
test_cmp_branch_file master imported-modified.txt
'
-test_expect_success 'check a file that was imported, modified, then imported again' '
+test_expect_success PERL 'check a file that was imported, modified, then imported again' '
test_cmp_branch_file master imported-modified-imported.txt
'
-test_expect_success 'check a file that was added to HEAD then imported' '
+test_expect_success PERL 'check a file that was added to HEAD then imported' '
test_cmp_branch_file master added-imported.txt
'
-test_expect_success 'a vendor branch whose tag has been removed' '
+test_expect_success PERL 'a vendor branch whose tag has been removed' '
test_cmp_branch_file master imported-anonymously.txt
diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh
index 67878b2..e1db323 100755
--- a/t/t9602-cvsimport-branches-tags.sh
+++ b/t/t9602-cvsimport-branches-tags.sh
@@ -6,70 +6,69 @@
test_description='git cvsimport handling of branches and tags'
. ./lib-cvs.sh
-CVSROOT="$TEST_DIRECTORY"/t9602/cvsroot
-export CVSROOT
+setup_cvs_test_repository t9602
-test_expect_success 'import module' '
+test_expect_success PERL 'import module' '
git cvsimport -C module-git module
'
-test_expect_success 'test branch master' '
+test_expect_success PERL 'test branch master' '
test_cmp_branch_tree master
'
-test_expect_success 'test branch vendorbranch' '
+test_expect_success PERL 'test branch vendorbranch' '
test_cmp_branch_tree vendorbranch
'
-test_expect_failure 'test branch B_FROM_INITIALS' '
+test_expect_failure PERL 'test branch B_FROM_INITIALS' '
test_cmp_branch_tree B_FROM_INITIALS
'
-test_expect_failure 'test branch B_FROM_INITIALS_BUT_ONE' '
+test_expect_failure PERL 'test branch B_FROM_INITIALS_BUT_ONE' '
test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE
'
-test_expect_failure 'test branch B_MIXED' '
+test_expect_failure PERL 'test branch B_MIXED' '
test_cmp_branch_tree B_MIXED
'
-test_expect_success 'test branch B_SPLIT' '
+test_expect_success PERL 'test branch B_SPLIT' '
test_cmp_branch_tree B_SPLIT
'
-test_expect_failure 'test tag vendortag' '
+test_expect_failure PERL 'test tag vendortag' '
test_cmp_branch_tree vendortag
'
-test_expect_success 'test tag T_ALL_INITIAL_FILES' '
+test_expect_success PERL 'test tag T_ALL_INITIAL_FILES' '
test_cmp_branch_tree T_ALL_INITIAL_FILES
'
-test_expect_failure 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
+test_expect_failure PERL 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE
'
-test_expect_failure 'test tag T_MIXED' '
+test_expect_failure PERL 'test tag T_MIXED' '
test_cmp_branch_tree T_MIXED
diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh
index 958bdce..52034c8 100755
--- a/t/t9603-cvsimport-patchsets.sh
+++ b/t/t9603-cvsimport-patchsets.sh
@@ -14,18 +14,17 @@
test_description='git cvsimport testing for correct patchset estimation'
. ./lib-cvs.sh
-CVSROOT="$TEST_DIRECTORY"/t9603/cvsroot
-export CVSROOT
+setup_cvs_test_repository t9603
test_expect_failure 'import with criss cross times on revisions' '
git cvsimport -p"-x" -C module-git module &&
- cd module-git &&
+ (cd module-git &&
git log --pretty=format:%s > ../actual-master &&
git log A~2..A --pretty="format:%s %ad" -- > ../actual-A &&
echo "" >> ../actual-master &&
- echo "" >> ../actual-A &&
- cd .. &&
+ echo "" >> ../actual-A
+ ) &&
echo "Rev 4
Rev 3
Rev 2
diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index 8686086..3787186 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -7,12 +7,12 @@ test_description='perl interface (Git.pm)'
. ./test-lib.sh
if ! test_have_prereq PERL; then
- say 'skipping perl interface tests, perl not available'
+ skip_all='skipping perl interface tests, perl not available'
test_done
fi
"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
- say "Perl Test::More unavailable, skipping test"
+ skip_all="Perl Test::More unavailable, skipping test"
test_done
}
@@ -46,6 +46,9 @@ test_expect_success \
git config --add test.int 2k
'
+# The external test will outputs its own plan
+test_external_has_tap=1
+
test_external_without_stderr \
'Perl API' \
"$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index 666722d..13ba96e 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -1,12 +1,19 @@
#!/usr/bin/perl
use lib (split(/:/, $ENV{GITPERLLIB}));
-use 5.006002;
+use 5.008;
use warnings;
use strict;
use Test::More qw(no_plan);
+BEGIN {
+ # t9700-perl-git.sh kicks off our testing, so we have to go from
+ # there.
+ Test::More->builder->current_test(1);
+ Test::More->builder->no_ending(1);
+}
+
use Cwd;
use File::Basename;
@@ -105,3 +112,18 @@ my $last_commit = $r2->command_oneline(qw(rev-parse --verify HEAD));
like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash');
my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.');
isnt($last_commit, $dir_commit, 'log . does not show last commit');
+
+# commands outside working tree
+chdir($abs_repo_dir . '/..');
+my $r3 = Git->repository(Directory => $abs_repo_dir);
+my $tmpfile3 = "$abs_repo_dir/file3.tmp";
+open TEMPFILE3, "+>$tmpfile3" or die "Can't open $tmpfile3: $!";
+is($r3->cat_blob($file1hash, \*TEMPFILE3), 15, "cat_blob(outside): size");
+close TEMPFILE3;
+unlink $tmpfile3;
+chdir($abs_repo_dir);
+
+printf "1..%d\n", Test::More->builder->current_test;
+
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
new file mode 100755
index 0000000..33b0127
--- /dev/null
+++ b/t/t9800-git-p4.sh
@@ -0,0 +1,264 @@
+#!/bin/sh
+
+test_description='git-p4 tests'
+
+. ./test-lib.sh
+
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+ skip_all='skipping git-p4 tests; no p4 or p4d'
+ test_done
+}
+
+GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
+P4DPORT=10669
+
+export P4PORT=localhost:$P4DPORT
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+
+test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
+test_expect_success setup '
+ mkdir -p "$db" &&
+ p4d -q -d -r "$db" -p $P4DPORT &&
+ mkdir -p "$cli" &&
+ mkdir -p "$git" &&
+ export P4PORT=localhost:$P4DPORT
+'
+
+test_expect_success 'add p4 files' '
+ cd "$cli" &&
+ p4 client -i <<-EOF &&
+ Client: client
+ Description: client
+ Root: $cli
+ View: //depot/... //client/...
+ EOF
+ export P4CLIENT=client &&
+ echo file1 >file1 &&
+ p4 add file1 &&
+ p4 submit -d "file1" &&
+ echo file2 >file2 &&
+ p4 add file2 &&
+ p4 submit -d "file2" &&
+ cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'basic git-p4 clone' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ git log --oneline >lines &&
+ test_line_count = 1 lines &&
+ cd .. &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'git-p4 clone @all' '
+ "$GITP4" clone --dest="$git" //depot@all &&
+ cd "$git" &&
+ git log --oneline >lines &&
+ test_line_count = 2 lines &&
+ cd .. &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'git-p4 sync uninitialized repo' '
+ test_create_repo "$git" &&
+ cd "$git" &&
+ test_must_fail "$GITP4" sync &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+#
+# Create a git repo by hand. Add a commit so that HEAD is valid.
+# Test imports a new p4 repository into a new git branch.
+#
+test_expect_success 'git-p4 sync new branch' '
+ test_create_repo "$git" &&
+ cd "$git" &&
+ test_commit head &&
+ "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
+ git log --oneline p4/depot >lines &&
+ cat lines &&
+ test_line_count = 2 lines &&
+ cd .. &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'exit when p4 fails to produce marshaled output' '
+ badp4dir="$TRASH_DIRECTORY/badp4dir" &&
+ mkdir -p "$badp4dir" &&
+ cat >"$badp4dir"/p4 <<-EOF &&
+ #!$SHELL_PATH
+ exit 1
+ EOF
+ chmod 755 "$badp4dir"/p4 &&
+ PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+ test $retval -eq 1 &&
+ test_must_fail grep -q Traceback errs
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+ cd "$cli" &&
+ echo file-wild-hash >file-wild#hash &&
+ echo file-wild-star >file-wild\*star &&
+ echo file-wild-at >file-wild@at &&
+ echo file-wild-percent >file-wild%percent &&
+ p4 add -f file-wild* &&
+ p4 submit -d "file wildcards" &&
+ cd "$TRASH_DIRECTORY"
+'
+
+test_expect_success 'wildcard files git-p4 clone' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ test -f file-wild#hash &&
+ test -f file-wild\*star &&
+ test -f file-wild@at &&
+ test -f file-wild%percent &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+test_expect_success 'clone bare' '
+ "$GITP4" clone --dest="$git" --bare //depot &&
+ cd "$git" &&
+ test ! -d .git &&
+ bare=`git config --get core.bare` &&
+ test "$bare" = true &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+p4_add_user() {
+ name=$1
+ fullname=$2
+ p4 user -f -i <<EOF &&
+User: $name
+Email: $name@localhost
+FullName: $fullname
+EOF
+ p4 passwd -P secret $name
+}
+
+p4_grant_admin() {
+ name=$1
+ p4 protect -o |\
+ awk "{print}END{print \" admin user $name * //depot/...\"}" |\
+ p4 protect -i
+}
+
+p4_check_commit_author() {
+ file=$1
+ user=$2
+ if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
+ return 0
+ else
+ echo "file $file not modified by user $user" 1>&2
+ return 1
+ fi
+}
+
+make_change_by_user() {
+ file=$1 name=$2 email=$3 &&
+ echo "username: a change by $name" >>"$file" &&
+ git add "$file" &&
+ git commit --author "$name <$email>" -m "a change by $name"
+}
+
+# Test username support, submitting as user 'alice'
+test_expect_success 'preserve users' '
+ p4_add_user alice Alice &&
+ p4_add_user bob Bob &&
+ p4_grant_admin alice &&
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ echo "username: a change by alice" >> file1 &&
+ echo "username: a change by bob" >> file2 &&
+ git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
+ git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+ git config git-p4.skipSubmitEditCheck true &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ p4_check_commit_author file1 alice &&
+ p4_check_commit_author file2 bob &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# Test username support, submitting as bob, who lacks admin rights. Should
+# not submit change to p4 (git diff should show deltas).
+test_expect_success 'refuse to preserve users without perms' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ echo "username-noperms: a change by alice" >> file1 &&
+ git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+ ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master > /dev/null &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# What happens with unknown author? Without allowMissingP4Users it should fail.
+test_expect_success 'preserve user where author is unknown to p4' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true
+ echo "username-bob: a change by bob" >> file1 &&
+ git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+ echo "username-unknown: a change by charlie" >> file1 &&
+ git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+ ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master > /dev/null &&
+ echo "$0: repeat with allowMissingP4Users enabled" &&
+ git config git-p4.allowMissingP4Users true &&
+ git config git-p4.preserveUser true &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
+ git diff --exit-code HEAD..p4/master > /dev/null &&
+ p4_check_commit_author file1 alice &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
+# changes that are not all ours.
+# Test: user in p4 and user unknown to p4.
+# Test: warning disabled and user is the same.
+test_expect_success 'not preserving user with mixed authorship' '
+ "$GITP4" clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ p4_add_user derek Derek &&
+
+ make_change_by_user usernamefile3 Derek derek@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ grep "git author derek@localhost does not match" actual &&
+
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ grep "git author charlie@localhost does not match" actual &&
+
+ make_change_by_user usernamefile3 alice alice@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ ! grep "git author.*does not match" actual &&
+
+ git config git-p4.skipUserNameCheck true &&
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ ! grep "git author.*does not match" actual &&
+
+ p4_check_commit_author usernamefile3 alice
+ ) &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+
+test_expect_success 'shutdown' '
+ pid=`pgrep -f p4d` &&
+ test -n "$pid" &&
+ test_debug "ps wl `echo $pid`" &&
+ kill $pid
+'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 367f053..df25f17 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -43,33 +43,25 @@ TERM=dumb
export LANG LC_ALL PAGER TERM TZ
EDITOR=:
unset VISUAL
-unset GIT_EDITOR
-unset AUTHOR_DATE
-unset AUTHOR_EMAIL
-unset AUTHOR_NAME
-unset COMMIT_AUTHOR_EMAIL
-unset COMMIT_AUTHOR_NAME
unset EMAIL
-unset GIT_ALTERNATE_OBJECT_DIRECTORIES
-unset GIT_AUTHOR_DATE
+unset $(perl -e '
+ my @env = keys %ENV;
+ my $ok = join("|", qw(
+ TRACE
+ DEBUG
+ USE_LOOKUP
+ TEST
+ .*_TEST
+ PROVE
+ VALGRIND
+ ));
+ my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
+ print join("\n", @vars);
+')
GIT_AUTHOR_EMAIL=author@example.com
GIT_AUTHOR_NAME='A U Thor'
-unset GIT_COMMITTER_DATE
GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
-unset GIT_DIFF_OPTS
-unset GIT_DIR
-unset GIT_WORK_TREE
-unset GIT_EXTERNAL_DIFF
-unset GIT_INDEX_FILE
-unset GIT_OBJECT_DIRECTORY
-unset GIT_CEILING_DIRECTORIES
-unset SHA1_FILE_DIRECTORIES
-unset SHA1_FILE_DIRECTORY
-unset GIT_NOTES_REF
-unset GIT_NOTES_DISPLAY_REF
-unset GIT_NOTES_REWRITE_REF
-unset GIT_NOTES_REWRITE_MODE
GIT_MERGE_VERBOSITY=5
export GIT_MERGE_VERBOSITY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
@@ -97,6 +89,9 @@ esac
_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
+# Zero SHA-1
+_z40=0000000000000000000000000000000000000000
+
# Each test should start with something like this, after copyright notices:
#
# test_description='Description of this test...
@@ -127,14 +122,13 @@ do
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
-q|--q|--qu|--qui|--quie|--quiet)
- quiet=t; shift ;;
+ # Ignore --quiet under a TAP::Harness. Saying how many tests
+ # passed without the ok/not ok details is always an error.
+ test -z "$HARNESS_ACTIVE" && quiet=t; shift ;;
--with-dashes)
with_dashes=t; shift ;;
--no-color)
color=; shift ;;
- --no-python)
- # noop now...
- shift ;;
--va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
valgrind=t; verbose=t; shift ;;
--tee)
@@ -160,7 +154,7 @@ if test -n "$color"; then
*) test -n "$quiet" && return;;
esac
shift
- printf "* %s" "$*"
+ printf "%s" "$*"
tput sgr0
echo
)
@@ -169,7 +163,7 @@ else
say_color() {
test -z "$1" && test -n "$quiet" && return
shift
- echo "* $*"
+ echo "$*"
}
fi
@@ -206,6 +200,8 @@ test_fixed=0
test_broken=0
test_success=0
+test_external_has_tap=0
+
die () {
code=$?
if test -n "$GIT_EXIT_OK"
@@ -237,14 +233,51 @@ test_set_editor () {
}
test_decode_color () {
- sed -e 's/.\[1m/<WHITE>/g' \
- -e 's/.\[31m/<RED>/g' \
- -e 's/.\[32m/<GREEN>/g' \
- -e 's/.\[33m/<YELLOW>/g' \
- -e 's/.\[34m/<BLUE>/g' \
- -e 's/.\[35m/<MAGENTA>/g' \
- -e 's/.\[36m/<CYAN>/g' \
- -e 's/.\[m/<RESET>/g'
+ awk '
+ function name(n) {
+ if (n == 0) return "RESET";
+ if (n == 1) return "BOLD";
+ if (n == 30) return "BLACK";
+ if (n == 31) return "RED";
+ if (n == 32) return "GREEN";
+ if (n == 33) return "YELLOW";
+ if (n == 34) return "BLUE";
+ if (n == 35) return "MAGENTA";
+ if (n == 36) return "CYAN";
+ if (n == 37) return "WHITE";
+ if (n == 40) return "BLACK";
+ if (n == 41) return "BRED";
+ if (n == 42) return "BGREEN";
+ if (n == 43) return "BYELLOW";
+ if (n == 44) return "BBLUE";
+ if (n == 45) return "BMAGENTA";
+ if (n == 46) return "BCYAN";
+ if (n == 47) return "BWHITE";
+ }
+ {
+ while (match($0, /\033\[[0-9;]*m/) != 0) {
+ printf "%s<", substr($0, 1, RSTART-1);
+ codes = substr($0, RSTART+2, RLENGTH-3);
+ if (length(codes) == 0)
+ printf "%s", name(0)
+ else {
+ n = split(codes, ary, ";");
+ sep = "";
+ for (i = 1; i <= n; i++) {
+ printf "%s%s", sep, name(ary[i]);
+ sep = ";"
+ }
+ }
+ printf ">";
+ $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
+ }
+ print
+ }
+ '
+}
+
+nul_to_q () {
+ perl -pe 'y/\000/Q/'
}
q_to_nul () {
@@ -255,6 +288,10 @@ q_to_cr () {
tr Q '\015'
}
+q_to_tab () {
+ tr Q '\011'
+}
+
append_cr () {
sed -e 's/$/Q/' | tr Q '\015'
}
@@ -263,6 +300,17 @@ remove_cr () {
tr '\015' Q | sed -e 's/Q$//'
}
+# In some bourne shell implementations, the "unset" builtin returns
+# nonzero status when a variable to be unset was not set in the first
+# place.
+#
+# Use sane_unset when that should not be considered an error.
+
+sane_unset () {
+ unset "$@"
+ return 0
+}
+
test_tick () {
if test -z "${test_tick+set}"
then
@@ -326,12 +374,44 @@ test_set_prereq () {
satisfied=" "
test_have_prereq () {
- case $satisfied in
- *" $1 "*)
- : yes, have it ;;
- *)
- ! : nope ;;
+ # prerequisites can be concatenated with ','
+ save_IFS=$IFS
+ IFS=,
+ set -- $*
+ IFS=$save_IFS
+
+ total_prereq=0
+ ok_prereq=0
+ missing_prereq=
+
+ for prerequisite
+ do
+ total_prereq=$(($total_prereq + 1))
+ case $satisfied in
+ *" $prerequisite "*)
+ ok_prereq=$(($ok_prereq + 1))
+ ;;
+ *)
+ # Keep a list of missing prerequisites
+ if test -z "$missing_prereq"
+ then
+ missing_prereq=$prerequisite
+ else
+ missing_prereq="$prerequisite,$missing_prereq"
+ fi
+ esac
+ done
+
+ test $total_prereq = $ok_prereq
+}
+
+test_declared_prereq () {
+ case ",$test_prereq," in
+ *,$1,*)
+ return 0
+ ;;
esac
+ return 1
}
# You are not expected to call test_ok_ and test_failure_ directly, use
@@ -339,25 +419,25 @@ test_have_prereq () {
test_ok_ () {
test_success=$(($test_success + 1))
- say_color "" " ok $test_count: $@"
+ say_color "" "ok $test_count - $@"
}
test_failure_ () {
test_failure=$(($test_failure + 1))
- say_color error "FAIL $test_count: $1"
+ say_color error "not ok - $test_count $1"
shift
- echo "$@" | sed -e 's/^/ /'
+ echo "$@" | sed -e 's/^/# /'
test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
}
test_known_broken_ok_ () {
test_fixed=$(($test_fixed+1))
- say_color "" " FIXED $test_count: $@"
+ say_color "" "ok $test_count - $@ # TODO known breakage"
}
test_known_broken_failure_ () {
test_broken=$(($test_broken+1))
- say_color skip " still broken $test_count: $@"
+ say_color skip "not ok $test_count - $@ # TODO known breakage"
}
test_debug () {
@@ -366,9 +446,17 @@ test_debug () {
test_run_ () {
test_cleanup=:
+ expecting_failure=$2
eval >&3 2>&4 "$1"
eval_ret=$?
- eval >&3 2>&4 "$test_cleanup"
+
+ if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
+ then
+ eval >&3 2>&4 "$test_cleanup"
+ fi
+ if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
+ echo ""
+ fi
return 0
}
@@ -380,17 +468,24 @@ test_skip () {
case $this_test.$test_count in
$skp)
to_skip=t
+ break
esac
done
- if test -z "$to_skip" && test -n "$prereq" &&
- ! test_have_prereq "$prereq"
+ if test -z "$to_skip" && test -n "$test_prereq" &&
+ ! test_have_prereq "$test_prereq"
then
to_skip=t
fi
case "$to_skip" in
t)
+ of_prereq=
+ if test "$missing_prereq" != "$test_prereq"
+ then
+ of_prereq=" of $test_prereq"
+ fi
+
say_color skip >&3 "skipping test: $@"
- say_color skip "skip $test_count: $1"
+ say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
: true
;;
*)
@@ -400,13 +495,14 @@ test_skip () {
}
test_expect_failure () {
- test "$#" = 3 && { prereq=$1; shift; } || prereq=
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
+ export test_prereq
if ! test_skip "$@"
then
say >&3 "checking known breakage: $2"
- test_run_ "$2"
+ test_run_ "$2" expecting_failure
if [ "$?" = 0 -a "$eval_ret" = 0 ]
then
test_known_broken_ok_ "$1"
@@ -418,9 +514,10 @@ test_expect_failure () {
}
test_expect_success () {
- test "$#" = 3 && { prereq=$1; shift; } || prereq=
+ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 2 ||
error "bug in the test script: not 2 or 3 parameters to test-expect-success"
+ export test_prereq
if ! test_skip "$@"
then
say >&3 "expecting success: $2"
@@ -435,43 +532,26 @@ test_expect_success () {
echo >&3 ""
}
-test_expect_code () {
- test "$#" = 4 && { prereq=$1; shift; } || prereq=
- test "$#" = 3 ||
- error "bug in the test script: not 3 or 4 parameters to test-expect-code"
- if ! test_skip "$@"
- then
- say >&3 "expecting exit code $1: $3"
- test_run_ "$3"
- if [ "$?" = 0 -a "$eval_ret" = "$1" ]
- then
- test_ok_ "$2"
- else
- test_failure_ "$@"
- fi
- fi
- echo >&3 ""
-}
-
# test_external runs external test scripts that provide continuous
# test output about their progress, and succeeds/fails on
# zero/non-zero exit code. It outputs the test output on stdout even
-# in non-verbose mode, and announces the external script with "* run
+# in non-verbose mode, and announces the external script with "# run
# <n>: ..." before running it. When providing relative paths, keep in
# mind that all scripts run in "trash directory".
# Usage: test_external description command arguments...
# Example: test_external 'Perl API' perl ../path/to/test.pl
test_external () {
- test "$#" = 4 && { prereq=$1; shift; } || prereq=
+ test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
test "$#" = 3 ||
error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
descr="$1"
shift
+ export test_prereq
if ! test_skip "$descr" "$@"
then
# Announce the script to reduce confusion about the
# test output that follows.
- say_color "" " run $test_count: $descr ($*)"
+ say_color "" "# run $test_count: $descr ($*)"
# Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG
# to be able to use them in script
export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG
@@ -481,9 +561,19 @@ test_external () {
"$@" 2>&4
if [ "$?" = 0 ]
then
- test_ok_ "$descr"
+ if test $test_external_has_tap -eq 0; then
+ test_ok_ "$descr"
+ else
+ say_color "" "# test_external test $descr was ok"
+ test_success=$(($test_success + 1))
+ fi
else
- test_failure_ "$descr" "$@"
+ if test $test_external_has_tap -eq 0; then
+ test_failure_ "$descr" "$@"
+ else
+ say_color error "# test_external test $descr failed: $@"
+ test_failure=$(($test_failure + 1))
+ fi
fi
fi
}
@@ -493,25 +583,90 @@ test_external () {
test_external_without_stderr () {
# The temporary file has no (and must have no) security
# implications.
- tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi
+ tmp=${TMPDIR:-/tmp}
stderr="$tmp/git-external-stderr.$$.tmp"
test_external "$@" 4> "$stderr"
[ -f "$stderr" ] || error "Internal error: $stderr disappeared."
descr="no stderr: $1"
shift
- say >&3 "expecting no stderr from previous command"
+ say >&3 "# expecting no stderr from previous command"
if [ ! -s "$stderr" ]; then
rm "$stderr"
- test_ok_ "$descr"
+
+ if test $test_external_has_tap -eq 0; then
+ test_ok_ "$descr"
+ else
+ say_color "" "# test_external_without_stderr test $descr was ok"
+ test_success=$(($test_success + 1))
+ fi
else
if [ "$verbose" = t ]; then
- output=`echo; echo Stderr is:; cat "$stderr"`
+ output=`echo; echo "# Stderr is:"; cat "$stderr"`
else
output=
fi
# rm first in case test_failure exits.
rm "$stderr"
- test_failure_ "$descr" "$@" "$output"
+ if test $test_external_has_tap -eq 0; then
+ test_failure_ "$descr" "$@" "$output"
+ else
+ say_color error "# test_external_without_stderr test $descr failed: $@: $output"
+ test_failure=$(($test_failure + 1))
+ fi
+ fi
+}
+
+# debugging-friendly alternatives to "test [-f|-d|-e]"
+# The commands test the existence or non-existence of $1. $2 can be
+# given to provide a more precise diagnosis.
+test_path_is_file () {
+ if ! [ -f "$1" ]
+ then
+ echo "File $1 doesn't exist. $*"
+ false
+ fi
+}
+
+test_path_is_dir () {
+ if ! [ -d "$1" ]
+ then
+ echo "Directory $1 doesn't exist. $*"
+ false
+ fi
+}
+
+test_path_is_missing () {
+ if [ -e "$1" ]
+ then
+ echo "Path exists:"
+ ls -ld "$1"
+ if [ $# -ge 1 ]; then
+ echo "$*"
+ fi
+ false
+ fi
+}
+
+# test_line_count checks that a file has the number of lines it
+# ought to. For example:
+#
+# test_expect_success 'produce exactly one line of output' '
+# do something >output &&
+# test_line_count = 1 output
+# '
+#
+# is like "test $(wc -l <output) = 1" except that it passes the
+# output through when the number of lines is wrong.
+
+test_line_count () {
+ if test $# != 3
+ then
+ error "bug in the test script: not 3 parameters to test_line_count"
+ elif ! test $(wc -l <"$3") "$1" "$2"
+ then
+ echo "test_line_count: line count for $3 !$1 $2"
+ cat "$3"
+ return 1
fi
}
@@ -529,7 +684,18 @@ test_external_without_stderr () {
test_must_fail () {
"$@"
- test $? -gt 0 -a $? -le 129 -o $? -gt 192
+ exit_code=$?
+ if test $exit_code = 0; then
+ echo >&2 "test_must_fail: command succeeded: $*"
+ return 1
+ elif test $exit_code -gt 129 -a $exit_code -le 192; then
+ echo >&2 "test_must_fail: died by signal: $*"
+ return 1
+ elif test $exit_code = 127; then
+ echo >&2 "test_must_fail: command not found: $*"
+ return 1
+ fi
+ return 0
}
# Similar to test_must_fail, but tolerates success, too. This is
@@ -545,7 +711,36 @@ test_must_fail () {
test_might_fail () {
"$@"
- test $? -ge 0 -a $? -le 129 -o $? -gt 192
+ exit_code=$?
+ if test $exit_code -gt 129 -a $exit_code -le 192; then
+ echo >&2 "test_might_fail: died by signal: $*"
+ return 1
+ elif test $exit_code = 127; then
+ echo >&2 "test_might_fail: command not found: $*"
+ return 1
+ fi
+ return 0
+}
+
+# Similar to test_must_fail and test_might_fail, but check that a
+# given command exited with a given exit code. Meant to be used as:
+#
+# test_expect_success 'Merge with d/f conflicts' '
+# test_expect_code 1 git merge "merge msg" B master
+# '
+
+test_expect_code () {
+ want_code=$1
+ shift
+ "$@"
+ exit_code=$?
+ if test $exit_code = $want_code
+ then
+ return 0
+ fi
+
+ echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
+ return 1
}
# test_cmp is a helper function to compare actual and expected output.
@@ -584,6 +779,9 @@ test_cmp() {
#
# except that the greeting and config --unset must both succeed for
# the test to pass.
+#
+# Note that under --immediate mode, no clean-up is done to help diagnose
+# what went wrong.
test_when_finished () {
test_cleanup="{ $*
@@ -595,43 +793,54 @@ test_when_finished () {
test_create_repo () {
test "$#" = 1 ||
error "bug in the test script: not 1 parameter to test-create-repo"
- owd=`pwd`
repo="$1"
mkdir -p "$repo"
- cd "$repo" || error "Cannot setup test environment"
- "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 ||
- error "cannot run git init -- have you built things yet?"
- mv .git/hooks .git/hooks-disabled
- cd "$owd"
+ (
+ cd "$repo" || error "Cannot setup test environment"
+ "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 ||
+ error "cannot run git init -- have you built things yet?"
+ mv .git/hooks .git/hooks-disabled
+ ) || exit
}
test_done () {
GIT_EXIT_OK=t
- test_results_dir="$TEST_DIRECTORY/test-results"
- mkdir -p "$test_results_dir"
- test_results_path="$test_results_dir/${0%.sh}-$$"
- echo "total $test_count" >> $test_results_path
- echo "success $test_success" >> $test_results_path
- echo "fixed $test_fixed" >> $test_results_path
- echo "broken $test_broken" >> $test_results_path
- echo "failed $test_failure" >> $test_results_path
- echo "" >> $test_results_path
+ if test -z "$HARNESS_ACTIVE"; then
+ test_results_dir="$TEST_DIRECTORY/test-results"
+ mkdir -p "$test_results_dir"
+ test_results_path="$test_results_dir/${0%.sh}-$$.counts"
+
+ cat >>"$test_results_path" <<-EOF
+ total $test_count
+ success $test_success
+ fixed $test_fixed
+ broken $test_broken
+ failed $test_failure
+
+ EOF
+ fi
if test "$test_fixed" != 0
then
- say_color pass "fixed $test_fixed known breakage(s)"
+ say_color pass "# fixed $test_fixed known breakage(s)"
fi
if test "$test_broken" != 0
then
- say_color error "still have $test_broken known breakage(s)"
+ say_color error "# still have $test_broken known breakage(s)"
msg="remaining $(($test_count-$test_broken)) test(s)"
else
msg="$test_count test(s)"
fi
case "$test_failure" in
0)
- say_color pass "passed all $msg"
+ # Maybe print SKIP message
+ [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
+
+ if test $test_external_has_tap -eq 0; then
+ say_color pass "# passed all $msg"
+ say "1..$test_count$skip_all"
+ fi
test -d "$remove_trash" &&
cd "$(dirname "$remove_trash")" &&
@@ -640,7 +849,11 @@ test_done () {
exit 0 ;;
*)
- say_color error "failed $test_failure among $msg"
+ if test $test_external_has_tap -eq 0; then
+ say_color error "# failed $test_failure among $msg"
+ say "1..$test_count"
+ fi
+
exit 1 ;;
esac
@@ -648,7 +861,15 @@ test_done () {
# Test the binaries we have just built. The tests are kept in
# t/ subdirectory and are run in 'trash directory' subdirectory.
-TEST_DIRECTORY=$(pwd)
+if test -z "$TEST_DIRECTORY"
+then
+ # We allow tests to override this, in case they want to run tests
+ # outside of t/, e.g. for running tests on the test library
+ # itself.
+ TEST_DIRECTORY=$(pwd)
+fi
+GIT_BUILD_DIR="$TEST_DIRECTORY"/..
+
if test -n "$valgrind"
then
make_symlink () {
@@ -671,11 +892,16 @@ then
}
make_valgrind_symlink () {
- # handle only executables
- test -x "$1" || return
+ # handle only executables, unless they are shell libraries that
+ # need to be in the exec-path. We will just use "#!" as a
+ # guess for a shell-script, since we have no idea what the user
+ # may have configured as the shell path.
+ test -x "$1" ||
+ test "#!" = "$(head -c 2 <"$1")" ||
+ return;
base=$(basename "$1")
- symlink_target=$TEST_DIRECTORY/../$base
+ symlink_target=$GIT_BUILD_DIR/$base
# do not override scripts
if test -x "$symlink_target" &&
test ! -d "$symlink_target" &&
@@ -694,7 +920,7 @@ then
# override all git executables in TEST_DIRECTORY/..
GIT_VALGRIND=$TEST_DIRECTORY/valgrind
mkdir -p "$GIT_VALGRIND"/bin
- for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-*
+ for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
do
make_valgrind_symlink $file
done
@@ -715,10 +941,10 @@ then
elif test -n "$GIT_TEST_INSTALLED" ; then
GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
error "Cannot run git from $GIT_TEST_INSTALLED."
- PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
+ PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH
GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
else # normal case, use ../bin-wrappers only unless $with_dashes:
- git_bin_dir="$TEST_DIRECTORY/../bin-wrappers"
+ git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
if ! test -x "$git_bin_dir/git" ; then
if test -z "$with_dashes" ; then
say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
@@ -726,18 +952,18 @@ else # normal case, use ../bin-wrappers only unless $with_dashes:
with_dashes=t
fi
PATH="$git_bin_dir:$PATH"
- GIT_EXEC_PATH=$TEST_DIRECTORY/..
+ GIT_EXEC_PATH=$GIT_BUILD_DIR
if test -n "$with_dashes" ; then
- PATH="$TEST_DIRECTORY/..:$PATH"
+ PATH="$GIT_BUILD_DIR:$PATH"
fi
fi
-GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
+GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
unset GIT_CONFIG
GIT_CONFIG_NOSYSTEM=1
-GIT_CONFIG_NOGLOBAL=1
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
+GIT_ATTR_NOSYSTEM=1
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
-. ../GIT-BUILD-OPTIONS
+. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
if test -z "$GIT_TEST_CMP"
then
@@ -749,22 +975,22 @@ then
fi
fi
-GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
+GITPERLLIB="$GIT_BUILD_DIR"/perl/blib/lib:"$GIT_BUILD_DIR"/perl/blib/arch/auto/Git
export GITPERLLIB
-test -d ../templates/blt || {
+test -d "$GIT_BUILD_DIR"/templates/blt || {
error "You haven't built things yet, have you?"
}
if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
then
- GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
+ GITPYTHONLIB="$GIT_BUILD_DIR/git_remote_helpers/build/lib"
export GITPYTHONLIB
- test -d ../git_remote_helpers/build || {
+ test -d "$GIT_BUILD_DIR"/git_remote_helpers/build || {
error "You haven't built git_remote_helpers yet, have you?"
}
fi
-if ! test -x ../test-chmtime; then
+if ! test -x "$GIT_BUILD_DIR"/test-chmtime; then
echo >&2 'You need to build test-chmtime:'
echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
exit 1
@@ -784,6 +1010,9 @@ rm -fr "$test" || {
exit 1
}
+HOME="$TRASH_DIRECTORY"
+export HOME
+
test_create_repo "$test"
# Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons).
@@ -793,18 +1022,10 @@ this_test=${0##*/}
this_test=${this_test%%-*}
for skp in $GIT_SKIP_TESTS
do
- to_skip=
- for skp in $GIT_SKIP_TESTS
- do
- case "$this_test" in
- $skp)
- to_skip=t
- esac
- done
- case "$to_skip" in
- t)
+ case "$this_test" in
+ $skp)
say_color skip >&3 "skipping test $this_test altogether"
- say_color skip "skip all tests in $this_test"
+ skip_all="skip all tests in $this_test"
test_done
esac
done
@@ -844,17 +1065,66 @@ case $(uname -s) in
# no POSIX permissions
# backslashes in pathspec are converted to '/'
# exec does not inherit the PID
+ test_set_prereq MINGW
+ test_set_prereq SED_STRIPS_CR
+ ;;
+*CYGWIN*)
+ test_set_prereq POSIXPERM
+ test_set_prereq EXECKEEPSPID
+ test_set_prereq NOT_MINGW
+ test_set_prereq SED_STRIPS_CR
;;
*)
test_set_prereq POSIXPERM
test_set_prereq BSLASHPSPEC
test_set_prereq EXECKEEPSPID
+ test_set_prereq NOT_MINGW
;;
esac
test -z "$NO_PERL" && test_set_prereq PERL
test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+
+# Can we rely on git's output in the C locale?
+if test -n "$GETTEXT_POISON"
+then
+ GIT_GETTEXT_POISON=YesPlease
+ export GIT_GETTEXT_POISON
+else
+ test_set_prereq C_LOCALE_OUTPUT
+fi
+
+# Use this instead of test_cmp to compare files that contain expected and
+# actual output from git commands that can be translated. When running
+# under GETTEXT_POISON this pretends that the command produced expected
+# results.
+test_i18ncmp () {
+ test -n "$GETTEXT_POISON" || test_cmp "$@"
+}
+
+# Use this instead of "grep expected-string actual" to see if the
+# output from a git command that can be translated either contains an
+# expected string, or does not contain an unwanted one. When running
+# under GETTEXT_POISON this pretends that the command produced expected
+# results.
+test_i18ngrep () {
+ if test -n "$GETTEXT_POISON"
+ then
+ : # pretend success
+ elif test "x!" = "x$1"
+ then
+ shift
+ ! grep "$@"
+ else
+ grep "$@"
+ fi
+}
# test whether the filesystem supports symbolic links
ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
rm -f y
+
+# When the tests are run as root, permission tests will report that
+# things are writable when they shouldn't be.
+test -w / || test_set_prereq SANITY
diff --git a/t/t7006/test-terminal.perl b/t/test-terminal.perl
index 73ff809..ee01eb9 100755
--- a/t/t7006/test-terminal.perl
+++ b/t/test-terminal.perl
@@ -1,17 +1,19 @@
#!/usr/bin/perl
+use 5.008;
use strict;
use warnings;
use IO::Pty;
use File::Copy;
-# Run @$argv in the background with stdout redirected to $out.
+# Run @$argv in the background with stdio redirected to $out and $err.
sub start_child {
- my ($argv, $out) = @_;
+ my ($argv, $out, $err) = @_;
my $pid = fork;
if (not defined $pid) {
die "fork failed: $!"
} elsif ($pid == 0) {
open STDOUT, ">&", $out;
+ open STDERR, ">&", $err;
close $out;
exec(@$argv) or die "cannot exec '$argv->[0]': $!"
}
@@ -47,12 +49,28 @@ sub xsendfile {
copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!";
}
+sub copy_stdio {
+ my ($out, $err) = @_;
+ my $pid = fork;
+ defined $pid or die "fork failed: $!";
+ if (!$pid) {
+ close($out);
+ xsendfile(\*STDERR, $err);
+ exit 0;
+ }
+ close($err);
+ xsendfile(\*STDOUT, $out);
+ finish_child($pid) == 0
+ or exit 1;
+}
+
if ($#ARGV < 1) {
die "usage: test-terminal program args";
}
-my $master = new IO::Pty;
-my $slave = $master->slave;
-my $pid = start_child(\@ARGV, $slave);
-close $slave;
-xsendfile(\*STDOUT, $master);
+my $master_out = new IO::Pty;
+my $master_err = new IO::Pty;
+my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave);
+close $master_out->slave;
+close $master_err->slave;
+copy_stdio($master_out, $master_err);
exit(finish_child($pid));
diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp
index 9e013fa..0a6724f 100644
--- a/t/valgrind/default.supp
+++ b/t/valgrind/default.supp
@@ -43,3 +43,9 @@
fun:write_buffer
fun:write_loose_object
}
+
+{
+ ignore-sse-strlen-invalid-read-size
+ Memcheck:Addr4
+ fun:copy_ref
+}
diff --git a/tag.c b/tag.c
index 85607c2..7d38cc0 100644
--- a/tag.c
+++ b/tag.c
@@ -4,6 +4,9 @@
#include "tree.h"
#include "blob.h"
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
+
const char *tag_type = "tag";
struct object *deref_tag(struct object *o, const char *warn, int warnlen)
@@ -28,12 +31,12 @@ struct tag *lookup_tag(const unsigned char *sha1)
return create_object(sha1, OBJ_TAG, alloc_tag_node());
if (!obj->type)
obj->type = OBJ_TAG;
- if (obj->type != OBJ_TAG) {
- error("Object %s is a %s, not a tag",
- sha1_to_hex(sha1), typename(obj->type));
- return NULL;
- }
- return (struct tag *) obj;
+ if (obj->type != OBJ_TAG) {
+ error("Object %s is a %s, not a tag",
+ sha1_to_hex(sha1), typename(obj->type));
+ return NULL;
+ }
+ return (struct tag *) obj;
}
static unsigned long parse_tag_date(const char *buf, const char *tail)
@@ -53,7 +56,7 @@ static unsigned long parse_tag_date(const char *buf, const char *tail)
return strtoul(dateptr, NULL, 10);
}
-int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
+int parse_tag_buffer(struct tag *item, const void *data, unsigned long size)
{
unsigned char sha1[20];
char type[20];
@@ -94,7 +97,9 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
item->tagged = NULL;
}
- if (prefixcmp(bufptr, "tag "))
+ if (bufptr + 4 < tail && !prefixcmp(bufptr, "tag "))
+ ; /* good */
+ else
return -1;
bufptr += 4;
nl = memchr(bufptr, '\n', tail - bufptr);
@@ -103,7 +108,7 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
item->tag = xmemdupz(bufptr, nl - bufptr);
bufptr = nl + 1;
- if (!prefixcmp(bufptr, "tagger "))
+ if (bufptr + 7 < tail && !prefixcmp(bufptr, "tagger "))
item->date = parse_tag_date(bufptr, tail);
else
item->date = 0;
@@ -133,3 +138,15 @@ int parse_tag(struct tag *item)
free(data);
return ret;
}
+
+size_t parse_signature(const char *buf, unsigned long size)
+{
+ char *eol;
+ size_t len = 0;
+ while (len < size && prefixcmp(buf + len, PGP_SIGNATURE) &&
+ prefixcmp(buf + len, PGP_MESSAGE)) {
+ eol = memchr(buf + len, '\n', size - len);
+ len += eol ? eol - (buf + len) + 1 : size - len;
+ }
+ return len;
+}
diff --git a/tag.h b/tag.h
index 4766272..5ee88e6 100644
--- a/tag.h
+++ b/tag.h
@@ -13,8 +13,9 @@ struct tag {
};
extern struct tag *lookup_tag(const unsigned char *sha1);
-extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
+extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long size);
extern int parse_tag(struct tag *item);
extern struct object *deref_tag(struct object *, const char *, int);
+extern size_t parse_signature(const char *buf, unsigned long size);
#endif /* TAG_H */
diff --git a/test-date.c b/test-date.c
index a9e705f..6bcd5b0 100644
--- a/test-date.c
+++ b/test-date.c
@@ -20,13 +20,16 @@ static void parse_dates(char **argv, struct timeval *now)
{
for (; *argv; argv++) {
char result[100];
- time_t t;
+ unsigned long t;
+ int tz;
result[0] = 0;
parse_date(*argv, result, sizeof(result));
- t = strtoul(result, NULL, 0);
- printf("%s -> %s\n", *argv,
- t ? show_date(t, 0, DATE_ISO8601) : "bad");
+ if (sscanf(result, "%lu %d", &t, &tz) == 2)
+ printf("%s -> %s\n",
+ *argv, show_date(t, tz, DATE_ISO8601));
+ else
+ printf("%s -> bad\n", *argv);
}
}
diff --git a/test-line-buffer.c b/test-line-buffer.c
new file mode 100644
index 0000000..7ec9b13
--- /dev/null
+++ b/test-line-buffer.c
@@ -0,0 +1,92 @@
+/*
+ * test-line-buffer.c: code to exercise the svn importer's input helper
+ */
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+#include "vcs-svn/line_buffer.h"
+
+static uint32_t strtouint32(const char *s)
+{
+ char *end;
+ uintmax_t n = strtoumax(s, &end, 10);
+ if (*s == '\0' || *end != '\0')
+ die("invalid count: %s", s);
+ return (uint32_t) n;
+}
+
+static void handle_command(const char *command, const char *arg, struct line_buffer *buf)
+{
+ switch (*command) {
+ case 'b':
+ if (!prefixcmp(command, "binary ")) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addch(&sb, '>');
+ buffer_read_binary(buf, &sb, strtouint32(arg));
+ fwrite(sb.buf, 1, sb.len, stdout);
+ strbuf_release(&sb);
+ return;
+ }
+ case 'c':
+ if (!prefixcmp(command, "copy ")) {
+ buffer_copy_bytes(buf, strtouint32(arg));
+ return;
+ }
+ case 's':
+ if (!prefixcmp(command, "skip ")) {
+ buffer_skip_bytes(buf, strtouint32(arg));
+ return;
+ }
+ default:
+ die("unrecognized command: %s", command);
+ }
+}
+
+static void handle_line(const char *line, struct line_buffer *stdin_buf)
+{
+ const char *arg = strchr(line, ' ');
+ if (!arg)
+ die("no argument in line: %s", line);
+ handle_command(line, arg + 1, stdin_buf);
+}
+
+int main(int argc, char *argv[])
+{
+ struct line_buffer stdin_buf = LINE_BUFFER_INIT;
+ struct line_buffer file_buf = LINE_BUFFER_INIT;
+ struct line_buffer *input = &stdin_buf;
+ const char *filename;
+ char *s;
+
+ if (argc == 1)
+ filename = NULL;
+ else if (argc == 2)
+ filename = argv[1];
+ else
+ usage("test-line-buffer [file | &fd] < script");
+
+ if (buffer_init(&stdin_buf, NULL))
+ die_errno("open error");
+ if (filename) {
+ if (*filename == '&') {
+ if (buffer_fdinit(&file_buf, strtouint32(filename + 1)))
+ die_errno("error opening fd %s", filename + 1);
+ } else {
+ if (buffer_init(&file_buf, filename))
+ die_errno("error opening %s", filename);
+ }
+ input = &file_buf;
+ }
+
+ while ((s = buffer_read_line(&stdin_buf)))
+ handle_line(s, input);
+
+ if (filename && buffer_deinit(&file_buf))
+ die("error reading from %s", filename);
+ if (buffer_deinit(&stdin_buf))
+ die("input error");
+ if (ferror(stdout))
+ die("output error");
+ buffer_reset(&stdin_buf);
+ return 0;
+}
diff --git a/test-mktemp.c b/test-mktemp.c
new file mode 100644
index 0000000..c8c5421
--- /dev/null
+++ b/test-mktemp.c
@@ -0,0 +1,14 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */
+#include "git-compat-util.h"
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ usage("Expected 1 parameter defining the temporary file template");
+
+ xmkstemp(xstrdup(argv[1]));
+
+ return 0;
+}
diff --git a/test-obj-pool.c b/test-obj-pool.c
new file mode 100644
index 0000000..5018863
--- /dev/null
+++ b/test-obj-pool.c
@@ -0,0 +1,116 @@
+/*
+ * test-obj-pool.c: code to exercise the svn importer's object pool
+ */
+
+#include "cache.h"
+#include "vcs-svn/obj_pool.h"
+
+enum pool { POOL_ONE, POOL_TWO };
+obj_pool_gen(one, int, 1)
+obj_pool_gen(two, int, 4096)
+
+static uint32_t strtouint32(const char *s)
+{
+ char *end;
+ uintmax_t n = strtoumax(s, &end, 10);
+ if (*s == '\0' || (*end != '\n' && *end != '\0'))
+ die("invalid offset: %s", s);
+ return (uint32_t) n;
+}
+
+static void handle_command(const char *command, enum pool pool, const char *arg)
+{
+ switch (*command) {
+ case 'a':
+ if (!prefixcmp(command, "alloc ")) {
+ uint32_t n = strtouint32(arg);
+ printf("%"PRIu32"\n",
+ pool == POOL_ONE ?
+ one_alloc(n) : two_alloc(n));
+ return;
+ }
+ case 'c':
+ if (!prefixcmp(command, "commit ")) {
+ pool == POOL_ONE ? one_commit() : two_commit();
+ return;
+ }
+ if (!prefixcmp(command, "committed ")) {
+ printf("%"PRIu32"\n",
+ pool == POOL_ONE ?
+ one_pool.committed : two_pool.committed);
+ return;
+ }
+ case 'f':
+ if (!prefixcmp(command, "free ")) {
+ uint32_t n = strtouint32(arg);
+ pool == POOL_ONE ? one_free(n) : two_free(n);
+ return;
+ }
+ case 'n':
+ if (!prefixcmp(command, "null ")) {
+ printf("%"PRIu32"\n",
+ pool == POOL_ONE ?
+ one_offset(NULL) : two_offset(NULL));
+ return;
+ }
+ case 'o':
+ if (!prefixcmp(command, "offset ")) {
+ uint32_t n = strtouint32(arg);
+ printf("%"PRIu32"\n",
+ pool == POOL_ONE ?
+ one_offset(one_pointer(n)) :
+ two_offset(two_pointer(n)));
+ return;
+ }
+ case 'r':
+ if (!prefixcmp(command, "reset ")) {
+ pool == POOL_ONE ? one_reset() : two_reset();
+ return;
+ }
+ case 's':
+ if (!prefixcmp(command, "set ")) {
+ uint32_t n = strtouint32(arg);
+ if (pool == POOL_ONE)
+ *one_pointer(n) = 1;
+ else
+ *two_pointer(n) = 1;
+ return;
+ }
+ case 't':
+ if (!prefixcmp(command, "test ")) {
+ uint32_t n = strtouint32(arg);
+ printf("%d\n", pool == POOL_ONE ?
+ *one_pointer(n) : *two_pointer(n));
+ return;
+ }
+ default:
+ die("unrecognized command: %s", command);
+ }
+}
+
+static void handle_line(const char *line)
+{
+ const char *arg = strchr(line, ' ');
+ enum pool pool;
+
+ if (arg && !prefixcmp(arg + 1, "one"))
+ pool = POOL_ONE;
+ else if (arg && !prefixcmp(arg + 1, "two"))
+ pool = POOL_TWO;
+ else
+ die("no pool specified: %s", line);
+
+ handle_command(line, pool, arg + strlen("one "));
+}
+
+int main(int argc, char *argv[])
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (argc != 1)
+ usage("test-obj-str < script");
+
+ while (strbuf_getline(&sb, stdin, '\n') != EOF)
+ handle_line(sb.buf);
+ strbuf_release(&sb);
+ return 0;
+}
diff --git a/test-parse-options.c b/test-parse-options.c
index acd1a2b..91a5701 100644
--- a/test-parse-options.c
+++ b/test-parse-options.c
@@ -1,5 +1,6 @@
#include "cache.h"
#include "parse-options.h"
+#include "string-list.h"
static int boolean = 0;
static int integer = 0;
@@ -9,6 +10,7 @@ static int verbose = 0, dry_run = 0, quiet = 0;
static char *string = NULL;
static char *file = NULL;
static int ambiguous;
+static struct string_list list;
static int length_callback(const struct option *opt, const char *arg, int unset)
{
@@ -46,7 +48,7 @@ int main(int argc, const char **argv)
OPT_DATE('t', NULL, &timestamp, "get timestamp of <time>"),
OPT_CALLBACK('L', "length", &integer, "str",
"get length of <str>", length_callback),
- OPT_FILENAME('F', "file", &file, "set file to <FILE>"),
+ OPT_FILENAME('F', "file", &file, "set file to <file>"),
OPT_GROUP("String options"),
OPT_STRING('s', "string", &string, "string", "get a string"),
OPT_STRING(0, "string2", &string, "str", "get another string"),
@@ -54,6 +56,7 @@ int main(int argc, const char **argv)
OPT_STRING('o', NULL, &string, "str", "get another string"),
OPT_SET_PTR(0, "default-string", &string,
"set string to default", (unsigned long)"default"),
+ OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
OPT_GROUP("Magic arguments"),
OPT_ARGUMENT("quux", "means --quux"),
OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
@@ -66,9 +69,9 @@ int main(int argc, const char **argv)
"negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG },
OPT_GROUP("Standard options"),
OPT__ABBREV(&abbrev),
- OPT__VERBOSE(&verbose),
- OPT__DRY_RUN(&dry_run),
- OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__DRY_RUN(&dry_run, "dry run"),
+ OPT__QUIET(&quiet, "be quiet"),
OPT_END(),
};
int i;
@@ -85,6 +88,9 @@ int main(int argc, const char **argv)
printf("dry run: %s\n", dry_run ? "yes" : "no");
printf("file: %s\n", file ? file : "(not set)");
+ for (i = 0; i < list.nr; i++)
+ printf("list: %s\n", list.items[i].string);
+
for (i = 0; i < argc; i++)
printf("arg %02d: %s\n", i, argv[i]);
diff --git a/test-path-utils.c b/test-path-utils.c
index d261398..e767159 100644
--- a/test-path-utils.c
+++ b/test-path-utils.c
@@ -11,9 +11,9 @@ int main(int argc, char **argv)
return 0;
}
- if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) {
+ if (argc >= 2 && !strcmp(argv[1], "real_path")) {
while (argc > 2) {
- puts(make_absolute_path(argv[2]));
+ puts(real_path(argv[2]));
argc--;
argv++;
}
diff --git a/test-run-command.c b/test-run-command.c
index 0612bfa..37918e1 100644
--- a/test-run-command.c
+++ b/test-run-command.c
@@ -29,6 +29,8 @@ int main(int argc, char **argv)
fprintf(stderr, "FAIL %s\n", argv[1]);
return 1;
}
+ if (!strcmp(argv[1], "run-command"))
+ exit(run_command(&proc));
fprintf(stderr, "check usage\n");
return 1;
diff --git a/test-string-pool.c b/test-string-pool.c
new file mode 100644
index 0000000..c5782e6
--- /dev/null
+++ b/test-string-pool.c
@@ -0,0 +1,31 @@
+/*
+ * test-string-pool.c: code to exercise the svn importer's string pool
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/string_pool.h"
+
+int main(int argc, char *argv[])
+{
+ const uint32_t unequal = pool_intern("does not equal");
+ const uint32_t equal = pool_intern("equals");
+ uint32_t buf[3];
+ uint32_t n;
+
+ if (argc != 2)
+ usage("test-string-pool <string>,<string>");
+
+ n = pool_tok_seq(3, buf, ",-", argv[1]);
+ if (n >= 3)
+ die("too many strings");
+ if (n <= 1)
+ die("too few strings");
+
+ buf[2] = buf[1];
+ buf[1] = (buf[0] == buf[2]) ? equal : unequal;
+ pool_print_seq(3, buf, ' ', stdout);
+ fputc('\n', stdout);
+
+ pool_reset();
+ return 0;
+}
diff --git a/test-subprocess.c b/test-subprocess.c
new file mode 100644
index 0000000..8926bc5
--- /dev/null
+++ b/test-subprocess.c
@@ -0,0 +1,20 @@
+#include "cache.h"
+#include "run-command.h"
+
+int main(int argc, char **argv)
+{
+ struct child_process cp;
+ int nogit = 0;
+
+ setup_git_directory_gently(&nogit);
+ if (nogit)
+ die("No git repo found");
+ if (!strcmp(argv[1], "--setup-work-tree")) {
+ setup_work_tree();
+ argv++;
+ }
+ memset(&cp, 0, sizeof(cp));
+ cp.git_cmd = 1;
+ cp.argv = (const char **)argv+1;
+ return run_command(&cp);
+}
diff --git a/test-svn-fe.c b/test-svn-fe.c
new file mode 100644
index 0000000..b42ba78
--- /dev/null
+++ b/test-svn-fe.c
@@ -0,0 +1,18 @@
+/*
+ * test-svn-fe: Code to exercise the svn import lib
+ */
+
+#include "git-compat-util.h"
+#include "vcs-svn/svndump.h"
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ usage("test-svn-fe <file>");
+ if (svndump_init(argv[1]))
+ return 1;
+ svndump_read(NULL);
+ svndump_deinit();
+ svndump_reset();
+ return 0;
+}
diff --git a/test-treap.c b/test-treap.c
new file mode 100644
index 0000000..ab8c951
--- /dev/null
+++ b/test-treap.c
@@ -0,0 +1,70 @@
+/*
+ * test-treap.c: code to exercise the svn importer's treap structure
+ */
+
+#include "cache.h"
+#include "vcs-svn/obj_pool.h"
+#include "vcs-svn/trp.h"
+
+struct int_node {
+ uintmax_t n;
+ struct trp_node children;
+};
+
+obj_pool_gen(node, struct int_node, 3)
+
+static int node_cmp(struct int_node *a, struct int_node *b)
+{
+ return (a->n > b->n) - (a->n < b->n);
+}
+
+trp_gen(static, treap_, struct int_node, children, node, node_cmp)
+
+static void strtonode(struct int_node *item, const char *s)
+{
+ char *end;
+ item->n = strtoumax(s, &end, 10);
+ if (*s == '\0' || (*end != '\n' && *end != '\0'))
+ die("invalid integer: %s", s);
+}
+
+int main(int argc, char *argv[])
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct trp_root root = { ~0 };
+ uint32_t item;
+
+ if (argc != 1)
+ usage("test-treap < ints");
+
+ while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+ struct int_node *node = node_pointer(node_alloc(1));
+
+ item = node_offset(node);
+ strtonode(node, sb.buf);
+ node = treap_insert(&root, node_pointer(item));
+ if (node_offset(node) != item)
+ die("inserted %"PRIu32" in place of %"PRIu32"",
+ node_offset(node), item);
+ }
+
+ item = node_offset(treap_first(&root));
+ while (~item) {
+ uint32_t next;
+ struct int_node *tmp = node_pointer(node_alloc(1));
+
+ tmp->n = node_pointer(item)->n;
+ next = node_offset(treap_next(&root, node_pointer(item)));
+
+ treap_remove(&root, node_pointer(item));
+ item = node_offset(treap_nsearch(&root, tmp));
+
+ if (item != next && (!~item || node_pointer(item)->n != tmp->n))
+ die("found %"PRIuMAX" in place of %"PRIuMAX"",
+ ~item ? node_pointer(item)->n : ~(uintmax_t) 0,
+ ~next ? node_pointer(next)->n : ~(uintmax_t) 0);
+ printf("%"PRIuMAX"\n", tmp->n);
+ }
+ node_reset();
+ return 0;
+}
diff --git a/thread-utils.c b/thread-utils.c
index 589f838..7f4b76a 100644
--- a/thread-utils.c
+++ b/thread-utils.c
@@ -1,5 +1,5 @@
#include "cache.h"
-#include <pthread.h>
+#include "thread-utils.h"
#if defined(hpux) || defined(__hpux) || defined(_hpux)
# include <sys/pstat.h>
diff --git a/thread-utils.h b/thread-utils.h
index 1727a03..6fb98c3 100644
--- a/thread-utils.h
+++ b/thread-utils.h
@@ -1,7 +1,11 @@
#ifndef THREAD_COMPAT_H
#define THREAD_COMPAT_H
+#ifndef NO_PTHREADS
+#include <pthread.h>
+
extern int online_cpus(void);
extern int init_recursive_mutex(pthread_mutex_t*);
+#endif
#endif /* THREAD_COMPAT_H */
diff --git a/trace.c b/trace.c
index 1e560cb..d953416 100644
--- a/trace.c
+++ b/trace.c
@@ -25,14 +25,10 @@
#include "cache.h"
#include "quote.h"
-void do_nothing(size_t unused)
+/* Get a trace file descriptor from "key" env variable. */
+static int get_trace_fd(const char *key, int *need_close)
{
-}
-
-/* Get a trace file descriptor from GIT_TRACE env variable. */
-static int get_trace_fd(int *need_close)
-{
- char *trace = getenv("GIT_TRACE");
+ char *trace = getenv(key);
if (!trace || !strcmp(trace, "") ||
!strcmp(trace, "0") || !strcasecmp(trace, "false"))
@@ -54,10 +50,10 @@ static int get_trace_fd(int *need_close)
return fd;
}
- fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
+ fprintf(stderr, "What does '%s' for %s mean?\n", trace, key);
fprintf(stderr, "If you want to trace into a file, "
- "then please set GIT_TRACE to an absolute pathname "
- "(starting with /).\n");
+ "then please set %s to an absolute pathname "
+ "(starting with /).\n", key);
fprintf(stderr, "Defaulting to tracing on stderr...\n");
return STDERR_FILENO;
@@ -66,33 +62,44 @@ static int get_trace_fd(int *need_close)
static const char err_msg[] = "Could not trace into fd given by "
"GIT_TRACE environment variable";
-void trace_printf(const char *fmt, ...)
+void trace_vprintf(const char *key, const char *fmt, va_list ap)
{
- struct strbuf buf;
- va_list ap;
- int fd, len, need_close = 0;
+ struct strbuf buf = STRBUF_INIT;
- fd = get_trace_fd(&need_close);
- if (!fd)
+ if (!trace_want(key))
return;
- set_try_to_free_routine(do_nothing); /* is never reset */
- strbuf_init(&buf, 64);
+ set_try_to_free_routine(NULL); /* is never reset */
+ strbuf_vaddf(&buf, fmt, ap);
+ trace_strbuf(key, &buf);
+ strbuf_release(&buf);
+}
+
+static void trace_printf_key(const char *key, const char *fmt, ...)
+{
+ va_list ap;
va_start(ap, fmt);
- len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
+ trace_vprintf(key, fmt, ap);
va_end(ap);
- if (len >= strbuf_avail(&buf)) {
- strbuf_grow(&buf, len - strbuf_avail(&buf) + 128);
- va_start(ap, fmt);
- len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
- va_end(ap);
- if (len >= strbuf_avail(&buf))
- die("broken vsnprintf");
- }
- strbuf_setlen(&buf, len);
+}
- write_or_whine_pipe(fd, buf.buf, buf.len, err_msg);
- strbuf_release(&buf);
+void trace_printf(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ trace_vprintf("GIT_TRACE", fmt, ap);
+ va_end(ap);
+}
+
+void trace_strbuf(const char *key, const struct strbuf *buf)
+{
+ int fd, need_close = 0;
+
+ fd = get_trace_fd(key, &need_close);
+ if (!fd)
+ return;
+
+ write_or_whine_pipe(fd, buf->buf, buf->len, err_msg);
if (need_close)
close(fd);
@@ -100,28 +107,18 @@ void trace_printf(const char *fmt, ...)
void trace_argv_printf(const char **argv, const char *fmt, ...)
{
- struct strbuf buf;
+ struct strbuf buf = STRBUF_INIT;
va_list ap;
- int fd, len, need_close = 0;
+ int fd, need_close = 0;
- fd = get_trace_fd(&need_close);
+ fd = get_trace_fd("GIT_TRACE", &need_close);
if (!fd)
return;
- set_try_to_free_routine(do_nothing); /* is never reset */
- strbuf_init(&buf, 64);
+ set_try_to_free_routine(NULL); /* is never reset */
va_start(ap, fmt);
- len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
+ strbuf_vaddf(&buf, fmt, ap);
va_end(ap);
- if (len >= strbuf_avail(&buf)) {
- strbuf_grow(&buf, len - strbuf_avail(&buf) + 128);
- va_start(ap, fmt);
- len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
- va_end(ap);
- if (len >= strbuf_avail(&buf))
- die("broken vsnprintf");
- }
- strbuf_setlen(&buf, len);
sq_quote_argv(&buf, argv, 0);
strbuf_addch(&buf, '\n');
@@ -131,3 +128,61 @@ void trace_argv_printf(const char **argv, const char *fmt, ...)
if (need_close)
close(fd);
}
+
+static const char *quote_crnl(const char *path)
+{
+ static char new_path[PATH_MAX];
+ const char *p2 = path;
+ char *p1 = new_path;
+
+ if (!path)
+ return NULL;
+
+ while (*p2) {
+ switch (*p2) {
+ case '\\': *p1++ = '\\'; *p1++ = '\\'; break;
+ case '\n': *p1++ = '\\'; *p1++ = 'n'; break;
+ case '\r': *p1++ = '\\'; *p1++ = 'r'; break;
+ default:
+ *p1++ = *p2;
+ }
+ p2++;
+ }
+ *p1 = '\0';
+ return new_path;
+}
+
+/* FIXME: move prefix to startup_info struct and get rid of this arg */
+void trace_repo_setup(const char *prefix)
+{
+ static const char *key = "GIT_TRACE_SETUP";
+ const char *git_work_tree;
+ char cwd[PATH_MAX];
+
+ if (!trace_want(key))
+ return;
+
+ if (!getcwd(cwd, PATH_MAX))
+ die("Unable to get current working directory");
+
+ if (!(git_work_tree = get_git_work_tree()))
+ git_work_tree = "(null)";
+
+ if (!prefix)
+ prefix = "(null)";
+
+ trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
+ trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
+ trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd));
+ trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix));
+}
+
+int trace_want(const char *key)
+{
+ const char *trace = getenv(key);
+
+ if (!trace || !strcmp(trace, "") ||
+ !strcmp(trace, "0") || !strcasecmp(trace, "false"))
+ return 0;
+ return 1;
+}
diff --git a/transport-helper.c b/transport-helper.c
index 191fbf7..660147f 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -8,11 +8,11 @@
#include "quote.h"
#include "remote.h"
#include "string-list.h"
+#include "thread-utils.h"
static int debug;
-struct helper_data
-{
+struct helper_data {
const char *name;
struct child_process *helper;
FILE *out;
@@ -76,7 +76,7 @@ static void write_constant(int fd, const char *str)
die_errno("Full write to remote helper failed");
}
-const char *remove_ext_force(const char *url)
+static const char *remove_ext_force(const char *url)
{
if (url) {
const char *colon = strchr(url, ':');
@@ -561,10 +561,9 @@ static int push_refs_with_push(struct transport *transport,
int mirror = flags & TRANSPORT_PUSH_MIRROR;
struct helper_data *data = transport->data;
struct strbuf buf = STRBUF_INIT;
- struct child_process *helper;
struct ref *ref;
- helper = get_helper(transport);
+ get_helper(transport);
if (!data->push)
return 1;
@@ -689,7 +688,7 @@ static int push_refs_with_export(struct transport *transport,
struct child_process *helper, exporter;
struct helper_data *data = transport->data;
char *export_marks = NULL, *import_marks = NULL;
- struct string_list revlist_args = { NULL, 0, 0 };
+ struct string_list revlist_args = STRING_LIST_INIT_NODUP;
struct strbuf buf = STRBUF_INIT;
helper = get_helper(transport);
@@ -862,3 +861,314 @@ int transport_helper_init(struct transport *transport, const char *name)
transport->smart_options = &(data->transport_options);
return 0;
}
+
+/*
+ * Linux pipes can buffer 65536 bytes at once (and most platforms can
+ * buffer less), so attempt reads and writes with up to that size.
+ */
+#define BUFFERSIZE 65536
+/* This should be enough to hold debugging message. */
+#define PBUFFERSIZE 8192
+
+/* Print bidirectional transfer loop debug message. */
+static void transfer_debug(const char *fmt, ...)
+{
+ va_list args;
+ char msgbuf[PBUFFERSIZE];
+ static int debug_enabled = -1;
+
+ if (debug_enabled < 0)
+ debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0;
+ if (!debug_enabled)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(msgbuf, PBUFFERSIZE, fmt, args);
+ va_end(args);
+ fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf);
+}
+
+/* Stream state: More data may be coming in this direction. */
+#define SSTATE_TRANSFERING 0
+/*
+ * Stream state: No more data coming in this direction, flushing rest of
+ * data.
+ */
+#define SSTATE_FLUSHING 1
+/* Stream state: Transfer in this direction finished. */
+#define SSTATE_FINISHED 2
+
+#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING)
+#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
+#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
+
+/* Unidirectional transfer. */
+struct unidirectional_transfer {
+ /* Source */
+ int src;
+ /* Destination */
+ int dest;
+ /* Is source socket? */
+ int src_is_sock;
+ /* Is destination socket? */
+ int dest_is_sock;
+ /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */
+ int state;
+ /* Buffer. */
+ char buf[BUFFERSIZE];
+ /* Buffer used. */
+ size_t bufuse;
+ /* Name of source. */
+ const char *src_name;
+ /* Name of destination. */
+ const char *dest_name;
+};
+
+/* Closes the target (for writing) if transfer has finished. */
+static void udt_close_if_finished(struct unidirectional_transfer *t)
+{
+ if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) {
+ t->state = SSTATE_FINISHED;
+ if (t->dest_is_sock)
+ shutdown(t->dest, SHUT_WR);
+ else
+ close(t->dest);
+ transfer_debug("Closed %s.", t->dest_name);
+ }
+}
+
+/*
+ * Tries to read read data from source into buffer. If buffer is full,
+ * no data is read. Returns 0 on success, -1 on error.
+ */
+static int udt_do_read(struct unidirectional_transfer *t)
+{
+ ssize_t bytes;
+
+ if (t->bufuse == BUFFERSIZE)
+ return 0; /* No space for more. */
+
+ transfer_debug("%s is readable", t->src_name);
+ bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
+ if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+ errno != EINTR) {
+ error("read(%s) failed: %s", t->src_name, strerror(errno));
+ return -1;
+ } else if (bytes == 0) {
+ transfer_debug("%s EOF (with %i bytes in buffer)",
+ t->src_name, t->bufuse);
+ t->state = SSTATE_FLUSHING;
+ } else if (bytes > 0) {
+ t->bufuse += bytes;
+ transfer_debug("Read %i bytes from %s (buffer now at %i)",
+ (int)bytes, t->src_name, (int)t->bufuse);
+ }
+ return 0;
+}
+
+/* Tries to write data from buffer into destination. If buffer is empty,
+ * no data is written. Returns 0 on success, -1 on error.
+ */
+static int udt_do_write(struct unidirectional_transfer *t)
+{
+ ssize_t bytes;
+
+ if (t->bufuse == 0)
+ return 0; /* Nothing to write. */
+
+ transfer_debug("%s is writable", t->dest_name);
+ bytes = write(t->dest, t->buf, t->bufuse);
+ if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
+ errno != EINTR) {
+ error("write(%s) failed: %s", t->dest_name, strerror(errno));
+ return -1;
+ } else if (bytes > 0) {
+ t->bufuse -= bytes;
+ if (t->bufuse)
+ memmove(t->buf, t->buf + bytes, t->bufuse);
+ transfer_debug("Wrote %i bytes to %s (buffer now at %i)",
+ (int)bytes, t->dest_name, (int)t->bufuse);
+ }
+ return 0;
+}
+
+
+/* State of bidirectional transfer loop. */
+struct bidirectional_transfer_state {
+ /* Direction from program to git. */
+ struct unidirectional_transfer ptg;
+ /* Direction from git to program. */
+ struct unidirectional_transfer gtp;
+};
+
+static void *udt_copy_task_routine(void *udt)
+{
+ struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt;
+ while (t->state != SSTATE_FINISHED) {
+ if (STATE_NEEDS_READING(t->state))
+ if (udt_do_read(t))
+ return NULL;
+ if (STATE_NEEDS_WRITING(t->state))
+ if (udt_do_write(t))
+ return NULL;
+ if (STATE_NEEDS_CLOSING(t->state))
+ udt_close_if_finished(t);
+ }
+ return udt; /* Just some non-NULL value. */
+}
+
+#ifndef NO_PTHREADS
+
+/*
+ * Join thread, with apporiate errors on failure. Name is name for the
+ * thread (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pthread_t thread, const char *name)
+{
+ int err;
+ void *tret;
+ err = pthread_join(thread, &tret);
+ if (!tret) {
+ error("%s thread failed", name);
+ return 1;
+ }
+ if (err) {
+ error("%s thread failed to join: %s", name, strerror(err));
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+ pthread_t gtp_thread;
+ pthread_t ptg_thread;
+ int err;
+ int ret = 0;
+ err = pthread_create(&gtp_thread, NULL, udt_copy_task_routine,
+ &s->gtp);
+ if (err)
+ die("Can't start thread for copying data: %s", strerror(err));
+ err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine,
+ &s->ptg);
+ if (err)
+ die("Can't start thread for copying data: %s", strerror(err));
+
+ ret |= tloop_join(gtp_thread, "Git to program copy");
+ ret |= tloop_join(ptg_thread, "Program to git copy");
+ return ret;
+}
+#else
+
+/* Close the source and target (for writing) for transfer. */
+static void udt_kill_transfer(struct unidirectional_transfer *t)
+{
+ t->state = SSTATE_FINISHED;
+ /*
+ * Socket read end left open isn't a disaster if nobody
+ * attempts to read from it (mingw compat headers do not
+ * have SHUT_RD)...
+ *
+ * We can't fully close the socket since otherwise gtp
+ * task would first close the socket it sends data to
+ * while closing the ptg file descriptors.
+ */
+ if (!t->src_is_sock)
+ close(t->src);
+ if (t->dest_is_sock)
+ shutdown(t->dest, SHUT_WR);
+ else
+ close(t->dest);
+}
+
+/*
+ * Join process, with apporiate errors on failure. Name is name for the
+ * process (for error messages). Returns 0 on success, 1 on failure.
+ */
+static int tloop_join(pid_t pid, const char *name)
+{
+ int tret;
+ if (waitpid(pid, &tret, 0) < 0) {
+ error("%s process failed to wait: %s", name, strerror(errno));
+ return 1;
+ }
+ if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
+ error("%s process failed", name);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Spawn the transfer tasks and then wait for them. Returns 0 on success,
+ * -1 on failure.
+ */
+static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s)
+{
+ pid_t pid1, pid2;
+ int ret = 0;
+
+ /* Fork thread #1: git to program. */
+ pid1 = fork();
+ if (pid1 < 0)
+ die_errno("Can't start thread for copying data");
+ else if (pid1 == 0) {
+ udt_kill_transfer(&s->ptg);
+ exit(udt_copy_task_routine(&s->gtp) ? 0 : 1);
+ }
+
+ /* Fork thread #2: program to git. */
+ pid2 = fork();
+ if (pid2 < 0)
+ die_errno("Can't start thread for copying data");
+ else if (pid2 == 0) {
+ udt_kill_transfer(&s->gtp);
+ exit(udt_copy_task_routine(&s->ptg) ? 0 : 1);
+ }
+
+ /*
+ * Close both streams in parent as to not interfere with
+ * end of file detection and wait for both tasks to finish.
+ */
+ udt_kill_transfer(&s->gtp);
+ udt_kill_transfer(&s->ptg);
+ ret |= tloop_join(pid1, "Git to program copy");
+ ret |= tloop_join(pid2, "Program to git copy");
+ return ret;
+}
+#endif
+
+/*
+ * Copies data from stdin to output and from input to stdout simultaneously.
+ * Additionally filtering through given filter. If filter is NULL, uses
+ * identity filter.
+ */
+int bidirectional_transfer_loop(int input, int output)
+{
+ struct bidirectional_transfer_state state;
+
+ /* Fill the state fields. */
+ state.ptg.src = input;
+ state.ptg.dest = 1;
+ state.ptg.src_is_sock = (input == output);
+ state.ptg.dest_is_sock = 0;
+ state.ptg.state = SSTATE_TRANSFERING;
+ state.ptg.bufuse = 0;
+ state.ptg.src_name = "remote input";
+ state.ptg.dest_name = "stdout";
+
+ state.gtp.src = 0;
+ state.gtp.dest = output;
+ state.gtp.src_is_sock = 0;
+ state.gtp.dest_is_sock = (input == output);
+ state.gtp.state = SSTATE_TRANSFERING;
+ state.gtp.bufuse = 0;
+ state.gtp.src_name = "stdin";
+ state.gtp.dest_name = "remote output";
+
+ return tloop_spawnwait_tasks(&state);
+}
diff --git a/transport.c b/transport.c
index 4dba6f8..c9c8056 100644
--- a/transport.c
+++ b/transport.c
@@ -156,7 +156,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
continue;
if (!ref->peer_ref)
continue;
- if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+ if (is_null_sha1(ref->new_sha1))
continue;
/* Follow symbolic refs (mainly for HEAD). */
@@ -192,7 +192,7 @@ static const char *rsync_url(const char *url)
static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
{
struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
- struct ref dummy = {0}, *tail = &dummy;
+ struct ref dummy = {NULL}, *tail = &dummy;
struct child_process rsync;
const char *args[5];
int temp_dir_len;
@@ -789,6 +789,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.use_thin_pack = data->options.thin;
args.verbose = (transport->verbose > 0);
args.quiet = (transport->verbose < 0);
+ args.progress = transport->progress;
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
@@ -1188,3 +1189,51 @@ char *transport_anonymize_url(const char *url)
literal_copy:
return xstrdup(url);
}
+
+struct alternate_refs_data {
+ alternate_ref_fn *fn;
+ void *data;
+};
+
+static int refs_from_alternate_cb(struct alternate_object_database *e,
+ void *data)
+{
+ char *other;
+ size_t len;
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *extra;
+ struct alternate_refs_data *cb = data;
+
+ e->name[-1] = '\0';
+ other = xstrdup(real_path(e->base));
+ e->name[-1] = '/';
+ len = strlen(other);
+
+ while (other[len-1] == '/')
+ other[--len] = '\0';
+ if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+ return 0;
+ /* Is this a git repository with refs? */
+ memcpy(other + len - 8, "/refs", 6);
+ if (!is_directory(other))
+ return 0;
+ other[len - 8] = '\0';
+ remote = remote_get(other);
+ transport = transport_get(remote, other);
+ for (extra = transport_get_remote_refs(transport);
+ extra;
+ extra = extra->next)
+ cb->fn(extra, cb->data);
+ transport_disconnect(transport);
+ free(other);
+ return 0;
+}
+
+void for_each_alternate_ref(alternate_ref_fn fn, void *data)
+{
+ struct alternate_refs_data cb;
+ cb.fn = fn;
+ cb.data = data;
+ foreach_alt_odb(refs_from_alternate_cb, &cb);
+}
diff --git a/transport.h b/transport.h
index c59d973..161d724 100644
--- a/transport.h
+++ b/transport.h
@@ -154,6 +154,7 @@ int transport_connect(struct transport *transport, const char *name,
/* Transport methods defined outside transport.c */
int transport_helper_init(struct transport *transport, const char *name);
+int bidirectional_transfer_loop(int input, int output);
/* common methods used by transport.c and builtin-send-pack.c */
void transport_verify_remote_names(int nr_heads, const char **heads);
@@ -165,4 +166,7 @@ int transport_refs_pushed(struct ref *ref);
void transport_print_push_status(const char *dest, struct ref *refs,
int verbose, int porcelain, int *nonfastforward);
+typedef void alternate_ref_fn(const struct ref *, void *);
+extern void for_each_alternate_ref(alternate_ref_fn, void *);
+
#endif
diff --git a/tree-diff.c b/tree-diff.c
index 1fb3e94..b3cc2e4 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -6,34 +6,17 @@
#include "diffcore.h"
#include "tree.h"
-static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
-{
- char *newbase = xmalloc(baselen + pathlen + 2);
- memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, path, pathlen);
- memcpy(newbase + baselen + pathlen, "/", 2);
- return newbase;
-}
-
-static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
-{
- char *fullname = xmalloc(baselen + pathlen + 1);
- memcpy(fullname, base, baselen);
- memcpy(fullname + baselen, path, pathlen);
- fullname[baselen + pathlen] = 0;
- return fullname;
-}
+static void show_entry(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base);
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
- const char *base, int baselen);
-
-static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
+ struct strbuf *base, struct diff_options *opt)
{
unsigned mode1, mode2;
const char *path1, *path2;
const unsigned char *sha1, *sha2;
int cmp, pathlen1, pathlen2;
- char *fullname;
+ int old_baselen = base->len;
sha1 = tree_entry_extract(t1, &path1, &mode1);
sha2 = tree_entry_extract(t2, &path2, &mode2);
@@ -42,11 +25,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
pathlen2 = tree_entry_len(path2, sha2);
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
if (cmp < 0) {
- show_entry(opt, "-", t1, base, baselen);
+ show_entry(opt, "-", t1, base);
return -1;
}
if (cmp > 0) {
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "+", t2, base);
return 1;
}
if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
@@ -57,180 +40,57 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
* file, we need to consider it a remove and an add.
*/
if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
- show_entry(opt, "-", t1, base, baselen);
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "-", t1, base);
+ show_entry(opt, "+", t2, base);
return 0;
}
+ strbuf_add(base, path1, pathlen1);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
- int retval;
- char *newbase = malloc_base(base, baselen, path1, pathlen1);
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
- newbase[baselen + pathlen1] = 0;
opt->change(opt, mode1, mode2,
- sha1, sha2, newbase, 0, 0);
- newbase[baselen + pathlen1] = '/';
+ sha1, sha2, base->buf, 0, 0);
}
- retval = diff_tree_sha1(sha1, sha2, newbase, opt);
- free(newbase);
- return retval;
+ strbuf_addch(base, '/');
+ diff_tree_sha1(sha1, sha2, base->buf, opt);
+ } else {
+ opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0);
}
-
- fullname = malloc_fullname(base, baselen, path1, pathlen1);
- opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
- free(fullname);
+ strbuf_setlen(base, old_baselen);
return 0;
}
-/*
- * Is a tree entry interesting given the pathspec we have?
- *
- * Return:
- * - 2 for "yes, and all subsequent entries will be"
- * - 1 for yes
- * - zero for no
- * - negative for "no, and no subsequent entries will be either"
- */
-static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
-{
- const char *path;
- const unsigned char *sha1;
- unsigned mode;
- int i;
- int pathlen;
- int never_interesting = -1;
-
- if (!opt->nr_paths)
- return 1;
-
- sha1 = tree_entry_extract(desc, &path, &mode);
-
- pathlen = tree_entry_len(path, sha1);
-
- for (i = 0; i < opt->nr_paths; i++) {
- const char *match = opt->paths[i];
- int matchlen = opt->pathlens[i];
- int m = -1; /* signals that we haven't called strncmp() */
-
- if (baselen >= matchlen) {
- /* If it doesn't match, move along... */
- if (strncmp(base, match, matchlen))
- continue;
-
- /*
- * If the base is a subdirectory of a path which
- * was specified, all of them are interesting.
- */
- if (!matchlen ||
- base[matchlen] == '/' ||
- match[matchlen - 1] == '/')
- return 2;
-
- /* Just a random prefix match */
- continue;
- }
-
- /* Does the base match? */
- if (strncmp(base, match, baselen))
- continue;
-
- match += baselen;
- matchlen -= baselen;
-
- if (never_interesting) {
- /*
- * We have not seen any match that sorts later
- * than the current path.
- */
-
- /*
- * Does match sort strictly earlier than path
- * with their common parts?
- */
- m = strncmp(match, path,
- (matchlen < pathlen) ? matchlen : pathlen);
- if (m < 0)
- continue;
-
- /*
- * If we come here even once, that means there is at
- * least one pathspec that would sort equal to or
- * later than the path we are currently looking at.
- * In other words, if we have never reached this point
- * after iterating all pathspecs, it means all
- * pathspecs are either outside of base, or inside the
- * base but sorts strictly earlier than the current
- * one. In either case, they will never match the
- * subsequent entries. In such a case, we initialized
- * the variable to -1 and that is what will be
- * returned, allowing the caller to terminate early.
- */
- never_interesting = 0;
- }
-
- if (pathlen > matchlen)
- continue;
-
- if (matchlen > pathlen) {
- if (match[pathlen] != '/')
- continue;
- if (!S_ISDIR(mode))
- continue;
- }
-
- if (m == -1)
- /*
- * we cheated and did not do strncmp(), so we do
- * that here.
- */
- m = strncmp(match, path, pathlen);
-
- /*
- * If common part matched earlier then it is a hit,
- * because we rejected the case where path is not a
- * leading directory and is shorter than match.
- */
- if (!m)
- return 1;
- }
- return never_interesting; /* No matches */
-}
-
/* A whole sub-tree went away or appeared */
-static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
+static void show_tree(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base)
{
- int all_interesting = 0;
- while (desc->size) {
- int show;
-
- if (all_interesting)
- show = 1;
- else {
- show = tree_entry_interesting(desc, base, baselen,
- opt);
- if (show == 2)
- all_interesting = 1;
+ int match = 0;
+ for (; desc->size; update_tree_entry(desc)) {
+ if (match != 2) {
+ match = tree_entry_interesting(&desc->entry, base, 0,
+ &opt->pathspec);
+ if (match < 0)
+ break;
+ if (match == 0)
+ continue;
}
- if (show < 0)
- break;
- if (show)
- show_entry(opt, prefix, desc, base, baselen);
- update_tree_entry(desc);
+ show_entry(opt, prefix, desc, base);
}
}
/* A file entry went away or appeared */
-static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
- const char *base, int baselen)
+static void show_entry(struct diff_options *opt, const char *prefix,
+ struct tree_desc *desc, struct strbuf *base)
{
unsigned mode;
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
int pathlen = tree_entry_len(path, sha1);
+ int old_baselen = base->len;
+ strbuf_add(base, path, pathlen);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
enum object_type type;
- char *newbase = malloc_base(base, baselen, path, pathlen);
struct tree_desc inner;
void *tree;
unsigned long size;
@@ -239,73 +99,68 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
if (!tree || type != OBJ_TREE)
die("corrupt tree sha %s", sha1_to_hex(sha1));
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
- newbase[baselen + pathlen] = 0;
- opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
- newbase[baselen + pathlen] = '/';
- }
+ if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+ opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0);
- init_tree_desc(&inner, tree, size);
- show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
+ strbuf_addch(base, '/');
+ init_tree_desc(&inner, tree, size);
+ show_tree(opt, prefix, &inner, base);
free(tree);
- free(newbase);
- } else {
- char *fullname = malloc_fullname(base, baselen, path, pathlen);
- opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
- free(fullname);
- }
+ } else
+ opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0);
+
+ strbuf_setlen(base, old_baselen);
}
-static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt)
+static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
+ struct diff_options *opt, int *match)
{
- int all_interesting = 0;
while (t->size) {
- int show;
-
- if (all_interesting)
- show = 1;
- else {
- show = tree_entry_interesting(t, base, baselen, opt);
- if (show == 2)
- all_interesting = 1;
- }
- if (!show) {
- update_tree_entry(t);
- continue;
+ *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
+ if (*match) {
+ if (*match < 0)
+ t->size = 0;
+ break;
}
- /* Skip it all? */
- if (show < 0)
- t->size = 0;
- return;
+ update_tree_entry(t);
}
}
-int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+ const char *base_str, struct diff_options *opt)
{
- int baselen = strlen(base);
+ struct strbuf base;
+ int baselen = strlen(base_str);
+ int t1_match = 0, t2_match = 0;
+
+ /* Enable recursion indefinitely */
+ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
+ opt->pathspec.max_depth = -1;
+
+ strbuf_init(&base, PATH_MAX);
+ strbuf_add(&base, base_str, baselen);
for (;;) {
- if (DIFF_OPT_TST(opt, QUICK) &&
- DIFF_OPT_TST(opt, HAS_CHANGES))
+ if (diff_can_quit_early(opt))
break;
- if (opt->nr_paths) {
- skip_uninteresting(t1, base, baselen, opt);
- skip_uninteresting(t2, base, baselen, opt);
+ if (opt->pathspec.nr) {
+ skip_uninteresting(t1, &base, opt, &t1_match);
+ skip_uninteresting(t2, &base, opt, &t2_match);
}
if (!t1->size) {
if (!t2->size)
break;
- show_entry(opt, "+", t2, base, baselen);
+ show_entry(opt, "+", t2, &base);
update_tree_entry(t2);
continue;
}
if (!t2->size) {
- show_entry(opt, "-", t1, base, baselen);
+ show_entry(opt, "-", t1, &base);
update_tree_entry(t1);
continue;
}
- switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
+ switch (compare_tree_entry(t1, t2, &base, opt)) {
case -1:
update_tree_entry(t1);
continue;
@@ -318,6 +173,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
}
die("git diff-tree: internal error");
}
+
+ strbuf_release(&base);
return 0;
}
@@ -348,7 +205,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
DIFF_OPT_SET(&diff_opts, RECURSIVE);
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_opts.single_follow = opt->paths[0];
+ diff_opts.single_follow = opt->pathspec.raw[0];
diff_opts.break_opt = opt->break_opt;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
@@ -359,6 +216,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
diff_tree_release_paths(&diff_opts);
/* Go through the new set of filepairing, and see if we find a more interesting one */
+ opt->found_follow = 0;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
@@ -367,15 +225,26 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
* diff_queued_diff, we will also use that as the path in
* the future!
*/
- if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+ if ((p->status == 'R' || p->status == 'C') &&
+ !strcmp(p->two->path, opt->pathspec.raw[0])) {
/* Switch the file-pairs around */
q->queue[i] = choice;
choice = p;
/* Update the path we use from now on.. */
diff_tree_release_paths(opt);
- opt->paths[0] = xstrdup(p->one->path);
- diff_tree_setup_paths(opt->paths, opt);
+ opt->pathspec.raw[0] = xstrdup(p->one->path);
+ diff_tree_setup_paths(opt->pathspec.raw, opt);
+
+ /*
+ * The caller expects us to return a set of vanilla
+ * filepairs to let a later call to diffcore_std()
+ * it makes to sort the renames out (among other
+ * things), but we already have found renames
+ * ourselves; signal diffcore_std() not to muck with
+ * rename information.
+ */
+ opt->found_follow = 1;
break;
}
}
@@ -412,7 +281,7 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
init_tree_desc(&t1, tree1, size1);
init_tree_desc(&t2, tree2, size2);
retval = diff_tree(&t1, &t2, base, opt);
- if (DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
+ if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
init_tree_desc(&t1, tree1, size1);
init_tree_desc(&t2, tree2, size2);
try_to_follow_renames(&t1, &t2, base, opt);
@@ -440,36 +309,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_
return retval;
}
-static int count_paths(const char **paths)
-{
- int i = 0;
- while (*paths++)
- i++;
- return i;
-}
-
void diff_tree_release_paths(struct diff_options *opt)
{
- free(opt->pathlens);
+ free_pathspec(&opt->pathspec);
}
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
{
- opt->nr_paths = 0;
- opt->pathlens = NULL;
- opt->paths = NULL;
-
- if (p) {
- int i;
-
- opt->paths = p;
- opt->nr_paths = count_paths(p);
- if (opt->nr_paths == 0) {
- opt->pathlens = NULL;
- return;
- }
- opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
- for (i=0; i < opt->nr_paths; i++)
- opt->pathlens[i] = strlen(p[i]);
- }
+ init_pathspec(&opt->pathspec, p);
}
diff --git a/tree-walk.c b/tree-walk.c
index 67a9a0c..33f749e 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -1,5 +1,7 @@
#include "cache.h"
#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "dir.h"
#include "tree.h"
static const char *get_mode(const char *str, unsigned int *modep)
@@ -310,6 +312,7 @@ static void free_extended_entry(struct tree_desc_x *t)
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
{
int ret = 0;
+ int error = 0;
struct name_entry *entry = xmalloc(n*sizeof(*entry));
int i;
struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
@@ -377,8 +380,11 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
if (!mask)
break;
ret = info->fn(n, mask, dirmask, entry, info);
- if (ret < 0)
- break;
+ if (ret < 0) {
+ error = ret;
+ if (!info->show_all_errors)
+ break;
+ }
mask &= ret;
ret = 0;
for (i = 0; i < n; i++)
@@ -389,7 +395,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
for (i = 0; i < n; i++)
free_extended_entry(tx + i);
free(tx);
- return ret;
+ return error;
}
static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
@@ -450,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
free(tree);
return retval;
}
+
+static int match_entry(const struct name_entry *entry, int pathlen,
+ const char *match, int matchlen,
+ int *never_interesting)
+{
+ int m = -1; /* signals that we haven't called strncmp() */
+
+ if (*never_interesting) {
+ /*
+ * We have not seen any match that sorts later
+ * than the current path.
+ */
+
+ /*
+ * Does match sort strictly earlier than path
+ * with their common parts?
+ */
+ m = strncmp(match, entry->path,
+ (matchlen < pathlen) ? matchlen : pathlen);
+ if (m < 0)
+ return 0;
+
+ /*
+ * If we come here even once, that means there is at
+ * least one pathspec that would sort equal to or
+ * later than the path we are currently looking at.
+ * In other words, if we have never reached this point
+ * after iterating all pathspecs, it means all
+ * pathspecs are either outside of base, or inside the
+ * base but sorts strictly earlier than the current
+ * one. In either case, they will never match the
+ * subsequent entries. In such a case, we initialized
+ * the variable to -1 and that is what will be
+ * returned, allowing the caller to terminate early.
+ */
+ *never_interesting = 0;
+ }
+
+ if (pathlen > matchlen)
+ return 0;
+
+ if (matchlen > pathlen) {
+ if (match[pathlen] != '/')
+ return 0;
+ if (!S_ISDIR(entry->mode))
+ return 0;
+ }
+
+ if (m == -1)
+ /*
+ * we cheated and did not do strncmp(), so we do
+ * that here.
+ */
+ m = strncmp(match, entry->path, pathlen);
+
+ /*
+ * If common part matched earlier then it is a hit,
+ * because we rejected the case where path is not a
+ * leading directory and is shorter than match.
+ */
+ if (!m)
+ return 1;
+
+ return 0;
+}
+
+static int match_dir_prefix(const char *base, int baselen,
+ const char *match, int matchlen)
+{
+ if (strncmp(base, match, matchlen))
+ return 0;
+
+ /*
+ * If the base is a subdirectory of a path which
+ * was specified, all of them are interesting.
+ */
+ if (!matchlen ||
+ base[matchlen] == '/' ||
+ match[matchlen - 1] == '/')
+ return 1;
+
+ /* Just a random prefix match */
+ return 0;
+}
+
+/*
+ * Is a tree entry interesting given the pathspec we have?
+ *
+ * Pre-condition: either baselen == base_offset (i.e. empty path)
+ * or base[baselen-1] == '/' (i.e. with trailing slash).
+ *
+ * Return:
+ * - 2 for "yes, and all subsequent entries will be"
+ * - 1 for yes
+ * - zero for no
+ * - negative for "no, and no subsequent entries will be either"
+ */
+int tree_entry_interesting(const struct name_entry *entry,
+ struct strbuf *base, int base_offset,
+ const struct pathspec *ps)
+{
+ int i;
+ int pathlen, baselen = base->len - base_offset;
+ int never_interesting = ps->has_wildcard ? 0 : -1;
+
+ if (!ps->nr) {
+ if (!ps->recursive || ps->max_depth == -1)
+ return 2;
+ return !!within_depth(base->buf + base_offset, baselen,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth);
+ }
+
+ pathlen = tree_entry_len(entry->path, entry->sha1);
+
+ for (i = ps->nr - 1; i >= 0; i--) {
+ const struct pathspec_item *item = ps->items+i;
+ const char *match = item->match;
+ const char *base_str = base->buf + base_offset;
+ int matchlen = item->len;
+
+ if (baselen >= matchlen) {
+ /* If it doesn't match, move along... */
+ if (!match_dir_prefix(base_str, baselen, match, matchlen))
+ goto match_wildcards;
+
+ if (!ps->recursive || ps->max_depth == -1)
+ return 2;
+
+ return !!within_depth(base_str + matchlen + 1,
+ baselen - matchlen - 1,
+ !!S_ISDIR(entry->mode),
+ ps->max_depth);
+ }
+
+ /* Does the base match? */
+ if (!strncmp(base_str, match, baselen)) {
+ if (match_entry(entry, pathlen,
+ match + baselen, matchlen - baselen,
+ &never_interesting))
+ return 1;
+
+ if (ps->items[i].use_wildcard) {
+ if (!fnmatch(match + baselen, entry->path, 0))
+ return 1;
+
+ /*
+ * Match all directories. We'll try to
+ * match files later on.
+ */
+ if (ps->recursive && S_ISDIR(entry->mode))
+ return 1;
+ }
+
+ continue;
+ }
+
+match_wildcards:
+ if (!ps->items[i].use_wildcard)
+ continue;
+
+ /*
+ * Concatenate base and entry->path into one and do
+ * fnmatch() on it.
+ */
+
+ strbuf_add(base, entry->path, pathlen);
+
+ if (!fnmatch(match, base->buf + base_offset, 0)) {
+ strbuf_setlen(base, base_offset + baselen);
+ return 1;
+ }
+ strbuf_setlen(base, base_offset + baselen);
+
+ /*
+ * Match all directories. We'll try to match files
+ * later on.
+ */
+ if (ps->recursive && S_ISDIR(entry->mode))
+ return 1;
+ }
+ return never_interesting; /* No matches */
+}
diff --git a/tree-walk.h b/tree-walk.h
index 42110a4..39524b7 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -28,7 +28,10 @@ static inline int tree_entry_len(const char *name, const unsigned char *sha1)
void update_tree_entry(struct tree_desc *);
void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size);
-/* Helper function that does both of the above and returns true for success */
+/*
+ * Helper function that does both tree_entry_extract() and update_tree_entry()
+ * and returns true for success
+ */
int tree_entry(struct tree_desc *, struct name_entry *);
void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1);
@@ -45,6 +48,7 @@ struct traverse_info {
unsigned long conflicts;
traverse_callback_t fn;
void *data;
+ int show_all_errors;
};
int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
@@ -56,4 +60,6 @@ static inline int traverse_path_len(const struct traverse_info *info, const stru
return info->pathlen + tree_entry_len(n->path, n->sha1);
}
+extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+
#endif
diff --git a/tree.c b/tree.c
index 5ab90af..698ecf7 100644
--- a/tree.c
+++ b/tree.c
@@ -45,62 +45,14 @@ static int read_one_entry_quick(const unsigned char *sha1, const char *base, int
ADD_CACHE_JUST_APPEND);
}
-static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths)
-{
- const char *match;
- int pathlen;
-
- if (!paths)
- return 1;
- pathlen = strlen(path);
- while ((match = *paths++) != NULL) {
- int matchlen = strlen(match);
-
- if (baselen >= matchlen) {
- /* If it doesn't match, move along... */
- if (strncmp(base, match, matchlen))
- continue;
- /* pathspecs match only at the directory boundaries */
- if (!matchlen ||
- baselen == matchlen ||
- base[matchlen] == '/' ||
- match[matchlen - 1] == '/')
- return 1;
- continue;
- }
-
- /* Does the base match? */
- if (strncmp(base, match, baselen))
- continue;
-
- match += baselen;
- matchlen -= baselen;
-
- if (pathlen > matchlen)
- continue;
-
- if (matchlen > pathlen) {
- if (match[pathlen] != '/')
- continue;
- if (!S_ISDIR(mode))
- continue;
- }
-
- if (strncmp(path, match, pathlen))
- continue;
-
- return 1;
- }
- return 0;
-}
-
-int read_tree_recursive(struct tree *tree,
- const char *base, int baselen,
- int stage, const char **match,
- read_tree_fn_t fn, void *context)
+static int read_tree_1(struct tree *tree, struct strbuf *base,
+ int stage, struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context)
{
struct tree_desc desc;
struct name_entry entry;
+ unsigned char sha1[20];
+ int len, retval = 0, oldlen = base->len;
if (parse_tree(tree))
return -1;
@@ -108,10 +60,16 @@ int read_tree_recursive(struct tree *tree,
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- if (!match_tree_entry(base, baselen, entry.path, entry.mode, match))
- continue;
+ if (retval != 2) {
+ retval = tree_entry_interesting(&entry, base, 0, pathspec);
+ if (retval < 0)
+ break;
+ if (retval == 0)
+ continue;
+ }
- switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage, context)) {
+ switch (fn(entry.sha1, base->buf, base->len,
+ entry.path, entry.mode, stage, context)) {
case 0:
continue;
case READ_TREE_RECURSIVE:
@@ -119,56 +77,55 @@ int read_tree_recursive(struct tree *tree,
default:
return -1;
}
- if (S_ISDIR(entry.mode)) {
- int retval;
- char *newbase;
- unsigned int pathlen = tree_entry_len(entry.path, entry.sha1);
-
- newbase = xmalloc(baselen + 1 + pathlen);
- memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, entry.path, pathlen);
- newbase[baselen + pathlen] = '/';
- retval = read_tree_recursive(lookup_tree(entry.sha1),
- newbase,
- baselen + pathlen + 1,
- stage, match, fn, context);
- free(newbase);
- if (retval)
- return -1;
- continue;
- } else if (S_ISGITLINK(entry.mode)) {
- int retval;
- struct strbuf path;
- unsigned int entrylen;
- struct commit *commit;
- entrylen = tree_entry_len(entry.path, entry.sha1);
- strbuf_init(&path, baselen + entrylen + 1);
- strbuf_add(&path, base, baselen);
- strbuf_add(&path, entry.path, entrylen);
- strbuf_addch(&path, '/');
+ if (S_ISDIR(entry.mode))
+ hashcpy(sha1, entry.sha1);
+ else if (S_ISGITLINK(entry.mode)) {
+ struct commit *commit;
commit = lookup_commit(entry.sha1);
if (!commit)
- die("Commit %s in submodule path %s not found",
- sha1_to_hex(entry.sha1), path.buf);
+ die("Commit %s in submodule path %s%s not found",
+ sha1_to_hex(entry.sha1),
+ base->buf, entry.path);
if (parse_commit(commit))
- die("Invalid commit %s in submodule path %s",
- sha1_to_hex(entry.sha1), path.buf);
-
- retval = read_tree_recursive(commit->tree,
- path.buf, path.len,
- stage, match, fn, context);
- strbuf_release(&path);
- if (retval)
- return -1;
- continue;
+ die("Invalid commit %s in submodule path %s%s",
+ sha1_to_hex(entry.sha1),
+ base->buf, entry.path);
+
+ hashcpy(sha1, commit->tree->object.sha1);
}
+ else
+ continue;
+
+ len = tree_entry_len(entry.path, entry.sha1);
+ strbuf_add(base, entry.path, len);
+ strbuf_addch(base, '/');
+ retval = read_tree_1(lookup_tree(sha1),
+ base, stage, pathspec,
+ fn, context);
+ strbuf_setlen(base, oldlen);
+ if (retval)
+ return -1;
}
return 0;
}
+int read_tree_recursive(struct tree *tree,
+ const char *base, int baselen,
+ int stage, struct pathspec *pathspec,
+ read_tree_fn_t fn, void *context)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ strbuf_add(&sb, base, baselen);
+ ret = read_tree_1(tree, &sb, stage, pathspec, fn, context);
+ strbuf_release(&sb);
+ return ret;
+}
+
static int cmp_cache_name_compare(const void *a_, const void *b_)
{
const struct cache_entry *ce1, *ce2;
@@ -179,7 +136,7 @@ static int cmp_cache_name_compare(const void *a_, const void *b_)
ce2->name, ce2->ce_flags);
}
-int read_tree(struct tree *tree, int stage, const char **match)
+int read_tree(struct tree *tree, int stage, struct pathspec *match)
{
read_tree_fn_t fn = NULL;
int i, err;
diff --git a/tree.h b/tree.h
index 2ff01a4..69bcb5e 100644
--- a/tree.h
+++ b/tree.h
@@ -25,9 +25,9 @@ typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const ch
extern int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
- int stage, const char **match,
+ int stage, struct pathspec *pathspec,
read_tree_fn_t fn, void *context);
-extern int read_tree(struct tree *tree, int stage, const char **paths);
+extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec);
#endif /* TREE_H */
diff --git a/unpack-trees.c b/unpack-trees.c
index 8cf0da3..3a61d82 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -13,37 +13,94 @@
* Error messages expected by scripts out of plumbing commands such as
* read-tree. Non-scripted Porcelain is not required to use these messages
* and in fact are encouraged to reword them to better suit their particular
- * situation better. See how "git checkout" replaces not_uptodate_file to
- * explain why it does not allow switching between branches when you have
- * local changes, for example.
+ * situation better. See how "git checkout" and "git merge" replaces
+ * them using setup_unpack_trees_porcelain(), for example.
*/
-static struct unpack_trees_error_msgs unpack_plumbing_errors = {
- /* would_overwrite */
+static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
+ /* ERROR_WOULD_OVERWRITE */
"Entry '%s' would be overwritten by merge. Cannot merge.",
- /* not_uptodate_file */
+ /* ERROR_NOT_UPTODATE_FILE */
"Entry '%s' not uptodate. Cannot merge.",
- /* not_uptodate_dir */
+ /* ERROR_NOT_UPTODATE_DIR */
"Updating '%s' would lose untracked files in it",
- /* would_lose_untracked */
- "Untracked working tree file '%s' would be %s by merge.",
+ /* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */
+ "Untracked working tree file '%s' would be overwritten by merge.",
- /* bind_overlap */
+ /* ERROR_WOULD_LOSE_UNTRACKED_REMOVED */
+ "Untracked working tree file '%s' would be removed by merge.",
+
+ /* ERROR_BIND_OVERLAP */
"Entry '%s' overlaps with '%s'. Cannot bind.",
- /* sparse_not_uptodate_file */
+ /* ERROR_SPARSE_NOT_UPTODATE_FILE */
"Entry '%s' not uptodate. Cannot update sparse checkout.",
- /* would_lose_orphaned */
- "Working tree file '%s' would be %s by sparse checkout update.",
+ /* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */
+ "Working tree file '%s' would be overwritten by sparse checkout update.",
+
+ /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
+ "Working tree file '%s' would be removed by sparse checkout update.",
};
-#define ERRORMSG(o,fld) \
- ( ((o) && (o)->msgs.fld) \
- ? ((o)->msgs.fld) \
- : (unpack_plumbing_errors.fld) )
+#define ERRORMSG(o,type) \
+ ( ((o) && (o)->msgs[(type)]) \
+ ? ((o)->msgs[(type)]) \
+ : (unpack_plumbing_errors[(type)]) )
+
+void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
+ const char *cmd)
+{
+ int i;
+ const char **msgs = opts->msgs;
+ const char *msg;
+ char *tmp;
+ const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
+ if (advice_commit_before_merge)
+ msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
+ "Please, commit your changes or stash them before you can %s.";
+ else
+ msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
+ sprintf(tmp, msg, cmd, cmd2);
+ msgs[ERROR_WOULD_OVERWRITE] = tmp;
+ msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
+
+ msgs[ERROR_NOT_UPTODATE_DIR] =
+ "Updating the following directories would lose untracked files in it:\n%s";
+
+ if (advice_commit_before_merge)
+ msg = "The following untracked working tree files would be %s by %s:\n%%s"
+ "Please move or remove them before you can %s.";
+ else
+ msg = "The following untracked working tree files would be %s by %s:\n%%s";
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
+ sprintf(tmp, msg, "removed", cmd, cmd2);
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
+ tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
+ sprintf(tmp, msg, "overwritten", cmd, cmd2);
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
+
+ /*
+ * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
+ * cannot easily display it as a list.
+ */
+ msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind.";
+
+ msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
+ "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
+ msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
+ "The following Working tree files would be overwritten by sparse checkout update:\n%s";
+ msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
+ "The following Working tree files would be removed by sparse checkout update:\n%s";
+
+ opts->show_all_errors = 1;
+ /* rejected paths may not have a static buffer */
+ for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++)
+ opts->unpack_rejects[i].strdup_strings = 1;
+}
static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
unsigned int set, unsigned int clear)
@@ -53,6 +110,9 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
clear |= CE_HASHED | CE_UNHASHED;
+ if (set & CE_REMOVE)
+ set |= CE_WT_REMOVE;
+
memcpy(new, ce, size);
new->next = NULL;
new->ce_flags = (new->ce_flags & ~clear) | set;
@@ -60,12 +120,55 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
}
/*
+ * add error messages on path <path>
+ * corresponding to the type <e> with the message <msg>
+ * indicating if it should be display in porcelain or not
+ */
+static int add_rejected_path(struct unpack_trees_options *o,
+ enum unpack_trees_error_types e,
+ const char *path)
+{
+ if (!o->show_all_errors)
+ return error(ERRORMSG(o, e), path);
+
+ /*
+ * Otherwise, insert in a list for future display by
+ * display_error_msgs()
+ */
+ string_list_append(&o->unpack_rejects[e], path);
+ return -1;
+}
+
+/*
+ * display all the error messages stored in a nice way
+ */
+static void display_error_msgs(struct unpack_trees_options *o)
+{
+ int e, i;
+ int something_displayed = 0;
+ for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
+ struct string_list *rejects = &o->unpack_rejects[e];
+ if (rejects->nr > 0) {
+ struct strbuf path = STRBUF_INIT;
+ something_displayed = 1;
+ for (i = 0; i < rejects->nr; i++)
+ strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
+ error(ERRORMSG(o, e), path.buf);
+ strbuf_release(&path);
+ }
+ string_list_clear(rejects, 0);
+ }
+ if (something_displayed)
+ printf("Aborting\n");
+}
+
+/*
* Unlink the last component and schedule the leading directories for
* removal, such that empty directories get removed.
*/
static void unlink_entry(struct cache_entry *ce)
{
- if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
+ if (!check_leading_path(ce->name, ce_namelen(ce)))
return;
if (remove_or_warn(ce->ce_mode, ce->name))
return;
@@ -84,7 +187,7 @@ static int check_updates(struct unpack_trees_options *o)
if (o->update && o->verbose_update) {
for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
struct cache_entry *ce = index->cache[cnt];
- if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE))
+ if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE))
total++;
}
@@ -100,16 +203,10 @@ static int check_updates(struct unpack_trees_options *o)
if (ce->ce_flags & CE_WT_REMOVE) {
display_progress(progress, ++cnt);
- if (o->update)
+ if (o->update && !o->dry_run)
unlink_entry(ce);
continue;
}
-
- if (ce->ce_flags & CE_REMOVE) {
- display_progress(progress, ++cnt);
- if (o->update)
- unlink_entry(ce);
- }
}
remove_marked_cache_entries(&o->result);
remove_scheduled_dirs();
@@ -120,7 +217,7 @@ static int check_updates(struct unpack_trees_options *o)
if (ce->ce_flags & CE_UPDATE) {
display_progress(progress, ++cnt);
ce->ce_flags &= ~CE_UPDATE;
- if (o->update) {
+ if (o->update && !o->dry_run) {
errs |= checkout_entry(ce, &state, NULL);
}
}
@@ -132,37 +229,42 @@ static int check_updates(struct unpack_trees_options *o)
}
static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
-static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o);
-
-static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
-{
- const char *basename;
-
- if (ce_stage(ce))
- return 0;
-
- basename = strrchr(ce->name, '/');
- basename = basename ? basename+1 : ce->name;
- return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
-}
+static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
{
int was_skip_worktree = ce_skip_worktree(ce);
- if (will_have_skip_worktree(ce, o))
+ if (ce->ce_flags & CE_NEW_SKIP_WORKTREE)
ce->ce_flags |= CE_SKIP_WORKTREE;
else
ce->ce_flags &= ~CE_SKIP_WORKTREE;
/*
- * We only care about files getting into the checkout area
- * If merge strategies want to remove some, go ahead, this
- * flag will be removed eventually in unpack_trees() if it's
- * outside checkout area.
+ * if (!was_skip_worktree && !ce_skip_worktree()) {
+ * This is perfectly normal. Move on;
+ * }
*/
- if (ce->ce_flags & CE_REMOVE)
- return 0;
+
+ /*
+ * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
+ * area as a result of ce_skip_worktree() shortcuts in
+ * verify_absent() and verify_uptodate().
+ * Make sure they don't modify worktree if they are already
+ * outside checkout area
+ */
+ if (was_skip_worktree && ce_skip_worktree(ce)) {
+ ce->ce_flags &= ~CE_UPDATE;
+
+ /*
+ * By default, when CE_REMOVE is on, CE_WT_REMOVE is also
+ * on to get that file removed from both index and worktree.
+ * If that file is already outside worktree area, don't
+ * bother remove it.
+ */
+ if (ce->ce_flags & CE_REMOVE)
+ ce->ce_flags &= ~CE_WT_REMOVE;
+ }
if (!was_skip_worktree && ce_skip_worktree(ce)) {
/*
@@ -175,7 +277,7 @@ static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_opt
ce->ce_flags |= CE_WT_REMOVE;
}
if (was_skip_worktree && !ce_skip_worktree(ce)) {
- if (verify_absent_sparse(ce, "overwritten", o))
+ if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
return -1;
ce->ce_flags |= CE_UPDATE;
}
@@ -208,7 +310,7 @@ static void mark_all_ce_unused(struct index_state *index)
{
int i;
for (i = 0; i < index->cache_nr; i++)
- index->cache[i]->ce_flags &= ~CE_UNPACKED;
+ index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);
}
static int locate_in_src_index(struct cache_entry *ce,
@@ -279,7 +381,7 @@ static void add_same_unmerged(struct cache_entry *ce,
static int unpack_index_entry(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- struct cache_entry *src[5] = { NULL };
+ struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
int ret;
src[0] = ce;
@@ -325,10 +427,14 @@ static int switch_cache_bottom(struct traverse_info *info)
return ret;
}
-static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
+static int traverse_trees_recursive(int n, unsigned long dirmask,
+ unsigned long df_conflicts,
+ struct name_entry *names,
+ struct traverse_info *info)
{
int i, ret, bottom;
struct tree_desc t[MAX_UNPACK_TREES];
+ void *buf[MAX_UNPACK_TREES];
struct traverse_info newinfo;
struct name_entry *p;
@@ -346,12 +452,16 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long
const unsigned char *sha1 = NULL;
if (dirmask & 1)
sha1 = names[i].sha1;
- fill_tree_descriptor(t+i, sha1);
+ buf[i] = fill_tree_descriptor(t+i, sha1);
}
bottom = switch_cache_bottom(&newinfo);
ret = traverse_trees(n, t, &newinfo);
restore_cache_bottom(&newinfo, bottom);
+
+ for (i = 0; i < n; i++)
+ free(buf[i]);
+
return ret;
}
@@ -483,7 +593,7 @@ static int unpack_nondirectories(int n, unsigned long mask,
static int unpack_failed(struct unpack_trees_options *o, const char *message)
{
discard_index(&o->result);
- if (!o->gently) {
+ if (!o->gently && !o->exiting_early) {
if (message)
return error("%s", message);
return -1;
@@ -704,9 +814,182 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
return mask;
}
+static int clear_ce_flags_1(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ int select_mask, int clear_mask,
+ struct exclude_list *el, int defval);
+
+/* Whole directory matching */
+static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ char *basename,
+ int select_mask, int clear_mask,
+ struct exclude_list *el, int defval)
+{
+ struct cache_entry **cache_end;
+ int dtype = DT_DIR;
+ int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
+
+ prefix[prefix_len++] = '/';
+
+ /* If undecided, use matching result of parent dir in defval */
+ if (ret < 0)
+ ret = defval;
+
+ for (cache_end = cache; cache_end != cache + nr; cache_end++) {
+ struct cache_entry *ce = *cache_end;
+ if (strncmp(ce->name, prefix, prefix_len))
+ break;
+ }
+
+ /*
+ * TODO: check el, if there are no patterns that may conflict
+ * with ret (iow, we know in advance the incl/excl
+ * decision for the entire directory), clear flag here without
+ * calling clear_ce_flags_1(). That function will call
+ * the expensive excluded_from_list() on every entry.
+ */
+ return clear_ce_flags_1(cache, cache_end - cache,
+ prefix, prefix_len,
+ select_mask, clear_mask,
+ el, ret);
+}
+
+/*
+ * Traverse the index, find every entry that matches according to
+ * o->el. Do "ce_flags &= ~clear_mask" on those entries. Return the
+ * number of traversed entries.
+ *
+ * If select_mask is non-zero, only entries whose ce_flags has on of
+ * those bits enabled are traversed.
+ *
+ * cache : pointer to an index entry
+ * prefix_len : an offset to its path
+ *
+ * The current path ("prefix") including the trailing '/' is
+ * cache[0]->name[0..(prefix_len-1)]
+ * Top level path has prefix_len zero.
+ */
+static int clear_ce_flags_1(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ int select_mask, int clear_mask,
+ struct exclude_list *el, int defval)
+{
+ struct cache_entry **cache_end = cache + nr;
+
+ /*
+ * Process all entries that have the given prefix and meet
+ * select_mask condition
+ */
+ while(cache != cache_end) {
+ struct cache_entry *ce = *cache;
+ const char *name, *slash;
+ int len, dtype, ret;
+
+ if (select_mask && !(ce->ce_flags & select_mask)) {
+ cache++;
+ continue;
+ }
+
+ if (prefix_len && strncmp(ce->name, prefix, prefix_len))
+ break;
+
+ name = ce->name + prefix_len;
+ slash = strchr(name, '/');
+
+ /* If it's a directory, try whole directory match first */
+ if (slash) {
+ int processed;
+
+ len = slash - name;
+ memcpy(prefix + prefix_len, name, len);
+
+ /*
+ * terminate the string (no trailing slash),
+ * clear_c_f_dir needs it
+ */
+ prefix[prefix_len + len] = '\0';
+ processed = clear_ce_flags_dir(cache, cache_end - cache,
+ prefix, prefix_len + len,
+ prefix + prefix_len,
+ select_mask, clear_mask,
+ el, defval);
+
+ /* clear_c_f_dir eats a whole dir already? */
+ if (processed) {
+ cache += processed;
+ continue;
+ }
+
+ prefix[prefix_len + len++] = '/';
+ cache += clear_ce_flags_1(cache, cache_end - cache,
+ prefix, prefix_len + len,
+ select_mask, clear_mask, el, defval);
+ continue;
+ }
+
+ /* Non-directory */
+ dtype = ce_to_dtype(ce);
+ ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el);
+ if (ret < 0)
+ ret = defval;
+ if (ret > 0)
+ ce->ce_flags &= ~clear_mask;
+ cache++;
+ }
+ return nr - (cache_end - cache);
+}
+
+static int clear_ce_flags(struct cache_entry **cache, int nr,
+ int select_mask, int clear_mask,
+ struct exclude_list *el)
+{
+ char prefix[PATH_MAX];
+ return clear_ce_flags_1(cache, nr,
+ prefix, 0,
+ select_mask, clear_mask,
+ el, 0);
+}
+
+/*
+ * Set/Clear CE_NEW_SKIP_WORKTREE according to $GIT_DIR/info/sparse-checkout
+ */
+static void mark_new_skip_worktree(struct exclude_list *el,
+ struct index_state *the_index,
+ int select_flag, int skip_wt_flag)
+{
+ int i;
+
+ /*
+ * 1. Pretend the narrowest worktree: only unmerged entries
+ * are checked out
+ */
+ for (i = 0; i < the_index->cache_nr; i++) {
+ struct cache_entry *ce = the_index->cache[i];
+
+ if (select_flag && !(ce->ce_flags & select_flag))
+ continue;
+
+ if (!ce_stage(ce))
+ ce->ce_flags |= skip_wt_flag;
+ else
+ ce->ce_flags &= ~skip_wt_flag;
+ }
+
+ /*
+ * 2. Widen worktree according to sparse-checkout file.
+ * Matched entries will have skip_wt_flag cleared (i.e. "in")
+ */
+ clear_ce_flags(the_index->cache, the_index->cache_nr,
+ select_flag, skip_wt_flag, el);
+}
+
+static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *);
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
+ *
+ * CE_ADDED, CE_UNPACKED and CE_NEW_SKIP_WORKTREE are used internally
*/
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
{
@@ -739,6 +1022,12 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->merge_size = len;
mark_all_ce_unused(o->src_index);
+ /*
+ * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
+ */
+ if (!o->skip_sparse_checkout)
+ mark_new_skip_worktree(o->el, o->src_index, 0, CE_NEW_SKIP_WORKTREE);
+
if (!dfc)
dfc = xcalloc(1, cache_entry_size(0));
o->df_conflict_entry = dfc;
@@ -750,6 +1039,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
setup_traverse_info(&info, prefix);
info.fn = unpack_callback;
info.data = o;
+ info.show_all_errors = o->show_all_errors;
if (o->prefix) {
/*
@@ -791,25 +1081,39 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (!o->skip_sparse_checkout) {
int empty_worktree = 1;
- for (i = 0;i < o->result.cache_nr;i++) {
+
+ /*
+ * Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
+ * If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
+ * so apply_sparse_checkout() won't attempt to remove it from worktree
+ */
+ mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
+
+ for (i = 0; i < o->result.cache_nr; i++) {
struct cache_entry *ce = o->result.cache[i];
+ /*
+ * Entries marked with CE_ADDED in merged_entry() do not have
+ * verify_absent() check (the check is effectively disabled
+ * because CE_NEW_SKIP_WORKTREE is set unconditionally).
+ *
+ * Do the real check now because we have had
+ * correct CE_NEW_SKIP_WORKTREE
+ */
+ if (ce->ce_flags & CE_ADDED &&
+ verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+ return -1;
+
if (apply_sparse_checkout(ce, o)) {
ret = -1;
goto done;
}
- /*
- * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
- * area as a result of ce_skip_worktree() shortcuts in
- * verify_absent() and verify_uptodate(). Clear them.
- */
- if (ce_skip_worktree(ce))
- ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE);
- else
+ if (!ce_skip_worktree(ce))
empty_worktree = 0;
}
if (o->result.cache_nr && empty_worktree) {
+ /* dubious---why should this fail??? */
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
goto done;
}
@@ -821,16 +1125,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
*o->dst_index = o->result;
done:
- for (i = 0;i < el.nr;i++)
- free(el.excludes[i]);
- if (el.excludes)
- free(el.excludes);
-
+ free_excludes(&el);
return ret;
return_failed:
+ if (o->show_all_errors)
+ display_error_msgs(o);
mark_all_ce_unused(o->src_index);
ret = unpack_failed(o, NULL);
+ if (o->exiting_early)
+ ret = 0;
goto done;
}
@@ -838,7 +1142,7 @@ return_failed:
static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
{
- return error(ERRORMSG(o, would_overwrite), ce->name);
+ return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
}
static int same(struct cache_entry *a, struct cache_entry *b)
@@ -860,7 +1164,7 @@ static int same(struct cache_entry *a, struct cache_entry *b)
*/
static int verify_uptodate_1(struct cache_entry *ce,
struct unpack_trees_options *o,
- const char *error_msg)
+ enum unpack_trees_error_types error_type)
{
struct stat st;
@@ -885,21 +1189,21 @@ static int verify_uptodate_1(struct cache_entry *ce,
if (errno == ENOENT)
return 0;
return o->gently ? -1 :
- error(error_msg, ce->name);
+ add_rejected_path(o, error_type, ce->name);
}
static int verify_uptodate(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
- return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file));
+ return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
}
static int verify_uptodate_sparse(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file));
+ return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
}
static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -915,13 +1219,15 @@ static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_optio
* Currently, git does not checkout subprojects during a superproject
* checkout, so it is not going to overwrite anything.
*/
-static int verify_clean_submodule(struct cache_entry *ce, const char *action,
+static int verify_clean_submodule(struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
return 0;
}
-static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
+static int verify_clean_subdirectory(struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
/*
@@ -942,7 +1248,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
*/
if (!hashcmp(sha1, ce->sha1))
return 0;
- return verify_clean_submodule(ce, action, o);
+ return verify_clean_submodule(ce, error_type, o);
}
/*
@@ -986,7 +1292,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
i = read_directory(&d, pathbuf, namelen+1, NULL);
if (i)
return o->gently ? -1 :
- error(ERRORMSG(o, not_uptodate_dir), ce->name);
+ add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name);
free(pathbuf);
return cnt;
}
@@ -999,91 +1305,122 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* See if we can find a case-insensitive match in the index that also
* matches the stat information, and assume it's that other file!
*/
-static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct stat *st)
+static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st)
{
struct cache_entry *src;
- src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
+ src = index_name_exists(o->src_index, name, len, 1);
return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
}
-/*
- * We do not want to remove or overwrite a working tree file that
- * is not tracked, unless it is ignored.
- */
-static int verify_absent_1(struct cache_entry *ce, const char *action,
- struct unpack_trees_options *o,
- const char *error_msg)
+static int check_ok_to_remove(const char *name, int len, int dtype,
+ struct cache_entry *ce, struct stat *st,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
- struct stat st;
+ struct cache_entry *result;
- if (o->index_only || o->reset || !o->update)
+ /*
+ * It may be that the 'lstat()' succeeded even though
+ * target 'ce' was absent, because there is an old
+ * entry that is different only in case..
+ *
+ * Ignore that lstat() if it matches.
+ */
+ if (ignore_case && icase_exists(o, name, len, st))
return 0;
- if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
+ if (o->dir && excluded(o->dir, name, &dtype))
+ /*
+ * ce->name is explicitly excluded, so it is Ok to
+ * overwrite it.
+ */
return 0;
-
- if (!lstat(ce->name, &st)) {
- int dtype = ce_to_dtype(ce);
- struct cache_entry *result;
-
+ if (S_ISDIR(st->st_mode)) {
/*
- * It may be that the 'lstat()' succeeded even though
- * target 'ce' was absent, because there is an old
- * entry that is different only in case..
- *
- * Ignore that lstat() if it matches.
+ * We are checking out path "foo" and
+ * found "foo/." in the working tree.
+ * This is tricky -- if we have modified
+ * files that are in "foo/" we would lose
+ * them.
*/
- if (ignore_case && icase_exists(o, ce, &st))
- return 0;
+ if (verify_clean_subdirectory(ce, error_type, o) < 0)
+ return -1;
+ return 0;
+ }
- if (o->dir && excluded(o->dir, ce->name, &dtype))
- /*
- * ce->name is explicitly excluded, so it is Ok to
- * overwrite it.
- */
- return 0;
- if (S_ISDIR(st.st_mode)) {
- /*
- * We are checking out path "foo" and
- * found "foo/." in the working tree.
- * This is tricky -- if we have modified
- * files that are in "foo/" we would lose
- * them.
- */
- if (verify_clean_subdirectory(ce, action, o) < 0)
- return -1;
+ /*
+ * The previous round may already have decided to
+ * delete this path, which is in a subdirectory that
+ * is being replaced with a blob.
+ */
+ result = index_name_exists(&o->result, name, len, 0);
+ if (result) {
+ if (result->ce_flags & CE_REMOVE)
return 0;
- }
+ }
- /*
- * The previous round may already have decided to
- * delete this path, which is in a subdirectory that
- * is being replaced with a blob.
- */
- result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0);
- if (result) {
- if (result->ce_flags & CE_REMOVE)
- return 0;
- }
+ return o->gently ? -1 :
+ add_rejected_path(o, error_type, name);
+}
- return o->gently ? -1 :
- error(ERRORMSG(o, would_lose_untracked), ce->name, action);
+/*
+ * We do not want to remove or overwrite a working tree file that
+ * is not tracked, unless it is ignored.
+ */
+static int verify_absent_1(struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
+{
+ int len;
+ struct stat st;
+
+ if (o->index_only || o->reset || !o->update)
+ return 0;
+
+ len = check_leading_path(ce->name, ce_namelen(ce));
+ if (!len)
+ return 0;
+ else if (len > 0) {
+ char path[PATH_MAX + 1];
+ memcpy(path, ce->name, len);
+ path[len] = 0;
+ if (lstat(path, &st))
+ return error("cannot stat '%s': %s", path,
+ strerror(errno));
+
+ return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st,
+ error_type, o);
+ } else if (lstat(ce->name, &st)) {
+ if (errno != ENOENT)
+ return error("cannot stat '%s': %s", ce->name,
+ strerror(errno));
+ return 0;
+ } else {
+ return check_ok_to_remove(ce->name, ce_namelen(ce),
+ ce_to_dtype(ce), ce, &st,
+ error_type, o);
}
- return 0;
}
-static int verify_absent(struct cache_entry *ce, const char *action,
+
+static int verify_absent(struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
- if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return 0;
- return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked));
+ return verify_absent_1(ce, error_type, o);
}
-static int verify_absent_sparse(struct cache_entry *ce, const char *action,
+static int verify_absent_sparse(struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
- return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned));
+ enum unpack_trees_error_types orphaned_error = error_type;
+ if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
+ orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
+
+ return verify_absent_1(ce, orphaned_error, o);
}
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@ -1092,7 +1429,22 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
int update = CE_UPDATE;
if (!old) {
- if (verify_absent(merge, "overwritten", o))
+ /*
+ * New index entries. In sparse checkout, the following
+ * verify_absent() will be delayed until after
+ * traverse_trees() finishes in unpack_trees(), then:
+ *
+ * - CE_NEW_SKIP_WORKTREE will be computed correctly
+ * - verify_absent() be called again, this time with
+ * correct CE_NEW_SKIP_WORKTREE
+ *
+ * verify_absent() call here does nothing in sparse
+ * checkout (i.e. o->skip_sparse_checkout == 0)
+ */
+ update |= CE_ADDED;
+ merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
+
+ if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
return -1;
invalidate_ce_path(merge, o);
} else if (!(old->ce_flags & CE_CONFLICTED)) {
@@ -1109,8 +1461,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
} else {
if (verify_uptodate(old, o))
return -1;
- if (ce_skip_worktree(old))
- update |= CE_SKIP_WORKTREE;
+ /* Migrate old flags over */
+ update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
invalidate_ce_path(old, o);
}
} else {
@@ -1130,7 +1482,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
{
/* Did it exist in the index? */
if (!old) {
- if (verify_absent(ce, "removed", o))
+ if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
return -1;
return 0;
}
@@ -1279,7 +1631,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
if (index)
return deleted_entry(index, index, o);
if (ce && !head_deleted) {
- if (verify_absent(ce, "removed", o))
+ if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
return -1;
}
return 0;
@@ -1412,7 +1764,7 @@ int bind_merge(struct cache_entry **src,
o->merge_size);
if (a && old)
return o->gently ? -1 :
- error(ERRORMSG(o, bind_overlap), a->name, old->name);
+ error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name);
if (!a)
return keep_entry(old, o);
else
diff --git a/unpack-trees.h b/unpack-trees.h
index ef70eab..7998948 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -1,6 +1,8 @@
#ifndef UNPACK_TREES_H
#define UNPACK_TREES_H
+#include "string-list.h"
+
#define MAX_UNPACK_TREES 8
struct unpack_trees_options;
@@ -9,16 +11,26 @@ struct exclude_list;
typedef int (*merge_fn_t)(struct cache_entry **src,
struct unpack_trees_options *options);
-struct unpack_trees_error_msgs {
- const char *would_overwrite;
- const char *not_uptodate_file;
- const char *not_uptodate_dir;
- const char *would_lose_untracked;
- const char *bind_overlap;
- const char *sparse_not_uptodate_file;
- const char *would_lose_orphaned;
+enum unpack_trees_error_types {
+ ERROR_WOULD_OVERWRITE = 0,
+ ERROR_NOT_UPTODATE_FILE,
+ ERROR_NOT_UPTODATE_DIR,
+ ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,
+ ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
+ ERROR_BIND_OVERLAP,
+ ERROR_SPARSE_NOT_UPTODATE_FILE,
+ ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN,
+ ERROR_WOULD_LOSE_ORPHANED_REMOVED,
+ NB_UNPACK_TREES_ERROR_TYPES
};
+/*
+ * Sets the list of user-friendly error messages to be used by the
+ * command "cmd" (either merge or checkout), and show_all_errors to 1.
+ */
+void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
+ const char *cmd);
+
struct unpack_trees_options {
unsigned int reset,
merge,
@@ -33,12 +45,20 @@ struct unpack_trees_options {
diff_index_cached,
debug_unpack,
skip_sparse_checkout,
- gently;
+ gently,
+ exiting_early,
+ show_all_errors,
+ dry_run;
const char *prefix;
int cache_bottom;
struct dir_struct *dir;
merge_fn_t fn;
- struct unpack_trees_error_msgs msgs;
+ const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
+ /*
+ * Store error messages in an array, each case
+ * corresponding to a error message type
+ */
+ struct string_list unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
int head_idx;
int merge_size;
diff --git a/upload-pack.c b/upload-pack.c
index dc464d7..ce5cbbe 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -11,7 +11,7 @@
#include "list-objects.h"
#include "run-command.h"
-static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
+static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
/* bits #0..7 in revision.h, #8..10 in commit.c */
#define THEY_HAVE (1u << 11)
@@ -27,6 +27,7 @@ static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=n
static unsigned long oldest_have;
static int multi_ack, nr_our_refs;
+static int no_done;
static int use_thin_pack, use_ofs_delta, use_include_tag;
static int no_progress, daemon_mode;
static int shallow_nr;
@@ -105,7 +106,7 @@ static void show_edge(struct commit *commit)
fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
}
-static int do_rev_list(int in, int out, void *create_full_pack)
+static int do_rev_list(int in, int out, void *user_data)
{
int i;
struct rev_info revs;
@@ -118,23 +119,18 @@ static int do_rev_list(int in, int out, void *create_full_pack)
if (use_thin_pack)
revs.edge_hint = 1;
- if (create_full_pack) {
- const char *args[] = {"rev-list", "--all", NULL};
- setup_revisions(2, args, &revs, NULL);
- } else {
- for (i = 0; i < want_obj.nr; i++) {
- struct object *o = want_obj.objects[i].item;
- /* why??? */
- o->flags &= ~UNINTERESTING;
- add_pending_object(&revs, o, NULL);
- }
- for (i = 0; i < have_obj.nr; i++) {
- struct object *o = have_obj.objects[i].item;
- o->flags |= UNINTERESTING;
- add_pending_object(&revs, o, NULL);
- }
- setup_revisions(0, NULL, &revs, NULL);
+ for (i = 0; i < want_obj.nr; i++) {
+ struct object *o = want_obj.objects[i].item;
+ /* why??? */
+ o->flags &= ~UNINTERESTING;
+ add_pending_object(&revs, o, NULL);
+ }
+ for (i = 0; i < have_obj.nr; i++) {
+ struct object *o = have_obj.objects[i].item;
+ o->flags |= UNINTERESTING;
+ add_pending_object(&revs, o, NULL);
}
+ setup_revisions(0, NULL, &revs, NULL);
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
@@ -161,15 +157,8 @@ static void create_pack_file(void)
const char *argv[10];
int arg = 0;
- if (shallow_nr) {
- memset(&rev_list, 0, sizeof(rev_list));
- rev_list.proc = do_rev_list;
- rev_list.out = -1;
- if (start_async(&rev_list))
- die("git upload-pack: unable to fork git-rev-list");
- argv[arg++] = "pack-objects";
- } else {
- argv[arg++] = "pack-objects";
+ argv[arg++] = "pack-objects";
+ if (!shallow_nr) {
argv[arg++] = "--revs";
if (create_full_pack)
argv[arg++] = "--all";
@@ -187,7 +176,7 @@ static void create_pack_file(void)
argv[arg++] = NULL;
memset(&pack_objects, 0, sizeof(pack_objects));
- pack_objects.in = shallow_nr ? rev_list.out : -1;
+ pack_objects.in = -1;
pack_objects.out = -1;
pack_objects.err = -1;
pack_objects.git_cmd = 1;
@@ -196,8 +185,14 @@ static void create_pack_file(void)
if (start_command(&pack_objects))
die("git upload-pack: unable to fork git-pack-objects");
- /* pass on revisions we (don't) want */
- if (!shallow_nr) {
+ if (shallow_nr) {
+ memset(&rev_list, 0, sizeof(rev_list));
+ rev_list.proc = do_rev_list;
+ rev_list.out = pack_objects.in;
+ if (start_async(&rev_list))
+ die("git upload-pack: unable to fork git-rev-list");
+ }
+ else {
FILE *pipe_fd = xfdopen(pack_objects.in, "w");
if (!create_full_pack) {
int i;
@@ -371,7 +366,7 @@ static int reachable(struct commit *want)
{
struct commit_list *work = NULL;
- insert_by_date(want, &work);
+ commit_list_insert_by_date(want, &work);
while (work) {
struct commit_list *list = work->next;
struct commit *commit = work->item;
@@ -392,7 +387,7 @@ static int reachable(struct commit *want)
for (list = commit->parents; list; list = list->next) {
struct commit *parent = list->item;
if (!(parent->object.flags & REACHABLE))
- insert_by_date(parent, &work);
+ commit_list_insert_by_date(parent, &work);
}
}
want->object.flags |= REACHABLE;
@@ -434,6 +429,9 @@ static int get_common_commits(void)
static char line[1000];
unsigned char sha1[20];
char last_hex[41];
+ int got_common = 0;
+ int got_other = 0;
+ int sent_ready = 0;
save_commit_buffer = 0;
@@ -442,25 +440,40 @@ static int get_common_commits(void)
reset_timeout();
if (!len) {
+ if (multi_ack == 2 && got_common
+ && !got_other && ok_to_give_up()) {
+ sent_ready = 1;
+ packet_write(1, "ACK %s ready\n", last_hex);
+ }
if (have_obj.nr == 0 || multi_ack)
packet_write(1, "NAK\n");
+
+ if (no_done && sent_ready) {
+ packet_write(1, "ACK %s\n", last_hex);
+ return 0;
+ }
if (stateless_rpc)
exit(0);
+ got_common = 0;
+ got_other = 0;
continue;
}
strip(line, len);
if (!prefixcmp(line, "have ")) {
switch (got_sha1(line+5, sha1)) {
case -1: /* they have what we do not */
+ got_other = 1;
if (multi_ack && ok_to_give_up()) {
const char *hex = sha1_to_hex(sha1);
- if (multi_ack == 2)
+ if (multi_ack == 2) {
+ sent_ready = 1;
packet_write(1, "ACK %s ready\n", hex);
- else
+ } else
packet_write(1, "ACK %s continue\n", hex);
}
break;
default:
+ got_common = 1;
memcpy(last_hex, sha1_to_hex(sha1), 41);
if (multi_ack == 2)
packet_write(1, "ACK %s common\n", last_hex);
@@ -487,7 +500,7 @@ static int get_common_commits(void)
static void receive_needs(void)
{
- struct object_array shallows = {0, 0, NULL};
+ struct object_array shallows = OBJECT_ARRAY_INIT;
static char line[1000];
int len, depth = 0;
@@ -531,6 +544,8 @@ static void receive_needs(void)
multi_ack = 2;
else if (strstr(line+45, "multi_ack"))
multi_ack = 1;
+ if (strstr(line+45, "no-done"))
+ no_done = 1;
if (strstr(line+45, "thin-pack"))
use_thin_pack = 1;
if (strstr(line+45, "ofs-delta"))
@@ -554,7 +569,8 @@ static void receive_needs(void)
*/
o = lookup_object(sha1_buf);
if (!o || !(o->flags & OUR_REF))
- die("git upload-pack: not our ref %s", line+5);
+ die("git upload-pack: not our ref %s",
+ sha1_to_hex(sha1_buf));
if (!(o->flags & WANTED)) {
o->flags |= WANTED;
add_object_array(o, NULL, &want_obj);
@@ -630,8 +646,9 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
if (capabilities)
- packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
- 0, capabilities);
+ packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname,
+ 0, capabilities,
+ stateless_rpc ? " no-done" : "");
else
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
capabilities = NULL;
@@ -686,6 +703,7 @@ int main(int argc, char **argv)
int i;
int strict = 0;
+ packet_trace_identity("upload-pack");
git_extract_argv0_path(argv[0]);
read_replace_refs = 0;
diff --git a/url.c b/url.c
index 2306236..3e06fd3 100644
--- a/url.c
+++ b/url.c
@@ -1,4 +1,5 @@
#include "cache.h"
+#include "url.h"
int is_urlschemechar(int first_flag, int ch)
{
@@ -67,7 +68,8 @@ static int url_decode_char(const char *q)
return val;
}
-static char *url_decode_internal(const char **query, const char *stop_at, struct strbuf *out)
+static char *url_decode_internal(const char **query, const char *stop_at,
+ struct strbuf *out, int decode_plus)
{
const char *q = *query;
@@ -90,7 +92,7 @@ static char *url_decode_internal(const char **query, const char *stop_at, struct
}
}
- if (c == '+')
+ if (decode_plus && c == '+')
strbuf_addch(out, ' ');
else
strbuf_addch(out, c);
@@ -110,17 +112,31 @@ char *url_decode(const char *url)
strbuf_add(&out, url, colon - url);
url = colon;
}
- return url_decode_internal(&url, NULL, &out);
+ return url_decode_internal(&url, NULL, &out, 0);
}
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
- return url_decode_internal(query, "&=", &out);
+ return url_decode_internal(query, "&=", &out, 1);
}
char *url_decode_parameter_value(const char **query)
{
struct strbuf out = STRBUF_INIT;
- return url_decode_internal(query, "&", &out);
+ return url_decode_internal(query, "&", &out, 1);
+}
+
+void end_url_with_slash(struct strbuf *buf, const char *url)
+{
+ strbuf_addstr(buf, url);
+ if (buf->len && buf->buf[buf->len - 1] != '/')
+ strbuf_addstr(buf, "/");
+}
+
+void str_end_url_with_slash(const char *url, char **dest) {
+ struct strbuf buf = STRBUF_INIT;
+ end_url_with_slash(&buf, url);
+ free(*dest);
+ *dest = strbuf_detach(&buf, NULL);
}
diff --git a/url.h b/url.h
index 15817f8..7100e32 100644
--- a/url.h
+++ b/url.h
@@ -7,4 +7,7 @@ extern char *url_decode(const char *url);
extern char *url_decode_parameter_name(const char **query);
extern char *url_decode_parameter_value(const char **query);
+extern void end_url_with_slash(struct strbuf *buf, const char *url);
+extern void str_end_url_with_slash(const char *url, char **dest);
+
#endif /* URL_H */
diff --git a/usage.c b/usage.c
index ec4cf53..b5e67e3 100644
--- a/usage.c
+++ b/usage.c
@@ -46,7 +46,7 @@ void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list param
die_routine = routine;
}
-void usagef(const char *err, ...)
+void NORETURN usagef(const char *err, ...)
{
va_list params;
@@ -55,12 +55,12 @@ void usagef(const char *err, ...)
va_end(params);
}
-void usage(const char *err)
+void NORETURN usage(const char *err)
{
usagef("%s", err);
}
-void die(const char *err, ...)
+void NORETURN die(const char *err, ...)
{
va_list params;
@@ -69,7 +69,7 @@ void die(const char *err, ...)
va_end(params);
}
-void die_errno(const char *fmt, ...)
+void NORETURN die_errno(const char *fmt, ...)
{
va_list params;
char fmt_with_err[1024];
diff --git a/userdiff.c b/userdiff.c
index c49cc1b..01d3a8b 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -8,10 +8,27 @@ static int ndrivers;
static int drivers_alloc;
#define PATTERNS(name, pattern, word_regex) \
- { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex }
+ { name, NULL, -1, { pattern, REG_EXTENDED }, \
+ word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+#define IPATTERN(name, pattern, word_regex) \
+ { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
+ word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
static struct userdiff_driver builtin_drivers[] = {
+IPATTERN("fortran",
+ "!^([C*]|[ \t]*!)\n"
+ "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n"
+ "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA"
+ "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$",
+ /* -- */
+ "[a-zA-Z][a-zA-Z0-9_]*"
+ "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\."
+ /* numbers and format statements like 2E14.4, or ES12.6, 9X.
+ * Don't worry about format statements without leading digits since
+ * they would have been matched above as a variable anyway. */
+ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
+ "|//|\\*\\*|::|[/<>=]="),
PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
- "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"),
+ "[^<>= \t]+"),
PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
"^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
@@ -19,8 +36,7 @@ PATTERNS("java",
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
"|[-+*/<>%&^|=!]="
- "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
PATTERNS("objc",
/* Negate C statements that can look like functions */
"!^[ \t]*(do|for|if|else|return|switch|while)\n"
@@ -33,43 +49,68 @@ PATTERNS("objc",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
PATTERNS("pascal",
- "^((procedure|function|constructor|destructor|interface|"
+ "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|"
"implementation|initialization|finalization)[ \t]*.*)$"
"\n"
"^(.*=[ \t]*(class|record).*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
- "|<>|<=|>=|:=|\\.\\."
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|<>|<=|>=|:=|\\.\\."),
+PATTERNS("perl",
+ "^package .*\n"
+ "^sub [[:alnum:]_':]+[ \t]*"
+ "(\\([^)]*\\)[ \t]*)?" /* prototype */
+ /*
+ * Attributes. A regex can't count nested parentheses,
+ * so just slurp up whatever we see, taking care not
+ * to accept lines like "sub foo; # defined elsewhere".
+ *
+ * An attribute could contain a semicolon, but at that
+ * point it seems reasonable enough to give up.
+ */
+ "(:[^;#]*)?"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n" /* comment */
+ "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n"
+ "^=head[0-9] .*", /* POD */
+ /* -- */
+ "[[:alpha:]_'][[:alnum:]_']*"
+ "|0[xb]?[0-9a-fA-F_]*"
+ /* taking care not to interpret 3..5 as (3.)(.5) */
+ "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?"
+ "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::"
+ "|&&=|\\|\\|=|//=|\\*\\*="
+ "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?"
+ "|[-+*/%.^&<>=!|]="
+ "|=~|!~"
+ "|<<|<>|<=>|>>"),
PATTERNS("php",
"^[\t ]*(((public|protected|private|static)[\t ]+)*function.*)$\n"
"^[\t ]*(class.*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
- "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"),
PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"
- "|[^[:space:]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"),
/* -- */
PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
/* -- */
"(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
- "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"
- "|[^[:space:]|[\x80-\xff]+"),
+ "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"),
PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
"[={}\"]|[^={}\" \t]+"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
- "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"),
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
PATTERNS("cpp",
/* Jump targets or access declarations */
"!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
@@ -80,11 +121,26 @@ PATTERNS("cpp",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
- "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
- "|[^[:space:]]|[\x80-\xff]+"),
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+PATTERNS("csharp",
+ /* Keywords */
+ "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
+ /* Methods and constructors */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+ /* Properties */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+ /* Type definitions */
+ "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
+ /* Namespace */
+ "^[ \t]*(namespace[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
{ "default", NULL, -1, { NULL, 0 } },
};
#undef PATTERNS
+#undef IPATTERN
static struct userdiff_driver driver_true = {
"diff=true",
@@ -225,3 +281,20 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
return NULL;
return userdiff_find_by_name(check.value);
}
+
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
+{
+ if (!driver->textconv)
+ return NULL;
+
+ if (driver->textconv_want_cache && !driver->textconv_cache) {
+ struct notes_cache *c = xmalloc(sizeof(*c));
+ struct strbuf name = STRBUF_INIT;
+
+ strbuf_addf(&name, "textconv/%s", driver->name);
+ notes_cache_init(c, name.buf, driver->textconv);
+ driver->textconv_cache = c;
+ }
+
+ return driver;
+}
diff --git a/userdiff.h b/userdiff.h
index 942d594..4a7e78f 100644
--- a/userdiff.h
+++ b/userdiff.h
@@ -23,4 +23,6 @@ int userdiff_config(const char *k, const char *v);
struct userdiff_driver *userdiff_find_by_name(const char *name);
struct userdiff_driver *userdiff_find_by_path(const char *path);
+struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver);
+
#endif /* USERDIFF */
diff --git a/utf8.c b/utf8.c
index 84cfc72..8acbc66 100644
--- a/utf8.c
+++ b/utf8.c
@@ -405,6 +405,15 @@ new_line:
}
}
+int strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
+ int indent, int indent2, int width)
+{
+ char *tmp = xstrndup(data, len);
+ int r = strbuf_add_wrapped_text(buf, tmp, indent, indent2, width);
+ free(tmp);
+ return r;
+}
+
int is_encoding_utf8(const char *name)
{
if (!name)
diff --git a/utf8.h b/utf8.h
index ebc4d2f..81f2c82 100644
--- a/utf8.h
+++ b/utf8.h
@@ -10,6 +10,8 @@ int is_encoding_utf8(const char *name);
int strbuf_add_wrapped_text(struct strbuf *buf,
const char *text, int indent, int indent2, int width);
+int strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
+ int indent, int indent2, int width);
#ifndef NO_ICONV
char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
diff --git a/vcs-svn/LICENSE b/vcs-svn/LICENSE
new file mode 100644
index 0000000..0a5e3c4
--- /dev/null
+++ b/vcs-svn/LICENSE
@@ -0,0 +1,33 @@
+Copyright (C) 2010 David Barr <david.barr@cordelta.com>.
+All rights reserved.
+
+Copyright (C) 2008 Jason Evans <jasone@canonware.com>.
+All rights reserved.
+
+Copyright (C) 2005 Stefan Hegny, hydrografix Consulting GmbH,
+Frankfurt/Main, Germany
+and others, see http://svn2cc.sarovar.org
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice(s), this list of conditions and the following disclaimer
+ unmodified other than the allowable addition of one or more
+ copyright notices.
+2. Redistributions in binary form must reproduce the above copyright
+ notice(s), this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c
new file mode 100644
index 0000000..99ed70b
--- /dev/null
+++ b/vcs-svn/fast_export.c
@@ -0,0 +1,88 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "fast_export.h"
+#include "line_buffer.h"
+#include "repo_tree.h"
+#include "string_pool.h"
+
+#define MAX_GITSVN_LINE_LEN 4096
+
+static uint32_t first_commit_done;
+
+void fast_export_delete(uint32_t depth, uint32_t *path)
+{
+ putchar('D');
+ putchar(' ');
+ pool_print_seq(depth, path, '/', stdout);
+ putchar('\n');
+}
+
+void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
+ uint32_t mark)
+{
+ /* Mode must be 100644, 100755, 120000, or 160000. */
+ printf("M %06"PRIo32" :%"PRIu32" ", mode, mark);
+ pool_print_seq(depth, path, '/', stdout);
+ putchar('\n');
+}
+
+static char gitsvnline[MAX_GITSVN_LINE_LEN];
+void fast_export_commit(uint32_t revision, const char *author,
+ const struct strbuf *log,
+ const char *uuid, const char *url,
+ unsigned long timestamp)
+{
+ static const struct strbuf empty = STRBUF_INIT;
+ if (!log)
+ log = &empty;
+ if (*uuid && *url) {
+ snprintf(gitsvnline, MAX_GITSVN_LINE_LEN,
+ "\n\ngit-svn-id: %s@%"PRIu32" %s\n",
+ url, revision, uuid);
+ } else {
+ *gitsvnline = '\0';
+ }
+ printf("commit refs/heads/master\n");
+ printf("committer %s <%s@%s> %ld +0000\n",
+ *author ? author : "nobody",
+ *author ? author : "nobody",
+ *uuid ? uuid : "local", timestamp);
+ printf("data %"PRIuMAX"\n",
+ (uintmax_t) (log->len + strlen(gitsvnline)));
+ fwrite(log->buf, log->len, 1, stdout);
+ printf("%s\n", gitsvnline);
+ if (!first_commit_done) {
+ if (revision > 1)
+ printf("from refs/heads/master^0\n");
+ first_commit_done = 1;
+ }
+ repo_diff(revision - 1, revision);
+ fputc('\n', stdout);
+
+ printf("progress Imported commit %"PRIu32".\n\n", revision);
+}
+
+static void die_short_read(struct line_buffer *input)
+{
+ if (buffer_ferror(input))
+ die_errno("error reading dump file");
+ die("invalid dump: unexpected end of file");
+}
+
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len, struct line_buffer *input)
+{
+ if (mode == REPO_MODE_LNK) {
+ /* svn symlink blobs start with "link " */
+ len -= 5;
+ if (buffer_skip_bytes(input, 5) != 5)
+ die_short_read(input);
+ }
+ printf("blob\nmark :%"PRIu32"\ndata %"PRIu32"\n", mark, len);
+ if (buffer_copy_bytes(input, len) != len)
+ die_short_read(input);
+ fputc('\n', stdout);
+}
diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h
new file mode 100644
index 0000000..33a8fe9
--- /dev/null
+++ b/vcs-svn/fast_export.h
@@ -0,0 +1,16 @@
+#ifndef FAST_EXPORT_H_
+#define FAST_EXPORT_H_
+
+#include "line_buffer.h"
+struct strbuf;
+
+void fast_export_delete(uint32_t depth, uint32_t *path);
+void fast_export_modify(uint32_t depth, uint32_t *path, uint32_t mode,
+ uint32_t mark);
+void fast_export_commit(uint32_t revision, const char *author,
+ const struct strbuf *log, const char *uuid,
+ const char *url, unsigned long timestamp);
+void fast_export_blob(uint32_t mode, uint32_t mark, uint32_t len,
+ struct line_buffer *input);
+
+#endif
diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c
new file mode 100644
index 0000000..c390387
--- /dev/null
+++ b/vcs-svn/line_buffer.c
@@ -0,0 +1,130 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "line_buffer.h"
+#include "strbuf.h"
+
+#define COPY_BUFFER_LEN 4096
+
+int buffer_init(struct line_buffer *buf, const char *filename)
+{
+ buf->infile = filename ? fopen(filename, "r") : stdin;
+ if (!buf->infile)
+ return -1;
+ return 0;
+}
+
+int buffer_fdinit(struct line_buffer *buf, int fd)
+{
+ buf->infile = fdopen(fd, "r");
+ if (!buf->infile)
+ return -1;
+ return 0;
+}
+
+int buffer_tmpfile_init(struct line_buffer *buf)
+{
+ buf->infile = tmpfile();
+ if (!buf->infile)
+ return -1;
+ return 0;
+}
+
+int buffer_deinit(struct line_buffer *buf)
+{
+ int err;
+ if (buf->infile == stdin)
+ return ferror(buf->infile);
+ err = ferror(buf->infile);
+ err |= fclose(buf->infile);
+ return err;
+}
+
+FILE *buffer_tmpfile_rewind(struct line_buffer *buf)
+{
+ rewind(buf->infile);
+ return buf->infile;
+}
+
+long buffer_tmpfile_prepare_to_read(struct line_buffer *buf)
+{
+ long pos = ftell(buf->infile);
+ if (pos < 0)
+ return error("ftell error: %s", strerror(errno));
+ if (fseek(buf->infile, 0, SEEK_SET))
+ return error("seek error: %s", strerror(errno));
+ return pos;
+}
+
+int buffer_ferror(struct line_buffer *buf)
+{
+ return ferror(buf->infile);
+}
+
+int buffer_read_char(struct line_buffer *buf)
+{
+ return fgetc(buf->infile);
+}
+
+/* Read a line without trailing newline. */
+char *buffer_read_line(struct line_buffer *buf)
+{
+ char *end;
+ if (!fgets(buf->line_buffer, sizeof(buf->line_buffer), buf->infile))
+ /* Error or data exhausted. */
+ return NULL;
+ end = buf->line_buffer + strlen(buf->line_buffer);
+ if (end[-1] == '\n')
+ end[-1] = '\0';
+ else if (feof(buf->infile))
+ ; /* No newline at end of file. That's fine. */
+ else
+ /*
+ * Line was too long.
+ * There is probably a saner way to deal with this,
+ * but for now let's return an error.
+ */
+ return NULL;
+ return buf->line_buffer;
+}
+
+void buffer_read_binary(struct line_buffer *buf,
+ struct strbuf *sb, uint32_t size)
+{
+ strbuf_fread(sb, size, buf->infile);
+}
+
+off_t buffer_copy_bytes(struct line_buffer *buf, off_t nbytes)
+{
+ char byte_buffer[COPY_BUFFER_LEN];
+ off_t done = 0;
+ while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) {
+ off_t len = nbytes - done;
+ size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
+ in = fread(byte_buffer, 1, in, buf->infile);
+ done += in;
+ fwrite(byte_buffer, 1, in, stdout);
+ if (ferror(stdout))
+ return done + buffer_skip_bytes(buf, nbytes - done);
+ }
+ return done;
+}
+
+off_t buffer_skip_bytes(struct line_buffer *buf, off_t nbytes)
+{
+ char byte_buffer[COPY_BUFFER_LEN];
+ off_t done = 0;
+ while (done < nbytes && !feof(buf->infile) && !ferror(buf->infile)) {
+ off_t len = nbytes - done;
+ size_t in = len < COPY_BUFFER_LEN ? len : COPY_BUFFER_LEN;
+ done += fread(byte_buffer, 1, in, buf->infile);
+ }
+ return done;
+}
+
+void buffer_reset(struct line_buffer *buf)
+{
+}
diff --git a/vcs-svn/line_buffer.h b/vcs-svn/line_buffer.h
new file mode 100644
index 0000000..d0b22dd
--- /dev/null
+++ b/vcs-svn/line_buffer.h
@@ -0,0 +1,31 @@
+#ifndef LINE_BUFFER_H_
+#define LINE_BUFFER_H_
+
+#include "strbuf.h"
+
+#define LINE_BUFFER_LEN 10000
+
+struct line_buffer {
+ char line_buffer[LINE_BUFFER_LEN];
+ FILE *infile;
+};
+#define LINE_BUFFER_INIT { "", NULL }
+
+int buffer_init(struct line_buffer *buf, const char *filename);
+int buffer_fdinit(struct line_buffer *buf, int fd);
+int buffer_deinit(struct line_buffer *buf);
+void buffer_reset(struct line_buffer *buf);
+
+int buffer_tmpfile_init(struct line_buffer *buf);
+FILE *buffer_tmpfile_rewind(struct line_buffer *buf); /* prepare to write. */
+long buffer_tmpfile_prepare_to_read(struct line_buffer *buf);
+
+int buffer_ferror(struct line_buffer *buf);
+char *buffer_read_line(struct line_buffer *buf);
+int buffer_read_char(struct line_buffer *buf);
+void buffer_read_binary(struct line_buffer *buf, struct strbuf *sb, uint32_t len);
+/* Returns number of bytes read (not necessarily written). */
+off_t buffer_copy_bytes(struct line_buffer *buf, off_t len);
+off_t buffer_skip_bytes(struct line_buffer *buf, off_t len);
+
+#endif
diff --git a/vcs-svn/line_buffer.txt b/vcs-svn/line_buffer.txt
new file mode 100644
index 0000000..8e139eb
--- /dev/null
+++ b/vcs-svn/line_buffer.txt
@@ -0,0 +1,77 @@
+line_buffer API
+===============
+
+The line_buffer library provides a convenient interface for
+mostly-line-oriented input.
+
+Each line is not permitted to exceed 10000 bytes. The provided
+functions are not thread-safe or async-signal-safe, and like
+`fgets()`, they generally do not function correctly if interrupted
+by a signal without SA_RESTART set.
+
+Calling sequence
+----------------
+
+The calling program:
+
+ - initializes a `struct line_buffer` to LINE_BUFFER_INIT
+ - specifies a file to read with `buffer_init`
+ - processes input with `buffer_read_line`, `buffer_skip_bytes`,
+ and `buffer_copy_bytes`
+ - closes the file with `buffer_deinit`, perhaps to start over and
+ read another file.
+
+When finished, the caller can use `buffer_reset` to deallocate
+resources.
+
+Using temporary files
+---------------------
+
+Temporary files provide a place to store data that should not outlive
+the calling program. A program
+
+ - initializes a `struct line_buffer` to LINE_BUFFER_INIT
+ - requests a temporary file with `buffer_tmpfile_init`
+ - acquires an output handle by calling `buffer_tmpfile_rewind`
+ - uses standard I/O functions like `fprintf` and `fwrite` to fill
+ the temporary file
+ - declares writing is over with `buffer_tmpfile_prepare_to_read`
+ - can re-read what was written with `buffer_read_line`,
+ `buffer_copy_bytes`, and so on
+ - can reuse the temporary file by calling `buffer_tmpfile_rewind`
+ again
+ - removes the temporary file with `buffer_deinit`, perhaps to
+ reuse the line_buffer for some other file.
+
+When finished, the calling program can use `buffer_reset` to deallocate
+resources.
+
+Functions
+---------
+
+`buffer_init`, `buffer_fdinit`::
+ Open the named file or file descriptor for input.
+ buffer_init(buf, NULL) prepares to read from stdin.
+ On failure, returns -1 (with errno indicating the nature
+ of the failure).
+
+`buffer_deinit`::
+ Stop reading from the current file (closing it unless
+ it was stdin). Returns nonzero if `fclose` fails or
+ the error indicator was set.
+
+`buffer_read_line`::
+ Read a line and strip off the trailing newline.
+ On failure or end of file, returns NULL.
+
+`buffer_copy_bytes`::
+ Read `len` bytes of input and dump them to the standard output
+ stream. Returns early for error or end of file.
+
+`buffer_skip_bytes`::
+ Discards `len` bytes from the input stream (stopping early
+ if necessary because of an error or eof). Return value is
+ the number of bytes successfully read.
+
+`buffer_reset`::
+ Deallocates non-static buffers.
diff --git a/vcs-svn/obj_pool.h b/vcs-svn/obj_pool.h
new file mode 100644
index 0000000..deb6eb8
--- /dev/null
+++ b/vcs-svn/obj_pool.h
@@ -0,0 +1,61 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#ifndef OBJ_POOL_H_
+#define OBJ_POOL_H_
+
+#include "git-compat-util.h"
+
+#define MAYBE_UNUSED __attribute__((__unused__))
+
+#define obj_pool_gen(pre, obj_t, initial_capacity) \
+static struct { \
+ uint32_t committed; \
+ uint32_t size; \
+ uint32_t capacity; \
+ obj_t *base; \
+} pre##_pool = {0, 0, 0, NULL}; \
+static MAYBE_UNUSED uint32_t pre##_alloc(uint32_t count) \
+{ \
+ uint32_t offset; \
+ if (pre##_pool.size + count > pre##_pool.capacity) { \
+ while (pre##_pool.size + count > pre##_pool.capacity) \
+ if (pre##_pool.capacity) \
+ pre##_pool.capacity *= 2; \
+ else \
+ pre##_pool.capacity = initial_capacity; \
+ pre##_pool.base = realloc(pre##_pool.base, \
+ pre##_pool.capacity * sizeof(obj_t)); \
+ } \
+ offset = pre##_pool.size; \
+ pre##_pool.size += count; \
+ return offset; \
+} \
+static MAYBE_UNUSED void pre##_free(uint32_t count) \
+{ \
+ pre##_pool.size -= count; \
+} \
+static MAYBE_UNUSED uint32_t pre##_offset(obj_t *obj) \
+{ \
+ return obj == NULL ? ~0 : obj - pre##_pool.base; \
+} \
+static MAYBE_UNUSED obj_t *pre##_pointer(uint32_t offset) \
+{ \
+ return offset >= pre##_pool.size ? NULL : &pre##_pool.base[offset]; \
+} \
+static MAYBE_UNUSED void pre##_commit(void) \
+{ \
+ pre##_pool.committed = pre##_pool.size; \
+} \
+static MAYBE_UNUSED void pre##_reset(void) \
+{ \
+ free(pre##_pool.base); \
+ pre##_pool.base = NULL; \
+ pre##_pool.size = 0; \
+ pre##_pool.capacity = 0; \
+ pre##_pool.committed = 0; \
+}
+
+#endif
diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c
new file mode 100644
index 0000000..a21d89d
--- /dev/null
+++ b/vcs-svn/repo_tree.c
@@ -0,0 +1,326 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+
+#include "string_pool.h"
+#include "repo_tree.h"
+#include "obj_pool.h"
+#include "fast_export.h"
+
+#include "trp.h"
+
+struct repo_dirent {
+ uint32_t name_offset;
+ struct trp_node children;
+ uint32_t mode;
+ uint32_t content_offset;
+};
+
+struct repo_dir {
+ struct trp_root entries;
+};
+
+struct repo_commit {
+ uint32_t root_dir_offset;
+};
+
+/* Memory pools for commit, dir and dirent */
+obj_pool_gen(commit, struct repo_commit, 4096)
+obj_pool_gen(dir, struct repo_dir, 4096)
+obj_pool_gen(dent, struct repo_dirent, 4096)
+
+static uint32_t active_commit;
+static uint32_t mark;
+
+static int repo_dirent_name_cmp(const void *a, const void *b);
+
+/* Treap for directory entries */
+trp_gen(static, dent_, struct repo_dirent, children, dent, repo_dirent_name_cmp)
+
+uint32_t next_blob_mark(void)
+{
+ return mark++;
+}
+
+static struct repo_dir *repo_commit_root_dir(struct repo_commit *commit)
+{
+ return dir_pointer(commit->root_dir_offset);
+}
+
+static struct repo_dirent *repo_first_dirent(struct repo_dir *dir)
+{
+ return dent_first(&dir->entries);
+}
+
+static int repo_dirent_name_cmp(const void *a, const void *b)
+{
+ const struct repo_dirent *dent1 = a, *dent2 = b;
+ uint32_t a_offset = dent1->name_offset;
+ uint32_t b_offset = dent2->name_offset;
+ return (a_offset > b_offset) - (a_offset < b_offset);
+}
+
+static int repo_dirent_is_dir(struct repo_dirent *dent)
+{
+ return dent != NULL && dent->mode == REPO_MODE_DIR;
+}
+
+static struct repo_dir *repo_dir_from_dirent(struct repo_dirent *dent)
+{
+ if (!repo_dirent_is_dir(dent))
+ return NULL;
+ return dir_pointer(dent->content_offset);
+}
+
+static struct repo_dir *repo_clone_dir(struct repo_dir *orig_dir)
+{
+ uint32_t orig_o, new_o;
+ orig_o = dir_offset(orig_dir);
+ if (orig_o >= dir_pool.committed)
+ return orig_dir;
+ new_o = dir_alloc(1);
+ orig_dir = dir_pointer(orig_o);
+ *dir_pointer(new_o) = *orig_dir;
+ return dir_pointer(new_o);
+}
+
+static struct repo_dirent *repo_read_dirent(uint32_t revision,
+ const uint32_t *path)
+{
+ uint32_t name = 0;
+ struct repo_dirent *key = dent_pointer(dent_alloc(1));
+ struct repo_dir *dir = NULL;
+ struct repo_dirent *dent = NULL;
+ dir = repo_commit_root_dir(commit_pointer(revision));
+ while (~(name = *path++)) {
+ key->name_offset = name;
+ dent = dent_search(&dir->entries, key);
+ if (dent == NULL || !repo_dirent_is_dir(dent))
+ break;
+ dir = repo_dir_from_dirent(dent);
+ }
+ dent_free(1);
+ return dent;
+}
+
+static void repo_write_dirent(const uint32_t *path, uint32_t mode,
+ uint32_t content_offset, uint32_t del)
+{
+ uint32_t name, revision, dir_o = ~0, parent_dir_o = ~0;
+ struct repo_dir *dir;
+ struct repo_dirent *key;
+ struct repo_dirent *dent = NULL;
+ revision = active_commit;
+ dir = repo_commit_root_dir(commit_pointer(revision));
+ dir = repo_clone_dir(dir);
+ commit_pointer(revision)->root_dir_offset = dir_offset(dir);
+ while (~(name = *path++)) {
+ parent_dir_o = dir_offset(dir);
+
+ key = dent_pointer(dent_alloc(1));
+ key->name_offset = name;
+
+ dent = dent_search(&dir->entries, key);
+ if (dent == NULL)
+ dent = key;
+ else
+ dent_free(1);
+
+ if (dent == key) {
+ dent->mode = REPO_MODE_DIR;
+ dent->content_offset = 0;
+ dent = dent_insert(&dir->entries, dent);
+ }
+
+ if (dent_offset(dent) < dent_pool.committed) {
+ dir_o = repo_dirent_is_dir(dent) ?
+ dent->content_offset : ~0;
+ dent_remove(&dir->entries, dent);
+ dent = dent_pointer(dent_alloc(1));
+ dent->name_offset = name;
+ dent->mode = REPO_MODE_DIR;
+ dent->content_offset = dir_o;
+ dent = dent_insert(&dir->entries, dent);
+ }
+
+ dir = repo_dir_from_dirent(dent);
+ dir = repo_clone_dir(dir);
+ dent->content_offset = dir_offset(dir);
+ }
+ if (dent == NULL)
+ return;
+ dent->mode = mode;
+ dent->content_offset = content_offset;
+ if (del && ~parent_dir_o)
+ dent_remove(&dir_pointer(parent_dir_o)->entries, dent);
+}
+
+uint32_t repo_read_path(const uint32_t *path)
+{
+ uint32_t content_offset = 0;
+ struct repo_dirent *dent = repo_read_dirent(active_commit, path);
+ if (dent != NULL)
+ content_offset = dent->content_offset;
+ return content_offset;
+}
+
+uint32_t repo_read_mode(const uint32_t *path)
+{
+ struct repo_dirent *dent = repo_read_dirent(active_commit, path);
+ if (dent == NULL)
+ die("invalid dump: path to be modified is missing");
+ return dent->mode;
+}
+
+void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst)
+{
+ uint32_t mode = 0, content_offset = 0;
+ struct repo_dirent *src_dent;
+ src_dent = repo_read_dirent(revision, src);
+ if (src_dent != NULL) {
+ mode = src_dent->mode;
+ content_offset = src_dent->content_offset;
+ repo_write_dirent(dst, mode, content_offset, 0);
+ }
+}
+
+void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark)
+{
+ repo_write_dirent(path, mode, blob_mark, 0);
+}
+
+void repo_delete(uint32_t *path)
+{
+ repo_write_dirent(path, 0, 0, 1);
+}
+
+static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir);
+
+static void repo_git_add(uint32_t depth, uint32_t *path, struct repo_dirent *dent)
+{
+ if (repo_dirent_is_dir(dent))
+ repo_git_add_r(depth, path, repo_dir_from_dirent(dent));
+ else
+ fast_export_modify(depth, path,
+ dent->mode, dent->content_offset);
+}
+
+static void repo_git_add_r(uint32_t depth, uint32_t *path, struct repo_dir *dir)
+{
+ struct repo_dirent *de = repo_first_dirent(dir);
+ while (de) {
+ path[depth] = de->name_offset;
+ repo_git_add(depth + 1, path, de);
+ de = dent_next(&dir->entries, de);
+ }
+}
+
+static void repo_diff_r(uint32_t depth, uint32_t *path, struct repo_dir *dir1,
+ struct repo_dir *dir2)
+{
+ struct repo_dirent *de1, *de2;
+ de1 = repo_first_dirent(dir1);
+ de2 = repo_first_dirent(dir2);
+
+ while (de1 && de2) {
+ if (de1->name_offset < de2->name_offset) {
+ path[depth] = de1->name_offset;
+ fast_export_delete(depth + 1, path);
+ de1 = dent_next(&dir1->entries, de1);
+ continue;
+ }
+ if (de1->name_offset > de2->name_offset) {
+ path[depth] = de2->name_offset;
+ repo_git_add(depth + 1, path, de2);
+ de2 = dent_next(&dir2->entries, de2);
+ continue;
+ }
+ path[depth] = de1->name_offset;
+
+ if (de1->mode == de2->mode &&
+ de1->content_offset == de2->content_offset) {
+ ; /* No change. */
+ } else if (repo_dirent_is_dir(de1) && repo_dirent_is_dir(de2)) {
+ repo_diff_r(depth + 1, path,
+ repo_dir_from_dirent(de1),
+ repo_dir_from_dirent(de2));
+ } else if (!repo_dirent_is_dir(de1) && !repo_dirent_is_dir(de2)) {
+ repo_git_add(depth + 1, path, de2);
+ } else {
+ fast_export_delete(depth + 1, path);
+ repo_git_add(depth + 1, path, de2);
+ }
+ de1 = dent_next(&dir1->entries, de1);
+ de2 = dent_next(&dir2->entries, de2);
+ }
+ while (de1) {
+ path[depth] = de1->name_offset;
+ fast_export_delete(depth + 1, path);
+ de1 = dent_next(&dir1->entries, de1);
+ }
+ while (de2) {
+ path[depth] = de2->name_offset;
+ repo_git_add(depth + 1, path, de2);
+ de2 = dent_next(&dir2->entries, de2);
+ }
+}
+
+static uint32_t path_stack[REPO_MAX_PATH_DEPTH];
+
+void repo_diff(uint32_t r1, uint32_t r2)
+{
+ repo_diff_r(0,
+ path_stack,
+ repo_commit_root_dir(commit_pointer(r1)),
+ repo_commit_root_dir(commit_pointer(r2)));
+}
+
+void repo_commit(uint32_t revision, const char *author,
+ const struct strbuf *log, const char *uuid, const char *url,
+ unsigned long timestamp)
+{
+ fast_export_commit(revision, author, log, uuid, url, timestamp);
+ dent_commit();
+ dir_commit();
+ active_commit = commit_alloc(1);
+ commit_pointer(active_commit)->root_dir_offset =
+ commit_pointer(active_commit - 1)->root_dir_offset;
+}
+
+static void mark_init(void)
+{
+ uint32_t i;
+ mark = 0;
+ for (i = 0; i < dent_pool.size; i++)
+ if (!repo_dirent_is_dir(dent_pointer(i)) &&
+ dent_pointer(i)->content_offset > mark)
+ mark = dent_pointer(i)->content_offset;
+ mark++;
+}
+
+void repo_init(void)
+{
+ mark_init();
+ if (commit_pool.size == 0) {
+ /* Create empty tree for commit 0. */
+ commit_alloc(1);
+ commit_pointer(0)->root_dir_offset = dir_alloc(1);
+ dir_pointer(0)->entries.trp_root = ~0;
+ dir_commit();
+ }
+ /* Preallocate next commit, ready for changes. */
+ active_commit = commit_alloc(1);
+ commit_pointer(active_commit)->root_dir_offset =
+ commit_pointer(active_commit - 1)->root_dir_offset;
+}
+
+void repo_reset(void)
+{
+ pool_reset();
+ commit_reset();
+ dir_reset();
+ dent_reset();
+}
diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h
new file mode 100644
index 0000000..37bde2e
--- /dev/null
+++ b/vcs-svn/repo_tree.h
@@ -0,0 +1,27 @@
+#ifndef REPO_TREE_H_
+#define REPO_TREE_H_
+
+struct strbuf;
+
+#define REPO_MODE_DIR 0040000
+#define REPO_MODE_BLB 0100644
+#define REPO_MODE_EXE 0100755
+#define REPO_MODE_LNK 0120000
+
+#define REPO_MAX_PATH_LEN 4096
+#define REPO_MAX_PATH_DEPTH 1000
+
+uint32_t next_blob_mark(void);
+void repo_copy(uint32_t revision, const uint32_t *src, const uint32_t *dst);
+void repo_add(uint32_t *path, uint32_t mode, uint32_t blob_mark);
+uint32_t repo_read_path(const uint32_t *path);
+uint32_t repo_read_mode(const uint32_t *path);
+void repo_delete(uint32_t *path);
+void repo_commit(uint32_t revision, const char *author,
+ const struct strbuf *log, const char *uuid, const char *url,
+ long unsigned timestamp);
+void repo_diff(uint32_t r1, uint32_t r2);
+void repo_init(void);
+void repo_reset(void);
+
+#endif
diff --git a/vcs-svn/string_pool.c b/vcs-svn/string_pool.c
new file mode 100644
index 0000000..8af8d54
--- /dev/null
+++ b/vcs-svn/string_pool.c
@@ -0,0 +1,102 @@
+/*
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "git-compat-util.h"
+#include "trp.h"
+#include "obj_pool.h"
+#include "string_pool.h"
+
+static struct trp_root tree = { ~0 };
+
+struct node {
+ uint32_t offset;
+ struct trp_node children;
+};
+
+/* Two memory pools: one for struct node, and another for strings */
+obj_pool_gen(node, struct node, 4096)
+obj_pool_gen(string, char, 4096)
+
+static char *node_value(struct node *node)
+{
+ return node ? string_pointer(node->offset) : NULL;
+}
+
+static int node_cmp(struct node *a, struct node *b)
+{
+ return strcmp(node_value(a), node_value(b));
+}
+
+/* Build a Treap from the node structure (a trp_node w/ offset) */
+trp_gen(static, tree_, struct node, children, node, node_cmp)
+
+const char *pool_fetch(uint32_t entry)
+{
+ return node_value(node_pointer(entry));
+}
+
+uint32_t pool_intern(const char *key)
+{
+ /* Canonicalize key */
+ struct node *match = NULL, *node;
+ uint32_t key_len;
+ if (key == NULL)
+ return ~0;
+ key_len = strlen(key) + 1;
+ node = node_pointer(node_alloc(1));
+ node->offset = string_alloc(key_len);
+ strcpy(node_value(node), key);
+ match = tree_search(&tree, node);
+ if (!match) {
+ tree_insert(&tree, node);
+ } else {
+ node_free(1);
+ string_free(key_len);
+ node = match;
+ }
+ return node_offset(node);
+}
+
+uint32_t pool_tok_r(char *str, const char *delim, char **saveptr)
+{
+ char *token = strtok_r(str, delim, saveptr);
+ return token ? pool_intern(token) : ~0;
+}
+
+void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream)
+{
+ uint32_t i;
+ for (i = 0; i < len && ~seq[i]; i++) {
+ fputs(pool_fetch(seq[i]), stream);
+ if (i < len - 1 && ~seq[i + 1])
+ fputc(delim, stream);
+ }
+}
+
+uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str)
+{
+ char *context = NULL;
+ uint32_t token = ~0;
+ uint32_t length;
+
+ if (sz == 0)
+ return ~0;
+ if (str)
+ token = pool_tok_r(str, delim, &context);
+ for (length = 0; length < sz; length++) {
+ seq[length] = token;
+ if (token == ~0)
+ return length;
+ token = pool_tok_r(NULL, delim, &context);
+ }
+ seq[sz - 1] = ~0;
+ return sz;
+}
+
+void pool_reset(void)
+{
+ node_reset();
+ string_reset();
+}
diff --git a/vcs-svn/string_pool.h b/vcs-svn/string_pool.h
new file mode 100644
index 0000000..222fb66
--- /dev/null
+++ b/vcs-svn/string_pool.h
@@ -0,0 +1,11 @@
+#ifndef STRING_POOL_H_
+#define STRING_POOL_H_
+
+uint32_t pool_intern(const char *key);
+const char *pool_fetch(uint32_t entry);
+uint32_t pool_tok_r(char *str, const char *delim, char **saveptr);
+void pool_print_seq(uint32_t len, uint32_t *seq, char delim, FILE *stream);
+uint32_t pool_tok_seq(uint32_t sz, uint32_t *seq, const char *delim, char *str);
+void pool_reset(void);
+
+#endif
diff --git a/vcs-svn/string_pool.txt b/vcs-svn/string_pool.txt
new file mode 100644
index 0000000..1b41f15
--- /dev/null
+++ b/vcs-svn/string_pool.txt
@@ -0,0 +1,43 @@
+string_pool API
+===============
+
+The string_pool API provides facilities for replacing strings
+with integer keys that can be more easily compared and stored.
+The facilities are designed so that one could teach Git without
+too much trouble to store the information needed for these keys to
+remain valid over multiple executions.
+
+Functions
+---------
+
+pool_intern::
+ Include a string in the string pool and get its key.
+ If that string is already in the pool, retrieves its
+ existing key.
+
+pool_fetch::
+ Retrieve the string associated to a given key.
+
+pool_tok_r::
+ Extract the key of the next token from a string.
+ Interface mimics strtok_r.
+
+pool_print_seq::
+ Print a sequence of strings named by key to a file, using the
+ specified delimiter to separate them.
+
+ If NULL (key ~0) appears in the sequence, the sequence ends
+ early.
+
+pool_tok_seq::
+ Split a string into tokens, storing the keys of segments
+ into a caller-provided array.
+
+ Unless sz is 0, the array will always be ~0-terminated.
+ If there is not enough room for all the tokens, the
+ array holds as many tokens as fit in the entries before
+ the terminating ~0. Return value is the index after the
+ last token, or sz if the tokens did not fit.
+
+pool_reset::
+ Deallocate storage for the string pool.
diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c
new file mode 100644
index 0000000..bc79222
--- /dev/null
+++ b/vcs-svn/svndump.c
@@ -0,0 +1,454 @@
+/*
+ * Parse and rearrange a svnadmin dump.
+ * Create the dump with:
+ * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile
+ *
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#include "cache.h"
+#include "repo_tree.h"
+#include "fast_export.h"
+#include "line_buffer.h"
+#include "string_pool.h"
+#include "strbuf.h"
+#include "svndump.h"
+
+/*
+ * Compare start of string to literal of equal length;
+ * must be guarded by length test.
+ */
+#define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1)
+
+#define NODEACT_REPLACE 4
+#define NODEACT_DELETE 3
+#define NODEACT_ADD 2
+#define NODEACT_CHANGE 1
+#define NODEACT_UNKNOWN 0
+
+#define DUMP_CTX 0
+#define REV_CTX 1
+#define NODE_CTX 2
+
+#define LENGTH_UNKNOWN (~0)
+#define DATE_RFC2822_LEN 31
+
+static struct line_buffer input = LINE_BUFFER_INIT;
+
+static struct {
+ uint32_t action, propLength, textLength, srcRev, type;
+ uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
+ uint32_t text_delta, prop_delta;
+} node_ctx;
+
+static struct {
+ uint32_t revision;
+ unsigned long timestamp;
+ struct strbuf log, author;
+} rev_ctx;
+
+static struct {
+ uint32_t version;
+ struct strbuf uuid, url;
+} dump_ctx;
+
+static void reset_node_ctx(char *fname)
+{
+ node_ctx.type = 0;
+ node_ctx.action = NODEACT_UNKNOWN;
+ node_ctx.propLength = LENGTH_UNKNOWN;
+ node_ctx.textLength = LENGTH_UNKNOWN;
+ node_ctx.src[0] = ~0;
+ node_ctx.srcRev = 0;
+ pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
+ node_ctx.text_delta = 0;
+ node_ctx.prop_delta = 0;
+}
+
+static void reset_rev_ctx(uint32_t revision)
+{
+ rev_ctx.revision = revision;
+ rev_ctx.timestamp = 0;
+ strbuf_reset(&rev_ctx.log);
+ strbuf_reset(&rev_ctx.author);
+}
+
+static void reset_dump_ctx(const char *url)
+{
+ strbuf_reset(&dump_ctx.url);
+ if (url)
+ strbuf_addstr(&dump_ctx.url, url);
+ dump_ctx.version = 1;
+ strbuf_reset(&dump_ctx.uuid);
+}
+
+static void handle_property(const struct strbuf *key_buf,
+ struct strbuf *val,
+ uint32_t *type_set)
+{
+ const char *key = key_buf->buf;
+ size_t keylen = key_buf->len;
+
+ switch (keylen + 1) {
+ case sizeof("svn:log"):
+ if (constcmp(key, "svn:log"))
+ break;
+ if (!val)
+ die("invalid dump: unsets svn:log");
+ strbuf_swap(&rev_ctx.log, val);
+ break;
+ case sizeof("svn:author"):
+ if (constcmp(key, "svn:author"))
+ break;
+ if (!val)
+ strbuf_reset(&rev_ctx.author);
+ else
+ strbuf_swap(&rev_ctx.author, val);
+ break;
+ case sizeof("svn:date"):
+ if (constcmp(key, "svn:date"))
+ break;
+ if (!val)
+ die("invalid dump: unsets svn:date");
+ if (parse_date_basic(val->buf, &rev_ctx.timestamp, NULL))
+ warning("invalid timestamp: %s", val->buf);
+ break;
+ case sizeof("svn:executable"):
+ case sizeof("svn:special"):
+ if (keylen == strlen("svn:executable") &&
+ constcmp(key, "svn:executable"))
+ break;
+ if (keylen == strlen("svn:special") &&
+ constcmp(key, "svn:special"))
+ break;
+ if (*type_set) {
+ if (!val)
+ return;
+ die("invalid dump: sets type twice");
+ }
+ if (!val) {
+ node_ctx.type = REPO_MODE_BLB;
+ return;
+ }
+ *type_set = 1;
+ node_ctx.type = keylen == strlen("svn:executable") ?
+ REPO_MODE_EXE :
+ REPO_MODE_LNK;
+ }
+}
+
+static void die_short_read(void)
+{
+ if (buffer_ferror(&input))
+ die_errno("error reading dump file");
+ die("invalid dump: unexpected end of file");
+}
+
+static void read_props(void)
+{
+ static struct strbuf key = STRBUF_INIT;
+ static struct strbuf val = STRBUF_INIT;
+ const char *t;
+ /*
+ * NEEDSWORK: to support simple mode changes like
+ * K 11
+ * svn:special
+ * V 1
+ * *
+ * D 14
+ * svn:executable
+ * we keep track of whether a mode has been set and reset to
+ * plain file only if not. We should be keeping track of the
+ * symlink and executable bits separately instead.
+ */
+ uint32_t type_set = 0;
+ while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) {
+ uint32_t len;
+ const char type = t[0];
+ int ch;
+
+ if (!type || t[1] != ' ')
+ die("invalid property line: %s\n", t);
+ len = atoi(&t[2]);
+ strbuf_reset(&val);
+ buffer_read_binary(&input, &val, len);
+ if (val.len < len)
+ die_short_read();
+
+ /* Discard trailing newline. */
+ ch = buffer_read_char(&input);
+ if (ch == EOF)
+ die_short_read();
+ if (ch != '\n')
+ die("invalid dump: expected newline after %s", val.buf);
+
+ switch (type) {
+ case 'K':
+ strbuf_swap(&key, &val);
+ continue;
+ case 'D':
+ handle_property(&val, NULL, &type_set);
+ continue;
+ case 'V':
+ handle_property(&key, &val, &type_set);
+ strbuf_reset(&key);
+ continue;
+ default:
+ die("invalid property line: %s\n", t);
+ }
+ }
+}
+
+static void handle_node(void)
+{
+ uint32_t mark = 0;
+ const uint32_t type = node_ctx.type;
+ const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
+ const int have_text = node_ctx.textLength != LENGTH_UNKNOWN;
+
+ if (node_ctx.text_delta)
+ die("text deltas not supported");
+ if (have_text)
+ mark = next_blob_mark();
+ if (node_ctx.action == NODEACT_DELETE) {
+ if (have_text || have_props || node_ctx.srcRev)
+ die("invalid dump: deletion node has "
+ "copyfrom info, text, or properties");
+ repo_delete(node_ctx.dst);
+ return;
+ }
+ if (node_ctx.action == NODEACT_REPLACE) {
+ repo_delete(node_ctx.dst);
+ node_ctx.action = NODEACT_ADD;
+ }
+ if (node_ctx.srcRev) {
+ repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
+ if (node_ctx.action == NODEACT_ADD)
+ node_ctx.action = NODEACT_CHANGE;
+ }
+ if (have_text && type == REPO_MODE_DIR)
+ die("invalid dump: directories cannot have text attached");
+
+ /*
+ * Decide on the new content (mark) and mode (node_ctx.type).
+ */
+ if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) {
+ if (type != REPO_MODE_DIR)
+ die("invalid dump: root of tree is not a regular file");
+ } else if (node_ctx.action == NODEACT_CHANGE) {
+ uint32_t mode;
+ if (!have_text)
+ mark = repo_read_path(node_ctx.dst);
+ mode = repo_read_mode(node_ctx.dst);
+ if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR)
+ die("invalid dump: cannot modify a directory into a file");
+ if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR)
+ die("invalid dump: cannot modify a file into a directory");
+ node_ctx.type = mode;
+ } else if (node_ctx.action == NODEACT_ADD) {
+ if (!have_text && type != REPO_MODE_DIR)
+ die("invalid dump: adds node without text");
+ } else {
+ die("invalid dump: Node-path block lacks Node-action");
+ }
+
+ /*
+ * Adjust mode to reflect properties.
+ */
+ if (have_props) {
+ if (!node_ctx.prop_delta)
+ node_ctx.type = type;
+ if (node_ctx.propLength)
+ read_props();
+ }
+
+ /*
+ * Save the result.
+ */
+ repo_add(node_ctx.dst, node_ctx.type, mark);
+ if (have_text)
+ fast_export_blob(node_ctx.type, mark,
+ node_ctx.textLength, &input);
+}
+
+static void handle_revision(void)
+{
+ if (rev_ctx.revision)
+ repo_commit(rev_ctx.revision, rev_ctx.author.buf,
+ &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf,
+ rev_ctx.timestamp);
+}
+
+void svndump_read(const char *url)
+{
+ char *val;
+ char *t;
+ uint32_t active_ctx = DUMP_CTX;
+ uint32_t len;
+
+ reset_dump_ctx(url);
+ while ((t = buffer_read_line(&input))) {
+ val = strchr(t, ':');
+ if (!val)
+ continue;
+ val++;
+ if (*val != ' ')
+ continue;
+ val++;
+
+ /* strlen(key) + 1 */
+ switch (val - t - 1) {
+ case sizeof("SVN-fs-dump-format-version"):
+ if (constcmp(t, "SVN-fs-dump-format-version"))
+ continue;
+ dump_ctx.version = atoi(val);
+ if (dump_ctx.version > 3)
+ die("expected svn dump format version <= 3, found %"PRIu32,
+ dump_ctx.version);
+ break;
+ case sizeof("UUID"):
+ if (constcmp(t, "UUID"))
+ continue;
+ strbuf_reset(&dump_ctx.uuid);
+ strbuf_addstr(&dump_ctx.uuid, val);
+ break;
+ case sizeof("Revision-number"):
+ if (constcmp(t, "Revision-number"))
+ continue;
+ if (active_ctx == NODE_CTX)
+ handle_node();
+ if (active_ctx != DUMP_CTX)
+ handle_revision();
+ active_ctx = REV_CTX;
+ reset_rev_ctx(atoi(val));
+ break;
+ case sizeof("Node-path"):
+ if (prefixcmp(t, "Node-"))
+ continue;
+ if (!constcmp(t + strlen("Node-"), "path")) {
+ if (active_ctx == NODE_CTX)
+ handle_node();
+ active_ctx = NODE_CTX;
+ reset_node_ctx(val);
+ break;
+ }
+ if (constcmp(t + strlen("Node-"), "kind"))
+ continue;
+ if (!strcmp(val, "dir"))
+ node_ctx.type = REPO_MODE_DIR;
+ else if (!strcmp(val, "file"))
+ node_ctx.type = REPO_MODE_BLB;
+ else
+ fprintf(stderr, "Unknown node-kind: %s\n", val);
+ break;
+ case sizeof("Node-action"):
+ if (constcmp(t, "Node-action"))
+ continue;
+ if (!strcmp(val, "delete")) {
+ node_ctx.action = NODEACT_DELETE;
+ } else if (!strcmp(val, "add")) {
+ node_ctx.action = NODEACT_ADD;
+ } else if (!strcmp(val, "change")) {
+ node_ctx.action = NODEACT_CHANGE;
+ } else if (!strcmp(val, "replace")) {
+ node_ctx.action = NODEACT_REPLACE;
+ } else {
+ fprintf(stderr, "Unknown node-action: %s\n", val);
+ node_ctx.action = NODEACT_UNKNOWN;
+ }
+ break;
+ case sizeof("Node-copyfrom-path"):
+ if (constcmp(t, "Node-copyfrom-path"))
+ continue;
+ pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val);
+ break;
+ case sizeof("Node-copyfrom-rev"):
+ if (constcmp(t, "Node-copyfrom-rev"))
+ continue;
+ node_ctx.srcRev = atoi(val);
+ break;
+ case sizeof("Text-content-length"):
+ if (!constcmp(t, "Text-content-length")) {
+ node_ctx.textLength = atoi(val);
+ break;
+ }
+ if (constcmp(t, "Prop-content-length"))
+ continue;
+ node_ctx.propLength = atoi(val);
+ break;
+ case sizeof("Text-delta"):
+ if (!constcmp(t, "Text-delta")) {
+ node_ctx.text_delta = !strcmp(val, "true");
+ break;
+ }
+ if (constcmp(t, "Prop-delta"))
+ continue;
+ node_ctx.prop_delta = !strcmp(val, "true");
+ break;
+ case sizeof("Content-length"):
+ if (constcmp(t, "Content-length"))
+ continue;
+ len = atoi(val);
+ t = buffer_read_line(&input);
+ if (!t)
+ die_short_read();
+ if (*t)
+ die("invalid dump: expected blank line after content length header");
+ if (active_ctx == REV_CTX) {
+ read_props();
+ } else if (active_ctx == NODE_CTX) {
+ handle_node();
+ active_ctx = REV_CTX;
+ } else {
+ fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
+ if (buffer_skip_bytes(&input, len) != len)
+ die_short_read();
+ }
+ }
+ }
+ if (buffer_ferror(&input))
+ die_short_read();
+ if (active_ctx == NODE_CTX)
+ handle_node();
+ if (active_ctx != DUMP_CTX)
+ handle_revision();
+}
+
+int svndump_init(const char *filename)
+{
+ if (buffer_init(&input, filename))
+ return error("cannot open %s: %s", filename, strerror(errno));
+ repo_init();
+ strbuf_init(&dump_ctx.uuid, 4096);
+ strbuf_init(&dump_ctx.url, 4096);
+ strbuf_init(&rev_ctx.log, 4096);
+ strbuf_init(&rev_ctx.author, 4096);
+ reset_dump_ctx(NULL);
+ reset_rev_ctx(0);
+ reset_node_ctx(NULL);
+ return 0;
+}
+
+void svndump_deinit(void)
+{
+ repo_reset();
+ reset_dump_ctx(NULL);
+ reset_rev_ctx(0);
+ reset_node_ctx(NULL);
+ strbuf_release(&rev_ctx.log);
+ if (buffer_deinit(&input))
+ fprintf(stderr, "Input error\n");
+ if (ferror(stdout))
+ fprintf(stderr, "Output error\n");
+}
+
+void svndump_reset(void)
+{
+ buffer_reset(&input);
+ repo_reset();
+ strbuf_release(&dump_ctx.uuid);
+ strbuf_release(&dump_ctx.url);
+ strbuf_release(&rev_ctx.log);
+ strbuf_release(&rev_ctx.author);
+}
diff --git a/vcs-svn/svndump.h b/vcs-svn/svndump.h
new file mode 100644
index 0000000..df9ceb0
--- /dev/null
+++ b/vcs-svn/svndump.h
@@ -0,0 +1,9 @@
+#ifndef SVNDUMP_H_
+#define SVNDUMP_H_
+
+int svndump_init(const char *filename);
+void svndump_read(const char *url);
+void svndump_deinit(void);
+void svndump_reset(void);
+
+#endif
diff --git a/vcs-svn/trp.h b/vcs-svn/trp.h
new file mode 100644
index 0000000..c32b918
--- /dev/null
+++ b/vcs-svn/trp.h
@@ -0,0 +1,237 @@
+/*
+ * C macro implementation of treaps.
+ *
+ * Usage:
+ * #include <stdint.h>
+ * #include "trp.h"
+ * trp_gen(...)
+ *
+ * Licensed under a two-clause BSD-style license.
+ * See LICENSE for details.
+ */
+
+#ifndef TRP_H_
+#define TRP_H_
+
+#define MAYBE_UNUSED __attribute__((__unused__))
+
+/* Node structure. */
+struct trp_node {
+ uint32_t trpn_left;
+ uint32_t trpn_right;
+};
+
+/* Root structure. */
+struct trp_root {
+ uint32_t trp_root;
+};
+
+/* Pointer/Offset conversion. */
+#define trpn_pointer(a_base, a_offset) (a_base##_pointer(a_offset))
+#define trpn_offset(a_base, a_pointer) (a_base##_offset(a_pointer))
+#define trpn_modify(a_base, a_offset) \
+ do { \
+ if ((a_offset) < a_base##_pool.committed) { \
+ uint32_t old_offset = (a_offset);\
+ (a_offset) = a_base##_alloc(1); \
+ *trpn_pointer(a_base, a_offset) = \
+ *trpn_pointer(a_base, old_offset); \
+ } \
+ } while (0)
+
+/* Left accessors. */
+#define trp_left_get(a_base, a_field, a_node) \
+ (trpn_pointer(a_base, a_node)->a_field.trpn_left)
+#define trp_left_set(a_base, a_field, a_node, a_left) \
+ do { \
+ trpn_modify(a_base, a_node); \
+ trp_left_get(a_base, a_field, a_node) = (a_left); \
+ } while (0)
+
+/* Right accessors. */
+#define trp_right_get(a_base, a_field, a_node) \
+ (trpn_pointer(a_base, a_node)->a_field.trpn_right)
+#define trp_right_set(a_base, a_field, a_node, a_right) \
+ do { \
+ trpn_modify(a_base, a_node); \
+ trp_right_get(a_base, a_field, a_node) = (a_right); \
+ } while (0)
+
+/*
+ * Fibonacci hash function.
+ * The multiplier is the nearest prime to (2^32 times (√5 - 1)/2).
+ * See Knuth §6.4: volume 3, 3rd ed, p518.
+ */
+#define trpn_hash(a_node) (uint32_t) (2654435761u * (a_node))
+
+/* Priority accessors. */
+#define trp_prio_get(a_node) trpn_hash(a_node)
+
+/* Node initializer. */
+#define trp_node_new(a_base, a_field, a_node) \
+ do { \
+ trp_left_set(a_base, a_field, (a_node), ~0); \
+ trp_right_set(a_base, a_field, (a_node), ~0); \
+ } while (0)
+
+/* Internal utility macros. */
+#define trpn_first(a_base, a_field, a_root, r_node) \
+ do { \
+ (r_node) = (a_root); \
+ if ((r_node) == ~0) \
+ return NULL; \
+ while (~trp_left_get(a_base, a_field, (r_node))) \
+ (r_node) = trp_left_get(a_base, a_field, (r_node)); \
+ } while (0)
+
+#define trpn_rotate_left(a_base, a_field, a_node, r_node) \
+ do { \
+ (r_node) = trp_right_get(a_base, a_field, (a_node)); \
+ trp_right_set(a_base, a_field, (a_node), \
+ trp_left_get(a_base, a_field, (r_node))); \
+ trp_left_set(a_base, a_field, (r_node), (a_node)); \
+ } while (0)
+
+#define trpn_rotate_right(a_base, a_field, a_node, r_node) \
+ do { \
+ (r_node) = trp_left_get(a_base, a_field, (a_node)); \
+ trp_left_set(a_base, a_field, (a_node), \
+ trp_right_get(a_base, a_field, (r_node))); \
+ trp_right_set(a_base, a_field, (r_node), (a_node)); \
+ } while (0)
+
+#define trp_gen(a_attr, a_pre, a_type, a_field, a_base, a_cmp) \
+a_attr a_type MAYBE_UNUSED *a_pre##first(struct trp_root *treap) \
+{ \
+ uint32_t ret; \
+ trpn_first(a_base, a_field, treap->trp_root, ret); \
+ return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##next(struct trp_root *treap, a_type *node) \
+{ \
+ uint32_t ret; \
+ uint32_t offset = trpn_offset(a_base, node); \
+ if (~trp_right_get(a_base, a_field, offset)) { \
+ trpn_first(a_base, a_field, \
+ trp_right_get(a_base, a_field, offset), ret); \
+ } else { \
+ uint32_t tnode = treap->trp_root; \
+ ret = ~0; \
+ while (1) { \
+ int cmp = (a_cmp)(trpn_pointer(a_base, offset), \
+ trpn_pointer(a_base, tnode)); \
+ if (cmp < 0) { \
+ ret = tnode; \
+ tnode = trp_left_get(a_base, a_field, tnode); \
+ } else if (cmp > 0) { \
+ tnode = trp_right_get(a_base, a_field, tnode); \
+ } else { \
+ break; \
+ } \
+ } \
+ } \
+ return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##search(struct trp_root *treap, a_type *key) \
+{ \
+ int cmp; \
+ uint32_t ret = treap->trp_root; \
+ while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
+ if (cmp < 0) { \
+ ret = trp_left_get(a_base, a_field, ret); \
+ } else { \
+ ret = trp_right_get(a_base, a_field, ret); \
+ } \
+ } \
+ return trpn_pointer(a_base, ret); \
+} \
+a_attr a_type MAYBE_UNUSED *a_pre##nsearch(struct trp_root *treap, a_type *key) \
+{ \
+ int cmp; \
+ uint32_t ret = treap->trp_root; \
+ while (~ret && (cmp = (a_cmp)(key, trpn_pointer(a_base, ret)))) { \
+ if (cmp < 0) { \
+ if (!~trp_left_get(a_base, a_field, ret)) \
+ break; \
+ ret = trp_left_get(a_base, a_field, ret); \
+ } else { \
+ ret = trp_right_get(a_base, a_field, ret); \
+ } \
+ } \
+ return trpn_pointer(a_base, ret); \
+} \
+a_attr uint32_t MAYBE_UNUSED a_pre##insert_recurse(uint32_t cur_node, uint32_t ins_node) \
+{ \
+ if (cur_node == ~0) { \
+ return ins_node; \
+ } else { \
+ uint32_t ret; \
+ int cmp = (a_cmp)(trpn_pointer(a_base, ins_node), \
+ trpn_pointer(a_base, cur_node)); \
+ if (cmp < 0) { \
+ uint32_t left = a_pre##insert_recurse( \
+ trp_left_get(a_base, a_field, cur_node), ins_node); \
+ trp_left_set(a_base, a_field, cur_node, left); \
+ if (trp_prio_get(left) < trp_prio_get(cur_node)) \
+ trpn_rotate_right(a_base, a_field, cur_node, ret); \
+ else \
+ ret = cur_node; \
+ } else { \
+ uint32_t right = a_pre##insert_recurse( \
+ trp_right_get(a_base, a_field, cur_node), ins_node); \
+ trp_right_set(a_base, a_field, cur_node, right); \
+ if (trp_prio_get(right) < trp_prio_get(cur_node)) \
+ trpn_rotate_left(a_base, a_field, cur_node, ret); \
+ else \
+ ret = cur_node; \
+ } \
+ return ret; \
+ } \
+} \
+a_attr a_type *MAYBE_UNUSED a_pre##insert(struct trp_root *treap, a_type *node) \
+{ \
+ uint32_t offset = trpn_offset(a_base, node); \
+ trp_node_new(a_base, a_field, offset); \
+ treap->trp_root = a_pre##insert_recurse(treap->trp_root, offset); \
+ return trpn_pointer(a_base, offset); \
+} \
+a_attr uint32_t MAYBE_UNUSED a_pre##remove_recurse(uint32_t cur_node, uint32_t rem_node) \
+{ \
+ int cmp = a_cmp(trpn_pointer(a_base, rem_node), \
+ trpn_pointer(a_base, cur_node)); \
+ if (cmp == 0) { \
+ uint32_t ret; \
+ uint32_t left = trp_left_get(a_base, a_field, cur_node); \
+ uint32_t right = trp_right_get(a_base, a_field, cur_node); \
+ if (left == ~0) { \
+ if (right == ~0) \
+ return ~0; \
+ } else if (right == ~0 || trp_prio_get(left) < trp_prio_get(right)) { \
+ trpn_rotate_right(a_base, a_field, cur_node, ret); \
+ right = a_pre##remove_recurse(cur_node, rem_node); \
+ trp_right_set(a_base, a_field, ret, right); \
+ return ret; \
+ } \
+ trpn_rotate_left(a_base, a_field, cur_node, ret); \
+ left = a_pre##remove_recurse(cur_node, rem_node); \
+ trp_left_set(a_base, a_field, ret, left); \
+ return ret; \
+ } else if (cmp < 0) { \
+ uint32_t left = a_pre##remove_recurse( \
+ trp_left_get(a_base, a_field, cur_node), rem_node); \
+ trp_left_set(a_base, a_field, cur_node, left); \
+ return cur_node; \
+ } else { \
+ uint32_t right = a_pre##remove_recurse( \
+ trp_right_get(a_base, a_field, cur_node), rem_node); \
+ trp_right_set(a_base, a_field, cur_node, right); \
+ return cur_node; \
+ } \
+} \
+a_attr void MAYBE_UNUSED a_pre##remove(struct trp_root *treap, a_type *node) \
+{ \
+ treap->trp_root = a_pre##remove_recurse(treap->trp_root, \
+ trpn_offset(a_base, node)); \
+} \
+
+#endif
diff --git a/vcs-svn/trp.txt b/vcs-svn/trp.txt
new file mode 100644
index 0000000..177ebca
--- /dev/null
+++ b/vcs-svn/trp.txt
@@ -0,0 +1,109 @@
+Motivation
+==========
+
+Treaps provide a memory-efficient binary search tree structure.
+Insertion/deletion/search are about as about as fast in the average
+case as red-black trees and the chances of worst-case behavior are
+vanishingly small, thanks to (pseudo-)randomness. The bad worst-case
+behavior is a small price to pay, given that treaps are much simpler
+to implement.
+
+API
+===
+
+The trp API generates a data structure and functions to handle a
+large growing set of objects stored in a pool.
+
+The caller:
+
+. Specifies parameters for the generated functions with the
+ trp_gen(static, foo_, ...) macro.
+
+. Allocates a `struct trp_root` variable and sets it to {~0}.
+
+. Adds new nodes to the set using `foo_insert`. Any pointers
+ to existing nodes cannot be relied upon any more, so the caller
+ might retrieve them anew with `foo_pointer`.
+
+. Can find a specific item in the set using `foo_search`.
+
+. Can iterate over items in the set using `foo_first` and `foo_next`.
+
+. Can remove an item from the set using `foo_remove`.
+
+Example:
+
+----
+struct ex_node {
+ const char *s;
+ struct trp_node ex_link;
+};
+static struct trp_root ex_base = {~0};
+obj_pool_gen(ex, struct ex_node, 4096);
+trp_gen(static, ex_, struct ex_node, ex_link, ex, strcmp)
+struct ex_node *item;
+
+item = ex_pointer(ex_alloc(1));
+item->s = "hello";
+ex_insert(&ex_base, item);
+item = ex_pointer(ex_alloc(1));
+item->s = "goodbye";
+ex_insert(&ex_base, item);
+for (item = ex_first(&ex_base); item; item = ex_next(&ex_base, item))
+ printf("%s\n", item->s);
+----
+
+Functions
+---------
+
+trp_gen(attr, foo_, node_type, link_field, pool, cmp)::
+
+ Generate a type-specific treap implementation.
++
+. The storage class for generated functions will be 'attr' (e.g., `static`).
+. Generated function names are prefixed with 'foo_' (e.g., `treap_`).
+. Treap nodes will be of type 'node_type' (e.g., `struct treap_node`).
+ This type must be a struct with at least one `struct trp_node` field
+ to point to its children.
+. The field used to access child nodes will be 'link_field'.
+. All treap nodes must lie in the 'pool' object pool.
+. Treap nodes must be totally ordered by the 'cmp' relation, with the
+ following prototype:
++
+int (*cmp)(node_type \*a, node_type \*b)
++
+and returning a value less than, equal to, or greater than zero
+according to the result of comparison.
+
+node_type {asterisk}foo_insert(struct trp_root *treap, node_type \*node)::
+
+ Insert node into treap. If inserted multiple times,
+ a node will appear in the treap multiple times.
++
+The return value is the address of the node within the treap,
+which might differ from `node` if `pool_alloc` had to call
+`realloc` to expand the pool.
+
+void foo_remove(struct trp_root *treap, node_type \*node)::
+
+ Remove node from treap. Caller must ensure node is
+ present in treap before using this function.
+
+node_type *foo_search(struct trp_root \*treap, node_type \*key)::
+
+ Search for a node that matches key. If no match is found,
+ result is NULL.
+
+node_type *foo_nsearch(struct trp_root \*treap, node_type \*key)::
+
+ Like `foo_search`, but if the key is missing return what
+ would be key's successor, were key in treap (NULL if no
+ successor).
+
+node_type *foo_first(struct trp_root \*treap)::
+
+ Find the first item from the treap, in sorted order.
+
+node_type *foo_next(struct trp_root \*treap, node_type \*node)::
+
+ Find the next item.
diff --git a/walker.c b/walker.c
index 11d9052..dce7128 100644
--- a/walker.c
+++ b/walker.c
@@ -207,7 +207,7 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag,
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
commit->object.flags |= COMPLETE;
- insert_by_date(commit, &complete);
+ commit_list_insert_by_date(commit, &complete);
}
return 0;
}
diff --git a/wrapper.c b/wrapper.c
index afb4f6f..85f09df 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -3,16 +3,17 @@
*/
#include "cache.h"
-static void try_to_free_builtin(size_t size)
+static void do_nothing(size_t size)
{
- release_pack_memory(size, -1);
}
-static void (*try_to_free_routine)(size_t size) = try_to_free_builtin;
+static void (*try_to_free_routine)(size_t size) = do_nothing;
try_to_free_t set_try_to_free_routine(try_to_free_t routine)
{
try_to_free_t old = try_to_free_routine;
+ if (!routine)
+ routine = do_nothing;
try_to_free_routine = routine;
return old;
}
@@ -40,7 +41,8 @@ void *xmalloc(size_t size)
if (!ret && !size)
ret = malloc(1);
if (!ret)
- die("Out of memory, malloc failed");
+ die("Out of memory, malloc failed (tried to allocate %lu bytes)",
+ (unsigned long)size);
}
#ifdef XMALLOC_POISON
memset(ret, 0xA5, size);
@@ -51,7 +53,7 @@ void *xmalloc(size_t size)
void *xmallocz(size_t size)
{
void *ret;
- if (size + 1 < size)
+ if (unsigned_add_overflows(size, 1))
die("Data too large to fit into virtual memory space.");
ret = xmalloc(size + 1);
((char*)ret)[size] = 0;
@@ -107,21 +109,6 @@ void *xcalloc(size_t nmemb, size_t size)
return ret;
}
-void *xmmap(void *start, size_t length,
- int prot, int flags, int fd, off_t offset)
-{
- void *ret = mmap(start, length, prot, flags, fd, offset);
- if (ret == MAP_FAILED) {
- if (!length)
- return NULL;
- release_pack_memory(length, fd);
- ret = mmap(start, length, prot, flags, fd, offset);
- if (ret == MAP_FAILED)
- die_errno("Out of memory? mmap failed");
- }
- return ret;
-}
-
/*
* xread() is the same a read(), but it automatically restarts read()
* operations with a recoverable error (EAGAIN and EINTR). xread()
@@ -161,8 +148,10 @@ ssize_t read_in_full(int fd, void *buf, size_t count)
while (count > 0) {
ssize_t loaded = xread(fd, p, count);
- if (loaded <= 0)
- return total ? total : loaded;
+ if (loaded < 0)
+ return -1;
+ if (loaded == 0)
+ return total;
count -= loaded;
p += loaded;
total += loaded;
@@ -211,118 +200,158 @@ FILE *xfdopen(int fd, const char *mode)
int xmkstemp(char *template)
{
int fd;
+ char origtemplate[PATH_MAX];
+ strlcpy(origtemplate, template, sizeof(origtemplate));
fd = mkstemp(template);
- if (fd < 0)
- die_errno("Unable to create temporary file");
- return fd;
-}
+ if (fd < 0) {
+ int saved_errno = errno;
+ const char *nonrelative_template;
-int xmkstemp_mode(char *template, int mode)
-{
- int fd;
+ if (!template[0])
+ template = origtemplate;
- fd = git_mkstemp_mode(template, mode);
- if (fd < 0)
- die_errno("Unable to create temporary file");
+ nonrelative_template = absolute_path(template);
+ errno = saved_errno;
+ die_errno("Unable to create temporary file '%s'",
+ nonrelative_template);
+ }
return fd;
}
-/*
- * zlib wrappers to make sure we don't silently miss errors
- * at init time.
- */
-void git_inflate_init(z_streamp strm)
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
{
- const char *err;
-
- switch (inflateInit(strm)) {
- case Z_OK:
- return;
-
- case Z_MEM_ERROR:
- err = "out of memory";
- break;
- case Z_VERSION_ERROR:
- err = "wrong version";
- break;
- default:
- err = "error";
+ const char *tmp;
+ size_t n;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ n = snprintf(path, len, "%s/%s", tmp, template);
+ if (len <= n) {
+ errno = ENAMETOOLONG;
+ return -1;
}
- die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message");
+ return mkstemp(path);
}
-void git_inflate_end(z_streamp strm)
+/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
+int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
{
- if (inflateEnd(strm) != Z_OK)
- error("inflateEnd: %s", strm->msg ? strm->msg : "failed");
+ const char *tmp;
+ size_t n;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ n = snprintf(path, len, "%s/%s", tmp, template);
+ if (len <= n) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ return mkstemps(path, suffix_len);
}
-int git_inflate(z_streamp strm, int flush)
+/* Adapted from libiberty's mkstemp.c. */
+
+#undef TMP_MAX
+#define TMP_MAX 16384
+
+int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
{
- int ret = inflate(strm, flush);
- const char *err;
-
- switch (ret) {
- /* Out of memory is fatal. */
- case Z_MEM_ERROR:
- die("inflate: out of memory");
-
- /* Data corruption errors: we may want to recover from them (fsck) */
- case Z_NEED_DICT:
- err = "needs dictionary"; break;
- case Z_DATA_ERROR:
- err = "data stream error"; break;
- case Z_STREAM_ERROR:
- err = "stream consistency error"; break;
- default:
- err = "unknown error"; break;
-
- /* Z_BUF_ERROR: normal, needs more space in the output buffer */
- case Z_BUF_ERROR:
- case Z_OK:
- case Z_STREAM_END:
- return ret;
+ static const char letters[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+ static const int num_letters = 62;
+ uint64_t value;
+ struct timeval tv;
+ char *template;
+ size_t len;
+ int fd, count;
+
+ len = strlen(pattern);
+
+ if (len < 6 + suffix_len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) {
+ errno = EINVAL;
+ return -1;
}
- error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
- return ret;
-}
-int odb_mkstemp(char *template, size_t limit, const char *pattern)
-{
- int fd;
/*
- * we let the umask do its job, don't try to be more
- * restrictive except to remove write permission.
+ * Replace pattern's XXXXXX characters with randomness.
+ * Try TMP_MAX different filenames.
*/
- int mode = 0444;
- snprintf(template, limit, "%s/%s",
- get_object_directory(), pattern);
- fd = git_mkstemp_mode(template, mode);
- if (0 <= fd)
- return fd;
-
- /* slow path */
- /* some mkstemp implementations erase template on failure */
- snprintf(template, limit, "%s/%s",
- get_object_directory(), pattern);
- safe_create_leading_directories(template);
- return xmkstemp_mode(template, mode);
+ gettimeofday(&tv, NULL);
+ value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid();
+ template = &pattern[len - 6 - suffix_len];
+ for (count = 0; count < TMP_MAX; ++count) {
+ uint64_t v = value;
+ /* Fill in the random bits. */
+ template[0] = letters[v % num_letters]; v /= num_letters;
+ template[1] = letters[v % num_letters]; v /= num_letters;
+ template[2] = letters[v % num_letters]; v /= num_letters;
+ template[3] = letters[v % num_letters]; v /= num_letters;
+ template[4] = letters[v % num_letters]; v /= num_letters;
+ template[5] = letters[v % num_letters]; v /= num_letters;
+
+ fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode);
+ if (fd > 0)
+ return fd;
+ /*
+ * Fatal error (EPERM, ENOSPC etc).
+ * It doesn't make sense to loop.
+ */
+ if (errno != EEXIST)
+ break;
+ /*
+ * This is a random value. It is only necessary that
+ * the next TMP_MAX values generated by adding 7777 to
+ * VALUE are different with (module 2^32).
+ */
+ value += 7777;
+ }
+ /* We return the null string if we can't find a unique file name. */
+ pattern[0] = '\0';
+ return -1;
+}
+
+int git_mkstemp_mode(char *pattern, int mode)
+{
+ /* mkstemp is just mkstemps with no suffix */
+ return git_mkstemps_mode(pattern, 0, mode);
}
-int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+int gitmkstemps(char *pattern, int suffix_len)
+{
+ return git_mkstemps_mode(pattern, suffix_len, 0600);
+}
+
+int xmkstemp_mode(char *template, int mode)
{
int fd;
+ char origtemplate[PATH_MAX];
+ strlcpy(origtemplate, template, sizeof(origtemplate));
- snprintf(name, namesz, "%s/pack/pack-%s.keep",
- get_object_directory(), sha1_to_hex(sha1));
- fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
- if (0 <= fd)
- return fd;
+ fd = git_mkstemp_mode(template, mode);
+ if (fd < 0) {
+ int saved_errno = errno;
+ const char *nonrelative_template;
+
+ if (!template[0])
+ template = origtemplate;
- /* slow path */
- safe_create_leading_directories(name);
- return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ nonrelative_template = absolute_path(template);
+ errno = saved_errno;
+ die_errno("Unable to create temporary file '%s'",
+ nonrelative_template);
+ }
+ return fd;
}
static int warn_if_unremovable(const char *op, const char *file, int rc)
diff --git a/ws.c b/ws.c
index d7b8c33..9fb9b14 100644
--- a/ws.c
+++ b/ws.c
@@ -56,6 +56,16 @@ unsigned parse_whitespace_rule(const char *string)
rule |= whitespace_rule_names[i].rule_bits;
break;
}
+ if (strncmp(string, "tabwidth=", 9) == 0) {
+ unsigned tabwidth = atoi(string + 9);
+ if (0 < tabwidth && tabwidth < 0100) {
+ rule &= ~WS_TAB_WIDTH_MASK;
+ rule |= tabwidth;
+ }
+ else
+ warning("tabwidth %.*s out of range",
+ (int)(len - 9), string + 9);
+ }
string = ep;
}
@@ -84,7 +94,7 @@ unsigned whitespace_rule(const char *pathname)
value = attr_whitespace_rule.value;
if (ATTR_TRUE(value)) {
/* true (whitespace) */
- unsigned all_rule = 0;
+ unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
int i;
for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
if (!whitespace_rule_names[i].loosens_error &&
@@ -93,7 +103,7 @@ unsigned whitespace_rule(const char *pathname)
return all_rule;
} else if (ATTR_FALSE(value)) {
/* false (-whitespace) */
- return 0;
+ return ws_tab_width(whitespace_rule_cfg);
} else if (ATTR_UNSET(value)) {
/* reset to default (!whitespace) */
return whitespace_rule_cfg;
@@ -174,8 +184,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
}
}
+ if (trailing_whitespace == -1)
+ trailing_whitespace = len;
+
/* Check indentation */
- for (i = 0; i < len; i++) {
+ for (i = 0; i < trailing_whitespace; i++) {
if (line[i] == ' ')
continue;
if (line[i] != '\t')
@@ -203,7 +216,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
}
/* Check for indent using non-tab. */
- if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
+ if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {
result |= WS_INDENT_WITH_NON_TAB;
if (stream) {
fputs(ws, stream);
@@ -218,8 +231,6 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
* Now the rest of the line starts at "written".
* The non-highlighted part ends at "trailing_whitespace".
*/
- if (trailing_whitespace == -1)
- trailing_whitespace = len;
/* Emit non-highlighted (middle) segment. */
if (trailing_whitespace - written > 0) {
@@ -319,7 +330,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
} else if (ch == ' ') {
last_space_in_indent = i;
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
- 8 <= i - last_tab_in_indent)
+ ws_tab_width(ws_rule) <= i - last_tab_in_indent)
need_fix_leading_space = 1;
} else
break;
@@ -349,7 +360,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
strbuf_addch(dst, ch);
} else {
consecutive_spaces++;
- if (consecutive_spaces == 8) {
+ if (consecutive_spaces == ws_tab_width(ws_rule)) {
strbuf_addch(dst, '\t');
consecutive_spaces = 0;
}
@@ -362,12 +373,13 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
fixed = 1;
} else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) {
/* Expand tabs into spaces */
+ int start = dst->len;
int last = last_tab_in_indent + 1;
for (i = 0; i < last; i++) {
if (src[i] == '\t')
do {
strbuf_addch(dst, ' ');
- } while (dst->len % 8);
+ } while ((dst->len - start) % ws_tab_width(ws_rule));
else
strbuf_addch(dst, src[i]);
}
diff --git a/wt-status.c b/wt-status.c
index 2f9e33c..0237772 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -21,11 +21,89 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RED, /* WT_STATUS_UNMERGED */
GIT_COLOR_GREEN, /* WT_STATUS_LOCAL_BRANCH */
GIT_COLOR_RED, /* WT_STATUS_REMOTE_BRANCH */
+ GIT_COLOR_NIL, /* WT_STATUS_ONBRANCH */
};
static const char *color(int slot, struct wt_status *s)
{
- return s->use_color > 0 ? s->color_palette[slot] : "";
+ const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+ if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
+ c = s->color_palette[WT_STATUS_HEADER];
+ return c;
+}
+
+static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
+ const char *fmt, va_list ap, const char *trail)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf linebuf = STRBUF_INIT;
+ const char *line, *eol;
+
+ strbuf_vaddf(&sb, fmt, ap);
+ if (!sb.len) {
+ strbuf_addch(&sb, '#');
+ if (!trail)
+ strbuf_addch(&sb, ' ');
+ color_print_strbuf(s->fp, color, &sb);
+ if (trail)
+ fprintf(s->fp, "%s", trail);
+ strbuf_release(&sb);
+ return;
+ }
+ for (line = sb.buf; *line; line = eol + 1) {
+ eol = strchr(line, '\n');
+
+ strbuf_reset(&linebuf);
+ if (at_bol) {
+ strbuf_addch(&linebuf, '#');
+ if (*line != '\n' && *line != '\t')
+ strbuf_addch(&linebuf, ' ');
+ }
+ if (eol)
+ strbuf_add(&linebuf, line, eol - line);
+ else
+ strbuf_addstr(&linebuf, line);
+ color_print_strbuf(s->fp, color, &linebuf);
+ if (eol)
+ fprintf(s->fp, "\n");
+ else
+ break;
+ at_bol = 1;
+ }
+ if (trail)
+ fprintf(s->fp, "%s", trail);
+ strbuf_release(&linebuf);
+ strbuf_release(&sb);
+}
+
+void status_printf_ln(struct wt_status *s, const char *color,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ status_vprintf(s, 1, color, fmt, ap, "\n");
+ va_end(ap);
+}
+
+void status_printf(struct wt_status *s, const char *color,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ status_vprintf(s, 1, color, fmt, ap, NULL);
+ va_end(ap);
+}
+
+void status_printf_more(struct wt_status *s, const char *color,
+ const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ status_vprintf(s, 0, color, fmt, ap, NULL);
+ va_end(ap);
}
void wt_status_prepare(struct wt_status *s)
@@ -53,33 +131,33 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
{
const char *c = color(WT_STATUS_HEADER, s);
- color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+ status_printf_ln(s, c, _("Unmerged paths:"));
if (!advice_status_hints)
return;
- if (s->in_merge)
+ if (s->whence != FROM_COMMIT)
;
else if (!s->is_initial)
- color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
+ status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference);
else
- color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)");
- color_fprintf_ln(s->fp, c, "# (use \"git add/rm <file>...\" as appropriate to mark resolution)");
- color_fprintf_ln(s->fp, c, "#");
+ status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)"));
+ status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+ status_printf_ln(s, c, "");
}
static void wt_status_print_cached_header(struct wt_status *s)
{
const char *c = color(WT_STATUS_HEADER, s);
- color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+ status_printf_ln(s, c, _("Changes to be committed:"));
if (!advice_status_hints)
return;
- if (s->in_merge)
+ if (s->whence != FROM_COMMIT)
; /* NEEDSWORK: use "git reset --unresolve"??? */
else if (!s->is_initial)
- color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
+ status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference);
else
- color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)");
- color_fprintf_ln(s->fp, c, "#");
+ status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)"));
+ status_printf_ln(s, c, "");
}
static void wt_status_print_dirty_header(struct wt_status *s,
@@ -88,17 +166,17 @@ static void wt_status_print_dirty_header(struct wt_status *s,
{
const char *c = color(WT_STATUS_HEADER, s);
- color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+ status_printf_ln(s, c, _("Changes not staged for commit:"));
if (!advice_status_hints)
return;
if (!has_deleted)
- color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)");
+ status_printf_ln(s, c, _(" (use \"git add <file>...\" to update what will be committed)"));
else
- color_fprintf_ln(s->fp, c, "# (use \"git add/rm <file>...\" to update what will be committed)");
- color_fprintf_ln(s->fp, c, "# (use \"git checkout -- <file>...\" to discard changes in working directory)");
+ status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" to update what will be committed)"));
+ status_printf_ln(s, c, _(" (use \"git checkout -- <file>...\" to discard changes in working directory)"));
if (has_dirty_submodules)
- color_fprintf_ln(s->fp, c, "# (commit or discard the untracked or modified content in submodules)");
- color_fprintf_ln(s->fp, c, "#");
+ status_printf_ln(s, c, _(" (commit or discard the untracked or modified content in submodules)"));
+ status_printf_ln(s, c, "");
}
static void wt_status_print_other_header(struct wt_status *s,
@@ -106,16 +184,16 @@ static void wt_status_print_other_header(struct wt_status *s,
const char *how)
{
const char *c = color(WT_STATUS_HEADER, s);
- color_fprintf_ln(s->fp, c, "# %s files:", what);
+ status_printf_ln(s, c, _("%s files:"), what);
if (!advice_status_hints)
return;
- color_fprintf_ln(s->fp, c, "# (use \"git %s <file>...\" to include in what will be committed)", how);
- color_fprintf_ln(s->fp, c, "#");
+ status_printf_ln(s, c, _(" (use \"git %s <file>...\" to include in what will be committed)"), how);
+ status_printf_ln(s, c, "");
}
static void wt_status_print_trailer(struct wt_status *s)
{
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
}
#define quote_path quote_path_relative
@@ -126,20 +204,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
const char *c = color(WT_STATUS_UNMERGED, s);
struct wt_status_change_data *d = it->util;
struct strbuf onebuf = STRBUF_INIT;
- const char *one, *how = "bug";
+ const char *one, *how = _("bug");
one = quote_path(it->string, -1, &onebuf, s->prefix);
- color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+ status_printf(s, color(WT_STATUS_HEADER, s), "\t");
switch (d->stagemask) {
- case 1: how = "both deleted:"; break;
- case 2: how = "added by us:"; break;
- case 3: how = "deleted by them:"; break;
- case 4: how = "added by them:"; break;
- case 5: how = "deleted by us:"; break;
- case 6: how = "both added:"; break;
- case 7: how = "both modified:"; break;
- }
- color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+ case 1: how = _("both deleted:"); break;
+ case 2: how = _("added by us:"); break;
+ case 3: how = _("deleted by them:"); break;
+ case 4: how = _("added by them:"); break;
+ case 5: how = _("deleted by us:"); break;
+ case 6: how = _("both added:"); break;
+ case 7: how = _("both modified:"); break;
+ }
+ status_printf_more(s, c, "%-20s%s\n", how, one);
strbuf_release(&onebuf);
}
@@ -167,11 +245,11 @@ static void wt_status_print_change_data(struct wt_status *s,
if (d->new_submodule_commits || d->dirty_submodule) {
strbuf_addstr(&extra, " (");
if (d->new_submodule_commits)
- strbuf_addf(&extra, "new commits, ");
+ strbuf_addf(&extra, _("new commits, "));
if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
- strbuf_addf(&extra, "modified content, ");
+ strbuf_addf(&extra, _("modified content, "));
if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
- strbuf_addf(&extra, "untracked content, ");
+ strbuf_addf(&extra, _("untracked content, "));
strbuf_setlen(&extra, extra.len - 2);
strbuf_addch(&extra, ')');
}
@@ -182,40 +260,40 @@ static void wt_status_print_change_data(struct wt_status *s,
one = quote_path(one_name, -1, &onebuf, s->prefix);
two = quote_path(two_name, -1, &twobuf, s->prefix);
- color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+ status_printf(s, color(WT_STATUS_HEADER, s), "\t");
switch (status) {
case DIFF_STATUS_ADDED:
- color_fprintf(s->fp, c, "new file: %s", one);
+ status_printf_more(s, c, _("new file: %s"), one);
break;
case DIFF_STATUS_COPIED:
- color_fprintf(s->fp, c, "copied: %s -> %s", one, two);
+ status_printf_more(s, c, _("copied: %s -> %s"), one, two);
break;
case DIFF_STATUS_DELETED:
- color_fprintf(s->fp, c, "deleted: %s", one);
+ status_printf_more(s, c, _("deleted: %s"), one);
break;
case DIFF_STATUS_MODIFIED:
- color_fprintf(s->fp, c, "modified: %s", one);
+ status_printf_more(s, c, _("modified: %s"), one);
break;
case DIFF_STATUS_RENAMED:
- color_fprintf(s->fp, c, "renamed: %s -> %s", one, two);
+ status_printf_more(s, c, _("renamed: %s -> %s"), one, two);
break;
case DIFF_STATUS_TYPE_CHANGED:
- color_fprintf(s->fp, c, "typechange: %s", one);
+ status_printf_more(s, c, _("typechange: %s"), one);
break;
case DIFF_STATUS_UNKNOWN:
- color_fprintf(s->fp, c, "unknown: %s", one);
+ status_printf_more(s, c, _("unknown: %s"), one);
break;
case DIFF_STATUS_UNMERGED:
- color_fprintf(s->fp, c, "unmerged: %s", one);
+ status_printf_more(s, c, _("unmerged: %s"), one);
break;
default:
- die("bug: unhandled diff status %c", status);
+ die(_("bug: unhandled diff status %c"), status);
}
if (extra.len) {
- color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
+ status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
strbuf_release(&extra);
}
- fprintf(s->fp, "\n");
+ status_printf_more(s, GIT_COLOR_NORMAL, "\n");
strbuf_release(&onebuf);
strbuf_release(&twobuf);
}
@@ -313,11 +391,13 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
if (!s->show_untracked_files)
DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
- if (s->ignore_submodule_arg)
+ if (s->ignore_submodule_arg) {
+ DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
+ }
rev.diffopt.format_callback = wt_status_collect_changed_cb;
rev.diffopt.format_callback_data = s;
- rev.prune_data = s->pathspec;
+ init_pathspec(&rev.prune_data, s->pathspec);
run_diff_files(&rev, 0);
}
@@ -331,8 +411,10 @@ static void wt_status_collect_changes_index(struct wt_status *s)
opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
setup_revisions(0, NULL, &rev, &opt);
- if (s->ignore_submodule_arg)
+ if (s->ignore_submodule_arg) {
+ DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
+ }
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_collect_updated_cb;
@@ -340,20 +422,22 @@ static void wt_status_collect_changes_index(struct wt_status *s)
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 200;
rev.diffopt.break_opt = 0;
- rev.prune_data = s->pathspec;
+ init_pathspec(&rev.prune_data, s->pathspec);
run_diff_index(&rev, 1);
}
static void wt_status_collect_changes_initial(struct wt_status *s)
{
+ struct pathspec pathspec;
int i;
+ init_pathspec(&pathspec, s->pathspec);
for (i = 0; i < active_nr; i++) {
struct string_list_item *it;
struct wt_status_change_data *d;
struct cache_entry *ce = active_cache[i];
- if (!ce_path_match(ce, s->pathspec))
+ if (!ce_path_match(ce, &pathspec))
continue;
it = string_list_insert(&s->change, ce->name);
d = it->util;
@@ -368,6 +452,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
else
d->index_status = DIFF_STATUS_ADDED;
}
+ free_pathspec(&pathspec);
}
static void wt_status_collect_untracked(struct wt_status *s)
@@ -386,11 +471,9 @@ static void wt_status_collect_untracked(struct wt_status *s)
fill_directory(&dir, s->pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (!cache_name_is_other(ent->name, ent->len))
- continue;
- if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
- continue;
- string_list_insert(&s->untracked, ent->name);
+ if (cache_name_is_other(ent->name, ent->len) &&
+ match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+ string_list_insert(&s->untracked, ent->name);
free(ent);
}
@@ -400,11 +483,9 @@ static void wt_status_collect_untracked(struct wt_status *s)
fill_directory(&dir, s->pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (!cache_name_is_other(ent->name, ent->len))
- continue;
- if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
- continue;
- string_list_insert(&s->ignored, ent->name);
+ if (cache_name_is_other(ent->name, ent->len) &&
+ match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+ string_list_insert(&s->ignored, ent->name);
free(ent);
}
}
@@ -561,7 +642,7 @@ static void wt_status_print_other(struct wt_status *s,
int i;
struct strbuf buf = STRBUF_INIT;
- if (!s->untracked.nr)
+ if (!l->nr)
return;
wt_status_print_other_header(s, what, how);
@@ -569,9 +650,9 @@ static void wt_status_print_other(struct wt_status *s,
for (i = 0; i < l->nr; i++) {
struct string_list_item *it;
it = &(l->items[i]);
- color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
- color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s",
- quote_path(it->string, strlen(it->string),
+ status_printf(s, color(WT_STATUS_HEADER, s), "\t");
+ status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
+ "%s\n", quote_path(it->string, strlen(it->string),
&buf, s->prefix));
}
strbuf_release(&buf);
@@ -625,28 +706,30 @@ static void wt_status_print_tracking(struct wt_status *s)
void wt_status_print(struct wt_status *s)
{
- const char *branch_color = color(WT_STATUS_HEADER, s);
+ const char *branch_color = color(WT_STATUS_ONBRANCH, s);
+ const char *branch_status_color = color(WT_STATUS_HEADER, s);
if (s->branch) {
- const char *on_what = "On branch ";
+ const char *on_what = _("On branch ");
const char *branch_name = s->branch;
if (!prefixcmp(branch_name, "refs/heads/"))
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
branch_name = "";
- branch_color = color(WT_STATUS_NOBRANCH, s);
- on_what = "Not currently on any branch.";
+ branch_status_color = color(WT_STATUS_NOBRANCH, s);
+ on_what = _("Not currently on any branch.");
}
- color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
- color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
+ status_printf(s, color(WT_STATUS_HEADER, s), "");
+ status_printf_more(s, branch_status_color, "%s", on_what);
+ status_printf_more(s, branch_color, "%s\n", branch_name);
if (!s->is_initial)
wt_status_print_tracking(s);
}
if (s->is_initial) {
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
+ status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
}
wt_status_print_updated(s);
@@ -659,38 +742,38 @@ void wt_status_print(struct wt_status *s)
wt_status_print_submodule_summary(s, 1); /* unstaged */
}
if (s->show_untracked_files) {
- wt_status_print_other(s, &s->untracked, "Untracked", "add");
+ wt_status_print_other(s, &s->untracked, _("Untracked"), "add");
if (s->show_ignored_files)
- wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
+ wt_status_print_other(s, &s->ignored, _("Ignored"), "add -f");
} else if (s->commitable)
- fprintf(s->fp, "# Untracked files not listed%s\n",
+ status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
advice_status_hints
- ? " (use -u option to show untracked files)" : "");
+ ? _(" (use -u option to show untracked files)") : "");
if (s->verbose)
wt_status_print_verbose(s);
if (!s->commitable) {
if (s->amend)
- fprintf(s->fp, "# No changes\n");
+ status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
else if (s->nowarn)
; /* nothing */
else if (s->workdir_dirty)
- printf("no changes added to commit%s\n",
+ printf(_("no changes added to commit%s\n"),
advice_status_hints
- ? " (use \"git add\" and/or \"git commit -a\")" : "");
+ ? _(" (use \"git add\" and/or \"git commit -a\")") : "");
else if (s->untracked.nr)
- printf("nothing added to commit but untracked files present%s\n",
+ printf(_("nothing added to commit but untracked files present%s\n"),
advice_status_hints
- ? " (use \"git add\" to track)" : "");
+ ? _(" (use \"git add\" to track)") : "");
else if (s->is_initial)
- printf("nothing to commit%s\n", advice_status_hints
- ? " (create/copy files and use \"git add\" to track)" : "");
+ printf(_("nothing to commit%s\n"), advice_status_hints
+ ? _(" (create/copy files and use \"git add\" to track)") : "");
else if (!s->show_untracked_files)
- printf("nothing to commit%s\n", advice_status_hints
- ? " (use -u to show untracked files)" : "");
+ printf(_("nothing to commit%s\n"), advice_status_hints
+ ? _(" (use -u to show untracked files)") : "");
else
- printf("nothing to commit%s\n", advice_status_hints
- ? " (working directory clean)" : "");
+ printf(_("nothing to commit%s\n"), advice_status_hints
+ ? _(" (working directory clean)") : "");
}
}
@@ -744,10 +827,20 @@ static void wt_shortstatus_status(int null_termination, struct string_list_item
const char *one;
if (d->head_path) {
one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+ if (*one != '"' && strchr(one, ' ') != NULL) {
+ putchar('"');
+ strbuf_addch(&onebuf, '"');
+ one = onebuf.buf;
+ }
printf("%s -> ", one);
strbuf_release(&onebuf);
}
one = quote_path(it->string, -1, &onebuf, s->prefix);
+ if (*one != '"' && strchr(one, ' ') != NULL) {
+ putchar('"');
+ strbuf_addch(&onebuf, '"');
+ one = onebuf.buf;
+ }
printf("%s\n", one);
strbuf_release(&onebuf);
}
@@ -788,13 +881,13 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
if (!prefixcmp(branch_name, "refs/heads/"))
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
- branch_name = "HEAD (no branch)";
+ branch_name = _("HEAD (no branch)");
branch_color_local = color(WT_STATUS_NOBRANCH, s);
}
branch = branch_get(s->branch + 11);
if (s->is_initial)
- color_fprintf(s->fp, header_color, "Initial commit on ");
+ color_fprintf(s->fp, header_color, _("Initial commit on "));
if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
color_fprintf_ln(s->fp, branch_color_local,
"%s", branch_name);
@@ -809,15 +902,15 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
color_fprintf(s->fp, header_color, " [");
if (!num_ours) {
- color_fprintf(s->fp, header_color, "behind ");
+ color_fprintf(s->fp, header_color, _("behind "));
color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
} else if (!num_theirs) {
- color_fprintf(s->fp, header_color, "ahead ");
+ color_fprintf(s->fp, header_color, _("ahead "));
color_fprintf(s->fp, branch_color_local, "%d", num_ours);
} else {
- color_fprintf(s->fp, header_color, "ahead ");
+ color_fprintf(s->fp, header_color, _("ahead "));
color_fprintf(s->fp, branch_color_local, "%d", num_ours);
- color_fprintf(s->fp, header_color, ", behind ");
+ color_fprintf(s->fp, header_color, _(", behind "));
color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
}
diff --git a/wt-status.h b/wt-status.h
index 9df9c9f..682b4c8 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -13,7 +13,9 @@ enum color_wt_status {
WT_STATUS_NOBRANCH,
WT_STATUS_UNMERGED,
WT_STATUS_LOCAL_BRANCH,
- WT_STATUS_REMOTE_BRANCH
+ WT_STATUS_REMOTE_BRANCH,
+ WT_STATUS_ONBRANCH,
+ WT_STATUS_MAXSLOT
};
enum untracked_status_type {
@@ -22,6 +24,13 @@ enum untracked_status_type {
SHOW_ALL_UNTRACKED_FILES
};
+/* from where does this commit originate */
+enum commit_whence {
+ FROM_COMMIT, /* normal */
+ FROM_MERGE, /* commit came from merge */
+ FROM_CHERRY_PICK /* commit came from cherry-pick */
+};
+
struct wt_status_change_data {
int worktree_status;
int index_status;
@@ -38,7 +47,7 @@ struct wt_status {
const char **pathspec;
int verbose;
int amend;
- int in_merge;
+ enum commit_whence whence;
int nowarn;
int use_color;
int relative_paths;
@@ -46,7 +55,7 @@ struct wt_status {
int show_ignored_files;
enum untracked_status_type show_untracked_files;
const char *ignore_submodule_arg;
- char color_palette[WT_STATUS_REMOTE_BRANCH+1][COLOR_MAXLEN];
+ char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
/* These are computed during processing of the individual sections */
int commitable;
@@ -66,4 +75,11 @@ void wt_status_collect(struct wt_status *s);
void wt_shortstatus_print(struct wt_status *s, int null_termination, int show_branch);
void wt_porcelain_print(struct wt_status *s, int null_termination);
+void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...)
+ ;
+void status_printf(struct wt_status *s, const char *color, const char *fmt, ...)
+ ;
+void status_printf_more(struct wt_status *s, const char *color, const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
#endif /* STATUS_H */
diff --git a/xdiff-interface.c b/xdiff-interface.c
index cd2285d..0e2c169 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -212,8 +212,10 @@ int read_mmfile(mmfile_t *ptr, const char *filename)
return error("Could not open %s", filename);
sz = xsize_t(st.st_size);
ptr->ptr = xmalloc(sz ? sz : 1);
- if (sz && fread(ptr->ptr, sz, 1, f) != 1)
+ if (sz && fread(ptr->ptr, sz, 1, f) != 1) {
+ fclose(f);
return error("Could not read %s", filename);
+ }
fclose(f);
ptr->size = sz;
return 0;
@@ -286,9 +288,8 @@ static long ff_regexp(const char *line, long len,
result = pmatch[i].rm_eo - pmatch[i].rm_so;
if (result > buffer_size)
result = buffer_size;
- else
- while (result > 0 && (isspace(line[result - 1])))
- result--;
+ while (result > 0 && (isspace(line[result - 1])))
+ result--;
memcpy(buffer, line, result);
fail:
free(line_buffer);
@@ -346,7 +347,7 @@ int git_xmerge_style = -1;
int git_xmerge_config(const char *var, const char *value, void *cb)
{
- if (!strcasecmp(var, "merge.conflictstyle")) {
+ if (!strcmp(var, "merge.conflictstyle")) {
if (!value)
die("'%s' is not a boolean", var);
if (!strcmp(value, "diff3"))
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index c4bedf0..277e2ee 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -85,27 +85,6 @@ static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
return -1;
}
-static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll,
- find_func_t ff, void *ff_priv) {
-
- /*
- * Be quite stupid about this for now. Find a line in the old file
- * before the start of the hunk (and context) which starts with a
- * plausible character.
- */
-
- const char *rec;
- long len;
-
- while (i-- > 0) {
- len = xdl_get_rec(xf, i, &rec);
- if ((*ll = ff(rec, len, buf, sz, ff_priv)) >= 0)
- return;
- }
- *ll = 0;
-}
-
-
static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) {
xdfile_t *xdf = &xe->xdf1;
@@ -127,6 +106,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdchange_t *xch, *xche;
char funcbuf[80];
long funclen = 0;
+ long funclineprev = -1;
find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
if (xecfg->flags & XDL_EMIT_COMMON)
@@ -150,9 +130,19 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
*/
if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
- xdl_find_func(&xe->xdf1, s1, funcbuf,
- sizeof(funcbuf), &funclen,
- ff, xecfg->find_func_priv);
+ long l;
+ for (l = s1 - 1; l >= 0 && l > funclineprev; l--) {
+ const char *rec;
+ long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+ long newfunclen = ff(rec, reclen, funcbuf,
+ sizeof(funcbuf),
+ xecfg->find_func_priv);
+ if (newfunclen >= 0) {
+ funclen = newfunclen;
+ break;
+ }
+ }
+ funclineprev = s1 - 1;
}
if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
funcbuf, funclen, ecb) < 0)
diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h
index 8ef232c..165a895 100644
--- a/xdiff/xmacros.h
+++ b/xdiff/xmacros.h
@@ -30,6 +30,7 @@
#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+#define XDL_ISSPACE(c) (isspace((unsigned char)(c)))
#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index 6d6fc1b..9e13b25 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -336,7 +336,7 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
static int line_contains_alnum(const char *ptr, long size)
{
while (size--)
- if (isalnum(*(ptr++)))
+ if (isalnum((unsigned char)*(ptr++)))
return 1;
return 0;
}
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index bc12f29..ab65034 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -190,8 +190,10 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
{
int i1, i2;
+ if (s1 == s2 && !memcmp(l1, l2, s1))
+ return 1;
if (!(flags & XDF_WHITESPACE_FLAGS))
- return s1 == s2 && !memcmp(l1, l2, s1);
+ return 0;
i1 = 0;
i2 = 0;
@@ -209,18 +211,18 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
if (l1[i1++] != l2[i2++])
return 0;
skip_ws:
- while (i1 < s1 && isspace(l1[i1]))
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
i1++;
- while (i2 < s2 && isspace(l2[i2]))
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
i2++;
}
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
while (i1 < s1 && i2 < s2) {
- if (isspace(l1[i1]) && isspace(l2[i2])) {
+ if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
/* Skip matching spaces and try again */
- while (i1 < s1 && isspace(l1[i1]))
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
i1++;
- while (i2 < s2 && isspace(l2[i2]))
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
i2++;
continue;
}
@@ -239,13 +241,13 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
* while there still are characters remaining on both lines.
*/
if (i1 < s1) {
- while (i1 < s1 && isspace(l1[i1]))
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
i1++;
if (s1 != i1)
return 0;
}
if (i2 < s2) {
- while (i2 < s2 && isspace(l2[i2]))
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
i2++;
return (s2 == i2);
}
@@ -258,10 +260,10 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
char const *ptr = *data;
for (; ptr < top && *ptr != '\n'; ptr++) {
- if (isspace(*ptr)) {
+ if (XDL_ISSPACE(*ptr)) {
const char *ptr2 = ptr;
int at_eol;
- while (ptr + 1 < top && isspace(ptr[1])
+ while (ptr + 1 < top && XDL_ISSPACE(ptr[1])
&& ptr[1] != '\n')
ptr++;
at_eol = (top <= ptr + 1 || ptr[1] == '\n');
diff --git a/zlib.c b/zlib.c
new file mode 100644
index 0000000..3c63d48
--- /dev/null
+++ b/zlib.c
@@ -0,0 +1,251 @@
+/*
+ * zlib wrappers to make sure we don't silently miss errors
+ * at init time.
+ */
+#include "cache.h"
+
+static const char *zerr_to_string(int status)
+{
+ switch (status) {
+ case Z_MEM_ERROR:
+ return "out of memory";
+ case Z_VERSION_ERROR:
+ return "wrong version";
+ case Z_NEED_DICT:
+ return "needs dictionary";
+ case Z_DATA_ERROR:
+ return "data stream error";
+ case Z_STREAM_ERROR:
+ return "stream consistency error";
+ default:
+ return "unknown error";
+ }
+}
+
+/*
+ * avail_in and avail_out in zlib are counted in uInt, which typically
+ * limits the size of the buffer we can use to 4GB when interacting
+ * with zlib in a single call to inflate/deflate.
+ */
+/* #define ZLIB_BUF_MAX ((uInt)-1) */
+#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */
+static inline uInt zlib_buf_cap(unsigned long len)
+{
+ return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len;
+}
+
+static void zlib_pre_call(git_zstream *s)
+{
+ s->z.next_in = s->next_in;
+ s->z.next_out = s->next_out;
+ s->z.total_in = s->total_in;
+ s->z.total_out = s->total_out;
+ s->z.avail_in = zlib_buf_cap(s->avail_in);
+ s->z.avail_out = zlib_buf_cap(s->avail_out);
+}
+
+static void zlib_post_call(git_zstream *s)
+{
+ unsigned long bytes_consumed;
+ unsigned long bytes_produced;
+
+ bytes_consumed = s->z.next_in - s->next_in;
+ bytes_produced = s->z.next_out - s->next_out;
+ if (s->z.total_out != s->total_out + bytes_produced)
+ die("BUG: total_out mismatch");
+ if (s->z.total_in != s->total_in + bytes_consumed)
+ die("BUG: total_in mismatch");
+
+ s->total_out = s->z.total_out;
+ s->total_in = s->z.total_in;
+ s->next_in = s->z.next_in;
+ s->next_out = s->z.next_out;
+ s->avail_in -= bytes_consumed;
+ s->avail_out -= bytes_produced;
+}
+
+void git_inflate_init(git_zstream *strm)
+{
+ int status;
+
+ zlib_pre_call(strm);
+ status = inflateInit(&strm->z);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ die("inflateInit: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_inflate_init_gzip_only(git_zstream *strm)
+{
+ /*
+ * Use default 15 bits, +16 is to accept only gzip and to
+ * yield Z_DATA_ERROR when fed zlib format.
+ */
+ const int windowBits = 15 + 16;
+ int status;
+
+ zlib_pre_call(strm);
+ status = inflateInit2(&strm->z, windowBits);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ die("inflateInit2: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_inflate_end(git_zstream *strm)
+{
+ int status;
+
+ zlib_pre_call(strm);
+ status = inflateEnd(&strm->z);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ error("inflateEnd: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+int git_inflate(git_zstream *strm, int flush)
+{
+ int status;
+
+ for (;;) {
+ zlib_pre_call(strm);
+ /* Never say Z_FINISH unless we are feeding everything */
+ status = inflate(&strm->z,
+ (strm->z.avail_in != strm->avail_in)
+ ? 0 : flush);
+ if (status == Z_MEM_ERROR)
+ die("inflate: out of memory");
+ zlib_post_call(strm);
+
+ /*
+ * Let zlib work another round, while we can still
+ * make progress.
+ */
+ if ((strm->avail_out && !strm->z.avail_out) &&
+ (status == Z_OK || status == Z_BUF_ERROR))
+ continue;
+ break;
+ }
+
+ switch (status) {
+ /* Z_BUF_ERROR: normal, needs more space in the output buffer */
+ case Z_BUF_ERROR:
+ case Z_OK:
+ case Z_STREAM_END:
+ return status;
+ default:
+ break;
+ }
+ error("inflate: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+ return status;
+}
+
+#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
+#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
+#endif
+
+unsigned long git_deflate_bound(git_zstream *strm, unsigned long size)
+{
+ return deflateBound(&strm->z, size);
+}
+
+void git_deflate_init(git_zstream *strm, int level)
+{
+ int status;
+
+ zlib_pre_call(strm);
+ status = deflateInit(&strm->z, level);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ die("deflateInit: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_deflate_init_gzip(git_zstream *strm, int level)
+{
+ /*
+ * Use default 15 bits, +16 is to generate gzip header/trailer
+ * instead of the zlib wrapper.
+ */
+ const int windowBits = 15 + 16;
+ int status;
+
+ zlib_pre_call(strm);
+ status = deflateInit2(&strm->z, level,
+ Z_DEFLATED, windowBits,
+ 8, Z_DEFAULT_STRATEGY);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ die("deflateInit2: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+void git_deflate_end(git_zstream *strm)
+{
+ int status;
+
+ zlib_pre_call(strm);
+ status = deflateEnd(&strm->z);
+ zlib_post_call(strm);
+ if (status == Z_OK)
+ return;
+ error("deflateEnd: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+}
+
+int git_deflate_end_gently(git_zstream *strm)
+{
+ int status;
+
+ zlib_pre_call(strm);
+ status = deflateEnd(&strm->z);
+ zlib_post_call(strm);
+ return status;
+}
+
+int git_deflate(git_zstream *strm, int flush)
+{
+ int status;
+
+ for (;;) {
+ zlib_pre_call(strm);
+
+ /* Never say Z_FINISH unless we are feeding everything */
+ status = deflate(&strm->z,
+ (strm->z.avail_in != strm->avail_in)
+ ? 0 : flush);
+ if (status == Z_MEM_ERROR)
+ die("deflate: out of memory");
+ zlib_post_call(strm);
+
+ /*
+ * Let zlib work another round, while we can still
+ * make progress.
+ */
+ if ((strm->avail_out && !strm->z.avail_out) &&
+ (status == Z_OK || status == Z_BUF_ERROR))
+ continue;
+ break;
+ }
+
+ switch (status) {
+ /* Z_BUF_ERROR: normal, needs more space in the output buffer */
+ case Z_BUF_ERROR:
+ case Z_OK:
+ case Z_STREAM_END:
+ return status;
+ default:
+ break;
+ }
+ error("deflate: %s (%s)", zerr_to_string(status),
+ strm->z.msg ? strm->z.msg : "no message");
+ return status;
+}